Check if a given metatype is an enum

2.2k Views Asked by At

Given the method

func enumCaseCount<T: Hashable>(ofType type: T.Type) -> Int {
    // Needed check if type is an enum type

   return 3
}

Used as follows

private enum SimpleEnum: String {
    case a = "A"
    case b = "B"
    case c = "C"
}

enumCaseCount(ofType: SimpleEnum.self)

Any idea how to check if the given metatype is an enum?


Classes can be tested this way

class Test {}
Test.self is AnyClass // returns true
3

There are 3 best solutions below

17
On BEST ANSWER

For the fun of it, as a (workaround) hack, we could instantiate an instance of T and perform runtime instrospection on it using Mirror, specifically its displayStyle property. Before we proceed, we note that we'll only use this for debugging purposes

Mirrors are used by playgrounds and the debugger.

I'll also point out that we're really chasing our tail here as we resort to runtime to query things known (by the compiler, at least) at compile time.


Anyway, first of all, I'll rename enumCaseCount(...) to isEnum(...), as this question only covers querying whether a metatype is an enum or not. For similar (somewhat brittle) hacks to query the number of cases of a given enum, see:

Now, the generic placeholder T in isEnum(...) only knows that it is a type conforming to Hashable, which doesn't give us any straight-forward way to instantiate an instance of T (if Hashable blueprinted, say, an initializer init(), we'd could readily construct an instance of T an perform runtime introspection upon it). Instead, we'll resort to manually allocating raw memory for a single T instance (UnsafeMutableRawPointer.allocate(bytes:alignedTo:)), binding it to T (bindMemory(to:capacity:)), and finally deallocating the memory (deallocate(bytes:alignedTo:)) once we've finished our runtime introspection of the instance referenced to by the pointer to the bound memory. As for the runtime introspection, we simply use Mirror to check whether its displayStyle is enum or not.

func isEnum<T: Hashable>(_: T.Type) -> Bool {
    var result = false
    // Allocate memory with size and alignment matching T.
    let bytesPointer = UnsafeMutableRawPointer.allocate(
        bytes: MemoryLayout<T>.size,
        alignedTo: MemoryLayout<T>.alignment)
    // Bind memory to T and perform introspection on the instance
    // reference to by the bound memory.
    if case .some(.`enum`) = Mirror(reflecting:
        bytesPointer.bindMemory(to: T.self, capacity: 1).pointee)
        .displayStyle {
        print("Is an enum")
        result = true
    } else { print("Is not an enum") }
    // Deallocate the manually allocate memory.
    bytesPointer.deallocate(bytes: MemoryLayout<T>.size,
                            alignedTo: MemoryLayout<T>.alignment)
    return result
}

Example usage:

enum SimpleEnum { case a, b, c }

enum SimpleStrEnum: String {
    case a = "A"
    case b = "B"
    case c = "C"
}

enum SimpleExplicitIntEnum: Int { case a, b, c }

struct SimpleStruct: Hashable {
    let i: Int
    // Hashable
    var hashValue: Int { return 0 }
    static func ==(lhs: SimpleStruct, rhs: SimpleStruct) -> Bool { return true }
}

print(isEnum(SimpleEnum.self))            // true
print(isEnum(SimpleStrEnum.self))         // true
print(isEnum(SimpleExplicitIntEnum.self)) // true
print(isEnum(SimpleStruct.self))          // false
0
On

As others have mentioned, there's no great non-hacky way to do this in Swift. However, it's one of the example use cases of Sourcery, a metaprogramming library (this means it analyzes your code to generate additional code). You write a Stencil template to describe its behavior, and it's executed as a build phase in Xcode. It can autogenerate this code for any enums found in your project.

AutoCases enum example

10
On

To check whether a certain type is in fact an Enum you can use:

func isEnum<T>(_ type: T.Type) -> Bool {
    let ptr = unsafeBitCast(T.self as Any.Type, to: UnsafeRawPointer.self)
    return ptr.load(as: Int.self) == 513
}

And you can simply use it like so:

enum MyEnum {}
struct MyStruct {}
class MyClass {}

isEnum(MyEnum.self)   // this returns true
isEnum(MyStruct.self) // this returns false
isEnum(MyClass.self)  // this returns false

And in your example you would use it like so:

func enumCaseCount<T: Hashable>(ofType type: T.Type) -> Int {
   isEnum(T.self) // returns true if enum
   return 3
}