Skip to content

Commit 512fbd5

Browse files
authored
Fix regression in exhausitivity of HK types (#18303)
Reverts a key part of #16958, which is a complicated case, to fix the regression(s).
2 parents 965818a + 0839123 commit 512fbd5

File tree

10 files changed

+65
-20
lines changed

10 files changed

+65
-20
lines changed

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

+8-16
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ object SpaceEngine {
321321
* The types should be atomic (non-decomposable) and unrelated (neither
322322
* should be a subtype of the other).
323323
*/
324-
def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type)(sp: Space)(using Context): Space = trace(i"atomic intersection: ${AndType(tp1, tp2)}", debug) {
324+
def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type)(sp: Space)(using Context): Space = trace(i"atomic intersection: ${AndType(tp1, tp2)}", debug, show) {
325325
// Precondition: !isSubType(tp1, tp2) && !isSubType(tp2, tp1).
326326
if !ctx.mode.is(Mode.SafeNulls) && (tp1.isNullType || tp2.isNullType) then
327327
// Since projections of types don't include null, intersection with null is empty.
@@ -459,17 +459,8 @@ object SpaceEngine {
459459
WildcardType
460460

461461
case tp @ AppliedType(tycon, args) =>
462-
val args2 =
463-
if tycon.isRef(defn.ArrayClass) then
464-
args.map(arg => erase(arg, inArray = true, isValue = false))
465-
else tycon.typeParams.lazyZip(args).map { (tparam, arg) =>
466-
if isValue && tparam.paramVarianceSign == 0 then
467-
// when matching against a value,
468-
// any type argument for an invariant type parameter will be unchecked,
469-
// meaning it won't fail to match against anything; thus the wildcard replacement
470-
WildcardType
471-
else erase(arg, inArray = false, isValue = false)
472-
}
462+
val inArray = tycon.isRef(defn.ArrayClass)
463+
val args2 = args.map(arg => erase(arg, inArray = inArray, isValue = false))
473464
tp.derivedAppliedType(erase(tycon, inArray, isValue = false), args2)
474465

475466
case tp @ OrType(tp1, tp2) =>
@@ -640,7 +631,7 @@ object SpaceEngine {
640631
// For instance, from i15029, `decompose((X | Y).Field[T]) = [X.Field[T], Y.Field[T]]`.
641632
parts.map(tp.derivedAppliedType(_, targs))
642633

643-
case tp if tp.classSymbol.isDecomposableToChildren =>
634+
case tp if tp.isDecomposableToChildren =>
644635
def getChildren(sym: Symbol): List[Symbol] =
645636
sym.children.flatMap { child =>
646637
if child eq sym then List(sym) // i3145: sealed trait Baz, val x = new Baz {}, Baz.children returns Baz...
@@ -676,8 +667,8 @@ object SpaceEngine {
676667
rec(tp, Nil)
677668
}
678669

679-
extension (cls: Symbol)
680-
/** A type is decomposable to children if it's sealed,
670+
extension (tp: Type)
671+
/** A type is decomposable to children if it has a simple kind, it's sealed,
681672
* abstract (or a trait) - so its not a sealed concrete class that can be instantiated on its own,
682673
* has no anonymous children, which we wouldn't be able to name as counter-examples,
683674
* but does have children.
@@ -686,7 +677,8 @@ object SpaceEngine {
686677
* A sealed trait with subclasses that then get removed after `refineUsingParent`, decomposes to the empty list.
687678
* So that's why we consider whether a type has children. */
688679
def isDecomposableToChildren(using Context): Boolean =
689-
cls.is(Sealed) && cls.isOneOf(AbstractOrTrait) && !cls.hasAnonymousChild && cls.children.nonEmpty
680+
val cls = tp.classSymbol
681+
tp.hasSimpleKind && cls.is(Sealed) && cls.isOneOf(AbstractOrTrait) && !cls.hasAnonymousChild && cls.children.nonEmpty
690682

691683
val ListOfNoType = List(NoType)
692684
val ListOfTypNoType = ListOfNoType.map(Typ(_, decomposed = true))

tests/neg-custom-args/fatal-warnings/suppressed-type-test-warnings.scala

+2
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ object Test {
1818
def err2[A, B](value: Foo[A, B], a: A => Int): B = value match {
1919
case b: Bar[B] => // spurious // error
2020
b.x
21+
case _ => ??? // avoid fatal inexhaustivity warnings suppressing the uncheckable warning
2122
}
2223

2324
def fail[A, B](value: Foo[A, B], a: A => Int): B = value match {
2425
case b: Bar[Int] => // error
2526
b.x
27+
case _ => ??? // avoid fatal inexhaustivity warnings suppressing the uncheckable warning
2628
}
2729
}

tests/neg-custom-args/isInstanceOf/enum-approx2.scala

+2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ case class Fun[A, B](f: Exp[A => B]) extends Exp[A => B]
44
class Test {
55
def eval(e: Fun[Int, Int]) = e match {
66
case Fun(x: Fun[Int, Double]) => ??? // error
7+
case Fun(x: Exp[Int => String]) => ??? // error
8+
case _ =>
79
}
810
}

tests/neg-custom-args/isInstanceOf/i11178.scala

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ object Test1 {
1212
def test[A](bar: Bar[A]) =
1313
bar match {
1414
case _: Bar[Boolean] => ??? // error
15+
case _ => ???
1516
}
1617
}
1718

tests/neg-custom-args/isInstanceOf/i8932.scala

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class Dummy extends Bar[Nothing] with Foo[String]
66
def bugReport[A](foo: Foo[A]): Foo[A] =
77
foo match {
88
case bar: Bar[A] => bar // error
9+
case dummy: Dummy => ???
910
}
1011

1112
def test = bugReport(new Dummy: Foo[String])
File renamed without changes.

tests/neg/i16451.scala renamed to tests/pending/neg/i16451.scala

-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
// scalac: -Werror
22
enum Color:
33
case Red, Green
4-
//sealed trait Color
5-
//object Color:
6-
// case object Red extends Color
7-
// case object Green extends Color
84

95
case class Wrapper[A](value: A)
106

tests/pos/i17230.bootstrap.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
type Untyped = Type | Null
2+
3+
class Type
4+
abstract class SearchFailureType extends Type
5+
6+
abstract class Tree[+T <: Untyped]:
7+
def tpe: T = null.asInstanceOf[T]
8+
9+
class SearchFailureIdent[+T <: Untyped] extends Tree[T]
10+
11+
class Test_i17230_bootstrap:
12+
def t1(arg: Tree[Type]) = arg match
13+
case arg: SearchFailureIdent[?] => arg.tpe match
14+
case x: SearchFailureType =>
15+
case _ =>
16+
case _ =>

tests/pos/i17230.min1.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// scalac: -Werror
2+
trait Foo:
3+
type Bar[_]
4+
5+
object Foo:
6+
type Aux[B[_]] = Foo { type Bar[A] = B[A] }
7+
8+
class Test:
9+
def t1[B[_]](self: Option[Foo.Aux[B]]) = self match
10+
case Some(_) => 1
11+
case None => 2
12+
13+
def t2[B[_]](self: Option[Foo.Aux[B]]) = self match
14+
case Some(f) => 1
15+
case None => 2

tests/pos/i17230.orig.scala

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// scalac: -Werror
2+
import scala.util.*
3+
4+
trait Transaction {
5+
type State[_]
6+
}
7+
object Transaction {
8+
type of[S[_]] = Transaction { type State[A] = S[A] }
9+
}
10+
trait DynamicScope[State[_]]
11+
12+
case class ScopeSearch[State[_]](self: Either[Transaction.of[State], DynamicScope[State]]) {
13+
14+
def embedTransaction[T](f: Transaction.of[State] => T): T =
15+
self match {
16+
case Left(integrated) => ???
17+
case Right(ds) => ???
18+
}
19+
}
20+

0 commit comments

Comments
 (0)