Skip to content

Commit d3db811

Browse files
authored
Merge pull request #63423 from hborla/macro-expand-multi-file
2 parents 3c67b06 + 5227b54 commit d3db811

File tree

6 files changed

+289
-0
lines changed

6 files changed

+289
-0
lines changed

lib/AST/NameLookup.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2004,6 +2004,12 @@ QualifiedLookupRequest::evaluate(Evaluator &eval, const DeclContext *DC,
20042004
// Make sure we've resolved property wrappers, if we need them.
20052005
installPropertyWrapperMembersIfNeeded(current, member);
20062006

2007+
// Expand synthesized member macros.
2008+
auto &ctx = current->getASTContext();
2009+
evaluateOrDefault(ctx.evaluator,
2010+
ExpandSynthesizedMemberMacroRequest{current},
2011+
false);
2012+
20072013
// Look for results within the current nominal type and its extensions.
20082014
bool currentIsProtocol = isa<ProtocolDecl>(current);
20092015
auto flags = OptionSet<NominalTypeDecl::LookupDirectFlags>();

lib/Sema/LookupVisibleDecls.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,12 @@ static void synthesizeMemberDeclsForLookup(NominalTypeDecl *NTD,
605605
/*useResolver=*/true);
606606
}
607607

608+
// Expand synthesized member macros.
609+
auto &ctx = NTD->getASTContext();
610+
evaluateOrDefault(ctx.evaluator,
611+
ExpandSynthesizedMemberMacroRequest{NTD},
612+
false);
613+
608614
synthesizePropertyWrapperVariables(NTD);
609615
}
610616

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
struct DogObserver: Observer {
3+
typealias Subject = Dog
4+
}
5+
6+
func observeDog() {
7+
let dog = Dog()
8+
dog.name = "George"
9+
dog.treat = Treat()
10+
dog.bark()
11+
dog.addObserver(DogObserver())
12+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
public protocol Observable {}
3+
4+
public protocol Observer<Subject> {
5+
associatedtype Subject: Observable
6+
}
7+
8+
public struct ObservationRegistrar<Subject: Observable> {
9+
public init() {}
10+
11+
public func addObserver(_ observer: some Observer<Subject>) {}
12+
13+
public func removeObserver(_ observer: some Observer<Subject>) {}
14+
15+
public func beginAccess<Value>(_ keyPath: KeyPath<Subject, Value>) {}
16+
17+
public func beginAccess() {}
18+
19+
public func endAccess() {}
20+
21+
public func register<Value>(observable: Subject, willSet: KeyPath<Subject, Value>, to: Value) {}
22+
23+
public func register<Value>(observable: Subject, didSet: KeyPath<Subject, Value>) {}
24+
}
25+
26+
@attached(member)
27+
@attached(memberAttribute)
28+
public macro Observable() = #externalMacro(module: "MacroDefinition", type: "ObservableMacro")
29+
30+
@attached(accessor)
31+
public macro ObservableProperty() = #externalMacro(module: "MacroDefinition", type: "ObservablePropertyMacro")

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,3 +569,151 @@ public enum LeftHandOperandFinderMacro: ExpressionMacro {
569569
return node.argumentList.first!.expression
570570
}
571571
}
572+
573+
private extension DeclSyntaxProtocol {
574+
var isObservableStoredProperty: Bool {
575+
if let property = self.as(VariableDeclSyntax.self),
576+
let binding = property.bindings.first,
577+
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
578+
identifier.text != "_registrar", identifier.text != "_storage",
579+
binding.accessor == nil {
580+
return true
581+
}
582+
583+
return false
584+
}
585+
}
586+
587+
public struct ObservableMacro: MemberMacro, MemberAttributeMacro {
588+
589+
// MARK: - MemberMacro
590+
591+
public static func expansion(
592+
of node: AttributeSyntax,
593+
providingMembersOf declaration: some DeclGroupSyntax,
594+
in context: some MacroExpansionContext
595+
) throws -> [DeclSyntax] {
596+
guard let identified = declaration.asProtocol(IdentifiedDeclSyntax.self) else {
597+
return []
598+
}
599+
600+
let parentName = identified.identifier
601+
602+
let registrar: DeclSyntax =
603+
"""
604+
let _registrar = ObservationRegistrar<\(parentName)>()
605+
"""
606+
607+
let addObserver: DeclSyntax =
608+
"""
609+
public nonisolated func addObserver(_ observer: some Observer<\(parentName)>) {
610+
_registrar.addObserver(observer)
611+
}
612+
"""
613+
614+
let removeObserver: DeclSyntax =
615+
"""
616+
public nonisolated func removeObserver(_ observer: some Observer<\(parentName)>) {
617+
_registrar.removeObserver(observer)
618+
}
619+
"""
620+
621+
let withTransaction: DeclSyntax =
622+
"""
623+
private func withTransaction<T>(_ apply: () throws -> T) rethrows -> T {
624+
_registrar.beginAccess()
625+
defer { _registrar.endAccess() }
626+
return try apply()
627+
}
628+
"""
629+
630+
let memberList = MemberDeclListSyntax(
631+
declaration.members.members.filter {
632+
$0.decl.isObservableStoredProperty
633+
}
634+
)
635+
636+
let storageStruct: DeclSyntax =
637+
"""
638+
private struct Storage {
639+
\(memberList)
640+
}
641+
"""
642+
643+
let storage: DeclSyntax =
644+
"""
645+
private var _storage = Storage()
646+
"""
647+
648+
return [
649+
registrar,
650+
addObserver,
651+
removeObserver,
652+
withTransaction,
653+
storageStruct,
654+
storage,
655+
]
656+
}
657+
658+
// MARK: - MemberAttributeMacro
659+
660+
public static func expansion(
661+
of node: AttributeSyntax,
662+
attachedTo declaration: some DeclGroupSyntax,
663+
providingAttributesFor member: DeclSyntax,
664+
in context: some MacroExpansionContext
665+
) throws -> [SwiftSyntax.AttributeSyntax] {
666+
guard member.isObservableStoredProperty else {
667+
return []
668+
}
669+
670+
return [
671+
AttributeSyntax(
672+
attributeName: SimpleTypeIdentifierSyntax(
673+
name: .identifier("ObservableProperty")
674+
)
675+
)
676+
]
677+
}
678+
679+
}
680+
681+
public struct ObservablePropertyMacro: AccessorMacro {
682+
public static func expansion(
683+
of node: AttributeSyntax,
684+
providingAccessorsOf declaration: some DeclSyntaxProtocol,
685+
in context: some MacroExpansionContext
686+
) throws -> [AccessorDeclSyntax] {
687+
guard let property = declaration.as(VariableDeclSyntax.self),
688+
let binding = property.bindings.first,
689+
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
690+
binding.accessor == nil
691+
else {
692+
return []
693+
}
694+
695+
let getAccessor: AccessorDeclSyntax =
696+
"""
697+
get {
698+
_registrar.beginAccess(\\.\(identifier))
699+
defer { _registrar.endAccess() }
700+
return _storage.\(identifier)
701+
}
702+
"""
703+
704+
let setAccessor: AccessorDeclSyntax =
705+
"""
706+
set {
707+
_registrar.beginAccess(\\.\(identifier))
708+
_registrar.register(observable: self, willSet: \\.\(identifier), to: newValue)
709+
defer {
710+
_registrar.register(observable: self, didSet: \\.\(identifier))
711+
_registrar.endAccess()
712+
}
713+
_storage.\(identifier) = newValue
714+
}
715+
"""
716+
717+
return [getAccessor, setAccessor]
718+
}
719+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %empty-directory(%t-scratch)
3+
// RUN: %target-build-swift -I %swift-host-lib-dir -L %swift-host-lib-dir -emit-library -o %t/%target-library-name(MacroDefinition) -module-name=MacroDefinition %S/Inputs/syntax_macro_definitions.swift -g -no-toolchain-stdlib-rpath
4+
// RUN: %target-swift-frontend -emit-module -o %t/macro_library.swiftmodule %S/Inputs/macro_library.swift -module-name macro_library -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir
5+
// RUN: %target-swift-frontend -typecheck -I%t -verify -primary-file %s %S/Inputs/macro_expand_other.swift -verify-ignore-unknown -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir -dump-macro-expansions > %t/expansions-dump.txt 2>&1
6+
// RUN: %FileCheck -check-prefix=CHECK-DUMP %s < %t/expansions-dump.txt
7+
8+
// REQUIRES: asserts
9+
// REQUIRES: OS=macosx
10+
11+
import macro_library
12+
13+
struct Treat {}
14+
15+
@Observable
16+
final class Dog: Observable {
17+
// CHECK-DUMP: public nonisolated func addObserver
18+
// CHECK-DUMP: public nonisolated func removeObserver
19+
20+
// CHECK-DUMP: private struct Storage {
21+
// CHECK-DUMP: var name: String?
22+
// CHECK-DUMP: var treat: Treat?
23+
// CHECK-DUMP: var isHappy: Bool = true
24+
// CHECK-DUMP: }
25+
26+
var name: String?
27+
// CHECK-DUMP: get {
28+
// CHECK-DUMP: _registrar.beginAccess(\.name)
29+
// CHECK-DUMP: defer { _registrar.endAccess() }
30+
// CHECK-DUMP: return _storage.name
31+
// CHECK-DUMP: }
32+
// CHECK-DUMP: set {
33+
// CHECK-DUMP: _registrar.beginAccess(\.name)
34+
// CHECK-DUMP: _registrar.register(observable: self, willSet: \.name, to: newValue)
35+
// CHECK-DUMP: defer {
36+
// CHECK-DUMP: _registrar.register(observable: self, didSet: \.name)
37+
// CHECK-DUMP: _registrar.endAccess()
38+
// CHECK-DUMP: }
39+
// CHECK-DUMP: _storage.name = newValue
40+
// CHECK-DUMP: }
41+
42+
43+
var treat: Treat?
44+
// CHECK-DUMP: get {
45+
// CHECK-DUMP: _registrar.beginAccess(\.treat)
46+
// CHECK-DUMP: defer { _registrar.endAccess() }
47+
// CHECK-DUMP: return _storage.treat
48+
// CHECK-DUMP: }
49+
// CHECK-DUMP: set {
50+
// CHECK-DUMP: _registrar.beginAccess(\.treat)
51+
// CHECK-DUMP: _registrar.register(observable: self, willSet: \.treat, to: newValue)
52+
// CHECK-DUMP: defer {
53+
// CHECK-DUMP: _registrar.register(observable: self, didSet: \.treat)
54+
// CHECK-DUMP: _registrar.endAccess()
55+
// CHECK-DUMP: }
56+
// CHECK-DUMP: _storage.treat = newValue
57+
// CHECK-DUMP: }
58+
59+
60+
var isHappy: Bool = true
61+
// CHECK-DUMP: get {
62+
// CHECK-DUMP: _registrar.beginAccess(\.isHappy)
63+
// CHECK-DUMP: defer { _registrar.endAccess() }
64+
// CHECK-DUMP: return _storage.isHappy
65+
// CHECK-DUMP: }
66+
// CHECK-DUMP: set {
67+
// CHECK-DUMP: _registrar.beginAccess(\.isHappy)
68+
// CHECK-DUMP: _registrar.register(observable: self, willSet: \.isHappy, to: newValue)
69+
// CHECK-DUMP: defer {
70+
// CHECK-DUMP: _registrar.register(observable: self, didSet: \.isHappy)
71+
// CHECK-DUMP: _registrar.endAccess()
72+
// CHECK-DUMP: }
73+
// CHECK-DUMP: _storage.isHappy = newValue
74+
// CHECK-DUMP: }
75+
76+
77+
init() {}
78+
79+
func bark() {
80+
print("bork bork")
81+
}
82+
}
83+
84+
func test() {
85+
observeDog()
86+
}

0 commit comments

Comments
 (0)