@attached(extension, conformances:) macro-generated conformance invisible across files in same module

Hey all,

I’m kinda trying to see if this is a real bug (feels like one), or am I “Holding it wrong” before I send a FB.

Small isolated repro example here:

But here are the main portions of it:

public protocol Storable {
    static var tableName: String { get }
}

public protocol MyModel: Storable {}

extension Storable {
    public static func deleteOne(key: String) -> Bool { true }
}

I have a protocol with a static deleteOne(key:) method on it.

Then, I have a macro which conforms a type to this protocol, like so:

@attached(extension, conformances: MyModel)
public macro Model() = #externalMacro(module: "MacroBugMacros", type: "ModelMacro")

public struct ModelMacro: 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] {
        let ext: DeclSyntax = "extension \(type.trimmed): MyModel {}"
        guard let extDecl = ext.as(ExtensionDeclSyntax.self) else { return [] }
        return [extDecl]
    }
}

When I attach the macro to a file, it doesn’t seem to see the static function. But If I attach the protocol “by hand”, it works correctly.

// File: NewFile.swift
@Model
struct Item: Sendable {
    static let tableName = "items"
    let slug: String
}

// File: main.swift
_ = Item.deleteOne(key: "item-1")
// ❌ error: referencing static method 'deleteOne(key:)' on 'Storable'
//           requires that 'Item' conform to 'Storable'

// Workaround
// Declare conformance directly on the struct instead of via the macro extension:

// @Model
// struct Item: MyModel, Sendable { ... }  // ✅ works

I believe @stephencelis & @mbrandonw had a similar bug in TCA Macros IIRC but might be wrong.

If anyone has any ideas I’d love to hear :slight_smile: Thanks!

Was able to write a reproducible test for it and fixed it (i hope!)

3 Likes

As an alternative workaround, we could perhaps declare conformance for both MyModel and Storable, which would not require user to specify conformance explicitly, though this fix would be welcome

-@attached(extension, conformances: MyModel)
+@attached(extension, conformances: MyModel, Storable)
public macro Model() = #externalMacro(module: "MacroBugMacros", type: "ModelMacro")

public struct ModelMacro: ExtensionMacro {
    public static func expansion(...) throws -> [ExtensionDeclSyntax] {
-       let ext: DeclSyntax = "extension \(type.trimmed): MyModel {}"
+       let ext: DeclSyntax = "extension \(type.trimmed): MyModel, Storable {}"
        // ...
        return [extDecl]
    }
}

Thanks! Yeah, that’s what I ended up doing, but agree it would be cool to have it “just work”