Skip to content

Backport "Fix #21914: Don't project nested wildcard patterns to nullable" to 3.3 LTS #91

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ object SpaceEngine {
def isSubspace(a: Space, b: Space)(using Context): Boolean = a.isSubspace(b)
def canDecompose(typ: Typ)(using Context): Boolean = typ.canDecompose
def decompose(typ: Typ)(using Context): List[Typ] = typ.decompose
def nullSpace(using Context): Space = Typ(ConstantType(Constant(null)), decomposed = false)

/** Simplify space such that a space equal to `Empty` becomes `Empty` */
def computeSimplify(space: Space)(using Context): Space = trace(i"simplify($space)")(space match {
Expand Down Expand Up @@ -690,7 +691,6 @@ object SpaceEngine {
else NoType
}.filter(_.exists)
parts

case _ => ListOfNoType
end rec

Expand Down Expand Up @@ -904,6 +904,10 @@ object SpaceEngine {
then project(OrType(selTyp, ConstantType(Constant(null)), soft = false))
else project(selTyp)
)
def projectPat(pat: Tree): Space =
// Project toplevel wildcard pattern to nullable
if isNullable && isWildcardArg(pat) then Or(project(pat) :: nullSpace :: Nil)
else project(pat)

var i = 0
val len = cases.length
Expand All @@ -913,7 +917,7 @@ object SpaceEngine {
while (i < len) {
val CaseDef(pat, guard, _) = cases(i)

val curr = trace(i"project($pat)")(project(pat))
val curr = trace(i"project($pat)")(projectPat(pat))

val covered = trace("covered")(simplify(intersect(curr, targetSpace)))

Expand Down
1 change: 1 addition & 0 deletions tests/patmat/null.check
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
6: Pattern Match
13: Pattern Match
20: Pattern Match
21: Match case Unreachable
1 change: 1 addition & 0 deletions tests/patmat/null.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ class Test {
case Some(null) =>
case None =>
case y =>
case _ =>
}
}
2 changes: 1 addition & 1 deletion tests/warn/i15503d.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ val a = Sum(S(S(Z)),Z) match {
case Sum(a@S(b@S(_)), Z) => a // warn
case Sum(a@S(b@(S(_))), Z) => Sum(a,b) // warn unreachable
case Sum(_,_) => Z // OK
case _ => Z // warn unreachable
case _ => Z
}

// todo : This should pass in the future
Expand Down
13 changes: 13 additions & 0 deletions tests/warn/i20121.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
sealed trait T_A[A, B]
type X = T_A[Byte, Byte]

case class CC_B[A](a: A) extends T_A[A, X]

val v_a: T_A[X, X] = CC_B(null)
val v_b = v_a match
case CC_B(_) => 0 // warn: unreachable
case _ => 1
// for CC_B[A] to match T_A[X, X]
// A := X
// so require X, aka T_A[Byte, Byte]
// which isn't instantiable, outside of null
17 changes: 17 additions & 0 deletions tests/warn/i20122.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
sealed trait T_B[C, D]

case class CC_A()
case class CC_B[A, C](a: A) extends T_B[C, CC_A]
case class CC_C[C, D](a: T_B[C, D]) extends T_B[Int, CC_A]
case class CC_E(a: CC_C[Char, Byte])

val v_a: T_B[Int, CC_A] = CC_B(CC_E(CC_C(null)))
val v_b = v_a match
case CC_B(CC_E(CC_C(_))) => 0 // warn: unreachable
case _ => 1
// for CC_B[A, C] to match T_B[C, CC_A]
// C <: Int, ok
// A <: CC_E, ok
// but you need a CC_C[Char, Byte]
// which requires a T_B[Char, Byte]
// which isn't instantiable, outside of null
16 changes: 16 additions & 0 deletions tests/warn/i20123.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
sealed trait T_A[A, B]
sealed trait T_B[C]

case class CC_D[A, C]() extends T_A[A, C]
case class CC_E() extends T_B[Nothing]
case class CC_G[A, C](c: C) extends T_A[A, C]

val v_a: T_A[Boolean, T_B[Boolean]] = CC_G(null)
val v_b = v_a match {
case CC_D() => 0
case CC_G(_) => 1 // warn: unreachable
// for CC_G[A, C] to match T_A[Boolean, T_B[Boolean]]
// A := Boolean, which is ok
// C := T_B[Boolean],
// which isn't instantiable, outside of null
}
4 changes: 2 additions & 2 deletions tests/warn/t2755.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object Test {
case x: Array[String] => x.size
case x: Array[AnyRef] => 5
case x: Array[?] => 6
case _ => 7 // warn: only null is matched
case _ => 7
}
def f3[T](a: Array[T]) = a match {
case x: Array[Int] => x(0)
Expand All @@ -28,7 +28,7 @@ object Test {
case x: Array[String] => x.size
case x: Array[AnyRef] => 5
case x: Array[?] => 6
case _ => 7 // warn: only null is matched
case _ => 7
}


Expand Down