Examples of ExtensionMacro

I'm trying to create a macro that generates some boilerplate code like this:

protocol Foo {
  associatedtype Bar
}

extension Foo where Self: View, Bar: View {
  func doDefaultThing() {
    // implementation here
  }
}

The freestanding macro that generates this code results in an error on the generated extensions that says Macro expansion cannot introduce extension. Having read through these forums, it seems like there is a recent update in the form of extension macros that would allow this if I create an additional extension macro and generate the boilerplate code like this:

@AddDefaultThingExtension
protocol Foo {
  associatedtype Bar
}

In theory, this would allow the extension macro to generate the second part:

extension Foo where Self: View, Bar: View {
  func doDefaultThing() {
    // implementation here
  }
}

Having read the proposal and review thread and played around with implementing this in Xcode, I'm still coming up short on understanding how to implement the extension macro. The necessary type signature is:

public struct AddDefaultThingExtensionMacro: ExtensionMacro {
    public static func expansion(of node: AttributeSyntax, attachedTo declaration: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext) throws -> [ExtensionDeclSyntax] {
       // Missing good examples of how to create ExtensionDeclSyntax
    }
}

In working on other macros, I've found Doug Gregor's Swift Macro Examples repo to be invaluable. But as this is a new update, I don't see any examples of it in this repo or anywhere else yet!

Does anyone know of good examples of Extension Macros or is anyone able to provide me with some advice for how to go about implementing this:

 public static func expansion(of node: AttributeSyntax, attachedTo declaration: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext) throws -> [ExtensionDeclSyntax] { }

so as to generate the output:

extension Foo where Self: View, Bar: View {
  func doDefaultThing() {
    // implementation here
  }
}

Thanks!

Hi! I don't know if I can be of any help but in my peregrination through swift Macro generating some DTO I managed to generate an extension implementing a protocol and generating the properties and methods required, I'm very still in the understanding process but here is what I get :

You should not you freestanding macro, they are not able to generate extension there are only here to generate a peace of code in place of where it stand and this peace of code should be "stand alone".

Now you are going on the right direction you should indeed use an attached Macro with the "@attached(conformance)" and then extend your macro with : ExtensionMacro. Once that done you will have to implement the method you quoted public static func expansion(of node: AttributeSyntax, attachedTo declaration: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext) throws -> [ExtensionDeclSyntax] In it once you create an ExtensionDeclSyntax, what you generated will be created, but for me knowledge it can be quite cumbersome. However the good thing is most of the parameters of the init are optional.

Now if you have some more specific problem I'd be glad to help.

Thanks for the reply!

You should not you freestanding macro, they are not able to generate extension there are only here to generate a peace of code in place of where it stand and this peace of code should be "stand alone".

Is this a limitation of freestanding macros? I've noticed a pattern in several of the Apple example macros where one macro will, for example, add a member in generated code and the member has its own macro @ attribute. Then another macro expansion happens to operate on the annotated member. So it seems reasonable to expect that a freestanding macro can also generate code which includes members or types that have their own macro @ attributes, no?

In it once you create an ExtensionDeclSyntax, what you generated will be created, but for me knowledge it can be quite cumbersome.

Yeah, this is exactly what I'm running into. I was hoping to see some examples of code that has successfully implemented this cumbersome process and generated an extension similar to the one I'm trying to produce. I haven't actually been able to find any code examples of extension macros yet however, most likely because they are very recent additions to the beta! I'm hoping someone in the community can provide or point me to a code example!

for what I understood it is a limitation on freestanding macro, but I might be wrong.

For what you want to do you should use an attached macro (conformance) to the protocol that will generate the extension with the default function the problem will be to know which method you want to add with a default implementation. On top of that eventually you can generate in this implementation a freestanding macro that does some more generation.

But I'm pretty sure you have to use an attached macro in any case.

(btw: there is a dedicated part of the forum for swift macro in Development → Macro, I just found out)

Thanks for the tip, I moved my question into Development -> Macro which looks like a better place for it since it's still a pre-release feature!

I'm also having trouble converting an @attached(conformance) macro to the new @attached(extension) method-ology. My big problem is the Protocol isn't defined in the scope of the macro, so I can't used named: - But I'd like to echo the OP, I just ran into this with Xcode 15b6 and I'd love some examples.

You should import the package/library that contains the protocol in your macro package.
Otherwise it's a recipe for disaster. because potentially you will add this macro to something that doesn't know the type you are adding.

But that should be another post in my opinion.

1 Like

There are some macro examples in the swift-syntax repository. Here's an example of an extension macro: https://github.com/apple/swift-syntax/blob/main/Examples/Sources/ExamplePlugin/Macros.swift#L92

public struct SendableExtensionMacro: ExtensionMacro {
  public static func expansion(
    of node: AttributeSyntax,
    attachedTo: some DeclGroupSyntax,
    providingExtensionsOf type: some TypeSyntaxProtocol,
    conformingTo protocols: [TypeSyntax],
    in context: some MacroExpansionContext
  ) throws -> [ExtensionDeclSyntax] {
    if protocols.isEmpty {
      return []
    }

    let sendableExtension: DeclSyntax =
      """
      extension \(type.trimmed): Sendable {}
      """

    guard let extensionDecl = sendableExtension.as(ExtensionDeclSyntax.self) else {
      return []
    }

    return [extensionDecl]
  }
}

Applying your example, something like this should generate the extension you need:

public struct ExampleMacro: ExtensionMacro {
  public static func expansion(
    of node: AttributeSyntax,
    attachedTo: some DeclGroupSyntax,
    providingExtensionsOf type: some TypeSyntaxProtocol,
    conformingTo protocols: [TypeSyntax],
    in context: some MacroExpansionContext
  ) throws -> [ExtensionDeclSyntax] {
    // you might want to assert that 'protocols' is empty here if your macro does not add conformances

    let decl: DeclSyntax =
      """
      extension \(type.trimmed) where Self: View, Bar: View {
        func doDefaultThing() {
          // implementation here
        }
      }
      """

    guard let extensionDecl = decl.as(ExtensionDeclSyntax.self) else {
      return []
    }

    return [extensionDecl]
  }
}
1 Like

Wow — thanks so much for taking the time to answer my question, Holly! Your example worked perfectly and accomplished exactly what I was hoping to do. :tada: I wasn't expecting it to be so easy to translate a multiline string directly into ExtensionDeclSyntax — this is awesome!

Thanks again for your help and all your exciting work on these new language capabilities :raised_hands:

1 Like