I have two questions about matching optionals in a switch statement. Apparently an Int? can be matched against non-optional integer literals or non-optional integer expressions:
let x: Int? = 1
switch x {
case 1: print("one")
case 1?: print("one?")
case 1+2: print("one+two")
default: print("default")
}
Question 1:case 1? is never executed. Should the compiler detect that and emit a warning?
An optional enum can not be matched against a non-optional value:
enum MyEnum { case a, b }
let e: MyEnum? = .a
switch e {
case .a: print("a") // error: Enum case 'a' not found in type 'MyEnum?'
case .a?: print("some a") // OK
default: print("default")
}
Question 2: Is that fundamentally different from the integer case, or should it compile as well?
(Tested with Xcode 9.3.1, with both the default toolchain and the Swift 4.2 snapshot from May 20, 2018.)
For question 1, there could be a warning when a case in a switch statement is unreachable, sure.
As for question 2, I believe the “dot syntax”/“implicit member expressions” are intentionally limited so that type inference doesn't have to do anything heroic. In this case, it's limited to looking up type methods and variables on MyEnum?, so you can use case .none, but not implicit member lookup on MyEnum which would be subsequently implicitly promoted to Optional. I don't know where these limitations are documented, though, and it's possible that some of them could be lifted.
I was going to write that this isn't that important because it's just a small convenience/syntactic sugar for MyEnum.a, but when I actually tried that in your example, using case MyEnum.a, I get
error: enum case 'a' is not a member of type 'MyEnum?'
case MyEnum.a: print("a") // error: Enum case 'a' not found in type 'MyEnum?'
which makes no sense to me, since I'm being explicit about the lookup and the result should be implicitly promoted. Trying another enum in that context shows that something seems broken with lookup in switch cases, even without the Optional.
enum MyEnum { case a, b }
enum E { case one }
let e: MyEnum = .a
switch e {
case E.one: print("one") // error: enum case 'one' is not a member of type 'MyEnum'
default: print("default")
}
And if you play around with it in a playground you can get truly nonsensical error messages like
error: NSCountedSet.playground:154:8: error: type 'E' has no member 'a'
case E.a: print("one") // error: enum case 'one' is not a member of type 'MyEnum'
which seem to be the result of some bad caching somewhere.
enum A { case one }
enum B { case one }
let e = A.one
switch e {
case A.one: print("one")
case B.one: print("one") // ERR: Enum case 'one' is not a member of type 'A'
}
But, but ... one is a member of type A!
@jawbroken: Do you mean this should be reported as being something more/worse than bad diagnostics?
Yeah, the whole thing seems broken. Thanks for resurrecting this thread, because I had forgotten about it. Your example is definitely bad diagnostics, but this one seems like a clear bug:
enum A { case one }
let e: A? = A.one
switch e {
case A.one: print("one") // error: Enum case 'one' is not a member of type 'A?'
}
or, similarly,
struct S {
var i: Int
static var ten: S { return S(i: 10)}
}
let s: S? = .ten
switch s {
case .ten: print("ten") // error: enum case 'ten' not found in type 'S?'
default: print("default")
}
And they both seem to stem from the same underlying issue with name lookups in switch statements. I filed a bug report.
Yeah, the whole thing seems broken. Thanks for resurrecting this thread, because I had forgotten about it. Your example is definitely bad diagnostics, but this one seems like a clear bug:
That's not a bug. The correct pattern for this is one of the following
switch e {
case A.one?: print("one")
}
switch e {
case .some(A.one): print("one")
}
We do not allow implicit conversions outside of expression patterns - which is arguably an accidental effect of TypeCheckPattern always trying to coerce to the scrutinee's type.
The diagnostics could be better here, but it's gonna take some serious special-casing.
Question 1: case 1? is never executed. Should the compiler detect that and emit a warning?
We should, but doing so will be tricky. We currently detect duplicate literals that have plain integral and floating type. Extending that check to optionals of literals requires checking for applies of ~= to an inject_into_optional
Question 2: Is that fundamentally different from the integer case, or should it compile as well?
The difference is that expression patterns allow conversions where normal patterns don't. This should not compile because the pattern in the first case does not reflect the construction of values of type A?. If it behaved this way, it would also be subject to the surprising behavior of your first example but we would at least be able to catch and warn about it. Checking arbitrary expression patterns, however, is subject to concerns isomorphic to solving the halting problem.
I looked up what an expression pattern is, and found it's the expression in a switch statement case label. So should this say “inside” not “outside”? I'm struggling to understand this sentence, perhaps because it's fairly heavy on internal compiler details.
If this isn't a bug, it's going to be hard to explain to people (including me) why those examples shouldn't work but these do:
let optionalInt: Int? = 1
switch optionalInt {
case 1: print("one")
default: print("default")
}
switch optionalInt {
case (1 as Int): print("one")
default: print("default")
}
let one: Int = 1
switch optionalInt {
case one: print("one")
default: print("default")
}
Can you elaborate on the mental model that I should have here?
case X.y is treated as case .y as X, in other words, as if there's a type cast pattern to the named enum inserted in between. This is what allows catch MyError.code to work to pattern-match a concrete type out of an Error value, for instance. We ought to emit a diagnostic that A and B are unrelated, so the cast will always fail, though.
That's an accident of the current implementation rather than intentional design. The pattern grammar in general attempts to follow the expression grammar, so that a pattern ...let x... looks like an expr ...123... that matches the pattern with x = 123.