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

如何在Swift ExtensionMacro中获取协议的详细定义?

解决ExtensionMacro中获取协议合规细节的问题

要在ExtensionMacro里从DeclGroupSyntax(比如你的FooSpy测试类)中提取目标协议的合规要求,核心是通过SwiftSyntax的语义查找能力定位协议声明,再解析其成员。以下是具体实现步骤和代码示例:

核心步骤

  1. 提取类遵守的协议:从DeclGroupSyntax的inheritanceClause中筛选出目标协议(比如Foo)。
  2. 查找协议的完整声明:利用MacroExpansionContext的语义查找能力,获取协议的ProtocolDeclSyntax实例。
  3. 解析协议成员要求:遍历协议的成员块,提取方法、属性等需要实现的内容。
  4. 生成Spy实现代码:基于提取的成员,生成包含XCTest期望和预设返回值的扩展成员。

完整代码示例

1. 宏的定义与扩展实现

import SwiftSyntax
import SwiftSyntaxMacros
import XCTest

public struct TestableMacro: ExtensionMacro {
    // 开启语义上下文支持,才能查找协议声明
    public static let requiresContext = true
    
    public static func expansion(
        of node: AttributeSyntax,
        attachedTo declaration: some DeclGroupSyntax,
        providingExtensionsOf type: some TypeSyntaxProtocol,
        conformingTo protocols: [TypeSyntax],
        in context: some MacroExpansionContext
    ) throws -> [ExtensionDeclSyntax] {
        // 1. 从测试类的继承列表中找到Foo协议
        guard let inheritanceClause = declaration.inheritanceClause,
              let fooType = inheritanceClause.inheritedTypes.first(where: {
                  $0.type.as(IdentifierTypeSyntax.self)?.name.text == "Foo"
              })?.type else {
            throw MacroError.testClassDoesNotConformToFoo
        }
        
        // 2. 通过语义上下文查找Foo协议的完整声明
        guard let fooProtocolDecl = context.findSymbol(for: fooType)?.declaration.as(ProtocolDeclSyntax.self) else {
            throw MacroError.cannotLocateFooProtocol
        }
        
        // 3. 遍历协议成员,生成Spy实现
        var extensionMembers: [DeclSyntax] = []
        for member in fooProtocolDecl.memberBlock.members {
            if let funcDecl = member.decl.as(FunctionDeclSyntax.self) {
                extensionMembers.append(DeclSyntax(generateSpyFunction(for: funcDecl)))
            } else if let varDecl = member.decl.as(VariableDeclSyntax.self) {
                extensionMembers.append(DeclSyntax(generateSpyVariable(for: varDecl)))
            }
        }
        
        // 4. 生成最终的扩展
        let extensionDecl = ExtensionDeclSyntax(
            extendedType: type,
            memberBlock: MemberBlockSyntax(members: MemberBlockItemListSyntax(extensionMembers))
        )
        
        return [extensionDecl]
    }
    
    // 生成带XCTest期望的方法实现
    private static func generateSpyFunction(for funcDecl: FunctionDeclSyntax) -> FunctionDeclSyntax {
        let funcName = funcDecl.name.text
        let returnType = funcDecl.signature.returnClause?.type
        
        // 为每个方法添加对应的期望属性
        let expectationProperty = VariableDeclSyntax(
            bindingKeyword: .keyword(.let),
            bindings: [
                PatternBindingSyntax(
                    pattern: IdentifierPatternSyntax(identifier: .identifier("\(funcName)Expectation")),
                    initializer: InitializerClauseSyntax(value: ExprSyntax("XCTestExpectation(description: \"\(funcName) invocation\")"))
                )
            ]
        )
        
        // 方法体:标记调用、满足期望、返回预设值
        var statements: [CodeBlockItemSyntax] = [
            CodeBlockItemSyntax(item: .expr(ExprSyntax("XCTAssert(true, \"\(funcName) was called\")"))),
            CodeBlockItemSyntax(item: .expr(ExprSyntax("\(funcName)Expectation.fulfill()")))
        ]
        
        if let returnType = returnType {
            statements.append(CodeBlockItemSyntax(item: .expr(ExprSyntax("return \(defaultValue(for: returnType))"))))
        }
        
        let body = FunctionBodySyntax(statements: CodeBlockItemListSyntax(statements))
        
        return FunctionDeclSyntax(
            funcKeyword: funcDecl.funcKeyword,
            name: funcDecl.name,
            signature: funcDecl.signature,
            body: body
        )
    }
    
    // 生成带预设值的属性实现
    private static func generateSpyVariable(for varDecl: VariableDeclSyntax) -> VariableDeclSyntax {
        guard let varName = varDecl.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text,
              let type = varDecl.typeAnnotation?.type else {
            return varDecl
        }
        
        return VariableDeclSyntax(
            bindingKeyword: .keyword(.var),
            bindings: [
                PatternBindingSyntax(
                    pattern: IdentifierPatternSyntax(identifier: .identifier(varName)),
                    typeAnnotation: TypeAnnotationSyntax(type: type),
                    initializer: InitializerClauseSyntax(value: ExprSyntax(defaultValue(for: type)))
                )
            ]
        )
    }
    
    // 根据类型生成默认返回值
    private static func defaultValue(for type: TypeSyntax) -> ExprSyntax {
        switch type.as(IdentifierTypeSyntax.self)?.name.text {
        case "Void": return ExprSyntax("()")
        case "Int": return ExprSyntax("0")
        case "String": return ExprSyntax("\"\"")
        case "Bool": return ExprSyntax("false")
        default: return ExprSyntax("nil")
        }
    }
}

enum MacroError: Error {
    case testClassDoesNotConformToFoo
    case cannotLocateFooProtocol
}

2. 宏的注册

在MacroPlugin中注册该宏:

import SwiftSyntaxMacros

public struct TestableMacroPlugin: MacroPlugin {
    public static let macros: [String: Macro.Type] = [
        "Testable": TestableMacro.self
    ]
}

关键注意事项

  • 开启语义上下文:必须设置requiresContext = true,否则无法通过context.findSymbol查找协议声明。
  • 测试Target环境:确保宏在测试Target中展开,测试Target已导入XCTest,避免生产代码引入测试依赖。
  • 成员类型适配:示例仅处理了基本方法和属性,可根据需求扩展处理关联类型、下标等协议成员。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 08:24:58