RawRepresentable Enum - literal requirement

I was looking the way to use custom type with RawRepresentable enums.
And seems it is possible if they can be instantiated from literals.

I looked at couple of topics around raw representable enums:

  1. Enum case value must be literal: is there a macro that does literal expression evaluation and fill in the case value?
  2. Decoupling enum case literals from `RawRepresentable`

I made the following experiment:

struct GamePiece: Equatable, ExpressibleByStringLiteral {                                                                                                                                                                                                                                                                                                    
    // empty struct - always equal to any other
    init() { }

    init(stringLiteral value: String) {                                                                                                                                      
        switch value {                                                                                                                                                       
        case "king":   self = .init()
        case "queen":  self = .init()
        case "rook":  self = .init()
        default:       self = .init()
        }
    }
}

enum ChessPiece: GamePiece, CaseIterable {
    case king  = "king"
    // case king  = GamePiece(stringLiteral: "king") // - error: raw value for enum case must be a literal
    case queen = "queen"
    // case rook = "queen" // - error: raw value for enum case is not unique
}

print(ChessPiece.king == ChessPiece.queen) // false
print(ChessPiece.king.rawValue == ChessPiece.queen.rawValue) // true

I wonder if that is kind of a bug or some work in the direction of using custom values with raw representable enums?

Both of these pass for me. Use a test to make sure you are not reading the output of something else.

#expect(ChessPiece.king == ChessPiece.queen)
#expect(ChessPiece.king.rawValue == ChessPiece.queen.rawValue)
1 Like

First returns false and second true: godbolt

I would expect that enum with the same raw representable enum cases won't compile or return true for both...

Hm, only now I see that this is only for dev snapshot. So, perhaps this is a bug.

1 Like

Made a bug report: [6.3] Comparing RawRepresentable Enums is broken? · Issue #87906 · swiftlang/swift · GitHub

But false for the first case is how it's supposed to be, so it was a bug until 6.2 which bug was fixed recently (around the end of last year IIRC).

2 Likes

Yeah, thank you.
I realized that actually it is a bit different thing.

@xwu left the comment in the case that compiler should check that RawType should be checked rather than literal.

There are a few cases when compiler would allow duplicated raw values, a few examples:

enum Test1: Double {
    case a = 0.0
    case b = -0.0
}
enum Test2: Float {
    case a = 0xCp127 // Warning: '0xCp127' overflows to inf during conversion to 'Float'
    case b = 0xCp128 // Warning: '0xCp128' overflows to inf during conversion to 'Float'
}
enum Test3: String {
    case a = "\u{00c5}"     // Å precomposed form
    case b = "A\u{030A}"    // Å decomposed form
}

Duplicated raw values per se do not seem to cause major harm..


As for the user ExpressibleBy...Literal types used as enum raw types, probably compiler could do a better job checking if it can guarantee the uniqueness of raw values at compile time. For example if the type has a custom func == operator or a custom init(xxxLiteral:) initialiser – all bets are off.

I generally agree though for enum Test2 in your example I would intuitively expect Test2.a == Test2.b.
Although result of init will give:

print(Test2.init(rawValue: 0xCp127)) // Optional(output.Test2.a)
print(Test2.init(rawValue: 0xCp128)) // Optional(output.Test2.a)

However, if the restriction for uniqueness is not a problem, I wonder why is there restriction for values to be initialized from literals?
My first guess was - to guarantee static check and values uniqueness and further correct instantiation but probably it is not a problem and this limitation can be lifted.

Should a.rawValue == b.rawValue imply a == b. It certainly isn't enforced by the compiler, nor can it.

But I guess it could be considered a requirement in the same category as the Hashable/Equatable-relationship, ie. a non-enforced semantic requirement, spelled out by documentation, and assumed by all consumers.

Duplicated raw values constitute a semantically invalid conformance to RawRepresentable—i.e., user error. Compiler diagnostics of this and all semantic requirements are only ever best-effort and (for obvious reasons) cannot encompass custom types. If a type violates the semantic invariants of a protocol, then all expectations as to the behaviors guaranteed by that protocol are out the window.

6 Likes