Skip to content

Type generated by peer macro can't conform to Equatable #68683

@KeithBauerANZ

Description

@KeithBauerANZ

Description

I want to use a peer macro to create a new type that conforms to the same protocols as the type it's attached to. This seems to work for Hashable, Encodable, Decodable, but when I try to implement Equatable, I get compiler errors. Here's a reduction of the problem:

struct Inequatable {}

@Duplicate
struct S: Equatable {
    let preventSynthesis = Inequatable()
    static func == (lhs: Self, rhs: Self) -> Bool {
        true
    }
}

And this is what the Duplicate macro expands to:

struct S_Duplicate: Equatable {
    let preventSynthesis = Inequatable()
    static func == (lhs: Self, rhs: Self) -> Bool {
        true
    }
}

I get: error: type 'S_Duplicate' does not conform to protocol 'Equatable'

For completeness, here's my stupid macro:

public struct DuplicateMacro: PeerMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingPeersOf declaration: some DeclSyntaxProtocol,
        in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
        var dup = declaration.as(StructDeclSyntax.self)!
        dup.attributes = [] // remove the macro to prevent recursion
        // dup.inheritanceClause = nil // remove the Equatable conformance (everything will compile, but S_Duplicate won't actually be Equatable)
        dup.name = TokenSyntax(.identifier("\(dup.name.text)_Duplicate"), presence: .present)
        return [dup.as(DeclSyntax.self)!]
    }
}

@main
struct MacroEquatablePlugin: CompilerPlugin {
    let providingMacros: [Macro.Type] = [
        DuplicateMacro.self,
    ]
}

and its declaration:

@attached(peer, names: suffixed(_Duplicate))
public macro Duplicate() = #externalMacro(module: "MacroEquatableMacros", type: "DuplicateMacro")

Steps to reproduce

Use Xcode's "new package" to create a macro package. Delete the unit tests.

Replace the macro declaration, macro implementation, and client code with the above snippets.

Compile (or rather, fail to). Full error output:

@__swiftmacro_20MacroEquatableClient1S9DuplicatefMp_.swift:1:8: error: type 'S_Duplicate' does not conform to protocol 'Equatable'
struct S_Duplicate: Equatable {
       ^
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
@__swiftmacro_20MacroEquatableClient1S9DuplicatefMp_.swift:2:9: note: stored property type 'Inequatable' does not conform to protocol 'Equatable', preventing synthesized conformance of 'S_Duplicate' to 'Equatable'
    let preventSynthesis = Inequatable()
        ^
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
Swift.==:1:24: note: candidate would match if 'S_Duplicate' conformed to 'RawRepresentable'
@inlinable public func == <T>(lhs: T, rhs: T) -> Bool where T : RawRepresentable, T.RawValue : Equatable
                       ^
Swift.FloatingPoint:2:24: note: candidate would match if 'S_Duplicate' conformed to 'FloatingPoint'
    public static func == (lhs: Self, rhs: Self) -> Bool
                       ^
Swift.BinaryInteger:2:24: note: candidate would match if 'S_Duplicate' conformed to 'BinaryInteger'
    public static func == <Other>(lhs: Self, rhs: Other) -> Bool where Other : BinaryInteger
                       ^
Swift._Pointer:2:24: note: candidate would match if 'S_Duplicate' conformed to '_Pointer'
    public static func == (lhs: Self, rhs: Self) -> Bool
                       ^
Swift._Pointer:3:35: note: candidate would match if 'S_Duplicate' conformed to '_Pointer'
    @inlinable public static func == <Other>(lhs: Self, rhs: Other) -> Bool where Other : _Pointer
                                  ^
Swift.Strideable:3:35: note: candidate would match if 'S_Duplicate' conformed to 'Strideable'
    @inlinable public static func == (x: Self, y: Self) -> Bool
                                  ^
Swift.StringProtocol:2:35: note: candidate would match if 'S_Duplicate' conformed to 'StringProtocol'
    @inlinable public static func == <RHS>(lhs: Self, rhs: RHS) -> Bool where RHS : StringProtocol
                                  ^
Swift.SIMD:4:24: note: candidate would match if 'S_Duplicate' conformed to 'SIMD'
    public static func == (a: Self, b: Self) -> Bool
                       ^
@__swiftmacro_20MacroEquatableClient1S9DuplicatefMp_.swift:1:8: note: do you want to add protocol stubs?
struct S_Duplicate: Equatable {
       ^
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~

Expected behavior

I expect my macro-generated struct to be able to conform to Equatable

Environment
$ swiftc --version

swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
Target: arm64-apple-macosx13.0

$ xcodebuild -version

Xcode 15.0
Build version 15A240d

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.triage neededThis issue needs more specific labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions