Consider this macro that generates an extra Int
property as a peer, prefixed with _
, just as an example:
public enum SomeMacro: PeerMacro {
public static func expansion(of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
guard let binding = declaration.as(VariableDeclSyntax.self)?.bindings.first else {
return []
}
return ["var _\(raw: binding.pattern) = 0"]
}
}
In the examples in the Swift Syntax repository (such as CaseDetection
), and is also what I did above, \(raw: ...)
was used to interpolate the old name to form the name of the new member.
From looking at the documentation for SyntaxStringInterpolation
, I found that there is also an appendInterpolation
overload with no parameter labels, and takes anything that conforms to SyntaxProtocol
. i.e. I can do:
return ["var _\(binding.pattern) = 0"]
This produces the same expansion in this case.
What is the difference between these two overloads of appendInterpolation
? When should I use each one? The documentation for SyntaxStringInterpolation
doesn't say. Is there a situation where they would expand to substantially different code?
Of course, I understand that one can put anything in\(raw: ...)
- I'm only comparing the situation where one is interpolating syntax nodes.
Side note: I also found another overload that takes a SyntaxProtocol
and a format:
parameter, so perhaps the difference between \(...)
and \(raw: ...)
is only whether formatting is performed?
After digging around in the SwiftSyntax source code, I found the two appendInterpolation
s are implemented quite differently (source):
public mutating func appendInterpolation<Node: SyntaxProtocol>( _ node: Node ) { let startIndex = sourceText.count let indentedNode: Node if let lastIndentation { indentedNode = Indenter.indent(node, indentation: lastIndentation) } else { indentedNode = node } sourceText.append(contentsOf: indentedNode.syntaxTextBytes) interpolatedSyntaxNodes.append( .init( node: Syntax(indentedNode), startIndex: startIndex, endIndex: sourceText.count ) ) self.lastIndentation = nil } public mutating func appendInterpolation<T>(raw value: T) { sourceText.append(contentsOf: String(describing: value).utf8) self.lastIndentation = nil }
Clearly, the SyntaxProtocol
overload is "smarter". It applies indentation to the code in the given syntax node, and also adds the node to the interpolatedSyntaxNodes
array. The raw:
overload only converts T
to String
, then appends it to sourceText
.
Then I tried to figure out how interpolatedSyntaxNodes
is used. For that, I went to init(stringInterpolation:)
:
public init(stringInterpolation: SyntaxStringInterpolation) { self = stringInterpolation.sourceText.withUnsafeBufferPointer { buffer in var parser = Parser(buffer) // FIXME: When the parser supports incremental parsing, put the // interpolatedSyntaxNodes in so we don't have to parse them again. let result = Self.parse(from: &parser) return result } if self.hasError { self.logStringInterpolationParsingError() } }
At the time of writing, it seems like interpolatedSyntaxNodes
is not used at all. Whichever overload I use, sourceText
gets appended with the new code, and then parsed by init(stringInterpolation:)
.
The FIXME comment suggests that in the future, the things in interpolatedSyntaxNodes
won't be parsed again.
From this, I think it is quite safe to assume that \(...)
is designed to be used when you want to add the syntax node itself to the AST, not when you just want to interpolate the textual content of that node. In cases like "var _\(binding.pattern) = 0"
, we don't want the node binding.pattern
itself in the AST - we want to form a new pattern node with _
and the textual content of binding.pattern
.
That said, I'm not sure what the parser will do to "var _\(binding.pattern) = 0"
, once incremental parsing is supported, but it is possible that it might fail to parse it.
To summarise, use \(...)
when you want to put the node itself into the AST, so that it doesn't have to be parsed again, e.g.
"var x = \(expr)" // where expr is an ExprSyntax
Use \(raw:...)
when you want to interpolate the textual content of a node to form new nodes, such as when forming new names, or referring to newly formed names.