Get name of enum case with associated data

I have an associated value enum:

enum Message {
   case simple(String)
}

How do I print the name Message.simple?

enum Message {
    case simple(String)
}

let message = Message.simple("Hello")
let m = Mirror(reflecting: message).children.first!
print("\(type(of: message)).\(m.label!)(\"\(m.value)\")")
// prints: Message.simple("Hello")
2 Likes

A generalised version that supports cases with no associated values.

protocol HasEnumCaseName {
    var caseName: String { get }
}

extension HasEnumCaseName {
    var caseName: String {
        Mirror(reflecting: self).children.first?.label ?? "\(self)"
    }
}

// example:

enum Message: HasEnumCaseName {
    case simple(String)
    case noAssocValue
}

print(Message.simple("Hello").caseName) // simple
print(Message.noAssocValue.caseName) // noAssocValue

if you need the type as well – add it separately with type(of: xxx).

3 Likes

Those answers are good, but assume that you have an instance of Message.

Do you?

Because the simplest answer to your question is

print("Message.simple")

Otherwise, without using a protocol, you can filter non-enums. subjectType is available too.

extension String {
  init?(droppingAssociatedValue enum: some Any) {
    let mirror = Mirror(reflecting: `enum`)
    guard mirror.displayStyle == .enum else { return nil }
    self = """
      \(mirror.subjectType).\
      \(mirror.children.first?.label ?? "\(`enum`)")
      """
  }
}
2 Likes

Actually, I don't... :smile:

Here's what's happening, I have messages of different types say:

enum Message {
   case simple(String)
   case  other(String)
}

And I'm doing some unit tests like this:

//arrange 
let message = "...."
//act
let parser = Parser()

let message: Message = parser.parser()
/// assert
guard case .simple(_)  = message else {
            XCTFail("Message is of type \(type(of: message)) when it should have been of type \("???")")                // <--- Here's the "thing"
}

I have no idea if that's possible without two instances.

guard case .simple = message else {
  return XCTFail(
    nonMatchingEnumCaseMessage(message, exampleMatch: .simple(""))
  )
}

XCTest's standard formatting is like this, though:

extension XCTestCase {
  func nonMatchingEnumCaseMessage<Enum>(
    _ nonMatch: Enum,
    exampleMatch: Enum
  ) -> String {
    func caseLabel(_ instance: Enum) -> String {
      Mirror(reflecting: instance).children.first?.label ?? "\(instance)"
    }

    return """
      \(Enum.self) is case \
      ("\(caseLabel(nonMatch))") \
      when it should have been \
      ("\(caseLabel(exampleMatch))")
      """
  }
}

I think it's not possible with macros in Swift 5.9

It is possible with macros, but just may be a little weird and error prone. You can implement a macro that would have syntax like this at the call site:

do {
  let text = try #match(message, case: Message.simple)
} catch {
  print(error) // "Value is of case 'other' when it should be 'simple'."
}

In particular, the error thrown by the #match macro can package up the case of the message passed in as well as the case you are trying to pattern match on.

The decleration of such a macro would look like something this:

@freestanding(expression)
macro match<Root, Value>(_ root: Root, case: (Value) -> Root) throws -> Value
  = #externalMacro(…)

struct CaseMismatch: Error, CustomStringConvertible {
  var matchingCase: String
  var rootCase: String
  var description: String {
    "Value is of type \(self.rootCase) when it should be \(self.matchingCase)"
  }
}

In the actual macro implementation you would just need to do some validation on the second argument and some string manipulation to extract out the "simple" substring from "Message.simple".

You can also play around with the syntax of the macro a bit to suite your needs, but I don't think you will be able to do it as a guard since the else branch has no information. You either need to deal with a throwing macro or a Result-returning macro.

1 Like