I have an associated value enum
:
enum Message {
case simple(String)
}
How do I print the name Message.simple
?
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")
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)
.
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`)")
"""
}
}
Actually, I don't...
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.