ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SE-0382 深度解析:Swift 表达式宏(Expression Macros)——从 `stringify` 到宏系统基石的完整实战指南

SE-0382 深度解析:Swift 表达式宏(Expression Macros)——从 `stringify` 到宏系统基石的完整实战指南 文档【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址https://gitcode.com/gh_mirrors/sw/swift-evolution点击查看免费下载表达式宏Expression Macros是 Swift 5.9 引入的宏系统基石之一它允许开发者用#前缀的表达式语法对源码做任意的语法树级转换从而以库能力替代过去只能通过新增语言特性才能实现的功能。本文将基于 swift-evolution 仓库中的 SE-0382 提案原文完整讲解表达式宏的设计动机、类型检查模型、语法转换机制、宏声明与展开的详细规则、宏实现库 API、标准库内置宏以及沙箱化与工具链支持并结合 宏愿景文档、SE-0389 附属宏、SE-0394 SwiftPM 宏支持 等配套提案帮助你从会用进阶到理解并独立编写表达式宏。提案背景与动机在宏系统出现之前Swift 表达式层面已经提供了不错的运行时行为抽象——你可以编写一个函数在任意位置以表达式形式调用它。但除了少数硬编码进编译器的特例如#file、#line表达式无法感知或修改正在编译的程序源码。这类需求过去只能求助于外部源码生成工具而这些工具往往难以与编译器、IDE、调试器等既有工具链干净地集成。表达式宏正是 A Vision for Macros in Swiftmacros 愿景文档的落地成果之一。该愿景认为宏系统的价值在于将语法糖民主化许多原本需要新语言特性或外部代码生成器的任务都可以实现为宏。宏虽然可能带来次优的语法、较弱的诊断或更差的编译期性能但它能让语言保持精简同时通过可扩展的库生态维持表达力。具体到表达式#前缀语法在 Swift 中已有大量先例#filePath、#line、#colorLiteral、#warning等用宏来统一并泛化这一语法是自然的选择。SE-0382 提出的方案引入表达式宏——它以#标记出现在源码中展开后仍是表达式它拥有类似函数的参数与结果类型签名从而在不实际展开宏的情况下就能描述宏展开的效果。核心方案概览表达式宏如何工作表达式宏的使用形式非常直观例如stringify宏#stringify(x y)编译器将其展开为(x y, x y)即既保留原始参数的值又生成一个包含该参数源码文本的字符串字面量。这个宏的声明形如函数是声明的一部分freestanding(expression) macro stringifyT(_: T) - (T, String)宏展开是源码到源码source-to-source的语法树转换宏实现拿到宏展开表达式本身的语法树从#开始到最后一个参数结束将其重写为新的语法树该语法树随后会对照宏的结果类型进行类型检查。类型检查的宏参数与结果两阶段模型SE-0382 最核心的设计决策之一是宏参数在宏实例化之前就已完成类型检查。以#stringify(x y)为例参数x y会先被类型检查若它非法例如x是Int而y是String宏永远不会被展开若合法泛型参数T会被推断为x y的结果类型并贯穿到宏的结果类型中。提案原文明确了这一类型检查优先模型的三个收益宏实现保证拿到类型正确的参数无需担心非法代码混入工具可以像对待函数一样对待宏代码补全、语法高亮等能力直接可用因为宏参数遵循与其他 Swift 代码相同的规则宏展开表达式可以在不展开宏的情况下部分类型检查这让工具在未做宏展开时也能给出合理结果同时提升编译性能——同一宏不会在类型推断过程中被反复展开。宏展开后产出的语法树会以宏结果类型作为上下文类型进行类型检查。例如#stringify(x y)在x y为Int时展开结果(x y, x y)会以(Int, String)为上下文类型接受检查。宏表达式的类型检查与函数调用类似类型推断信息可以在宏参数与结果类型之间双向流动。提案给出了一个很能说明问题的例子let (a, b): (Double, String) #stringify(1 2)这里的整型字面量1和2会被赋值为Double类型——类型信息从结果类型反推到了宏参数上宏在此处表现得如同一个普通泛型函数调用。语法转换为什么选择 source-to-source宏展开是纯语法操作输入是完整的宏展开表达式语法树输出是一棵新的语法树随后按宏结果类型检查。相对于直接操作编译器的 AST 或内部表示IR纯语法转换有一系列优点宏展开可以使用完整的 Swift 语言来表达其效果——只要在语法上该位置能写 Swift 源码宏就能展开成它Swift 程序员理解 Swift 源码因此可以推理宏的输出这对编写宏和使用宏都大有帮助使用宏的源码可以被展开以消除宏例如便于推理、调试或让代码在不支持宏的旧编译器上工作编译器的 AST 与内部表示无需暴露给客户端避免因向后兼容顾虑而限制编译器演进。但纯语法转换也有明确代价提案如实列出了三点易产生编译期失败本质上是把源码当字符串处理宏实现中很容易引入语法错误或类型错误重新解析与重新类型检查比直接操作 AST/IR 有更多编译期开销非卫生hygienic宏展开的处理方式取决于其展开环境且可能影响该环境。提案的结论是语法宏易用、易理解的优势压过了这些问题。关于非卫生带来的名字冲突风险将在下文makeUniqueName的设计中给出缓解方案。宏定义作为独立程序关于宏的展开操作如何定义提案对比了两大类方案声明式变换规则为语言扩展专门语法编译器对每次宏展开套用规则。C 预处理器的宏、Racket 的模式宏、Rust 的声明式宏macro_rules!属于此类Swift 若走这条路需要发明一套匹配与重写语法树的模式语言。可执行程序转换源码运行一个程序直接操作源码。Scala 3 借助 JVM 将目标代码与宿主代码交织Rust 过程宏则编译为独立 crate 供编译器交互。Swift 选择了后者宏定义是独立的程序通过 swift-syntax 包操作 Swift 语法树以符合ExpressionMacro协议的类型来表达public protocol ExpressionMacro: FreestandingMacro { /// Expand a macro described by the given freestanding macro expansion /// within the given context to produce a replacement expression. static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws - ExprSyntax }expansion(of:in:)方法接收宏展开表达式的语法节点例如#stringify(x y)以及提供编译上下文的context产出包含重写后语法树的宏结果。Macro、ExpressionMacro与MacroExpansionContext的细节将在下文详细设计中展开。动手实现第一个表达式宏StringifyMacrostringify的实现是一个符合ExpressionMacro的新类型StringifyMacroimport SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros public struct StringifyMacro: ExpressionMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) - ExprSyntax { guard let argument node.argumentList.first?.expression else { fatalError(compiler bug: the macro does not have any arguments) } return (\(argument), \(literal: argument.description)) } }这个实现非常精简因为stringify本身足够简单从语法树中取出宏参数即#stringify(x y)中的x y通过字符串插值构造元组表达式第一个元素是原始参数表达式本身第二个元素用literal:插值把argument.description参数的源码文本转义为字符串字面量该字符串被解析为表达式产生ExprSyntax节点作为宏展开结果。这实际上是SwiftSyntaxBuilder模块提供的一种**准引用quasi-quoting**形式主要语法节点此处为ExprSyntax实现了ExpressibleByStringInterpolation允许把已有语法节点插值到包含展开后 Swift 代码的字符串字面量中再整体解析回语法树。最后还需要把声明与实现绑定起来。SE-0382 提出用内置宏externalMacro来指代宏实现所在的模块与类型名写在之后freestanding(expression) macro stringifyT(_: T) - (T, String) #externalMacro(module: ExampleMacros, type: StringifyMacro)详细设计宏声明语法宏声明由以下文法描述SE-0382 原文declaration - macro-declaration macro-declaration - macro-head identifier generic-parameter-clause[opt] macro-signature macro-definition[opt] generic-where-clause[opt] macro-head - attributes[opt] declaration-modifiers[opt] macro macro-signature - parameter-clause macro-function-signature-result[opt] macro-function-signature-result - - type macro-definition - expression要点如下freestanding(expression)属性仅适用于宏标明该宏是表达式宏。freestanding一词来自 宏愿景文档用于描述以#前缀展开的宏。宏签名为函数式包含参数子句可为空与可选的结果类型。宏只能在文件作用域声明且可与函数一样重载——只要参数标签、参数类型或结果类型不同即可。macro-definition提供展开实现按一般表达式解析但必须是宏展开表达式macro-expansion-expression。因此所有非内置宏都以其他宏定义最终终止于由编译器提供实现的内置宏。定义中macro-expansion-expression的参数必须是对外层宏参数的直接引用或字面量。宏参数可以带默认值但默认值只能由字面量表达式和其他宏展开构成。宏支持不透明结果类型但唯一性规则与函数不同每次宏展开产出的不透明类型都被视为不同类型。例如下面代码是非法的freestanding(expression) macro someMacroWithOpaqueResult() - some CollectionUInt8 var a #someMacroWithOpaqueResult a #someMacroWithOpaqueResult // cannot assign value with type of macro expansion here to opaque type from macro expansion above详细设计宏展开流程宏展开表达式的文法SE-0382 原文primary-expression - macro-expansion-expression macro-expansion-expression - # identifier generic-argument-clause[opt] function-call-argument-clause[opt] trailing-closures[opt]#语法是刻意选定的Swift 已包含大量#前缀的类宏表达式其中一些可以直接实现为表达式宏。identifier引用的宏必须是表达式宏由声明上的freestanding(expression)标明。function-call-argument-clause与trailing-closures都是可选的两者都省略时宏按提供了空参数列表()展开。宏不像函数那样是一等实体不能被当作值传递也不需要未应用宏语法——这让#line等宏不必写成#line()。这也与属性包装器用于附属宏的先例一致。两阶段展开当源码中出现宏展开时其展开分两个阶段第一阶段类型检查阶段宏参数对照命名宏的参数进行类型检查命名宏的结果类型对照宏展开发生的上下文检查。这与函数调用的类型检查等价不涉及宏定义本身。第二阶段宏展开阶段宏参数的语法提供给宏定义。对内置宏行为取决于该宏的语义——例如externalMacro会调用外部程序向其提供宏展开的源码对其他宏参数被替换进定义的macro-expansion-expression中。提案用prohibitBinaryOperators与addBlocker展示了宏之间的组合freestanding(expression) macro prohibitBinaryOperatorsT(_ value: T, operators: [String]) - T #externalMacro(module: ExampleMacros, type: ProhibitBinaryOperators) freestanding(expression) macro addBlockerT(_ value: T) - T #prohibitBinaryOperators(value, operators: []) #addBlocker(x y * z)#addBlocker(x y * z)的展开会先变成#prohibitBinaryOperators(x y * z, operators: [])再交由ExampleMacros.ProhibitBinaryOperators一个符合ExpressionMacro的结构体处理。宏展开产出新的源码语法树随后以原宏结果类型作为上下文类型接受类型检查。例如stringify返回(T, String)参数为Int时展开结果按如下右侧方式检查let _: (Int, String) macro expansion result嵌套展开、递归限制与性能优化宏展开表达式可以出现在宏参数中#addBlocker(#stringify(1 2))第一阶段不做任何展开#stringify(1 2)推断出其T为Int、产生(Int, String)值addBlocker推断出其T为(Int, String)。第二阶段从外向内展开先展开addBlocker得到#prohibitBinaryOperators(#stringify(1 2), operators: [])再展开prohibitBinaryOperators其产出的结果重新类型检查时会再次类型检查并最终展开#stringify(1 2)。实现层面编译器保留避免重复类型检查的权利当同一语法节点被原样复用时可复用第一阶段算出的类型——这是类型检查器的重要性能优化。此外还有两条硬性规则宏展开不能递归若某个宏的展开产出的源码再次展开同一宏程序非法——这防止了无界宏展开除内置的源码位置宏#fileID、#line等外宏不能用作参数的默认参数。源码位置宏作为默认参数时会在调用点按调用方的源码位置展开这是既有且有用的行为但不一定适合所有宏故提案先禁止非内置宏作默认参数以避免困惑并保留未来重新审视的空间。宏实现库协议与上下文宏定义使用 swift-syntax 包提供 Swift 语法树操作与解析能力其中SwiftSyntaxMacros模块提供定义宏所需的功能。Macro协议族public protocol Macro { }Macro是所有宏定义的根协议目前没有任何要求。所有 freestanding 宏符合FreestandingMacropublic protocol FreestandingMacro: Macro { }表达式宏由ExpressionMacro描述是 freestanding 宏的一种public protocol ExpressionMacro: FreestandingMacro { /// Expand a macro described by the given freestanding macro expansion syntax node /// within the given context to produce a replacement expression. static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws - ExprSyntax }FreestandingMacroExpansionSyntax协议是描述上文macro-expansion-expression文法项的 swift-syntax 节点携带宏展开在源码中出现的完整语法树含所有空白与注释。宏实现若无法继续展开可以抛出错误而非尝试产出新语法节点编译器会将该错误报告给用户更详细的诊断可通过宏展开上下文提供。MacroExpansionContext上下文三件套宏展开上下文提供宏展开环境的信息可在展开过程中查询/// Protocol whose conforming types provide information about the context in /// which a given macro is being expanded. public protocol MacroExpansionContext: AnyObject { /// Generate a unique name for use in the macro. public func makeUniqueName(_ name: String) - TokenSyntax /// Emit a diagnostic (i.e., warning or error) that indicates a problem with the macro /// expansion. public func diagnose(_ diagnostic: Diagnostic) /// Retrieve a source location for the given syntax node. /// /// - Parameters: /// - node: The syntax node whose source location to produce. /// - position: The position within the syntax node for the resulting /// location. /// - filePathMode: How the file name contained in the source location is /// formed. /// /// - Returns: the source location within the given node, or nil if the /// given syntax node is not rooted in a source file that the macro /// expansion context knows about. func location( of node: some SyntaxProtocol, at position: PositionInSyntaxNode, filePathMode: SourceLocationFilePathMode ) - AbstractSourceLocation? }三个操作各有明确用途makeUniqueName(_:)生成唯一名字使宏展开能产出不会与同作用域其他声明冲突的新声明。返回的标识符 token 会融合传入的name以便调试。这让宏在一定程度上更卫生——不会引入影响宏参数代码类型检查的新名字。diagnose(_:)让宏实现在展开期间产出诊断警告或错误。Diagnostic类型属于 swift-syntax 库可表达编译器会产出的各类诊断警告、错误、范围高亮、Fix-It 与附加注释。典型场景是宏参数类型检查通过但宏实现无法理解其使用的某些 Swift 语法。产出诊断的宏仍应产出展开结果除非它也抛错——此时诊断与错误都会被报告。该 API 由展开宏的工具如编译器呈现。location(of:at:filePathMode:)确定语法节点的源码位置信息文件、行、列。position与filePathMode可定制输出——例如指向语法节点的哪一部分、文件名字如何呈现。配套的枚举与结构体如下/// Describe the position within a syntax node that can be used to compute /// source locations. public enum PositionInSyntaxNode { /// Refers to the start of the syntax nodes leading trivia, which is /// the first source location covered by the syntax node. case beforeLeadingTrivia /// Refers to the start of the syntax nodes first token, which /// immediately follows the leading trivia. case afterLeadingTrivia /// Refers to the end of the syntax nodes last token, right before the /// trailing trivia. case beforeTrailingTrivia /// Refers just past the end of the source text that is covered by the /// syntax node, after all trailing trivia. case afterTrailingTrivia } /// Describes how a source location file path will be formed. public enum SourceLocationFilePathMode { /// A file ID consisting of the module name and file name (without full path), /// as would be generated by the macro expansion #fileID. case fileID /// A full path name as would be generated by the macro expansion #filePath, /// e.g., /home/taylor/alison.swift. case filePath }源码位置以抽象形式描述可插值进期望字符串字面量文件名或整数字面量行、列的表达式位置。与makeUniqueName返回TokenSyntax而非String同理这种抽象允许编译器引入特殊语法节点甚至普通 Swift 无法表达的形式来表示这些值/// Abstractly represents a source location in the macro. public struct AbstractSourceLocation { /// A primary expression that represents the file and is ExpressibleByStringLiteral. public let file: ExprSyntax /// A primary expression that represents the line and is ExpressibleByIntegerLiteral. public let line: ExprSyntax /// A primary expression that represents the column and is ExpressibleByIntegerLiteral. public let column: ExprSyntax }提案同时指出MacroExpansionContext有意设计为可随时间扩展未来会纳入更多构建环境信息例如目标平台信息OS、架构、部署版本以及通过-D传入的编译期定义。标准库中的宏收编既有#表达式#语法与众多既有内建表达式如#line相同因此 SE-0382 提议把这些内建表达式收编为 Swift 标准库中的宏。宏实现仍由编译器提供甚至可能涉及纯语法宏无法实现的部分但通过提供宏声明语言中不再需要为它们保留特例并可享受为宏提供的全套工具能力。externalMacro内置宏macro externalMacroT(module: String, type: String) - T参数标识提供外部宏定义的类型所在模块与类型名。注意externalMacro很特殊它只能被展开来定义另一个宏在别处使用即为错误——这就是它不带freestanding(expression)属性的原因。源码位置宏// File and path-related information freestanding(expression) macro fileIDT: ExpressibleByStringLiteral() - T freestanding(expression) macro fileT: ExpressibleByStringLiteral() - T freestanding(expression) macro filePathT: ExpressibleByStringLiteral() - T // Current function freestanding(expression) macro functionT: ExpressibleByStringLiteral() - T // Source-location information freestanding(expression) macro lineT: ExpressibleByIntegerLiteral() - T freestanding(expression) macro columnT: ExpressibleByIntegerLiteral() - T // Current shared object handle. freestanding(expression) macro dsohandle() - UnsafeRawPointer绝大多数提供源码位置信息的操作可实现为符合ExpressionMacro的类型借助MacroExpansionContext的location操作。例外包括#file需扩展MacroExpansionContext以区分#file表现为#fileID还是#filePath的编译模式相关背景见 SE-0285 关于#file迁移的提案、dsohandle需要特定编译器支持、#function需要MacroExpansionContext中不存在的上下文信息。这些签名捕获了既有#file、#line等的大部分类型系统行为——它们像字面量一样处理可适配任何实现了对应ExpressibleBy*协议的上下文类型。但上面的实现无法通过下面这类代码的类型检查let x #file会报类似error: generic parameter T could not be inferred的错误。要匹配既有#file、#line的行为需要一个与字面量类型一致的类型默认化规则目前这需要编译器特殊处理未来若语言支持默认泛型参数或许可直接在类型系统中表达。Objective-C 辅助宏#selector与#keyPath的语法与类型检查行为可用宏声明表达freestanding(expression) macro selectorT(_ method: T) - Selector freestanding(expression) macro selectorT(getter property: T) - Selector freestanding(expression) macro selectorT(setter property: T) - Selector freestanding(expression) macro keyPathT(_ property: T) - String这些宏无法基于本提案的设施实现为ExpressionMacro类型因为需要确定宏展开参数如#selector(getter: Person.name)中引用了哪些声明。但为它们提供带内置实现的宏声明可以降低其特殊性减少语言中的特例。对象字面量宏freestanding(expression) macro colorLiteralT: ExpressibleByColorLiteral(red: Float, green: Float, blue: Float, alpha: Float) - T freestanding(expression) macro imageLiteralT: ExpressibleByImageLiteral(resourceName: String) - T freestanding(expression) macro fileLiteralT: ExpressibleByFileReferenceLiteral(resourceName: String) - T对象字面量允许在程序中引用各类资源。上述签名并非对象字面量当前类型检查方式的精确写照它们不必然是泛型的现在编译器会在当前模块中查找特殊命名类型如_ColorLiteralType作为对应字面量的类型。为保持行为不变提案提出对对象字面量的宏展开执行与今天相同的查找再把该类型作为对应宏的泛型实参——这样从语言内置的特殊对象字面量表达式迁移到带内置实现的宏声明时类型检查行为完全不变。沙箱化宏实现安全与可预测性宏实现模块如何构建与提供给编译器由 SE-0394 Package Manager Support for Custom Macros 等后续提案负责。但 SE-0382 明确划出了一条安全底线宏实现将在沙箱中执行与 SE-0303 SwiftPM 可扩展构建工具 的安全模型类似禁止文件系统与网络访问。这既是安全预防措施也是务实引导宏不应依赖除待展开的宏展开节点及其子节点而非父节点和宏展开上下文明确提供的信息之外的任何状态。未来若宏需要访问更多信息将通过扩展宏展开上下文实现——这也让编译器得以跟踪宏究竟查询了哪些信息。工具链使用与开发宏的支撑宏最核心的关切之一是易用性与可开发性我们如何知道宏对程序做了什么如何开发、调试一个新宏得益于宏展开结果永远是 Swift 源码的语法模型第一个问题很容易回答。工具至少应能展示任意宏用法的展开结果最小集合包括传给编译器的展开标志原型提供了-Xfrontend -dump-macro-expansions未来可能包括输出宏展开后源码文件的模式类似 C 编译器输出预处理文件。IDE 也应能就地展示某个宏用法的展开方便开发者检视宏行为——因为结果总是 Swift 源码比检视操作 AST/IR 的宏实现更容易推理。宏实现是独立程序这一事实反而让开发更容易可以为宏实现编写单元测试——提供宏的输入源码如#stringify(x y)用 swift-syntax 的设施展开宏验证结果代码无语法错误且与期望一致。swift-syntax 仓库中的宏系统测试文件即以这种方式开发了大多数内置宏示例。在 SE-0394 中这一开发与分发模型被具体化为 SwiftPM 的.macro目标类型宏被构建为面向宿主平台的可执行文件编译器通过构建系统传入可执行文件路径在编译过程中按需运行。宏实现通过CompilerPlugin入口点暴露import SwiftSyntax import SwiftCompilerPlugin import SwiftSyntaxBuilder import SwiftSyntaxMacros main struct MyPlugin: CompilerPlugin { var providingMacros: [Macro.Type] [FontLiteralMacro.self] }一个最小包同时包含宏实现、宏定义与宏客户端并辅以MacroTests测试目标import PackageDescription import CompilerPluginSupport let package Package( name: MacroPackage, dependencies: [ .package(url: https://github.com/apple/swift-syntax, from: 509.0.0), ], targets: [ .macro(name: MacroImpl, dependencies: [ .product(name: SwiftSyntaxMacros, package: swift-syntax), .product(name: SwiftCompilerPlugin, package: swift-syntax) ]), .target(name: MacroDef, dependencies: [MacroImpl]), .executableTarget(name: MacroClient, dependencies: [MacroDef]), .testTarget(name: MacroTests, dependencies: [MacroImpl]), ] )宏实现测试可声明对宏目标的依赖与可执行目标测试类似。SwiftPM 通过-load-plugin-executable传递宏可执行文件路径例如-load-plugin-executable /path/to/package/.build/debug/MacroImpl#MacroImpl#后是可用逗号分隔的模块名列表对应#externalMacro声明中module参数的引用。SwiftSyntax 的版本方案基于 Swift 主版本如 509.0.0 对应 Swift 5.9SwiftPM 的依赖解析会为所有宏及客户端合并到同一版本的 SwiftSyntax。更多表达式宏示例表达式宏的用途远超前文展示提案收集了若干基于既有#表达式与社区灵感的示例原型实现可在 swift-syntax 仓库的宏系统测试文件中找到。#colorLiteral宏为给定的红、绿、蓝、透明值提供颜色字面量语法// Declaration of #colorLiteral freestanding(expression) macro colorLiteral(red: Float, green: Float, blue: Float, alpha: Float) - _ColorLiteralType SwiftBuiltinMacros.ColorLiteralMacro // Implementation of #colorLiteral struct ColorLiteralMacro: ExpressionMacro { /// Replace the label of the first element in the tuple with the given /// new label. func replaceFirstLabel( of tuple: TupleExprElementListSyntax, with newLabel: String ) - TupleExprElementListSyntax{ guard let firstElement tuple.first else { return tuple } return tuple.replacing( childAt: 0, with: firstElement.withLabel(.identifier(newLabel))) } static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) - ExprSyntax { let argList replaceFirstLabel( of: node.argumentList, with: _colorLiteralRed ) let initSyntax: ExprSyntax .init(\(argList)) if let leadingTrivia node.leadingTrivia { return MacroResult(initSyntax.withLeadingTrivia(leadingTrivia)) } return initSyntax } }同样的思路可用于文件与图片字面量。注意它把元组第一个参数的标签替换为_colorLiteralRed生成.init(...)调用并保留前导 trivia空白与注释保持源码外观整洁。Power assertions强大断言由 Kishikawa Katsumi 提出断言宏捕获断言表达式中的中间值断言失败时展示这些值。原型输出如下#powerAssert(mike.isTeenager john.age mike.age) | | | | | | | | | true | | 42 | | 13 | | | | Person(name: Mike, age: 13) | | | false | | Person(name: John, age: 42) | false Person(name: Mike, age: 13)兼容性与稳定性影响源码兼容性宏是纯语言扩展使用全新语法不影响源码兼容性ABI 稳定性宏是源码到源码的转换工具无 ABI 影响API 弹性宏是源码到源码的转换工具对 API resilience 无影响。未来方向宏参数类型信息宏参数在调用宏实现前已完成完整类型检查但该类型检查产生的信息并未提供给宏——宏只能拿到原始源码。某些场景下参数及其子表达式的类型、表达式内引用声明的全名、类型检查中的隐式转换等信息会非常有用。例如 power assertions 的应用#assert(Color(parsing: red) .red)实现希望把的两个操作数拆到局部变量用唯一名以捕获值{ let _unique1 Color(parsing: red) let _unique2 .red if !(_unique1 _unique2) { fatalError(assertion failed: \(_unique1) ! \(_unique2)) } }()但这段代码无法通过类型检查——_unique2的初始化需要上下文信息来解析.red。若宏实现能拿到两个子表达式的类型就能生成可正确类型检查的版本{ let _unique1: Color Color(parsing: red) let _unique2: Color .red if !(_unique1 _unique2) { fatalError(assertion failed: \(_unique1) ! \(_unique2)) } }()宏展开上下文可扩展出产生语法节点类型的操作extension MacroExpansionContext { func type(of node: ExprSyntax) - Type? }Type需要能表达 Swift 类型系统的广度元组、函数等结构化类型以及 struct、enum、actor、protocol 等名义类型。还可提供解析后声明的信息例如.red解析为Color.red、解析为比较两个Color的具体运算符声明。此方向的主要复杂度在于定义描述 Swift 类型系统的稳定 API提案认为它价值极高但范围较大最好作为后续独立提案引入。更多种类的宏表达式只是语言中宏能发挥作用的一处。其他位置包括函数或闭包体如添加追踪或日志、类型或扩展定义内部如添加新成员、协议一致性上如综合协议一致性——宏愿景文档 提出了大量候选想法。这些方向后来分别由 SE-0389 附属宏member、peer、accessor、conformance 等角色与 SE-0397 自由声明宏freestanding(declaration)角色可收编 SE-0196 的#warning/#error逐步落地。基本macro声明形态保持一致区别仅在于宏可用的上下文、展开的拼写声明上可能用更合适、标明宏类型的属性以及SwiftSyntaxMacros模块中对应的继承自Macro的协议。结语SE-0382 确立了 Swift 宏系统的第一块基石类型检查优先、源码到源码转换、独立程序实现、上下文受控、沙箱化执行。理解表达式宏就等于理解了整个 Swift 宏体系的核心运转机制——两阶段展开、#externalMacro绑定、ExpressionMacro协议与MacroExpansionContext的职责划分。在此之上附属宏、自由声明宏 与 SwiftPM 宏打包分发 共同构成了 Swift 5.9 完整可用的宏生态。无论你接下来要编写#stringify式的工具宏还是设计领域专用的 DSL本文梳理的声明、展开、上下文与工具链知识都是你直接可用的起点。赞分享文档【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址https://gitcode.com/gh_mirrors/sw/swift-evolution点击查看免费下载相关推荐PDF补丁丁让PDF编辑像搭积木一样简单PDF补丁丁让PDF编辑像搭积木一样简单 还在为PDF文档的各种问题烦恼吗书签混乱找不到内容、页面尺寸不统一打印困难、多个PDF文件需要合并却找不到好工具桌面应用文档Swift Observation 观测框架深度解析从 SE-0395 提案到 Observable 宏与 ObservationRegistrar 的完整实现Swift Observation 观测框架深度解析从 SE 0395 提案到 Observable 宏与 ObservationRegistrar 的完整文档在编译时生成代码Swift 宏Macros完整指南在编译时生成代码Swift 宏Macros完整指南 本篇文章基于本仓库《The Swift Programming LanguageSwift 编程语言文档教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表