forked from DougGregor/swift-macro-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request DougGregor#6 from hborla/observable-macro
Add an `Observable` macro implementation example.
- Loading branch information
Showing
4 changed files
with
220 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import SwiftSyntax | ||
import SwiftSyntaxMacros | ||
|
||
private extension DeclSyntaxProtocol { | ||
var isObservableStoredProperty: Bool { | ||
guard let property = self.as(VariableDeclSyntax.self), | ||
let binding = property.bindings.first | ||
else { | ||
return false | ||
} | ||
|
||
return binding.accessor == nil | ||
} | ||
} | ||
|
||
public struct ObservableMacro: MemberMacro, MemberAttributeMacro { | ||
|
||
// MARK: - MemberMacro | ||
|
||
public static func expansion( | ||
of node: AttributeSyntax, | ||
providingMembersOf declaration: some DeclGroupSyntax, | ||
in context: some MacroExpansionContext | ||
) throws -> [DeclSyntax] { | ||
guard let identified = declaration.asProtocol(IdentifiedDeclSyntax.self) else { | ||
return [] | ||
} | ||
|
||
let parentName = identified.identifier | ||
|
||
let registrar: DeclSyntax = | ||
""" | ||
let _registrar = ObservationRegistrar<\(parentName)>() | ||
""" | ||
|
||
let addObserver: DeclSyntax = | ||
""" | ||
public nonisolated func addObserver(_ observer: some Observer<\(parentName)>) { | ||
_registrar.addObserver(observer) | ||
} | ||
""" | ||
|
||
let removeObserver: DeclSyntax = | ||
""" | ||
public nonisolated func removeObserver(_ observer: some Observer<\(parentName)>) { | ||
_registrar.removeObserver(observer) | ||
} | ||
""" | ||
|
||
let withTransaction: DeclSyntax = | ||
""" | ||
private func withTransaction<T>(_ apply: () throws -> T) rethrows -> T { | ||
_registrar.beginAccess() | ||
defer { _registrar.endAccess() } | ||
return try apply() | ||
} | ||
""" | ||
|
||
let memberList = MemberDeclListSyntax( | ||
declaration.members.members.filter { | ||
$0.decl.isObservableStoredProperty | ||
} | ||
) | ||
|
||
let storageStruct: DeclSyntax = | ||
""" | ||
private struct Storage { | ||
\(memberList) | ||
} | ||
""" | ||
|
||
let storage: DeclSyntax = | ||
""" | ||
private var _storage = Storage() | ||
""" | ||
|
||
return [ | ||
registrar, | ||
addObserver, | ||
removeObserver, | ||
withTransaction, | ||
storageStruct, | ||
storage, | ||
] | ||
} | ||
|
||
// MARK: - MemberAttributeMacro | ||
|
||
public static func expansion( | ||
of node: AttributeSyntax, | ||
attachedTo declaration: some DeclGroupSyntax, | ||
providingAttributesFor member: DeclSyntax, | ||
in context: some MacroExpansionContext | ||
) throws -> [SwiftSyntax.AttributeSyntax] { | ||
guard member.isObservableStoredProperty else { | ||
return [] | ||
} | ||
|
||
return [ | ||
AttributeSyntax( | ||
attributeName: SimpleTypeIdentifierSyntax( | ||
name: .identifier("ObservableProperty") | ||
) | ||
) | ||
] | ||
} | ||
|
||
} | ||
|
||
public struct ObservablePropertyMacro: AccessorMacro { | ||
public static func expansion( | ||
of node: AttributeSyntax, | ||
providingAccessorsOf declaration: some DeclSyntaxProtocol, | ||
in context: some MacroExpansionContext | ||
) throws -> [AccessorDeclSyntax] { | ||
guard let property = declaration.as(VariableDeclSyntax.self), | ||
let binding = property.bindings.first, | ||
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, | ||
binding.accessor == nil | ||
else { | ||
return [] | ||
} | ||
|
||
if identifier.text == "_registrar" || identifier.text == "_storage" { return [] } | ||
|
||
let getAccessor: AccessorDeclSyntax = | ||
""" | ||
get { | ||
_registrar.beginAccess(\\.\(identifier)) | ||
defer { _registrar.endAccess() } | ||
return _storage.\(identifier) | ||
} | ||
""" | ||
|
||
let setAccessor: AccessorDeclSyntax = | ||
""" | ||
set { | ||
_registrar.beginAccess(\\.\(identifier)) | ||
_registrar.register(observable: self, willSet: \\.\(identifier), to: newValue) | ||
defer { | ||
_registrar.register(observable: self, didSet: \\.\(identifier)) | ||
_registrar.endAccess() | ||
} | ||
_storage.\(identifier) = newValue | ||
} | ||
""" | ||
|
||
return [getAccessor, setAccessor] | ||
} | ||
} |