I am writing a macro that generates a code block that mostly consists of static content, with a part in the middle that is dynamic depending on the macro arguments. Here is an example to illustrate:
enum ExampleMacro: ExpressionMacro {
static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
let dynamicPart = CodeBlockItemListSyntax {
// I am using Int.random(..) to show that this part is dynamic
// in the real code it won't be Int.random(...) of course
for i in 0..<Int.random(in: 10..<20) {
"var value\(raw: i) = 0"
}
}
return """
someFunction {
// ... static portion that's always there...
\(dynamicPart)
// ... static portion that's always there...
}
"""
}
}
@freestanding(expression)
public macro example() = #externalMacro(module: "MyMacroMacros", type: "ExampleMacro")
Unexpectedly, #example
expands to
someFunction {
// ... static portion that's always there...
var value0 = 0var
value1 = 0var
value2 = 0var
value3 = 0var
value4 = 0var
value5 = 0var
value6 = 0var
value7 = 0var
value8 = 0var
value9 = 0
// ... static portion that's always there...
}
giving me many compiler errors about "0var".
How can I fix this?
When you add a whitespace or a semicolon to the end of the line, it works just fine. But I think I know what causes this. Look at the string below:
"var value\(raw: i) = 6 func foo() {}"
When I input it to the CodeBlockItemListSyntax
the macro generates this:
var value0 = 6
func foo() {
}
Did you see what it did? It automatically indented the code for you. It also does the same thing with the semicolon (and also escape sequences?), too:
"var value\(raw: i) = 6;func foo() {}"
Into:
var value0 = 6;
func foo() {
}
I think what CBILS
doest is just stash the string literals side by side (using your input):
"var value1 = 0var value2 = 0var value3 = 0"
When swift tries to parse this it does it like so:
(var value1 = 0var) (value2 = 0var) (value3 = 0)
┬────────────┬─── ┬──────────┬── ─┬────────
| ╰some | ╰─some ╰ set value
╰─ var init. value╰─ set value value
And when swift tries to indent this it puts a line break between every statement (in parenthesis), so the end result becomes:
var value1 = 0var
value2 = 0var
value3 = 0
But if you make the value a string literal an instead of an integer literal it works fine. Why is that?
Because anything that has a start and an end (terminating) (e.g. ()
""
[]
{}
) has no possibility of intersecting with something (e.g. ""abc
-> ("")(abc)
)
The developer for this library has forgot to put seperators between the code blocks. So put a whitespace or a semicolon at the end to fix this issue. And report the bug to the authors. :)