tera
May 22, 2025, 11:52pm
1
I've got an enum with custom description (CustomStringConvertible), is it possible to get the original case names?
Using mirror
enum E: CustomStringConvertible {
case payloadless
case withPayload(String)
case withPayloadAndLabel(label: String)
case withPayloadAndEmptyLabel(_ label: String)
case withPayloadAndTwoLabels(_ label: String, _ other: Int)
var description: String {
"Something + " + originalDescription(self)
}
}
func originalDescription(_ value: Any) -> String {
let mirror = Mirror(reflecting: value)
return switch mirror.displayStyle {
case .enum:
if let first = mirror.children.first {
if let label = first.label {
"\(label) \(first.value)"
} else {
"what do I do?"
}
} else {
"and what do I do here?"
}
default:
fatalError("usupported yet")
}
}
works ok for cases with associated values:
print(E.withPayload("123")) // Something + withPayload 123
print(E.withPayloadAndLabel(label: "123")) // Something + withPayloadAndLabel (label: "123")
print(E.withPayloadAndEmptyLabel("123")) // Something + withPayloadAndEmptyLabel 123
print(E.withPayloadAndTwoLabels("123", 1)) // Something + withPayloadAndTwoLabels ("123", 1)
but not for payload-less cases:
print(E.payloadless) // Something + and what do I do here?
vanvoorden
(Rick van Voorden)
May 23, 2025, 12:41am
2
Hmm… you're maybe looking for something like this?
Which maybe needs something like this?
// func _getEnumCaseName<T>(_ value: T) -> UnsafePointer<CChar>?
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
const char *swift_EnumCaseName(OpaqueValue *value, const Metadata *T) {
return call(value, T, nullptr, [](ReflectionMirrorImpl *impl) { return impl->enumCaseName(); });
}
This seems to be producing the correct output:
// https://github.com/swiftlang/swift/blob/main/docs/StandardLibraryProgrammersManual.md#_silgen_name
@_silgen_name("swift_EnumCaseName")
internal func _getEnumCaseName<T>(_ value: T) -> UnsafePointer<CChar>?
enum E: CustomStringConvertible {
case payloadless
case withPayload(String)
case withPayloadAndLabel(label: String)
case withPayloadAndEmptyLabel(_ label: String)
case withPayloadAndTwoLabels(_ label: String, _ other: Int)
var description: String {
"Something + " + originalDescription(self)
}
}
func originalDescription(_ value: Any) -> String {
let mirror = Mirror(reflecting: value)
return switch mirror.displayStyle {
case .enum:
if let first = mirror.children.first {
if let label = first.label {
"\(label) \(first.value)"
} else {
"what do I do?"
}
} else {
if let cString = _getEnumCaseName(value),
let caseName = String(validatingCString: cString) {
caseName
} else {
fatalError("usupported yet")
}
}
default:
fatalError("usupported yet")
}
}
print(E.payloadless)
Whether or not you want to ship this _silgen_name
solution in your production app is another question.
The docs say that shims should be preferred over _silgen_name
… but the documentation for shims is a TODO
:
1. Explain test/Prototypes, and how to use that for rapid (relatively speaking) prototyping
1. Library Concepts
1. Protocol hierarchy
1. Customization hooks
1. Use of classes, COW implementation, buffers, etc
1. Compatibility, `@available`, etc.
1. Resilience, ABI stability, `@inlinable`, `@usableFromInline`, etc
1. Strings and ICU
1. Lifetimes
1. withExtendedLifetime, withUnsafe...,
1. Shims and stubs
1. Coding Standards
1. High level concerns
1. Best practices
1. Formatting
1. Internals
1. `@inline(__always)` and `@inline(never)`
1. `@semantics(...)`
1. Builtins
1. Builtin.addressof, _isUnique, etc
1. Dirty hacks
1 Like