Swift中自定义Peer Macro时如何兼容@Observable宏展开?
解决@Resettable宏与@Observable宏的兼容问题
问题背景
实现了一个Resettable Peer Macro,用于为附加的变量声明生成同初始值的私有常量,代码如下:
public struct ResettableMacro: PeerMacro { public static func expansion(of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [DeclSyntax] { guard let variableDecl = declaration.as(VariableDeclSyntax.self) else { print("Can only be applied to variable declarations") return [] } guard variableDecl.bindingSpecifier.text == "var" else { print("Can only be a variable") return [] } guard variableDecl.bindings.allSatisfy({ $0.initializer != nil }) else { print("Must be an initialised stored property") return [] } // abstracted extracting the identifier pattern and the initial value for clarity return zip(variableDecl.identifiers, variableDecl.values).map { name, value in "private let $\(raw: name) = \(raw: value)" } } }
当将@Resettable应用于@Observable类的属性时,会触发错误:'self' used in property access '$_name' before 'self.init' call,示例代码:
@Observable class Person { @Resettable var name = "John Doe" // << error here due to @ObservationTracked }
问题原因
@Observable宏展开时,会将原始属性转换为带有访问器的计算属性,并生成一个附加了@Resettable和@ObservationIgnored的私有存储属性:
@Resettable @ObservationIgnored private var _name = "John Doe"
这个宏生成的私有属性会被Resettable宏再次处理,生成的$_name常量会在self.init完成前被访问,从而触发错误。
最简解决方案
在Resettable宏的逻辑中新增判断,跳过带有private访问修饰符的变量声明——因为@Observable生成的内部属性必然是私有的,而用户手写的需要重置的属性通常不会是私有(若需支持私有属性,可额外结合下划线前缀判断,但跳过私有变量已覆盖绝大多数场景)。
修改后的宏代码:
public struct ResettableMacro: PeerMacro { public static func expansion(of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [DeclSyntax] { guard let variableDecl = declaration.as(VariableDeclSyntax.self) else { print("Can only be applied to variable declarations") return [] } guard variableDecl.bindingSpecifier.text == "var" else { print("Can only be a variable") return [] } // 新增:跳过私有变量,避免处理宏生成的内部属性 guard !variableDecl.modifiers.contains(where: { $0.name.text == "private" }) else { return [] } guard variableDecl.bindings.allSatisfy({ $0.initializer != nil }) else { print("Must be an initialised stored property") return [] } // abstracted extracting the identifier pattern and the initial value for clarity return zip(variableDecl.identifiers, variableDecl.values).map { name, value in "private let $\(raw: name) = \(raw: value)" } } }
内容的提问来源于stack exchange,提问作者rayaantaneja
相关产品推荐
相关产品推荐

