Exhaustivity check fails for Java sealed classes on JDK 21?

I encountered a regression(?) bumping JDK from 17 to 21 (applies to Scala 2.13.17 and 3.7.1)

I have some sealed java class

public sealed class Sealed permits A, B {}

final class A extends Sealed {}

final class B extends Sealed {}

pattern matched from the Scala code

val a: Sealed = ???
a match {
  case a: A =>
  case b: B =>
}

When compiling with JDK 17 no error occurs. But for 21

for Scala 2

 It would fail on the following input: Sealed()

for Scala 3

match may not be exhaustive.
It would fail on pattern case: _: Sealed

Has Scala stopped understanding java sealed classes, or has it started checking them better, but I just don’t understand how they work in Java?

2 Likes

a is one 3 types, A, B, or Sealed. you need to check against all 3. Weird that there are instances where just checking 2 would work without at least warning.

I see the warning under JDK 17 with 2.13.17 but not with 3.7.3.

Scala 3 also fails to warn under JDK 25, so the difference is Scala version not JDK.

Confirming that it’s trivial to supply Sealed() and fail:

Exception in thread "main" scala.MatchError: Sealed@4c75cab9 (of class Sealed)

Edit: I did not check whether Scala 3 has different option flags to produce a warning, as that would take all day to track down.

Edit: let me try -Wall. It only warns about unused b because you’re allowed to refine the selector a without incurring a warning.

10 |      case b: B => 2
   |           ^
   |           unused pattern variable

Omg, that’s right. And scala sealed classes work the same way. Brain fog

public sealed class Sealed permits A, B {}

final class A extends Sealed {}

final class B extends Sealed {}

and

object Test {
  def test(a: Sealed): Int =
    a match {
      case a: A => 1
      case b: B => 2
    }

  def main(args: Array[String]): Unit = println {
    test(new Sealed)
  }
}

then

➜  snips javac -d /tmp Sealed.java
➜  snips scala-cli run --server=false -S 3.7.3 -Wall -cp /tmp sealed-test.scala
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:/home/amarki/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3/3.7.3/scala3-library_3-3.7.3.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
-- [E198] Unused Symbol Warning: /home/amarki/snips/sealed-test.scala:6:11 -----
6 |      case b: B => 2
  |           ^
  |           unused pattern variable
1 warning found
Exception in thread "main" scala.MatchError: Sealed@4c75cab9 (of class Sealed)
        at Test$.test(sealed-test.scala:6)
        at Test$.main(sealed-test.scala:10)
        at Test.main(sealed-test.scala)

I don’t see an exhaustivity warning.

Scala-only:

sealed class Sealed
final class A extends Sealed
final class B extends Sealed

object Test {
  def test(a: Sealed): Int =
    a match {
      case a: A => 1
      case b: B => 2
    }

  def main(args: Array[String]): Unit = println {
    test(new Sealed)
  }
}

then

➜  snips scala-cli run --server=false -S 3.7.3 -Wall sealed-scala.scala
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:/home/amarki/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3/3.7.3/scala3-library_3-3.7.3.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
-- [E198] Unused Symbol Warning: /home/amarki/snips/sealed-scala.scala:11:11 ---
11 |      case b: B => 2
   |           ^
   |           unused pattern variable
-- [E029] Pattern Match Exhaustivity Warning: /home/amarki/snips/sealed-scala.scala:9:4
9 |    a match {
  |    ^
  |    match may not be exhaustive.
  |
  |    It would fail on pattern case: _: Sealed
  |
  | longer explanation available when compiling with `-explain`
2 warnings found
Exception in thread "main" scala.MatchError: Sealed@4c75cab9 (of class Sealed)
        at Test$.test(sealed-scala.scala:11)
        at Test$.main(sealed-scala.scala:15)
        at Test.main(sealed-scala.scala)

but Scala 2 warns for both cases

➜  snips scala-cli run --server=false -S 2.13.17 -Wunused -cp /tmp sealed-test.scala
/home/amarki/snips/sealed-test.scala:6: warning: pattern var b in method test is never used
      case b: B => 2
           ^
/home/amarki/snips/sealed-test.scala:4: warning: match may not be exhaustive.
It would fail on the following input: Sealed()
    a match {
    ^
2 warnings
Exception in thread "main" scala.MatchError: Sealed@52d455b8 (of class Sealed)
        at Test$.test(sealed-test.scala:4)
        at Test$.main(sealed-test.scala:10)
        at Test.main(sealed-test.scala)

with the same result for Scala-only.

This is for my reference.

1 Like