4
4
5
5
宏会在编译你的源代码时对其进行转换,从而让你避免手动编写重复的代码。在编译过程中,Swift 会先展开代码中的所有宏,然后再像往常一样构建代码。
6
6
7
- ![ 一个显示宏展开概貌的图表 。左侧是 Swift 代码的风格化表示。右侧是由宏添加了几行的相同的代码。] ( macro-expansion )
7
+ ![ 一个宏展开示意图 。左侧是 Swift 代码的风格化表示。右侧是由宏添加了几行的相同的代码。] ( macro-expansion )
8
8
9
9
宏展开始终是一种加法操作:宏会添加新代码,但绝不会删除或修改现有代码。
10
10
11
- 每个宏的输入和宏展开的输出都会被检查,以确保它们是语法上有效的 Swift 代码。同样,你传给宏的值以及宏生成的代码中的值也会被检查,以确保它们具有正确的类型。此外,如果宏的实现在展开宏时遇到错误,编译器会将其视为编译错误。这些保证让使用了宏的代码更容易被推导,也更容易发现诸如宏使用不当或宏实现中的错误这样的问题 。
11
+ 每个宏的输入和宏展开的输出都会被检查,以确保它们是语法上有效的 Swift 代码。同样,你传给宏的值以及宏生成的代码中的值也会被检查,以确保它们具有正确的类型。此外,如果宏的实现在展开宏时遇到错误,编译器会将其视为编译错误。这些保证让使用了宏的代码更容易被推导,也让人更容易发现诸如宏使用不当或宏实现有错误这样的问题 。
12
12
13
13
Swift 有两种宏:
14
14
15
- - * 独立宏* 可单独出现,无需被附加到任何声明中。
15
+ - * 独立宏(Freestanding macros) * 可单独出现,无需被附加到任何声明中。
16
16
17
- - * 附加宏* 会修改它被附加到的声明。
17
+ - * 附加宏(Attached macros) * 会修改它被附加到的声明。
18
18
19
19
附加宏和独立宏的调用方式略有不同,但它们都遵循相同的宏展开模型,并都使用相同的方法来实现。下面的章节将更详细地描述这两种宏。
20
20
21
21
## 独立宏
22
22
23
- 要调用独立宏,需要在其名称前写入数字符号 (` # ` ),并在名称后的括号中写入宏的参数。例如:
23
+ 要调用独立宏,需要在其名称前写入井号 (` # ` ),并在名称后的括号中写入宏的参数。例如:
24
24
25
25
``` swift
26
26
func myFunction () {
@@ -54,7 +54,7 @@ struct SundaeToppings: OptionSet {
54
54
}
55
55
```
56
56
57
- 在这段代码中,` SundaeToppings ` 选项集中的每个选项都包括对初始化器的调用 ,这是重复的手动操作。这样的实现方式在添加新选项时容易出错,比如在行尾键入错误的数字。
57
+ 在这段代码中,` SundaeToppings ` 选项集中的每个选项都包括对构造器的调用 ,这是重复的手动操作。这样的实现方式在添加新选项时容易出错,比如在行尾键入错误的数字。
58
58
59
59
下面是该代码使用宏后的替代版本:
60
60
@@ -69,7 +69,7 @@ struct SundaeToppings {
69
69
}
70
70
```
71
71
72
- 此版本的 ` SundaeToppings ` 调用了 ` @OptionSet ` 宏。这个宏会读取私有枚举类中的 ` case ` 列表,生成每个选项的常量列表,并增加对 [ ` OptionSet ` ] [ ] 协议的遵循。
72
+ 此版本的 ` SundaeToppings ` 调用了 ` @OptionSet ` 宏。这个宏会读取私有枚举类中的枚举值列表,并为其中的每个值生成常量列表,同时也会为结构体增加对 [ ` OptionSet ` ] [ ] 协议的遵循。
73
73
74
74
[ `OptionSet` ] : https://developer.apple.com/documentation/swift/optionset
75
75
@@ -81,7 +81,7 @@ struct SundaeToppings {
81
81
-->
82
82
83
83
84
- 作为对比,下面是 ` @OptionSet ` 宏展开后的版本的样子 。这段代码不是由你自己编写的,只有当你特别要求 Swift 展示宏的展开时,你才会看到它。
84
+ 作为对比,` @OptionSet ` 宏展开后是下面这样 。这段代码不是由你自己编写的,只有当你特别要求 Swift 展示宏的展开时,你才会看到它。
85
85
86
86
``` swift
87
87
struct SundaeToppings {
@@ -102,7 +102,7 @@ struct SundaeToppings {
102
102
extension SundaeToppings : OptionSet { }
103
103
```
104
104
105
- 私有枚举类之后的所有代码都来自于 ` @OptionSet ` 宏。使用宏生成所有静态变量的 ` SundaeToppings ` 版本比前面手动编码的版本更易于阅读和维护。
105
+ 这个结构体中的私有枚举类之后的所有代码都来自于 ` @OptionSet ` 宏。使用宏生成所有静态变量的 ` SundaeToppings ` 版本比前面手动编码的版本更易于阅读和维护。
106
106
107
107
## 宏的声明
108
108
@@ -123,7 +123,7 @@ public macro OptionSet<RawType>() =
123
123
124
124
> 注意:
125
125
> 宏的可访问性总是被声明为 ` public ` 的。
126
- > 由于声明宏的代码与使用宏的代码位于不同的模块中,因此没有任何地方可以应用一个非公共可访问的宏 。
126
+ > 由于声明宏的代码与使用宏的代码位于不同的模块中,因此没有任何地方可以应用一个非公开可访问的宏 。
127
127
128
128
宏的声明定义了宏的* 角色* —— 包括宏在源代码中可以被调用的位置以及宏可以生成的代码种类。每个宏都有一个或多个角色,作为属性的一部分写在宏声明的开头。下面是 ` @OptionSet ` 的更完整的声明,包括了指定它的角色的属性:
129
129
@@ -134,7 +134,7 @@ public macro OptionSet<RawType>() =
134
134
#externalMacro (module : " SwiftMacros" , type : " OptionSetMacro" )
135
135
```
136
136
137
- ` @attached ` 属性在此声明中出现了两次,每个宏角色各用了一次。第一次使用时,` @attached(member) ` 表示这个宏会向被作用到的类型添加新的成员。按 ` OptionSet ` 协议以及一些附加成员的要求,` @OptionSet ` 宏添加了一个 ` init(rawValue:) ` 初始化器 。第二次使用时,` @attached(extension, conformances: OptionSet) ` 声明了 ` @OptionSet ` 会添加对 ` OptionSet ` 协议的遵循。` @OptionSet ` 宏会扩展被作用到的类型,使其遵循 ` OptionSet ` 协议。
137
+ ` @attached ` 属性在此声明中出现了两次,每个宏角色各用了一次。第一次使用时,` @attached(member) ` 表示这个宏会向被作用到的类型添加新的成员。按 ` OptionSet ` 协议以及一些附加成员的要求,` @OptionSet ` 宏添加了一个 ` init(rawValue:) ` 构造器 。第二次使用时,` @attached(extension, conformances: OptionSet) ` 声明了 ` @OptionSet ` 会添加对 ` OptionSet ` 协议的遵循。` @OptionSet ` 宏会扩展被作用到的类型,使其遵循 ` OptionSet ` 协议。
138
138
139
139
对于独立宏,你可以编写 ` @freestanding ` 属性来指定其角色:
140
140
@@ -195,33 +195,33 @@ let magicNumber = #fourCharacterCode("ABCD")
195
195
196
196
为了展开上述代码中的宏,编译器读取 Swift 文件并创建该代码的内存表示,也就是* 抽象语法树* (AST)。AST 使得代码的结构变得清晰,也使得编写与该结构进行交互的代码变得更容易 —— 例如编写编译器或宏的实现,都需要与 AST 进行交互。以下是上述代码的 AST 表示,略微简化,省略了一些额外的细节:
197
197
198
- ![ 一个树状图,以常量作为根结点 。该常量有一个名为 magicNumber 的名称和一个值。该常量的值是一个宏调用。这个宏调用有一个名为 fourCharacterCode 的名称和它的参数。参数是一个值为 ABCD 的字符串字面量。] ( macro-ast-original )
198
+ ![ 一个树状图,以常量作为根节点 。该常量有一个名为 magicNumber 的名称和一个值。该常量的值是一个宏调用。这个宏调用有一个名为 fourCharacterCode 的名称和它的参数。参数是一个值为 ABCD 的字符串字面量。] ( macro-ast-original )
199
199
200
- 上面的图展示了该代码的结构是如何在内存中表示的。AST 中的每个结点对应源代码的一部分 。AST 的 “Constant declaration(常量声明)”结点下有两个子结点 ,分别表示常量声明的两个部分:它的名称和它的值。“Macro call(宏调用)”结点则有表示宏的名称和传递给宏的参数列表的子结点 。
200
+ 上面的图展示了该代码的结构是如何在内存中表示的。AST 中的每个节点对应源代码的一部分 。AST 的 “Constant declaration(常量声明)”节点下有两个子节点 ,分别表示常量声明的两个部分:它的名称和它的值。“Macro call(宏调用)”节点则有表示宏的名称和传递给宏的参数列表的子节点 。
201
201
202
202
作为构建这个 AST 的一部分,编译器会检查源代码是否是有效的 Swift 代码。例如,` #fourCharacterCode ` 只接受一个参数,且该参数必须是一个字符串。如果你尝试传递一个整数参数,或者在字符串字面量的末尾忘记了引号 (` " ` ),你会在这个过程中的这个点上获得一个错误。
203
203
204
204
编译器会找到代码中调用宏的地方,并加载实现这些宏的外部二进制文件。对于每个宏调用,编译器将抽象语法树(AST)的一部分传递给该宏的实现。以下是这个部分 AST 的表示:
205
205
206
- ![ 一个树状图,以一个宏调用(Macro call)作为根结点 。这个宏调用有一个名为 fourCharacterCode 的名称和参数。这个参数是一个值为 ABCD 的字符串字面量。] ( macro-ast-input )
206
+ ![ 一个树状图,以一个宏调用(Macro call)作为根节点 。这个宏调用有一个名为 fourCharacterCode 的名称和参数。这个参数是一个值为 ABCD 的字符串字面量。] ( macro-ast-input )
207
207
208
208
` #fourCharacterCode ` 宏的实现会在展开这个宏时读取这个部分 AST 作为输入。宏的实现仅对其接收到的部分 AST 进行操作,这意味着无论这个宏的前后代码是什么,它的展开方式始终不变。这一限制有助于使宏展开更易于理解,并帮助你的代码能更快得到构建,因为 Swift 可以不必展开那些未变更过的宏。
209
209
<!-- TODO TR: Confirm -->
210
210
Swift 能通过限制实现宏的代码,帮助宏的作者避免意外读取其他输入:
211
211
212
- - 传递给宏实现的抽象语法树(AST)仅包含表示该宏的 AST 结点 ,而不包括其前后的任何代码。
212
+ - 传递给宏实现的抽象语法树(AST)仅包含表示该宏的 AST 节点 ,而不包括其前后的任何代码。
213
213
214
- - 宏的实现运行在一个沙箱环境中 ,这可以防止其访问文件系统或网络。
214
+ - 宏的实现运行在一个沙盒环境中 ,这可以防止其访问文件系统或网络。
215
215
216
216
除了这些保护措施,宏的作者有责任不读取或修改宏输入以外的任何内容。例如,宏的展开不得依赖于当前的时间。
217
217
218
218
` #fourCharacterCode ` 的实现会生成了一个包含展开后代码的新 AST。以下是上述代码会返回给编译器的内容:
219
219
220
220
![ 一个具有 UInt32 类型的整型字面量 1145258561 的树形图。] ( macro-ast-output )
221
221
222
- 当编译器接收到这个展开结果时,它用包含了这个宏展开结果的 AST 结点替换掉包含了宏调用的 AST 结点 。在宏展开后,编译器会再次检查以确保程序仍然是语法上有效的 Swift 代码,并且所有的类型都是正确的。这会生成一个可以像往常一样编译的最终 AST:
222
+ 当编译器接收到这个展开结果时,它用包含了这个宏展开结果的 AST 节点替换掉包含了宏调用的 AST 节点 。在宏展开后,编译器会再次检查以确保程序仍然是语法上有效的 Swift 代码,并且所有的类型都是正确的。这会生成一个可以像往常一样编译的最终 AST:
223
223
224
- ![ 一个树状图,以常量作为根结点 。该常量有一个名为 magicNumber 的名称和一个值。该常量的值是 UInt32 类型的整型字面量 1145258561。] ( macro-ast-result )
224
+ ![ 一个树状图,以常量作为根节点 。该常量有一个名为 magicNumber 的名称和一个值。该常量的值是 UInt32 类型的整型字面量 1145258561。] ( macro-ast-result )
225
225
226
226
这个 AST 对应于如下的 Swift 代码:
227
227
@@ -368,7 +368,7 @@ struct MyProjectMacros: CompilerPlugin {
368
368
369
369
要展开 ` #fourCharacterCode ` 宏,Swift 会将使用了此宏的代码的 AST 发送给包含该宏的实现的库。在这个库的内部,Swift 会调用 ` FourCharacterCode.expansion(of:in:) ` 方法,并将 AST 和上下文作为参数传递给该方法。` expansion(of:in:) ` 的实现会找到作为参数传递给 ` #fourCharacterCode ` 的字符串,并计算出相对应的 32 位无符号整型字面量的值。
370
370
371
- 在上面的示例中,第一个 ` guard ` 块从 AST 中提取出字符串字面量,并将该 AST 结点赋值给 ` literalSegment ` 。第二个 ` guard ` 块调用私有 ` fourCharacterCode(for:) ` 函数。如果宏使用不当,这两个代码块都可能会抛出错误 —— 错误信息会在被不当调用的位置作为编译器错误抛出。例如,如果你尝试以 ` #fourCharacterCode("AB" + "CD") ` 的方式来调用该宏,编译器会显示错误信息 "Need a static string"(“需要一个静态字符串”)。
371
+ 在上面的示例中,第一个 ` guard ` 块从 AST 中提取出字符串字面量,并将该 AST 节点赋值给 ` literalSegment ` 。第二个 ` guard ` 块调用私有 ` fourCharacterCode(for:) ` 函数。如果宏使用不当,这两个代码块都可能会抛出错误 —— 错误信息会在被不当调用的位置作为编译器错误抛出。例如,如果你尝试以 ` #fourCharacterCode("AB" + "CD") ` 的方式来调用该宏,编译器会显示错误信息 "Need a static string"(“需要一个静态字符串”)。
372
372
373
373
` expansion(of:in:) ` 方法返回了一个 ` ExprSyntax ` 的实例,` ExprSyntax ` 是 SwiftSyntax 中的一种用于表示 AST 中的表达式的类型。由于此类型遵循 ` StringLiteralConvertible ` 协议,作为一种轻量级的语法,这个宏的实现就使用了一个简单字符串字面量来创建其结果。所有从宏实现中返回的 SwiftSyntax 类型都遵循 ` StringLiteralConvertible ` 协议,因此你也可以在实现任何宏时使用这种方法。
374
374
0 commit comments