I am doing some testing and figuring out with the new Swift Macros but I came across a hurdle I can't seem to figure out, also because there isn't a lot of helpful guides on them yet.
So first of all, if the appliance of the macro is a good idea at all, we leave that aside for now.
What I am trying to achieve is to create an @attached(member)
macro which automatically adds an Service to a struct
with all the basic REST operations such as get(id: String)
, patch()
, getAll()
, delete(id: String)
etcetera.
So I came quite far with the implementation but I can't seem to figure out how to create the ReturnClauseSyntax
which should just return an instance of the type on which the macro is added. See code example below:
public struct ServiceMacro: MemberMacro {
public static func expansion(
of node: SwiftSyntax.AttributeSyntax,
providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax,
in context: some SwiftSyntaxMacros.MacroExpansionContext
) throws -> [SwiftSyntax.DeclSyntax] {
guard let structDecl = declaration.as(StructDeclSyntax.self) else {
// TODO: Emit an error
return []
}
let initializer = try EnumDeclSyntax("enum Service") {
FunctionDeclSyntax(
funcKeyword: "static func",
name: "get",
signature: FunctionSignatureSyntax(
parameterClause: FunctionParameterClauseSyntax(
parameters: FunctionParameterListSyntax(
arrayLiteral: FunctionParameterSyntax(
stringLiteral: "id: String"
)
)
),
trailingTrivia: .init(stringLiteral: " -> \(structDecl.name.trimmed)")
),
bodyBuilder: {
"return \(structDecl.name.trimmed)()"
}
)
}
return [DeclSyntax(initializer)]
}
}
So this results in the following expansion:
@Service
struct Recipe {
enum Service {
static func get(id: String) -> Recipe {
return Recipe()
}
}
}
But as you can see in the macro implementation code I am setting the trailing trivia by using a stringLiteral
which doesn't feel right and probably isn't right.
Now I am wondering how I can achieve the same by using the returnClause
parameter which expects a ReturnClauseSyntax
. I just for now want it to return an instance of the struct it is applied to.
Maybe it's really easy and I couldn't find it correctly in the docs, but also this whole syntax is new to me so would be really helpful if someone could point me in the right direction.
Also other suggestions on the implementation are more than welcome as this is new to me and a lot of people probably.
indeed it's not that hard.
You can retrieve the type name of the struct you are working on like that
guard let name = declaration.as(StructDeclSyntax.self)?.name.text
else {return []}
Then you can convert it to a TypeSyntax and pass this to the type parameter of ReturnClauseSyntax
initialiser like so :
ReturnClauseSyntax(type: TypeSyntax(stringLiteral: name)
Now you can combine all that together :
public struct ServiceMacro: MemberMacro {
public static func expansion(
of node: SwiftSyntax.AttributeSyntax,
providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax,
in context: some SwiftSyntaxMacros.MacroExpansionContext
) throws -> [SwiftSyntax.DeclSyntax] {
guard let structDecl = declaration.as(StructDeclSyntax.self) else {
// TODO: Emit an error
return []
}
let name = structDecl.name.text
let initializer = try EnumDeclSyntax("enum Service") {
FunctionDeclSyntax(
funcKeyword: "static func",
name: "get",
signature: FunctionSignatureSyntax(
parameterClause: FunctionParameterClauseSyntax(
parameters: FunctionParameterListSyntax(
arrayLiteral: FunctionParameterSyntax(
stringLiteral: "id: String"
)
)
),
returnClause: ReturnClauseSyntax(type: TypeSyntax(stringLiteral: name)
),
bodyBuilder: {
"return \(structDecl.name.trimmed)()"
}
)
}
return [DeclSyntax(initializer)]
}
}