Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-5421] Can't use same case in two enums #47995

Closed
swift-ci opened this issue Jul 10, 2017 · 5 comments
Closed

[SR-5421] Can't use same case in two enums #47995

swift-ci opened this issue Jul 10, 2017 · 5 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself regression swift 4.0

Comments

@swift-ci
Copy link
Collaborator

Previous ID SR-5421
Radar rdar://problem/33226924
Original Reporter smartgo (JIRA User)
Type Bug
Status Resolved
Resolution Invalid
Environment

Xcode 9.0 beta 3 (9M174d), both when compiled as Swift 3.2 and Swift 4.0.

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, 4.0Regression
Assignee None
Priority Medium

md5: 7001de01ec0f975646ce9040af0d79b9

Issue Description:

The following code shows a bug in Xcode 9.0 beta 3 (9M174d) that was not there in beta 2. The references to .black and .white are marked as errors, as it doesn't know whether to use the one in BlackWhite or the one in EmptyBlackWhite:
Ambiguous use of 'black'

When commenting out the comparison functions, the error disappears.

import Foundation

enum BlackWhite: Int {

    case black = 1
    case white = 2

    init(_ color: EmptyBlackWhite) {
        assert(color == .black || color == .white)
        self = BlackWhite(rawValue: color.rawValue)!
    }

    var isBlack: Bool { return self == .black }
    var isWhite: Bool { return self == .white }
}

enum EmptyBlackWhite: Int {

    case empty = 0
    case black = 1
    case white = 2

    init(_ color: BlackWhite) {
        assert(color == .black || color == .white)
        self = EmptyBlackWhite(rawValue: color.rawValue)!
    }

    var isBlackWhite: Bool { return self == .black || self == .white }
}

// Comment out the following lines, and the bug disapears.
func == (lhs: BlackWhite, rhs: EmptyBlackWhite) -> Bool {
    return lhs.rawValue == rhs.rawValue
}

func != (lhs: BlackWhite, rhs: EmptyBlackWhite) -> Bool {
    return lhs.rawValue != rhs.rawValue
}

func == (lhs: EmptyBlackWhite, rhs: BlackWhite) -> Bool {
    return lhs.rawValue == rhs.rawValue
}

func != (lhs: EmptyBlackWhite, rhs: BlackWhite) -> Bool {
    return lhs.rawValue != rhs.rawValue
}
@huonw
Copy link
Mannequin

huonw mannequin commented Jul 10, 2017

This is expected behavior: the compiler cannot tell that the two blacks and the two whites are semantically identical, and so needs to unambiguously choose one of them whenever those names are referenced. By default, enums with raw values automatically get == comparisons with their own type, meaning the following functions exist:

func == (lhs: BlackWhite, rhs: BlackWhite) -> Bool {
    // ...
}
func != (lhs: BlackWhite, rhs: BlackWhite) -> Bool {
    // ...
}
func == (lhs: EmptyBlackWhite, rhs: EmptyBlackWhite) -> Bool {
    // ...
}
func != (lhs: EmptyBlackWhite, rhs: EmptyBlackWhite) -> Bool {
    // ...
}

With just these implicit functions, references like `someBlackWhite == .black` or `someEmptyBlackWhite != .black` is unambiguous: for the first case, `someBlackWhite` has type `BlackWhite` and so the only `==` operator that can be used is the first one above, this forces the second argument to also have type `BlackWhite` and thus `.black` must be a member of this type. Similarly, the second example can only use the last `!=` operator and so the second argument is also forced to be `EmptyBlackWhite`.

With the new operators, there's no longer a single choice when the type of the first argument is chosen. For `someBlackWhite == .black`, this could refer to either

func == (lhs: BlackWhite, rhs: BlackWhite) -> Bool
func == (lhs: BlackWhite, rhs: EmptyBlackWhite) -> Bool

And the compiler has no way to work out which you wanted.

A good way to fix this is to not have two separate types, and, assuming the example is close to your real code, the correct way to add an "empty" case is to use `Optional`. Instead of `EmptyBlackWhite`, one can write `BlackWhite?`, with `nil` being `.empty`. This is much "Swiftier" than having two separate enums, and also works nicely with operators like == due to careful overloads in the standard library, e.g. all of the following compile and behave as one might expect (and so do all the various permutations/combinations)

let x: BlackWhite = ...
let y: BlackWhite? = ...

x == y
x == .black
y == .black
y == nil

@swift-ci
Copy link
Collaborator Author

Comment by Anders Kierulf (JIRA)

Thanks for the explanation, makes sense; the bug was that this worked before, not that it is no longer working.

Using optional BlackWhite instead of EmptyBlackWhite is a good suggestion that will work in some cases where I'm using EmptyBlackWhite, but not in general, as it also includes a fourth case (blackAndWhite) that's used in low-level bitboards (for the game of Go).

@huonw
Copy link
Mannequin

huonw mannequin commented Jul 11, 2017

Oh, I apologize, I completely missed that it was a difference between versions. I can reproduce it, and I have no idea what's going on.

@swift-ci create

@rudkx
Copy link
Member

rudkx commented Jul 11, 2017

The current behavior looks correct to me, although I do not know what changed.

You can always disambiguate by using the full name rather than just the dot followed by member.

@rudkx
Copy link
Member

rudkx commented Jul 11, 2017

I took a closer look at this. We used to compile this without error due to a hack in the type checker that would stop looking at overloads under some conditions. That hack was narrowed, which is why we are now (correctly) diagnosing this as ambiguous.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself regression swift 4.0
Projects
None yet
Development

No branches or pull requests

3 participants