Hey there
,
I believe it's possible to achieve your goal with the current state of Swift Macros. First, let's see how to extend the SomeAPI
protocol with implementations of its methods. This can be accomplished through the ExtensionMacro
feature.
public enum MyExtensionMacro: 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] {
// Ensure that the declaration is a protocol declaration
guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else {
// You can produce a diagnostic here if you wish.
return []
}
// Retrieve all methods from the protocol
var methods = protocolDecl.memberBlock.members
.map(\.decl)
.compactMap { $0.as(FunctionDeclSyntax.self) }
// Add implementation to all methods from the protocol
methods.indices.forEach { index in
methods[index].body = CodeBlockSyntax {
ExprSyntax(#"fatalError("whoops 😅")"#)
}
}
let extensionDecl = ExtensionDeclSyntax(extendedType: type) {
for method in methods {
MemberBlockItemSyntax(decl: method)
}
}
return [extensionDecl]
}
}
Next, let's create the macro interface:
@attached(extension, names: arbitrary)
public macro MyExtensionMacro() = #externalMacro(
module: "{your module name goes here}",
type: "MyExtensionMacro"
)
Note that we're not using the conformances
argument in the attached
attribute since the macro isn't introducing any new conformances. Instead, we use names
with the value arbitrary
because method names can vary.
Don't forget to register this macro in the providingMacros
field of your CompilerPlugin
implementation, like so:
#if canImport(SwiftCompilerPlugin)
import SwiftCompilerPlugin
import SwiftSyntaxMacros
@main
struct MyPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
MyExtensionMacro.self,
]
}
#endif
Now we can use the macro! 
@MyExtensionMacro
protocol SomeAPI {
func someQuery() throws -> String
func otherQuery() throws -> String
}
which expands to:

As you can see this macro will automatically add the fatalError
implementation to each of the protocol methods. You can also tailor this macro to add other default implementations based on your needs.
Now, let's discuss creating a new protocol. You're correct; you can't introduce an extension in a peer macro, but you can attach another macro to the new declaration created by a peer macro. The downside is that you'll need to copy the methods to the new protocol.
Here's how to implement it:
public enum MyPeerMacro: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Ensure that the declaration is a protocol declaration
guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else {
// Produce a diagnostic here if necessary.
return []
}
// Create a mutable copy of the original protocol declaration
var newProtocolDecl = protocolDecl
// Find and remove this macro attribute from the protocol declaration
let index = newProtocolDecl.attributes.firstIndex(where: { $0.trimmedDescription == "@MyPeerMacro" })
newProtocolDecl.attributes.remove(at: index!)
// Prefix the original protocol name to create a new name for the new protocol
newProtocolDecl.name = .identifier("MyPrefix" + protocolDecl.name.text)
// Update the inheritance clause to inherit from the original protocol
newProtocolDecl.inheritanceClause = InheritanceClauseSyntax(
inheritedTypes: InheritedTypeListSyntax(
arrayLiteral: InheritedTypeListSyntax.Element(
type: IdentifierTypeSyntax(name: protocolDecl.name)
)
)
)
// Attach the `MyExtensionMacro` attribute to the new protocol
newProtocolDecl.attributes.append(
.attribute(
AttributeSyntax(attributeName: IdentifierTypeSyntax(name: "MyExtensionMacro"))
)
)
return [DeclSyntax(newProtocolDecl)]
}
}
Since we'll always be adding a prefix to the name, the interface can be created as follows:
@attached(peer, names: prefixed(MyPrefix))
public macro MyPeerMacro() = #externalMacro(
module: "MacroExamplesImplementation",
type: "MyPeerMacro"
)
And, as before, don't forget to add the new macro to the providingMacros
in your implementation of CompilerPlugin
.
You can now happily use this setup in your unit tests! 
Hope this helps you out, and feel free to reach out if you have more questions or need further clarification. 