针对属性的条件编译优化

2022-11-09 14:16:54 浏览数 (1)

SE-0367, Swift 5.8 中实现

现有问题

随着时间的推移,Swift 引入了许多新的属性,用来在源代码中传递额外信息。现有代码可以利用新的构造来改进,引入新功能,提供新的编译检查,更好的性能等等。但是,现有代码引入新属性意味着不能在旧的编译器上使用。自然而然你会想到用条件编译来解决该问题。例如可以使用#if 检查编译器版本,查看是否可以使用@preconcurrency属性:

代码语言:Swift复制
#if compiler(>=5.6)
@preconcurrency protocol P: Sendable {
  func f()
  func g()
}
#else
protocol P: Sendable {
  func f()
  func g()
}
#endif

有没有发现这段代码有几个缺点?首先,有两段重复代码,因为P协议被定义2次;其次,Swift 5.6 是第一个包含@preconcurrency属性的编译器,但这不是由编译器自动记录的:该属性可能是由编译器标志启用的,也可能是在 Swift 5.7 开发到一半弃用的,因此检查不正确。而且,一些属性是否可用不是依赖编译器,而是平台和配置标志。例如,@objc仅在 Swift 运行时编译用于和 Objective-C 交互时可用。

尽管上述这些都是孤立的小问题,但它们让在现有代码中采用新属性比实际情况更困难。

提议方案

为了在现有代码中更容易使用新属性,本篇提议 2 个更改:

  • 无论属性声明在哪里,允许#if检查出现在声明属性的前面,无需再复制属性声明,仅表示为了采用新属性。
代码语言:Swift复制
#if hasAttribute(preconcurrency)
@preconcurrency
#endif
protocol P: Sendable {
  func f()
  func g()
}
  • 新增条件命令hasAttribute(AttributeName),如果当前语言环境支持AttributeName属性,则返回true, 反之则false。 如果把这两点运用到上面的例子,是不是更加简洁,可读性更强。

设计细节

语法改变

当前属性列表生成语法为:

代码语言:Swift复制
attributes → attribute attributes[opt]

将通过添加条件属性来生成:

代码语言:Swift复制
attributes → conditional-compilation-attributes attributes[opt]

conditional-compilation-attributes → if-directive-attributes elseif-directive-attributes[opt] else-directive-attributes[opt] endif-directive
if-directive-attributes → if-directive compilation-condition attributes[opt]
elseif-directive-attributes → elseif-directive-attributes elseif-directive-attributes[opt]
elseif-directive-attributes → elseif-directive compilation-condition attributes[opt]
else-directive-attributes → else-directive attributes[opt]

在属性列表中,可以存在一个条件子句#if...#endif 来包含另外一个属性列表,嵌套使用。

hasAttribute的校验属性对象是:作为当前语言的一部分

许多语言特性,包括属性包装器(property wrapper), 结果构建器(result builder),还有 global actors, 等等,都引入了自定义属性的方式来实现。例如, 类型 MyWrapper 使用属性@propertyWrapper标记,该类型已经实现@propertyWrapper属性的要求,那么该类型可以在其他地方通过@MyWrapper用法来使用它。虽然启用该功能的内置属性(也可以说是原始属性)可以被hasAttribute识别,比如hasAttribute(propertyWrapper) 结果会被判断为true,但是基于原始属性的自定义属性不会被识别。也就是说hasAttribute(MyWrapper)的结果是false, 不是true, 因为MyWrapper是自定义属性。

解析编译器不接受的条件编译 if 分支

由于支持自定义属性,属性具有非常通用的语法,对于我们在 Swift 引入任何新的特性来说,都足够了。

代码语言:Swift复制
attribute → @ attribute-name attribute-argument-clause[opt]
attribute-name → identifier
attribute-argument-clause → ( balanced-tokens[opt] )

因此,基于#if hasAttribute(UnknownAttributeName)的条件编译分支,仍然能在现有的编译器上解析,即使该条件不能用于声明上,因为虽然走进了对应的 if 分支,但是编译器有可能无法识别该内容。

代码语言:Swift复制
#if hasAttribute(UnknownAttributeName)
@@UnknownAttributeName(something we do not understand) // okay, we parse this but don't reject it
#endif
func f()

总结

  1. Swift 5.8 中使用#if hasAttribute(AttributeName)来检查当前语言环境下支持的属性关键字,取代冗长的版本判断,去除对声明的重复定义。
  2. 新属性在旧的编译器声明,编译检查不会报错。

0 人点赞