You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(声明宏),它会直接替换掉被标记的原声明。

具体修改步骤:

  1. 修改宏的协议实现:把宏结构体的继承协议从PeerMacro改成DeclarationMacro
  2. 调整扩展方法签名:将方法参数从providingPeersOf改为for,对应DeclarationMacro的标准方法签名
  3. 保留内部逻辑:原有的变量解析、闭包生成逻辑不需要修改

修改后的宏扩展方法签名示例:

public static func expansion(
    of node: SwiftSyntax.AttributeSyntax,
    for declaration: some SwiftSyntax.DeclSyntaxProtocol,
    in context: some SwiftSyntaxMacros.MacroExpansionContext
) throws -> [SwiftSyntax.DeclSyntax] {
    // 原有的内部逻辑完全保留
}

修改后,宏展开时会用你生成的带闭包的变量声明直接替换原变量声明,不会保留原代码,就能得到预期的展开结果。

内容的提问来源于stack exchange,提问作者Kwoner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 21:53:16