swiftmacrosprotocols

How to Apply a Custom Protocol in Swift Macro?


I have a question while studying Swift Macros.

When a specific type wants to conform to a custom protocol in Swift Macros, should that custom protocol be declared within the Macro Package? Or can it be declared in the main app project?

For example, if the @ServiceProvidable macro makes a specific class conform to the ServiceProvidable protocol and expands the code with a computed property, how should it be implemented?

@ServiceProvidable
class ViewControllerWrapper {

}

// Expanding a macro
class ViewControllerWrapper { } 
extension ViewControllerWrapper: ServiceProvidable {
    var service: ServiceProviderProtocol {
        guard 
            let serviceProvider = (UIApplication.shared.delegate as? AppDelegate)?.serviceProvider
        else { return ServiceProvider() }
        return serviceProvider
    }
}

Solution

  • You need to specify the conformances that a macro provides, when declaring the macro. e.g.

    @attached(extension, conformances: FooProtocol) // You need to be able to say "FooProtocol" here
    public macro MyMacro() = #externalMacro(module: "SomeImplementationModule", type: "MyMacro")
    

    Therefore, the protocol should be declared in a module that the code above can access, so either in the same module as the above code, or one of its dependencies. I usually just put it right next to the macro declaration.

    @attached(extension, conformances: FooProtocol)
    public macro MyMacro() = #externalMacro(module: "SomeImplementationModule", type: "MyMacro")
    
    public protocol FooProtocol {
        func foo()
    }
    

    Note that this is not the module where you implement the macro.

    You should implement the macro in another module (in the above example, in a module called "SomeImplementationModule"), created using .macro(...) instead of .target(...) in Package.swift.

    Here is an example implementation:

    enum MyMacro: ExtensionMacro {
        static func expansion(
            of node: AttributeSyntax,
            attachedTo declaration: some DeclGroupSyntax,
            providingExtensionsOf type: some TypeSyntaxProtocol,
            conformingTo protocols: [TypeSyntax],
            in context: some MacroExpansionContext
        ) throws -> [ExtensionDeclSyntax] {
            let name = declaration.as(ClassDeclSyntax.self)!.name
            return [
                try ExtensionDeclSyntax("extension \(name): FooProtocol") {
                    """
                    public func foo() { print("example implementation") }
                    """
                }
            ]
        }
    }
    
    
    @main
    struct MyMacroPlugin: CompilerPlugin {
        let providingMacros: [Macro.Type] = [
            MyMacro.self
        ]
    }