Dotty: Usage of `@unchecked` annotation with `val` is inconsistent between Scala 2 and 3

Created on 3 Dec 2020  Â·  9Comments  Â·  Source: lampepfl/dotty

in Scala 2, one annotates the scrutinee:

scala 2.13.4> val Some(x) = Option(3)
                                  ^
              warning: match may not be exhaustive.
              It would fail on the following input: None
val x: Int = 3

scala 2.13.4> val Some(x) = Option(3): @unchecked
val x: Int = 3

whereas in Scala 3.0.0-M2 under -source:3.1 one must annotate the pattern, while annotating the scrutinee is ignored:

scala> val Some(x) = Option(3)
1 |val Some(x) = Option(3)
  |    ^^^^^^^
  |pattern's type Some[Int] is more specialized than the right hand side expression's type Option[Int]
  |
  |If the narrowing is intentional, this can be communicated by writing `: @unchecked` after the full pattern.

scala> val Some(x) = Option(3): @unchecked                                                                              
1 |val Some(x) = Option(3): @unchecked
  |    ^^^^^^^
  |pattern's type Some[Int] is more specialized than the right hand side expression's type Option[Int] @unchecked
  |
  |If the narrowing is intentional, this can be communicated by writing `: @unchecked` after the full pattern.

scala> val Some(x): @unchecked = Option(3)
val x: Int = 3

Scala 3's behavior is as documented at https://dotty.epfl.ch/docs/reference/changed-features/pattern-bindings.html :

Sometimes one wants to decompose data anyway, even though the pattern is refutable. For instance, if at some point one knows that a list elems is non-empty one might want to decompose it like this:

val first :: rest = elems   // error

This works in Scala 2. In fact it is a typical use case for Scala 2's rules. But in Scala 3.1 it will give a type error. One can avoid the error by marking the pattern with an @unchecked annotation:

val first :: rest : @unchecked = elems   // OK

This will make the compiler accept the pattern binding. It might give an error at runtime instead, if the underlying assumption that elems can never be empty is wrong.

but I suggest that Scala 3 should adopt the Scala 2 convention, because:

  • it is more consistent with match; one also annotates the scrutinee with match, like (foo: @unchecked) match ...
  • we want to support cross-building between 2 and 3, and 2's style is the established precedent

(this came up at https://github.com/scala/bug/issues/12245)

This became more important with the release of Scala 2.13.4, where improved exhaustivity checking has sent many users scurrying to add @unchecked to their matches.

bug

Most helpful comment

During the meeting, it is agreed that Scala 3 should align with Scala 2.

All 9 comments

During the meeting, it is agreed that Scala 3 should align with Scala 2.

To calrify: in Scala 2.13.4, we need to enable scala -Xlint:valpattern to get the warnings:

scala> val List(x, y) = List(1, 2)
                            ^
       warning: match may not be exhaustive.
       It would fail on the following inputs: List(_), Nil
val x: Int = 1
val y: Int = 2

And it can be suppressed with : @unchecked:

scala> val List(x, y) = List(1, 2): @unchecked
val x: Int = 1
val y: Int = 2

Dotty needs to follow the Scala 2 behavior assuming -Xlint:valpattern in -source 3.1 mode.

To calcify, in Scala 2 @unchecked is automatically supplied and -Xlint suppresses it, at which point @unchecked can be supplied manually.

The change was prompted by lihaoyi's list of warts, but I see there was also an old ticket requesting relief from MatchError.

Even as a friendly lint, it didn't seem the PR would make it in, so it's nice that it landed on the right side of history, and that the @unchecked has also landed on the right side.

Thanks @som-snytt . In #10793 , -source 3.1 in Dotty has the same behavior as -Xlint:valpattern in Scala 2.13.4. Only syntax has changed in that PR.

There is a minor difference in behavior. Scala 2.13.4 with -Xlint:valpattern only warns for the 2nd case:

scala> val Some(a) = Some(5): Any
val a: Any = 5

scala> val Some(a) = Some(5): Option[Int]
                            ^
       warning: match may not be exhaustive.
       It would fail on the following input: None
val a: Int = 5

Dotty with -source 3.1 warns for both cases.

Apparently

➜  scala -Xlint:valpattern,strict-unsealed-patmat
Welcome to Scala 2.13.4 (OpenJDK 64-Bit Server VM, Java 15).
Type in expressions for evaluation. Or try :help.

scala> val Some(x) = Some(42): Any
                             ^
       warning: match may not be exhaustive.
       It would fail on the following input: (x: Any forSome x not in Some[?])
val x: Any = 42

scala> val Some(y) = Some(42): Option[Int]
                             ^
       warning: match may not be exhaustive.
       It would fail on the following input: None
val y: Int = 42

Thanks for the information @som-snytt, I see, there are many switches.

Another aspect we might want to align with Scala 2 is to issue warnings instead of issuing errors.

Another aspect we might want to align with Scala 2 is to issue warnings instead of issuing errors.

@b-studios What do you think about this?

I think warning vs errors should be done consistently with exhaustivity on normal matches.

I think warning vs errors should be done consistently with exhaustivity on normal matches.

Agreed, now it's changed to only report warnings, as in exhaustivity check and Scala 2.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

milessabin picture milessabin  Â·  3Comments

dwijnand picture dwijnand  Â·  3Comments

NightMachinary picture NightMachinary  Â·  3Comments

mcku picture mcku  Â·  3Comments

Blaisorblade picture Blaisorblade  Â·  3Comments