如何将新事例添加到枚举中?

How do I add a new case to an enum?

提问人:bubu 提问时间:10/17/2023 最后编辑:Ken Whitebubu 更新时间:10/17/2023 访问量:105

问:

我正在尝试使用宏和 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 中使用宏时,它无法使用:

命令 SwiftCompile 失败,退出代码为非零

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
}
swift-宏

评论


答:

1赞 Sweeper 10/17/2023 #1

查看崩溃日志,我可以看到在生成一致性时出了点问题。生成的 in 可能并不详尽,因为它没有考虑您的新案例。rawValueRawRepresentableswitchrawValue

快速的宏观扩展不应依赖于扩展的顺序。所有宏都被赋予原始 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)

评论

0赞 bubu 10/17/2023
谢谢@Sweeper,这很有帮助!编译器抱怨我的猜测是,这种情况是在 rawValue 语法扩展后添加的,就像您在答案中提到的那样,所以我只是删除了 else 部分并将 rawValue 硬编码为 unknown 与 init 相同。非常感谢!Switch must be exhaustive