在为 Swift 宏创建语法节点时,\(...) 和 \(raw: ...) 有什么区别?

What is the difference between \(...) and \(raw: ...) when creating syntax nodes for Swift Macros?

提问人:Sweeper 提问时间:10/8/2023 更新时间:10/9/2023 访问量:71

问:

考虑以下宏,该宏生成一个额外的属性作为对等方,以 为前缀,仅作为示例: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"]

在这种情况下,这将产生相同的扩展。

这两种重载有什么不一样?我应该在什么时候使用它们?文档没有说。是否存在它们会扩展到截然不同的代码的情况?appendInterpolationSyntaxStringInterpolation

当然,我知道一个人可以放任何东西——我只是在比较一个人插值语法节点的情况。\(raw: ...)


旁注:我还发现了另一个接受 a 和 a 参数的重载,所以也许 和之间的区别只是是否执行格式化?SyntaxProtocolformat:\(...)\(raw: ...)

字符串插值 swift-macro

评论


答:

0赞 Sweeper 10/9/2023 #1

在深入研究了 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
}

显然,重载是“更聪明的”。它将缩进应用于给定语法节点中的代码,并将该节点添加到数组中。重载仅转换为 ,然后将其附加到 。SyntaxProtocolinterpolatedSyntaxNodesraw:TStringsourceText

然后我试图弄清楚如何使用。为此,我转到了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()
  }
}

在撰写本文时,它似乎根本没有使用。无论我使用哪种重载,都会附加新代码,然后由 .interpolatedSyntaxNodessourceTextinit(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:...)