I discovered an issue I'm having with attached member macros, and Comparable conformance. I distilled it down to the following case, but I'm not identifying the issue, and therefore I'm thinking that there may be a bug. As usual this example is coming from a more intricate version. This is being done through Xcode. I have the following:
@testForceConstantVarDecl()
struct Junk: Comparable {
var ident: Int
#if true
static public
func < (lhs: Junk, rhs: Junk) -> Bool {
lhs.ident < rhs.ident
}
#endif
}
When I expand the macro in Xcode, I see--which is what was desired:
struct Junk: Comparable {
var ident: Int
#if true
static public
func < (lhs: Junk, rhs: Junk) -> Bool {
lhs.ident < rhs.ident
}
#endif
var hello: String
}
However in this case, the compiler complains with:
...: error: type 'Junk' does not conform to protocol 'Comparable'
struct Junk: Comparable {
^
Swift.Comparable:2:17: note: multiple matching functions named '<' with type '(Junk, Junk) -> Bool'
static func < (lhs: Self, rhs: Self) -> Bool
^
...: note: candidate exactly matches
func < (lhs: Junk, rhs: Junk) -> Bool {
^
...: note: candidate exactly matches
func < (lhs: Junk, rhs: Junk) -> Bool {
When I comment out the macro, as in:
//@testForceConstantVarDecl()
struct Junk: Comparable {
var ident: Int
#if true
static public
func < (lhs: Junk, rhs: Junk) -> Bool {
lhs.ident < rhs.ident
}
#endif
}
The compiler does not complain.
In the macro package the following are created in the appropriate files:
Macro Declaration
@attached(member, names: arbitrary)
public macro testForceConstantVarDecl() =
#externalMacro(
module: "PurGMCMacrosPlugin",
type: "PurGMCTestForceConstantVarDecl")
Macro Reference
#if canImport(SwiftCompilerPlugin)
import SwiftCompilerPlugin
import SwiftSyntaxMacros
@main
struct PurGMCMacrosPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
...,
PurGMCTestForceConstantVarDecl.self
]
}
#endif
Macro Implementation
import Foundation
import SwiftSyntax
import SwiftSyntaxMacros
public struct PurGMCTestForceConstantVarDecl: MemberMacro {
public static
func expansion(of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext) throws ->
[DeclSyntax] {
guard let attrsArgs = node.arguments,
case let .argumentList(attrsArgsList) = attrsArgs,
attrsArgsList.count == 0 //,
else {
return []
}
return [
"""
\n var hello: String
"""
]
}
}
So I'm suspecting the compiler is automatically generating, or believing it should/has generated Comparable conformance when the macro invocation is not commented out. If it has generated it, I do not know how to see it.
When the macro is "turned on", and the #if true
directive is changed to #if false
, the compiler correctly (I believe) complains with:
Automatic synthesis of 'Comparable' is not supported for struct declarations
I'm therefore confused by the state where the macro and compiler directive are both "on". Is there something that I'm doing incorrectly? Although this is a trivial macro, which would have no real use, I'm a little hesitant calling this a bug, since I would think this would have been seen. This test case is derived from a parametrized macro which provides for proprietary boiler plate code generation. It all works except for this conformance issue exemplified by this test case. Maybe this is a known issue. Is it?
Work Around
I soon realized this was solvable via an extension. So the above test code becomes:
@testForceConstantVarDecl()
struct Junk {
var ident: Int
#if true
static public
func < (lhs: Junk, rhs: Junk) -> Bool {
lhs.ident < rhs.ident
}
#endif
}
extension Junk: Comparable {
}
So yeah this works, and now I've convinced myself that yes this is a bug. Any incite? Even though the solution is fine, I thought I would post anyway, in case I learned something new from someone.