Swift Macro如何移除原变量并替换为闭包实现?
问题描述
我正在开发一个Swift宏(PeerMacro),基础功能接近完成,但现在需要移除原变量(如static let loans: String),用闭包完全替换它。目前宏展开后原变量仍会保留,以下是相关代码和展开结果,求实现方法(刚接触Swift Macros)。
宏扩展代码
public static func expansion( of node: SwiftSyntax.AttributeSyntax, providingPeersOf declaration: some SwiftSyntax.DeclSyntaxProtocol, in context: some SwiftSyntaxMacros.MacroExpansionContext ) throws -> [SwiftSyntax.DeclSyntax] { guard let variableSyntax = declaration.as(VariableDeclSyntax.self), let variableSyntaxAttributes = variableSyntax.attributes.first?.as(AttributeSyntax.self), let labelListSyntax = variableSyntaxAttributes.arguments?.as(LabeledExprListSyntax.self) else { throw MacroExpansionError.message( "Could not read the variable syntax" ) } // 校验变量格式 guard let staticModifierListSyntax = variableSyntax.modifiers.first?.as(DeclModifierSyntax.self), let bindingsListSyntax = variableSyntax.bindings.first?.as(PatternBindingSyntax.self), let identifierSyntax = bindingsListSyntax.pattern.as(IdentifierPatternSyntax.self) else { throw MacroExpansionError.message( "Variable is not properly formatted" ) } let staticKeyWord = staticModifierListSyntax.name.text let letKeyword = variableSyntax.bindingSpecifier.text let variableName = identifierSyntax.identifier.text guard staticKeyWord == "static", letKeyword == "let", !variableName.isEmpty else { throw MacroExpansionError.message( "Variable is not properly formatted" ) } // 校验URL参数 guard let urlStringLabelSyntax = labelListSyntax.first?.as(LabeledExprSyntax.self), let urlStringLiteralSyntax = urlStringLabelSyntax.expression.trimmed.as(StringLiteralExprSyntax.self), let urlStringSegmentSyntax = urlStringLiteralSyntax.segments.first?.as(StringSegmentSyntax.self) else { throw MacroExpansionError.message( "First argument for URL is missing or not the right type" ) } let urlString = urlStringSegmentSyntax.content.text return [ DeclSyntax(stringLiteral: """ \(staticKeyWord) \(letKeyword) \(variableName): String = { return String(format: "\(urlString)") }() """ ) ] }
测试代码
func testMacro() throws { #if canImport(AFEndpointMacros) assertMacroExpansion( """ struct Endpoints { @AFEndpoint("/v2/test/") static let test: String } """, expandedSource: """ struct Endpoints { static let test: String = { return String(format: "/v2/test/") }() } """, macros: testMacros ) #else throw XCTSkip("macros are only supported when running tests for the host platform") #endif }
实际与预期展开结果
预期输出:
struct Endpoints { static let test: String = { return String(format: "/v2/test/") }() }
实际输出:
struct Endpoints { static let test: String static let test: String = { return String(format: "/v2/test/") }() }
解决方案
问题出在你使用的是Peer Macro(同伴宏),这类宏的作用是在原声明的旁边添加新代码,不会替换原声明,所以原变量会被保留。要实现替换原变量的需求,你需要改用Declaration Macro(声明宏),它会直接替换掉被标记的原声明。
具体修改步骤:
- 修改宏的协议实现:把宏结构体的继承协议从
PeerMacro改成DeclarationMacro - 调整扩展方法签名:将方法参数从
providingPeersOf改为for,对应DeclarationMacro的标准方法签名 - 保留内部逻辑:原有的变量解析、闭包生成逻辑不需要修改
修改后的宏扩展方法签名示例:
public static func expansion( of node: SwiftSyntax.AttributeSyntax, for declaration: some SwiftSyntax.DeclSyntaxProtocol, in context: some SwiftSyntaxMacros.MacroExpansionContext ) throws -> [SwiftSyntax.DeclSyntax] { // 原有的内部逻辑完全保留 }
修改后,宏展开时会用你生成的带闭包的变量声明直接替换原变量声明,不会保留原代码,就能得到预期的展开结果。
内容的提问来源于stack exchange,提问作者Kwoner
相关产品推荐
相关产品推荐

