Hello,
I’m wondering why : @unchecked was chosen for pattern-matching vals
For example I wasn’t expecting the following to fail:
val (x: Int) :: rest = List("Hello", "World"): @unchecked
// error: this case is unreachable since type String is not a subclass of class Integer
It checks it, even tho the code says otherwise !
This might explain why @unchecked feels like such a code smell for me
I would be curious as to the experience of others
Is there a reason something like case val or val case was not chosen ?
(similar to what was done for for)
It seems like it wouldn’t have created ambiguity, as both case and val cannot be used as variable names
I like the idea of case val p to “enable” patterns in the definition. The existing syntax is still subtle after all these years.
I see @unchecked goes back to Scala 2.4. The current doc says it suppresses “additional compiler checks”, not all checks. That includes exhaustivity warnings.
Since the definition involves a pattern match, I think the annotation fell out naturally. Today, it would be called @nowarn.
I can’t wait for Scala 4 when we can write:
case val 1 = 2: @nowarn
That was a joke, I meant to say:
case val i: Int = (???: Any): @nowarn
by analogy to
val Some(i: Int) = Option("": Any)
The messaging for your example is odd. Scala 2 says
scala> val (x: Int) :: rest = List("Hello", "World"): @unchecked
^
error: scrutinee is incompatible with pattern type;
found : Int
required: String
instead of pattern type is incompatible with scrutinee.
Scala 3, as shown:
1 |val (x: Int) :: rest = List("Hello", "World"): @unchecked
| ^
| this case is unreachable since type String is not a subclass of class Integer
even though we are enjoined not to conflate types and classes, it refers to class Integer, presumably as the boxed Int.
In all, @unchecked is the least inscrutable part to me.
Note that this is not what I am advocating for, val (x, y) = (2, 3) should still work
I am saying the idiomatic way of doing val (x,y) = (tuple: Tuple) should be case val (x,y) = (tuple: Tuple), instead of val (x,y) = tuple: @unchecked.
But both ways would work
It is interesting how the same kind of problem gives different warnings in these 3 cases (Scala 3.2.1):
scala> val x +: xs = List(1,2,3)
-- Warning: -------------------------------------------------------------------------------------------------------------------
1 |val x +: xs = List(1,2,3)
| ^^^^^^^^^^^^^^^^^^^^^
| pattern binding uses refutable extractor `+:`
|
| If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression,
| which may result in a MatchError at runtime.
val x: Int = 1
val xs: List[Int] = List(2, 3)
scala> val List(x, xs*) = List(1,2,3)
-- Warning: -------------------------------------------------------------------------------------------------------------------
1 |val List(x, xs*) = List(1,2,3)
| ^^^^^^^^^^^
| pattern's type Int* does not match the right hand side expression's type Int
|
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
| which may result in a MatchError at runtime.
val x: Int = 1
val xs: Seq[Int] = List(2, 3)
scala> val x::xs = List(1,2,3)
-- Warning: -------------------------------------------------------------------------------------------------------------------
1 |val x::xs = List(1,2,3)
| ^^^^^^^^^^^
| pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int]
|
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
| which may result in a MatchError at runtime.
val x: Int = 1
val xs: List[Int] = List(2, 3)
Should there be some kind of more generalized warning that covers all these similar cases and hint that the match might fail (thinks the compiler)?
Could the compiler conclude somehow that a constructor with only a non-empty sequence of constants would actually not fail for that pattern in this simple case?