swiftenumsmetatype

Check if a given metatype is an enum


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

Solution

  • 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