Skip to content

Incorrect typing under match with alternatives #13090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
szeiger opened this issue Feb 25, 2025 · 2 comments
Open

Incorrect typing under match with alternatives #13090

szeiger opened this issue Feb 25, 2025 · 2 comments
Labels
Milestone

Comments

@szeiger
Copy link

szeiger commented Feb 25, 2025

Given

sealed trait A[T] { def x: T }
final case class B(x: String) extends A[String]
final case class C(x: Int) extends A[Int]

def f[T](a: A[T]): Unit = {
  def v: T = a match { case C(x) => 42 }
  a match {
    case B(_) | C(_) =>
      val v1: T = v
      val v2: T = a match { case C(x) => 42 }
      ()
  }
}

the definition of v1 typechecks correctly, but v2 fails:

$ ~/scala/scala-2.13.14/bin/scala adt.scala
adt.scala:10: error: type mismatch;
 found   : Int(42)
 required: T
      val v2: T = a match { case C(x) => 42 }
                                         ^

The failure is consistent in Scala 2.12, 2.13 and 3.6, but compiling with 3.6 gives a better hint at the problem:

$ ~/scala/scala3-3.6.3-aarch64-apple-darwin/bin/scala adt.scala
Compiling project (Scala 3.6.3, JVM (11))
[error] ./adt.scala:10:42
[error] Found:    (42 : Int)
[error] Required: T
[error]
[error] where:    T is a type in method f which is an alias of String
[error]       val v2: T = a match { case C(x) => 42 }
[error]                                          ^^
@lrytz
Copy link
Member

lrytz commented Mar 14, 2025

I took a quick look. In

def f[T](a: A[T]): T = a match { case C(x) => 42 }

the bounds of T are temporarily mutated to >: Int <: Int and later restored.

For an alternative pattern case C(_) | B(_), the bounds are first set to >: Int <: Int, and then the constraints are not updated because they are incompatible. That is actually unsound:

scala> sealed trait A[T] { def x: T }
     | final case class B(x: String) extends A[String]
     | final case class C(x: Int) extends A[Int]

scala> def f[T](a: A[T]): T = a match { case B(_) | C(_) => "plop" }

scala> f(C(1)) + 1
java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
  at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)

It's really fascinating that bugs like this are still discovered after so many years.

For the original example with the nested pattern

def f[T](a: A[T]): T = a match {
  case B(_) | C(_) =>
    a match { case C(_) => 42 }
}

I don't know if it's possible to support that. We'd have to know which of the temporary GADT bounds to reset for typing the nested pattern, and for which ones to keep the constraints from the enclosing pattern?

CC @dwijnand. I created a ticket on Scala 3 (scala/scala3#22805).

@lrytz lrytz added the gadt label Mar 14, 2025
@lrytz lrytz added this to the Backlog milestone Mar 14, 2025
@joroKr21
Copy link
Member

joroKr21 commented Mar 15, 2025

I guess in Scala 3 it could be String | Int and in Scala 2 it could be Any

No, better to reject this then

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants