Skip to content

Commit 246fc89

Browse files
authored
Merge pull request #9816 from dotty-staging/simplify-instantiateSub
A bunch of exhaustivity fixes
2 parents 01fc4a1 + bf1b9a6 commit 246fc89

File tree

11 files changed

+221
-49
lines changed

11 files changed

+221
-49
lines changed

compiler/src/dotty/tools/dotc/core/TypeOps.scala

+63-49
Original file line numberDiff line numberDiff line change
@@ -658,44 +658,70 @@ object TypeOps:
658658
* Otherwise, return NoType.
659659
*/
660660
private def instantiateToSubType(tp1: NamedType, tp2: Type)(using Context): Type = {
661-
/** expose abstract type references to their bounds or tvars according to variance */
662-
class AbstractTypeMap(maximize: Boolean)(using Context) extends TypeMap {
663-
def expose(lo: Type, hi: Type): Type =
664-
if (variance == 0)
665-
newTypeVar(TypeBounds(lo, hi))
666-
else if (variance == 1)
667-
if (maximize) hi else lo
668-
else
669-
if (maximize) lo else hi
661+
// In order for a child type S to qualify as a valid subtype of the parent
662+
// T, we need to test whether it is possible S <: T. Therefore, we replace
663+
// type parameters in T with tvars, and see if the subtyping is true.
664+
val approximateTypeParams = new TypeMap {
665+
val boundTypeParams = util.HashMap[TypeRef, TypeVar]()
670666

671-
def apply(tp: Type): Type = tp match {
667+
def apply(tp: Type): Type = tp.dealias match {
672668
case _: MatchType =>
673669
tp // break cycles
674670

675-
case tp: TypeRef if isBounds(tp.underlying) =>
676-
val lo = this(tp.info.loBound)
677-
val hi = this(tp.info.hiBound)
678-
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
679-
val exposed = expose(lo, hi)
680-
typr.println(s"$tp exposed to =====> $exposed")
681-
exposed
682-
683-
case AppliedType(tycon: TypeRef, args) if isBounds(tycon.underlying) =>
684-
val args2 = args.map(this)
685-
val lo = this(tycon.info.loBound).applyIfParameterized(args2)
686-
val hi = this(tycon.info.hiBound).applyIfParameterized(args2)
687-
val exposed = expose(lo, hi)
688-
typr.println(s"$tp exposed to =====> $exposed")
689-
exposed
671+
case tp: TypeRef if !tp.symbol.isClass =>
672+
def lo = LazyRef(apply(tp.underlying.loBound))
673+
def hi = LazyRef(apply(tp.underlying.hiBound))
674+
val lookup = boundTypeParams.lookup(tp)
675+
if lookup != null then lookup
676+
else
677+
val tv = newTypeVar(TypeBounds(lo, hi))
678+
boundTypeParams(tp) = tv
679+
// Force lazy ref eagerly using current context
680+
// Otherwise, the lazy ref will be forced with a unknown context,
681+
// which causes a problem in tests/patmat/i3645e.scala
682+
lo.ref
683+
hi.ref
684+
tv
685+
end if
686+
687+
case AppliedType(tycon: TypeRef, _) if !tycon.dealias.typeSymbol.isClass =>
688+
689+
// In tests/patmat/i3645g.scala, we need to tell whether it's possible
690+
// that K1 <: K[Foo]. If yes, we issue a warning; otherwise, no
691+
// warnings.
692+
//
693+
// - K1 <: K[Foo] is possible <==>
694+
// - K[Int] <: K[Foo] is possible <==>
695+
// - Int <: Foo is possible <==>
696+
// - Int <: Module.Foo.Type is possible
697+
//
698+
// If we remove this special case, we will encounter the case Int <:
699+
// X[Y], where X and Y are tvars. The subtype checking will simply
700+
// return false. But depending on the bounds of X and Y, the subtyping
701+
// can be true.
702+
//
703+
// As a workaround, we approximate higher-kinded type parameters with
704+
// the value types that can be instantiated from its bounds.
705+
//
706+
// Note that `HKTypeLambda.resType` may contain TypeParamRef that are
707+
// bound in the HKTypeLambda. This is fine, as the TypeComparer will
708+
// recurse on the bounds of `TypeParamRef`.
709+
val bounds: TypeBounds = tycon.underlying match {
710+
case TypeBounds(tl1: HKTypeLambda, tl2: HKTypeLambda) =>
711+
TypeBounds(tl1.resType, tl2.resType)
712+
case TypeBounds(tl1: HKTypeLambda, tp2) =>
713+
TypeBounds(tl1.resType, tp2)
714+
case TypeBounds(tp1, tl2: HKTypeLambda) =>
715+
TypeBounds(tp1, tl2.resType)
716+
}
690717

691-
case _ =>
718+
newTypeVar(bounds)
719+
720+
case tp =>
692721
mapOver(tp)
693722
}
694723
}
695724

696-
def minTypeMap(using Context) = new AbstractTypeMap(maximize = false)
697-
def maxTypeMap(using Context) = new AbstractTypeMap(maximize = true)
698-
699725
// Prefix inference, replace `p.C.this.Child` with `X.Child` where `X <: p.C`
700726
// Note: we need to strip ThisType in `p` recursively.
701727
//
@@ -721,37 +747,25 @@ object TypeOps:
721747
val tvars = tp1.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
722748
val protoTp1 = inferThisMap.apply(tp1).appliedTo(tvars)
723749

724-
val force = new ForceDegree.Value(
725-
tvar =>
726-
!(ctx.typerState.constraint.entry(tvar.origin) `eq` tvar.origin.underlying) ||
727-
(tvar `eq` inferThisMap.prefixTVar), // always instantiate prefix
728-
IfBottom.flip
729-
)
730-
731750
// If parent contains a reference to an abstract type, then we should
732751
// refine subtype checking to eliminate abstract types according to
733752
// variance. As this logic is only needed in exhaustivity check,
734753
// we manually patch subtyping check instead of changing TypeComparer.
735754
// See tests/patmat/i3645b.scala
736-
def parentQualify = tp1.widen.classSymbol.info.parents.exists { parent =>
737-
inContext(ctx.fresh.setNewTyperState()) {
738-
parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2)
739-
}
755+
def parentQualify(tp1: Type, tp2: Type) = tp1.widen.classSymbol.info.parents.exists { parent =>
756+
parent.argInfos.nonEmpty && approximateTypeParams(parent) <:< tp2
740757
}
741758

742-
if (protoTp1 <:< tp2) {
759+
def instantiate(): Type = {
743760
maximizeType(protoTp1, NoSpan, fromScala2x = false)
744761
wildApprox(protoTp1)
745762
}
763+
764+
if (protoTp1 <:< tp2) instantiate()
746765
else {
747-
val protoTp2 = maxTypeMap.apply(tp2)
748-
if (protoTp1 <:< protoTp2 || parentQualify)
749-
if (isFullyDefined(AndType(protoTp1, protoTp2), force)) protoTp1
750-
else wildApprox(protoTp1)
751-
else {
752-
typr.println(s"$protoTp1 <:< $protoTp2 = false")
753-
NoType
754-
}
766+
val protoTp2 = approximateTypeParams(tp2)
767+
if (protoTp1 <:< protoTp2 || parentQualify(protoTp1, protoTp2)) instantiate()
768+
else NoType
755769
}
756770
}
757771

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

+2
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,8 @@ class SpaceEngine(using Context) extends SpaceLogic {
623623
val sym1 = if (sym.is(ModuleClass)) sym.sourceModule else sym
624624
val refined = TypeOps.refineUsingParent(tp, sym1)
625625

626+
debug.println(sym1.show + " refined to " + refined.show)
627+
626628
def inhabited(tp: Type): Boolean =
627629
tp.dealias match {
628630
case AndType(tp1, tp2) => !TypeComparer.provablyDisjoint(tp1, tp2)

tests/patmat/i6088.scala

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/** Natural transformation. */
2+
trait ~>[F[_], G[_]] {
3+
def apply[A](fa: F[A]): G[A]
4+
}
5+
6+
/** Higher-kinded pattern functor typeclass. */
7+
trait HFunctor[H[f[_], i]] {
8+
def hmap[A[_], B[_]](nt: A ~> B): ([x] =>> H[A,x]) ~> ([x] =>> H[B,x])
9+
}
10+
11+
/** Some HK pattern functor. */
12+
enum ExprF[R[_],I] {
13+
case Const[R[_]](i: Int) extends ExprF[R,Int]
14+
case Neg[R[_]](l: R[Int]) extends ExprF[R,Int]
15+
case Eq[R[_]](l: R[Int], r: R[Int]) extends ExprF[R,Boolean]
16+
}
17+
18+
/** Companion. */
19+
object ExprF {
20+
given hfunctor as HFunctor[ExprF] {
21+
def hmap[A[_], B[_]](nt: A ~> B): ([x] =>> ExprF[A,x]) ~> ([x] =>> ExprF[B,x]) = {
22+
new ~>[[x] =>> ExprF[A,x], [x] =>> ExprF[B,x]] {
23+
def apply[I](fa: ExprF[A,I]): ExprF[B,I] = fa match {
24+
case Const(i) => Const(i)
25+
case Neg(l) => Neg(nt(l))
26+
case Eq(l, r) => Eq(nt(l), nt(r))
27+
}
28+
}
29+
}
30+
}
31+
}

tests/patmat/i9631.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
trait Txn[T <: Txn[T]]
2+
3+
sealed trait SkipList[T <: Txn[T]]
4+
5+
trait Set[T <: Txn[T]] extends SkipList[T]
6+
7+
object HASkipList {
8+
def debug[T <: Txn[T]](in: SkipList[T]): Set[T] = in match {
9+
case impl: Set[T] => impl
10+
// case _ =>
11+
}
12+
}

tests/patmat/i9841.scala

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
trait Txn[T <: Txn[T]]
2+
3+
object Impl {
4+
sealed trait Entry[T <: Txn[T], A]
5+
case class EntrySingle[T <: Txn[T], A](term: Long, v: A) extends Entry[T, A]
6+
}
7+
8+
trait Impl[T <: Txn[T], K] {
9+
import Impl._
10+
11+
def put[A](): Unit = {
12+
val opt: Option[Entry[T, A]] = ???
13+
14+
opt match {
15+
case Some(EntrySingle(_, prevValue)) => ??? // crashes
16+
case _ =>
17+
}
18+
}
19+
}

tests/patmat/t10100.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
12: Pattern Match Exhaustivity: (_, FancyFoo(_))

tests/patmat/t10100.scala

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
sealed trait Foo {
2+
val index: Int
3+
}
4+
5+
case class BasicFoo(index: Int) extends Foo
6+
7+
class NonExhaustive {
8+
case class FancyFoo(index: Int) extends Foo
9+
10+
def convert(foos: Vector[Foo]): Vector[Int] = {
11+
foos.foldLeft(Vector.empty[Int]) {
12+
case (acc, basic: BasicFoo) => acc :+ basic.index
13+
case (acc, fancy: FancyFoo) => acc :+ fancy.index
14+
}
15+
}
16+
}
17+
18+
@main
19+
def Test = {
20+
val a = new NonExhaustive
21+
val b = new NonExhaustive
22+
23+
val fa: Foo = a.FancyFoo(3)
24+
val fb: Foo = b.FancyFoo(4)
25+
26+
a.convert(Vector(fa, fb))
27+
}

tests/patmat/t11649.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
sealed trait Command { type Err }
2+
final case class Kick() extends Command { type Err = String }
3+
final case class Box() extends Command { type Err = Int }
4+
def handle[E](cmd: Command {type Err = E}): Either[E, Unit] = cmd match {
5+
case Kick() => ???
6+
case Box() => ???
7+
}

tests/pos-deep-subtype/i9631.scala

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
trait Txn[T <: Txn[T]]
2+
3+
object SkipList {
4+
trait Set[T <: Txn[T], A] extends SkipList[T, A, A]
5+
}
6+
sealed trait SkipList[T <: Txn[T], A, E]
7+
8+
object HASkipList {
9+
def debug[T <: Txn[T], A](in: SkipList[T, A, _], key: A)(implicit tx: T): Int = in match {
10+
case impl: Impl[T, A, _] => impl.foo(key)
11+
case _ => -1
12+
}
13+
14+
private trait Impl[T <: Txn[T], A, E] {
15+
self: SkipList[T, A, E] =>
16+
17+
def foo(key: A)(implicit tx: T): Int
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
abstract class Foo {
2+
def bar(): Unit = this match {
3+
case Foo_1() => //do something
4+
case Foo_2() => //do something
5+
// Works fine
6+
}
7+
8+
def baz(that: Foo): Unit = (this, that) match {
9+
case (Foo_1(), _) => //do something
10+
case (Foo_2(), _) => //do something
11+
// match may not be exhaustive
12+
}
13+
}
14+
case class Foo_1() extends Foo
15+
case class Foo_2() extends Foo

tests/pos/i9841b.scala

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
trait Exec[T <: Exec[T]]
2+
3+
object Tree {
4+
sealed trait Next[+T, +PL, +P, +H, +A]
5+
6+
sealed trait Child[+T, +PL, +P, +H, +A]
7+
8+
sealed trait Branch[T <: Exec[T], PL, P, H, A] extends Child[T, PL, P, H, A] with NonEmpty[T, PL, P, H]
9+
10+
sealed trait NonEmpty[T <: Exec[T], PL, P, H]
11+
12+
case object Empty extends Next[Nothing, Nothing, Nothing, Nothing, Nothing]
13+
14+
sealed trait RightBranch[T <: Exec[T], PL, P, H, A] extends Next[T, PL, P, H, A] with Branch[T, PL, P, H, A]
15+
16+
trait BranchImpl[T <: Exec[T], PL, P, H, A] {
17+
def next: Next[T, PL, P, H, A]
18+
19+
def nextOption: Option[Branch[T, PL, P, H, A]] =
20+
next match { // crashes
21+
case b: RightBranch[T, PL, P, H, A] => Some(b)
22+
case Empty => None
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)