Hi,
I was wondering if it is possible to incorporate compiler directives into the expression syntax. This could greatly reduce the amount of repetitive code caused by compiler control statements or preprocessor macros (e.g. #if DEBUG
statements). However, Xcode currently is unable to evaluate compiler control statements that are expanded from macros, resulting in the following error:
Error: Expected macro expansion to produce an expression
One potential workaround is to directly apply the compiler control statements within the macro. However, the conditional compilation behavior will not be visible at call site in this case, leading to ambiguous code generations.
Example: The following macro appends a Swift version suffix to a given string based on the compiler version.
/// A macro that adds a suffix to the given string.
@freestanding(expression)
public macro addSuffix(_ string: String) -> String = #externalMacro(module: "StringSuffixMacros", type: "AddSuffixMacro")
public struct AddSuffixMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
guard let argument = node.argumentList.first?.expression,
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
segments.count == 1,
case .stringSegment(let literalSegment)? = segments.first
else {
fatalError("#addSuffix requires a string literal")
}
// Xcode fails to evaluate the expanded macro
let exprSyntax: ExprSyntax =
"""
#if compiler(>=6)
"\(raw: literalSegment.content.text)_Swift6"
#elseif compiler(>=5)
"\(raw: literalSegment.content.text)_Swift5"
#else
"\(raw: literalSegment.content.text)_LegacySwift"
#endif
"""
// Workaround
let exprSyntax: ExprSyntax
#if compiler(>=6)
exprSyntax = "\"\(raw: literalSegment.content.text)_Swift6\""
#elseif compiler(>=5)
exprSyntax = "\"\(raw: literalSegment.content.text)_Swift5\""
#else
exprSyntax = "\"\(raw: literalSegment.content.text)_LegacySwift\""
#endif
return exprSyntax
}
}