-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Description
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