提问人:Sweeper 提问时间:10/8/2023 更新时间:10/9/2023 访问量:71
在为 Swift 宏创建语法节点时,\(...) 和 \(raw: ...) 有什么区别?
What is the difference between \(...) and \(raw: ...) when creating syntax nodes for Swift Macros?
问:
考虑以下宏,该宏生成一个额外的属性作为对等方,以 为前缀,仅作为示例:Int
_
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"]
}
}
在 Swift 语法存储库(例如 CaseDetection
)中的示例中,也是我上面所做的,用于插入旧名称以形成新成员的名称。\(raw: ...)
通过查看 SyntaxStringInterpolation
的文档,我发现还有一个没有参数标签的 appendInterpolation
重载,并采用任何符合 .即我可以做:SyntaxProtocol
return ["var _\(binding.pattern) = 0"]
在这种情况下,这将产生相同的扩展。
这两种重载有什么不一样?我应该在什么时候使用它们?文档没有说。是否存在它们会扩展到截然不同的代码的情况?appendInterpolation
SyntaxStringInterpolation
当然,我知道一个人可以放任何东西——我只是在比较一个人插值语法节点的情况。\(raw: ...)
旁注:我还发现了另一个接受 a 和 a 参数的重载,所以也许 和之间的区别只是是否执行格式化?SyntaxProtocol
format:
\(...)
\(raw: ...)
答:
在深入研究了 SwiftSyntax 源代码后,我发现这两个 s 的实现方式完全不同(源代码):appendInterpolation
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 }
显然,重载是“更聪明的”。它将缩进应用于给定语法节点中的代码,并将该节点添加到数组中。重载仅转换为 ,然后将其附加到 。SyntaxProtocol
interpolatedSyntaxNodes
raw:
T
String
sourceText
然后我试图弄清楚如何使用。为此,我转到了init(stringInterpolation:):
interpolatedSyntaxNodes
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() } }
在撰写本文时,它似乎根本没有使用。无论我使用哪种重载,都会附加新代码,然后由 .interpolatedSyntaxNodes
sourceText
init(stringInterpolation:)
FIXME的评论表明,将来不会再解析其中的内容。interpolatedSyntaxNodes
由此,我认为可以非常安全地假设,当您想将语法节点本身添加到 AST 时,而不是当您只想插入该节点的文本内容时,可以使用它。在像 这样的情况下,我们不希望节点本身在 AST 中 - 我们希望形成一个新的模式节点,其文本内容为 。\(...)
"var _\(binding.pattern) = 0"
binding.pattern
_
binding.pattern
也就是说,一旦支持增量解析,我不确定解析器会做什么,但它可能无法解析它。"var _\(binding.pattern) = 0"
总而言之,当您想将节点本身放入 AST 时,请使用,这样就不必再次解析它,例如\(...)
"var x = \(expr)" // where expr is an ExprSyntax
当您想要插入节点的文本内容以形成新节点时使用,例如在形成新名称时,或引用新形成的名称时。\(raw:...)
评论