Hi! I'm seeing some unexpected behavior when adding named symbols to a member
macro declaration. The name seems to be interfering with some Swift Protocol Conformance (Equatable
) in an unexpected way.
I'm starting from the main
branch of swift-syntax
and hacking on DictionaryStorage
(from Examples
). I start by conforming a Point
to Equatable
:
@DictionaryStorage
struct Point: Equatable {
var x: Int = 1
var y: Int = 2
}
This leads to a compiler error (the compiler does not synthesize conformance because the underlying Dictionary
does not conform to Equatable
. This makes sense… and I can create a void implementation to pass the compiler check:
@DictionaryStorage
struct Point: Equatable {
static func == (lhs: Point, rhs: Point) -> Bool { fatalError() }
var x: Int = 1
var y: Int = 2
}
This compiles. Suppose our DictionaryStorage
were somehow smart enough to synthesize an ==
function for us. We could start by adding that function name to the macro declaration:
@attached(memberAttribute)
@attached(member, names: named(_storage), named(==))
public macro DictionaryStorage() = #externalMacro(module: "MacroExamplesImplementation", type: "DictionaryStorageMacro")
It is legit to add symbol names without actually generating those symbols:[1]
When a macro declaration includes the
names:
argument, the macro implementation must generate only symbol with names that match that list. That said, a macro need not generate a symbol for every listed name.
This now leads to a compiler error on the Point
struct:
error: type 'Point' does not conform to protocol 'Equatable'
struct Point: Equatable {
^
Swift.Equatable:2:17: note: multiple matching functions named '==' with type '(Point, Point) -> Bool'
static func == (lhs: Self, rhs: Self) -> Bool
^
/Users/rick/Developer/swift-syntax/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift:21:15: note: candidate exactly matches
static func == (lhs: Point, rhs: Point) -> Bool { fatalError() }
^
/Users/rick/Developer/swift-syntax/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift:21:15: note: candidate exactly matches
static func == (lhs: Point, rhs: Point) -> Bool { fatalError() }
Where did this come from? My macro is not generating an ==
function… only declaring that it might choose to generate an ==
function.
What about Hashable
? Same problem?
@attached(memberAttribute)
@attached(member, names: named(_storage), named(hash))
public macro DictionaryStorage() = #externalMacro(module: "MacroExamplesImplementation", type: "DictionaryStorageMacro")
@DictionaryStorage
struct Point: Hashable {
static func == (lhs: Point, rhs: Point) -> Bool { fatalError() }
func hash(into hasher: inout Hasher) { fatalError() }
var x: Int = 1
var y: Int = 2
}
This code compiles without a problem… weird.
Why is that first example failing to compile? And why is that second example not failing to compile?
What about another protocol with a binary operator? Like Comparable
?
@attached(memberAttribute)
@attached(member, names: named(_storage), named(<), named(hash))
public macro DictionaryStorage() = #externalMacro(module: "MacroExamplesImplementation", type: "DictionaryStorageMacro")
@DictionaryStorage
struct Point: Comparable {
static func < (lhs: Point, rhs: Point) -> Bool { fatalError() }
static func == (lhs: Point, rhs: Point) -> Bool { fatalError() }
func hash(into hasher: inout Hasher) { fatalError() }
var x: Int = 1
var y: Int = 2
}
This code also fails (with a similar error to the Equatable
example).
Any ideas about what could be causing this? Does this error look legit… or is there some strange bug happening here?
Here is a diff on swift-syntax
to repro:
diff --git a/Examples/Sources/MacroExamples/Interface/ComplexMacros.swift b/Examples/Sources/MacroExamples/Interface/ComplexMacros.swift
index da160c99..46ca744e 100644
--- a/Examples/Sources/MacroExamples/Interface/ComplexMacros.swift
+++ b/Examples/Sources/MacroExamples/Interface/ComplexMacros.swift
@@ -21,7 +21,7 @@
/// * Member macro expansion, to add a `_storage` property with the actual
/// dictionary.
@attached(memberAttribute)
-@attached(member, names: named(_storage))
+@attached(member, names: named(_storage), named(==), named(hash))
public macro DictionaryStorage() = #externalMacro(module: "MacroExamplesImplementation", type: "DictionaryStorageMacro")
@attached(accessor)
diff --git a/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift b/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift
index 8e02d839..9c8a7207 100644
--- a/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift
+++ b/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift
@@ -17,7 +17,11 @@ import MacroExamplesInterface
// Move the storage from each of the stored properties into a dictionary
// called `_storage`, turning the stored properties into computed properties.
@DictionaryStorage
-struct Point {
+struct Point: Hashable {
+ static func == (lhs: Point, rhs: Point) -> Bool { fatalError() }
+
+ func hash(into hasher: inout Hasher) { fatalError() }
+
var x: Int = 1
var y: Int = 2
}