swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.41k stars 10.34k forks source link

Macro: unable to add Comparable conformance if user already defined required functions #70087

Open rayx opened 10 months ago

rayx commented 10 months ago

Description

I'm writing a macro to add Comparable conformance. It expands the code

@Foo
struct Name {
    var value: String
}

to

struct Name {
    var value: String
}

extension Name: Comparable {
    public static func < (lhs: Name, rhs: Name) -> Bool {
        lhs.value < rhs.value
    }
}

If user already defines < function, the macro skips generating it. In this case the expanded code is like the following:

struct Name {
    var value: String

    public static func < (lhs: Name, rhs: Name) -> Bool {
        lhs.value < rhs.value
    }
}

extension Name: Comparable {
}

The expanded code is valid (it compiles), but unfortunately the unexpanded code doesn't compile, with the following error:

Type 'Name' does not conform to protocol 'Comparable' Multiple matching functions named '<' with type '(Name, Name) -> Bool'

My further experiments show that Equatable or CustomStringConvertible doesn't have this issue. I also experimented custom protocols which have instance or static functions, they all worked fine. So the issue seems to be specific to Comparable.

Swift forums thread: https://forums.swift.org/t/macro-multiple-matching-functions-xyz-error-though-the-expanded-code-has-no-duplicate-functions-and-compiles/68700

Reproduction

Please note I simplified the code so it doesn't even actually generate < function in macro implementation, but just claims "<" function name in macro interface. But still it caused the error.

Macro implementation:

public enum FooMacro: 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 comparableExtension = try ExtensionDeclSyntax(
                """
                extension \(type.trimmed): Comparable {
                }
                """
            )

        return [comparableExtension]
    }
}

Macro interface:

// Note: chaning the name argument to "named(<(lhs:,rhs:))" has the same error
@attached(extension, conformances: Comparable, names: named(<))
public macro Foo() = #externalMacro(module: "impl", type: "FooMacro")

Test:

// Compile error:
//   Type 'Name' does not conform to protocol 'Comparable'
//   Multiple matching functions named '<' with type '(Name, Name) -> Bool'
// But the expanded code compiles.
@Foo
struct Name {
    var value: String

    public static func < (lhs: Name, rhs: Name) -> Bool {
        lhs.value < rhs.value
    }
}

Expected behavior

It should compile.

Environment

$ /Users/app/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -version swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) Target: x86_64-apple-macosx13.0

Xcode: 15.0, macOS: 13.6

Additional information

No response

vanvoorden commented 5 months ago

@rayx You might want to file this in the swift-syntax repo. Is there one being tracked there?

rayx commented 5 months ago

@vanvoorden In my understanding this is a macro issue, not a swift-syntax issue. Macro issue should be filed in swift repo (see #70299 for example).

vanvoorden commented 4 months ago

@ahoppen would you have any ability to help triage this? Are there any other tasks I can follow along for a potential fix? Thanks!

vanvoorden commented 4 months ago

https://forums.swift.org/t/swift-macro-member-named-symbols-interfering-with-compiler-protocol-conformance-check/71433/

Here is an additional test case.

hborla commented 4 months ago

Has anybody tested this using a recent 6.0 nightly snapshot? We fixed an issue a few months back where name lookup wasn't expanding extension macros, and that impacted protocol conformance checking when implementations were meant to be satisfied by extension macro expansions.

vanvoorden commented 4 months ago
diff --git a/Examples/Sources/MacroExamples/Interface/ComplexMacros.swift b/Examples/Sources/MacroExamples/Interface/ComplexMacros.swift
index da160c99..990c8846 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(==))
 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..a6396cdd 100644
--- a/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift
+++ b/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift
@@ -17,9 +17,10 @@ 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: Equatable {
   var x: Int = 1
   var y: Int = 2
+  static func == (lhs: Point, rhs: Point) -> Bool { fatalError() }
 }

 func runDictionaryStorageMacroPlayground() {

@hborla Here is the diff I have to repro a similar compiler error building the swift-syntax examples from swift-6.0-DEVELOPMENT-SNAPSHOT-2024-05-14.