I'm trying to add a case to an enum using macros along with an init to default it to the added case. For example, say my enum is:
@AddEnumCase
enum Employee: String {
case manager
}
then the expanded version should give:
enum Employee: String {
case manager
case developer
init(rawValue: String) {
switch rawValue {
case Self.manager.rawValue:
self = .manager
default:
self = .developer
}
Here is my code so far. It builds successfully when nothing is typed on main.swift and the tests also succeeds. But when the macro is used in main.swift, it fails to build with:
Command SwiftCompile failed with a nonzero exit code
public struct AddEnumCase: MemberMacro {
public static func expansion(of node: ....) throws -> [SwiftSyntax.DeclSyntax] {
guard let enumDecl = declaration.as(EnumDeclSyntax.self) else { return [] }
guard let inheritanceType = enumDecl.inheritanceClause?.inheritedTypes.first?.type else { return [] }
let cases = enumDecl.memberBlock.members.compactMap { $0.decl.as(EnumCaseDeclSyntax.self)?.elements) }
let newElement = try EnumCaseDeclSyntax("case developer")
let initializer = try InitializerDeclSyntax("init(rawValue: \(inheritanceType.trimmed))") {
try SwitchExprSyntax("switch rawValue") {
for name in cases {
SwitchCaseSyntax("""
case Self.\(raw: name).rawValue:
self = .\(raw: name)
""")
}
SwitchCaseSyntax("""
default:
self = .developer
""")
}
}
return [DeclSyntax(newElement), DeclSyntax(initializer)]
}
}
@attached(member, names: arbitrary)
public macro AddEnumCase() = #externalMacro(module: "MyMacros", type: "AddEnumCase")
The following code fails on main.swift
@AddEnumCase
enum Employee: String {
case Manager
}
Looking at the crash log, I can see that something went wrong when generating the
rawValue
for theRawRepresentable
conformance. It could be possible that theswitch
in the generatedrawValue
is not exhaustive because it didn't take into account your new case.Swift macro expansions shouldn't depend on the order of expansion. All the macros are given the original AST and they expand "at the same time". I think this might also be true for how
: String
gets lowered. You are depending on: String
lowering after your macro expands, which apparently is not the case here.What this basically means is that you also have to generate the
rawValue
implementation.Here is an example, only for
String
raw values:For numeric raw values, you would need to keep a counter that increments for each case, which is a bit more involved. To 100% replicate what Swift would have generated, you would also need to set the counter to any explicitly declared raw values. This is left as an exercise for the reader. (:D)