Skip to content

Commit 5c38727

Browse files
committed
[Macros] Invoke member macro expansion during qualified lookup.
Otherwise, members that are added via macro expansion will not be visible in other source files.
1 parent a81b0b6 commit 5c38727

File tree

6 files changed

+233
-0
lines changed

6 files changed

+233
-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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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
6+
7+
// REQUIRES: asserts
8+
// REQUIRES: OS=macosx
9+
10+
import macro_library
11+
12+
struct Treat {}
13+
14+
@Observable
15+
final class Dog: Observable {
16+
var name: String?
17+
var treat: Treat?
18+
19+
var isHappy: Bool = true
20+
21+
init() {}
22+
23+
func bark() {
24+
print("bork bork")
25+
}
26+
}
27+
28+
func test() {
29+
observeDog()
30+
}

0 commit comments

Comments
 (0)