Assume we have an enum like this:
enum Currency: String {
case usd = "USD"
case eur = "EUR"
case `try` = "TRY"
... 100 more lines ...
}
This enum is helpful when dealing with ISO-4217 currency codes, and may be generated fully automatically (which I've done via simple swift script).
Trying to implements a macro that could generate enum without any need to call any shell script build phases.
Can't figure out how can I achieve that. Tried different approaches:
- freestanding macro that simply returns a string that then is converted to
ExprSyntax
public struct CurrenciesGenerateMacro: ExpressionMacro {
public static func expansion(of node: some SwiftSyntax.FreestandingMacroExpansionSyntax, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> SwiftSyntax.ExprSyntax {
return """
enum Currency: String {
case adp = "ADP"
case aed = "AED"
}
"""
}
}
(actually it's a bit more complicated, with a lookup to NSLocale.isoCurrencyCodes
, but there's some weird error with is, I will mention it down below)
and the compiler warns me this doesn't work:
- tried to make an attached(member, names: arbitrary). but the compiler warns me there are no cases in enum for it to be a RawRepresentable:
@GenerateCurrencies
enum Currency: String { // <- 'Currency' declares raw type 'String', but does not conform to RawRepresentable and conformance could not be synthesized
}
but expanding a macro show all the cases are generated inside
Also, when debugging my implementation, I've faced with an error I cannot clarify for me:
What is wrong here, hence the macro seemed to expand everything correctly?
The macro code is:
extension String {
func wrappedInTicksIfNeeded() -> String {
let keywords: Set<String> = ["for", "try", "let", "var"]
return keywords.contains(self) ? "`\(self)`" : self
}
}
public struct CurrenciesGenerateMacro: ExpressionMacro {
public static func expansion(of node: some SwiftSyntax.FreestandingMacroExpansionSyntax, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> SwiftSyntax.ExprSyntax {
var result = "enum Currency: String {"
NSLocale.isoCurrencyCodes.forEach { code in
if let description = NSLocale.current.localizedString(forCurrencyCode: code) {
result += "\n /// \(description)"
} else {
result += "\n"
}
result += "\n case \(code.lowercased().wrappedInTicksIfNeeded()) = \"\(code)\"\n"
}
result += "}"
return ExprSyntax(stringLiteral: result)
}
}
Tried to eliminate any parsing issues via tree builder, but cannot figure out how to return a tree as an ExprSyntax
:
public struct CurrenciesGenerateMacro: ExpressionMacro {
public static func expansion(of node: some SwiftSyntax.FreestandingMacroExpansionSyntax, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> SwiftSyntax.ExprSyntax {
let syntax = TypeSyntax(
EnumDeclSyntax(name: .identifier("Currency"),
inheritanceClause: InheritanceClauseSyntax(inheritedTypes: InheritedTypeListSyntax {
InheritedTypeSyntax(type: IdentifierTypeSyntax(name: .identifier("String")))
}),
memberBlock: MemberBlockSyntax {
MemberBlockItemListSyntax {
for code in NSLocale.isoCurrencyCodes {
MemberBlockItemSyntax(decl:
EnumCaseDeclSyntax(leadingTrivia: Trivia.lineComment(NSLocale.current.localizedString(forCurrencyCode: code) ?? code.uppercased())) {
EnumCaseElementListSyntax {
EnumCaseElementSyntax(name: .identifier(code.lowercased().wrappedInTicksIfNeeded()),
rawValue: InitializerClauseSyntax(value: StringLiteralExprSyntax(content: code)))
}
}
)
}
}
})
return ??
}