Feedback on `: @unchecked` for pattern-matching `val`s?

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

1 Like

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.

2 Likes

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

Thanks for the clarification, if I understand you correctly.

You just want to distinguish “refutable” patterns.

I suggested, if you want a pattern match in a definition, use case val, and if in addition you’d like to silence warnings, use @unchecked.

You said, keep valpat definitions as is, but if the pattern is refutable, require case val instead of the annotation.

I don’t have an opinion except that I like the idea of case val differentiating the syntax (whether a pattern is accepted).

Your suggestion is that “irrefutable” is well-defined, but I’m not sure about that.

case means “filter” in for expressions. I think it would be problematic to have it mean “can crash” in val definitions.

Perhaps this?

footgun val (x: Int) :: rest = List("Hello", "World")

:upside_down_face:

3 Likes

for (case x: Int <- vs) means filter but case val x: Int = v signals “crash” with val.

Consider case class C as a syntactic juxtaposition where case alone does not carry complete semantic information.

The missing syntaxes are

for (case val x: Int <- vs) ???

case x: Int = v
// meaning
val x: Int = v match { case y: Int => y case _ => null.asInstanceOf[Int] }

Hmm, I’m not sure this is such a big problem, as @som-snytt mentionned, case is already used in other places to mean other things

I can also accept a different keyword, though maybe not footgun ^^

Or maybe even an annotation that is specific to this purpose like @unsafeMatch
Example: val (x: Int) :: rest = List("Hello", "World"): @unsafeMatch

My only goal is to make it stop looking like a code smell, the way is only accessory to that

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?

5 Likes