Where are attached macros actually expanded?

Hey! :wave:t2:

Recently, I’ve been researching Swift macros, and in the process, I came across some strange behavior which I’m struggling to understand.

The documentation implies that macros are expanded in-place:

  1. The compiler replaces the macro call with its expanded form.

From that I infer that the normal access-control principles apply, specifically:

Private access restricts the use of an entity to the enclosing declaration, and to extensions of that declaration that are in the same file.

However, the latter part seems to not be true, at least in the following scenario.

I’ve got a structure whose members are declared private but used to fulfill the Equatable requirements in an extension.

struct User {
    private let id: Int
    private let payload: Any?
}

// Manual conformance:
extension User: Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.id == rhs.id
    }
}

I created an InferEquatable(basedOn:) attached (extension) macro which emits the same extension:

enum InferEquatableMacro: ExtensionMacro {
    
    static func expansion(…) throws -> [ExtensionDeclSyntax] {
        // Get `memberName` from arguments
        // Do other stuff (e.g. make sure the member exists and conforms to `Equatable`)
        
        return [
            try ExtensionDeclSyntax("extension \(type.trimmed): Equatable") {
                """
                    static func == (lhs: Self, rhs: Self) -> Bool {
                        lhs.\(raw: memberName) == rhs.\(raw: memberName)
                    }
                """
            }
        ]
    }
    
}

After applying the macro to User, I get the following expanded code:

@InferEquatable(basedOn: "id")
struct User {
    private let id: Int
    private let payload: Any?
}

// Expanded macro (as shown by Xcode):
extension User: Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.id == rhs.id // ERROR: 'id' is inaccessible due to 'private' protection level
    }
}

However, this code, despite being 100% equivalent to my own, fails to build because of access-control violations. I’m getting two compiler errors, one for each of the two id references.

Any idea what could be the case? And where are attached macros expanded, actually?

Environment
  • Xcode 15.0.1
  • Swift 5.9
  • swift-syntax 509.0.0
2 Likes

I run into the same thing and I could only solve this by changing private to fileprivate.