swiftmacrosswift-macro

Swift Macros: ReturnClauseSyntax


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.


Solution

  • 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)]
        }
    }