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
appendInterpolations are implemented quite differently (source):Clearly, the
SyntaxProtocoloverload is "smarter". It applies indentation to the code in the given syntax node, and also adds the node to theinterpolatedSyntaxNodesarray. Theraw:overload only convertsTtoString, then appends it tosourceText.Then I tried to figure out how
interpolatedSyntaxNodesis used. For that, I went toinit(stringInterpolation:):At the time of writing, it seems like
interpolatedSyntaxNodesis not used at all. Whichever overload I use,sourceTextgets appended with the new code, and then parsed byinit(stringInterpolation:).The FIXME comment suggests that in the future, the things in
interpolatedSyntaxNodeswon'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 nodebinding.patternitself in the AST - we want to form a new pattern node with_and the textual content ofbinding.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.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.