Enums with boolean raw values

i was rather surprised that this does not work:

extension KYC
{
    @frozen public
    enum Verification:Bool, Equatable, Sendable
    {
        case again = false
        case debut = true 
    }
}
error: raw type 'Bool' is not expressible by a string, integer, 
or floating-point literal

you may ask, why do you even need an enum with a raw type of Bool? and the answer is database schema, because i want to encode this to BSON using RawRepresentable.

Certainly seems reasonable to me. It’s nothing you can’t write yourself, but while argument labels make it less common to use an enum instead of a boolean they don’t cover all the circumstances where you’d want to do that.

Interesting to see that Bool cannot be conformed and have an automatic synthesis but the lexical structure says: raw-value-literalnumeric-literal | static-string-literal | boolean-literal...

4 Likes

i ended up shooting myself in the foot writing this, as this relatively simple sketch segfaults for some reason:

@frozen public
enum BooleanEnum:Equatable, Sendable
{
    case a
    case b
}
extension BooleanEnum:RawRepresentable
{
    @inlinable public
    init?(rawValue:Bool) { self = rawValue ? .a : .b }

    @inlinable public
    var rawValue:Bool { self == .a }
}

@main
enum Main
{
    @inline(never) static
    func getBooleanCase() -> BooleanEnum
    {
        return .a
    }

    static
    func main() async throws
    {
        let _state:BooleanEnum = Self.getBooleanCase()
        let hidden:Bool = _state != .a

        print(hidden)
    }
}
$ swiftc crash4.swift -parse-as-library
$ ./crash4

💣 Program crashed: Bad pointer dereference at 0x00007ffd9c0beff8

Thread 0 "crash4" crashed:

     0 0x0000557fe2b3db1b BooleanEnum.rawValue.getter + 27 in crash4
     1 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
     2 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
     3 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
     4 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
     5 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
     6 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
     7 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
     8 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
     9 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    10 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
    11 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    12 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
    13 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    14 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
    15 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    16 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
    17 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    18 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
    19 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    20 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
    21 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    22 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
    23 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    24 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
    25 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    26 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
    27 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    28 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
    29 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
    30 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
   ... 
112230 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
112231 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
112232 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
112233 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
112234 0x0000557fe2b3db3e BooleanEnum.rawValue.getter + 61 in crash4
112235 0x00007f050bf5986d == infix<A>(_:_:) + 124 in libswiftCore.so
112236 0x0000557fe2b3dd50 static Main.main() + 207 in crash4
112237 0x0000557fe2b3dfe0 static Main.$main() in crash4

i haven’t had time to investigate exactly what is going on (i literally just isolated this), but from the stack trace i’m guessing that me calling !=(_:_:) fell into some kind of infinite recursion.

EDIT: facepalm the default implementation of ==(_:_:), which the Equatable.!=(_:_:) operator calls, is accessing rawValue, which is calling itself with ==(_:_:)

2 Likes

Yeah, you hit the not-quite-bug of the RawRepresentable ==

Same bug as this?

I don't think it's right for EQ to compare rawValues instead of enumeration tags, we had a thread on this recently. As a workaround I'd suggest to always override "==" when you adding RawRepresentable conformance, in this case:

    public static func == (lhs: Self, rhs: Self) -> Bool {
        switch (lhs, rhs) {
            case (.a, .a), (.b, .b): true
            case (.a, .b), (.b, .a): false
        }
    }
2 Likes

except i’m naturally not thrilled to have to write this every time (and i, or the AI code generator, might make a typo), it really ought to be something the compiler can generate automatically. it’s regrettable that the synthesized Equatable conformance gets disabled when enums have raw types.

1 Like

Interestingly, this compiles and works fine (but obviously bad):

extension Bool: ExpressibleByIntegerLiteral {
    public init(integerLiteral value: Int) { fatalError("don't call") }
}

enum Verification: Bool {
    case again = false
    case debut = true
}

Well spotted. I wonder what is the intended use-case here.

2 Likes

Does it still call the RawRepresentable version of == if you move the RawRepresentable conformance to a separate file from the enum?