swift - 如何向枚举添加新案例?

标签 swift swift-macro

我正在尝试使用宏和 init 将案例添加到枚举中,以将其默认为添加的案例。例如,假设我的枚举是:

@AddEnumCase
enum Employee: String {
   case manager
}

那么扩展版本应该给出:

enum Employee: String {
  case manager
  case developer 
  init(rawValue: String) {
    switch rawValue {
    case Self.manager.rawValue: 
      self = .manager
    default: 
      self = .developer
  }

这是到目前为止我的代码。当 main.swift 上没有输入任何内容时,它就会成功构建,并且测试也成功。但是当在 main.swift 中使用宏时,它无法构建:

Command SwiftCompile failed with a nonzero exit code

public struct AddEnumCase: MemberMacro {
    
    public static func expansion(of node: ....) throws -> [SwiftSyntax.DeclSyntax] {
        
        guard let enumDecl = declaration.as(EnumDeclSyntax.self) else { return [] }
        
        guard let inheritanceType = enumDecl.inheritanceClause?.inheritedTypes.first?.type else { return [] }
        
        let cases = enumDecl.memberBlock.members.compactMap { $0.decl.as(EnumCaseDeclSyntax.self)?.elements) }
        
        let newElement = try EnumCaseDeclSyntax("case developer")
        
        let initializer = try InitializerDeclSyntax("init(rawValue: \(inheritanceType.trimmed))") {
          try SwitchExprSyntax("switch rawValue") {
           for name in cases {
             SwitchCaseSyntax("""
               case Self.\(raw: name).rawValue:
                 self = .\(raw: name)
               """)
              }
              SwitchCaseSyntax("""
               default:
                 self = .developer
               """)
              }
           }
        return [DeclSyntax(newElement), DeclSyntax(initializer)]
    }
}

@attached(member, names: arbitrary)
public macro AddEnumCase() = #externalMacro(module: "MyMacros", type: "AddEnumCase")

以下代码在 main.swift 上失败

@AddEnumCase
enum Employee: String {
   case Manager
}

最佳答案

查看崩溃日志,我可以发现在为 RawRepresentable 一致性生成 rawValue 时出现了问题。生成的 rawValue 中的 switch 可能并不详尽,因为它没有考虑您的新情况。

Swift 宏扩展不应依赖于扩展顺序。所有宏都被赋予原始 AST,并且它们“同时”扩展。我认为这也可能适用于 : String 的降低方式。您依赖 : String 降低宏扩展后的,但这里显然不是这种情况。

这基本上意味着您还必须生成 rawValue 实现。

这是一个示例,仅适用于String原始值:

public struct AddEnumCase: MemberMacro {
    public static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
        guard let enumDecl = declaration.as(EnumDeclSyntax.self) else { return [] }
        
        guard let inheritanceType = enumDecl.inheritanceClause?.inheritedTypes.first?.type else { return [] }
        
        let cases = enumDecl.memberBlock.members.compactMap { $0.decl.as(EnumCaseDeclSyntax.self)?.elements }
        
        let newElement = try EnumCaseDeclSyntax("case developer")
        
        let initializer = try InitializerDeclSyntax("init(rawValue: \(String.self))") {
            try SwitchExprSyntax("switch rawValue") {
                for caseList in cases {
                    for `case` in caseList {
                        SwitchCaseSyntax("""
                           case Self.\(raw: `case`.name).rawValue:
                             self = .\(raw: `case`.name)
                           """)
                    }
                }
                SwitchCaseSyntax("""
                   default:
                     self = .developer
                   """)
            }
        }
        
        let rawValue = try VariableDeclSyntax("var rawValue: \(String.self)") {
            try SwitchExprSyntax("switch self") {
                for caseList in cases {
                    for `case` in caseList {
                        if let rawValueExpr = `case`.rawValue?.value {
                            SwitchCaseSyntax("""
                               case .\(raw: `case`.name):
                                 return \(rawValueExpr)
                               """)
                        } else {
                            SwitchCaseSyntax("""
                           case .\(raw: `case`.name):
                             return \(literal: `case`.name.text)
                           """)
                        }
                    }
                }
            }
        }
        
        return [DeclSyntax(newElement), DeclSyntax(initializer), DeclSyntax(rawValue)]
    }
}

对于数字原始值,您需要保留一个针对每种情况递增的计数器,这有点复杂。要 100% 复制 Swift 生成的内容,您还需要将计数器设置为任何显式声明的原始值。这留给读者作为练习。 (:D)

关于swift - 如何向枚举添加新案例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77304529/

相关文章:

ios - UISearchcontroller 取消按钮不起作用

swift - UINavigationBar foregroundColor 使用 Color 而不是 UIColor

ios - 从另一个类访问属性

ios - Swift 或 Objective C 中的 CalDAV 处理

ios - 如何在 UIsearchbar iOS swift 中为文本字段添加自定义图像