Reputation: 8432
I want to write a macro GetSetMacro
which is taking in the value of the stored property of the type and generate the accessors for another property based on it.
// Definition
@attached(accessor)
public macro GetSetMacro<Value>(_ : Value) = #externalMacro(module: "MyMacroModule", type: "GetSetMacro")
// Implementation
public struct GetSetMacro: AccessorMacro {
public static func expansion(of node: SwiftSyntax.AttributeSyntax, providingAccessorsOf declaration: some SwiftSyntax.DeclSyntaxProtocol, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.AccessorDeclSyntax] {
guard
case let .argumentList(arguments) = node.arguments,
let argument = arguments.first
else { return [] }
return [
"""
get {
// How to access the member value of the type in here?
???
}
""",
"""
set {
??? = newValue
}
"""
]
}
}
Example usage
var value = 1
@GetSetMacro(\.value)
var newIntValue: Int
// expansion
var newIntValue {
get {
value
} set {
value = newValue
}
}
Upvotes: 3
Views: 357
Reputation: 273850
In the macro declaration, take a WritableKeyPath
:
@attached(accessor, names: named(get), named(set))
public macro GetSet<Root, Value>(_ keyPath: WritableKeyPath<Root, Value>) = #externalMacro(...)
The implementation just uses the [keyPath: ...]
subscript to get and set the desired key path.
enum GetSetMacro: AccessorMacro {
static func expansion(of node: AttributeSyntax, providingAccessorsOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [AccessorDeclSyntax] {
guard let keyPath = node.arguments?.as(LabeledExprListSyntax.self)?.first?.expression else {
return []
}
return [
"""
get { self[keyPath: \(keyPath)] }
""",
"""
set { self[keyPath: \(keyPath)] = newValue }
"""
]
}
}
Example usage:
class Foo {
var value = 1
@GetSet(\Foo.value)
var newIntValue: Int
}
Notes:
\Foo.value
. The compiler cannot infer the root type.Upvotes: 0
Reputation: 3283
I created a basic solution to your problem; both with the String
approach as well as with the KeyPath
approach.
extension String: Error {}
public struct GetSetMacro: AccessorMacro {
public static func expansion(of node: AttributeSyntax, providingAccessorsOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [AccessorDeclSyntax] {
guard let arguments = node.arguments,
let expression = arguments.as(LabeledExprListSyntax.self)?.first?.expression else {
throw "Incorrect argument"
}
if let string = expression.as(StringLiteralExprSyntax.self) {
return [
"get { \(raw: string.segments) }",
"set { \(raw: string.segments) = newValue }",
]
} else if let keyPath = expression.as(KeyPathExprSyntax.self) {
return [
"get { self[keyPath: \(raw: keyPath.description)] }",
"set { self[keyPath: \(raw: keyPath.description)] = newValue }"
]
} else {
throw "Unsupported argument"
}
}
}
So in order to extract the variable passed into your macro, you'd have to traverse the node.arguments
in some way. If you use the Xcode template to create your macro, you can breakpoint inside your macro code and use lldb to see how you can extract the data you'd like to have.
Upvotes: 0