通过ExtensionMacro的协议符合,隐式的resultBuilder会丢失
我很确定这件事现在还不可能,但我还是发帖,希望我错了。
一个简单的 protocol,它使用一个简单的 resultBuilder:
@resultBuilder
public enum MyResultBuilder {
public static func buildBlock ( _ components: String... ) -> [ String ] { components }
}
public protocol MyProtocol {
@MyResultBuilder var strings: [ String ] { get }
}
...被制作成一个 ExtensionMacro(ConformanceMacro 已弃用:https://github.com/swiftlang/swift-evolution/blob/main/proposals/0402-extension-macros.md)
@attached( extension , conformances: MyProtocol )
public macro MyMacro ( ) = #externalMacro ( module: "MacroDefinitions" , type: "MyConformanceMacro" )
public struct MyConformanceMacro: ExtensionMacro {
public static func expansion (
of node: SwiftSyntax.AttributeSyntax ,
attachedTo declaration: some SwiftSyntax.DeclGroupSyntax ,
providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol ,
conformingTo protocols: [ SwiftSyntax.TypeSyntax ] ,
in context: some SwiftSyntaxMacros.MacroExpansionContext )
throws -> [ SwiftSyntax.ExtensionDeclSyntax ] {
let r: SwiftSyntax.ExtensionDeclSyntax = try .init ( "extension \(type): MyProtocol { }" )
return [ r ]
}
}
细节
变量 strings 旨在由开发者动态定义。
问题
当调用 @MyMacro 时,展开会生成对 MyProtocol 的扩展符合性,在此过程中预定义的 @MyResultBuilder 会丢失。
这会导致错误。
修复方法
inline宏并在目标对象内部定义strings变量。 // 宏开销大,不可行。- 让用户在
var strings: [ String ]前手动追加@MyResultBuilder。 // 呃
问题
如何编写一个实现协议符合性的宏,在其中隐含的 @MyResultBuilder 不会丢失?
目标
@MyMacro
struct MyStruct {
var strings: [ String ] { // no errors
"one"
"two"
"three"
}
}
解决方案
我的建议是让宏的使用者显式地写出对协议的符合性。也就是说,用户需要写成:
@MyMacro
struct MyStruct: MyProtocol {
// ^^^^^^^^^^^^
var strings: [String] {
"one"
"two"
"three"
}
}
这比让他们写 @MyResultBuilder 稍好,因为
- 即使存在多项协议要求并且使用了结果构建器,用户也只需要再多写一件事。
- 宏实现可以检查是否显式写入了协议符合性;若未写明,则给出有用的错误信息。另一方面,忘记写
@MyResultBuilder的错误信息并不会告诉用户应该怎么做。
这种检查可以这样写:
public struct MyConformanceMacro: ExtensionMacro {
public static func expansion (
of node: AttributeSyntax ,
attachedTo declaration: some DeclGroupSyntax ,
providingExtensionsOf type: some TypeSyntaxProtocol ,
conformingTo protocols: [TypeSyntax] ,
in context: some MacroExpansionContext )
throws -> [ExtensionDeclSyntax] {
if protocols.contains(where: { $0.as(IdentifierTypeSyntax.self)?.name.text == "MyProtocol" }) {
context.addDiagnostics(from: MacroExpansionErrorMessage("You must declare conformance to MyProtocol explicitly!"), node: declaration)
return []
}
return try [
ExtensionDeclSyntax("extension \(type)") {
// ... declare other members here...
}
]
}
}
当类型已经声明对 MyProtocol 的符合性时,protocols 参数将不包含 MyProtocol。
在Xcode中的示例输出:
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

