scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

pattern matcher generates illegal isInstanceOf[Null] for nullable parameter of unapply

Open olhotak opened this issue 6 months ago • 4 comments

Compiler version

Scala 3.7.2-RC1-bin-20250520-baac46c-NIGHTLY, JVM (8)

Minimized code

object Extractor:
  def unapply(s: String|Null): Boolean = true
  
class A

def main =
  ("foo": (A|String)) match
    case Extractor() => println("foo matched")
    case _ => println("foo didn't match")

Output

$ scala-cli -S 3.nightly patmat.scala 
Compiling project (Scala 3.7.2-RC1-bin-20250520-baac46c-NIGHTLY, JVM (8))
[error] ./patmat.scala:9:10
[error] class Null cannot be used in runtime type tests
[error]     case Extractor() => println("foo matched")
[error]          ^^^^^^^^^^^
Error compiling project (Scala 3.7.2-RC1-bin-20250520-baac46c-NIGHTLY, JVM (8))

Expectation

Is a nullable type parameter of unapply even allowed?

If not, the error message should point out the unapply method.

If yes, the pattern matcher should not generate an illegal isInstanceOf[Null] and the example should compile.

Note: the issue happens both with and without -Yexplicit-nulls.

Note: a similar unapply appears in the compiler source in dotty.tools.backend.jvm.DottyBackendInterface.DeconstructorCommon.

olhotak avatar May 22 '25 21:05 olhotak

cc @HarrisL2 @SuperCl4sh

olhotak avatar May 22 '25 21:05 olhotak

cc @noti0na1

Gedochao avatar May 23 '25 08:05 Gedochao

Hi, would it possible for me to get assigned to this? I believe I've located the cause, but I haven't produced a fix yet.

SuperCl4sh avatar May 27 '25 04:05 SuperCl4sh

An extractor pattern cannot match the value null. The implementation ensures that the unapply/unapplySeq method is not applied to null.

per spec.

The extractor is fine, but patmat should always do a null check or correct instanceof:

    def f[T <: String | Null](x: T): Unit =
      matchResult2[Unit]:
        {
          case val x3: (x : T) = x
          if (x3 ne null) && X.unapply(x3) then
            return[matchResult2]
              {
                println("foo matched")
              }
           else ()
          return[matchResult2]
            {
              println("foo didn\'t match")
            }
        }
    def g[T <: String | A](x: T): Unit =
      matchResult3[Unit]:
        {
          case val x4: (x : T) = x
          if x4.$isInstanceOf[String] && X.unapply(x4.$asInstanceOf[String])
             then
            return[matchResult3]
              {
                println("foo matched")
              }
           else ()
          return[matchResult3]
            {
              println("foo didn\'t match")
            }
        }

som-snytt avatar May 30 '25 10:05 som-snytt