Skip to content

Commit cebe706

Browse files
authored
Merge pull request #3646 from dotty-staging/fix-3645
fix #3645: handle type alias in child instantiation
2 parents 52bddff + cf594d4 commit cebe706

15 files changed

+293
-24
lines changed

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

+67-24
Original file line numberDiff line numberDiff line change
@@ -600,37 +600,70 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
600600
*
601601
*/
602602
def instantiate(tp1: Type, tp2: Type)(implicit ctx: Context): Type = {
603-
// map `ThisType` of `tp1` to a type variable
604-
// precondition: `tp1` should have the shape `path.Child`, thus `ThisType` is always covariant
605-
val thisTypeMap = new TypeMap {
606-
def apply(t: Type): Type = t match {
607-
case tp @ ThisType(tref) if !tref.symbol.isStaticOwner =>
608-
if (tref.symbol.is(Module)) mapOver(tref)
609-
else newTypeVar(TypeBounds.upper(tp.underlying))
610-
case _ =>
611-
mapOver(t)
603+
// expose abstract type references to their bounds or tvars according to variance
604+
abstract class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap {
605+
def expose(tp: TypeRef): Type = {
606+
val lo = this(tp.info.loBound)
607+
val hi = this(tp.info.hiBound)
608+
val exposed =
609+
if (variance == 0)
610+
newTypeVar(TypeBounds(lo, hi))
611+
else if (variance == 1)
612+
if (maximize) hi else lo
613+
else
614+
if (maximize) lo else hi
615+
616+
debug.println(s"$tp exposed to =====> $exposed")
617+
exposed
612618
}
613-
}
614619

615-
// replace type parameter references with bounds
616-
val typeParamMap = new TypeMap {
617-
def apply(t: Type): Type = t match {
618-
case tp: TypeRef if tp.symbol.is(TypeParam) && tp.underlying.isInstanceOf[TypeBounds] =>
620+
override def mapOver(tp: Type): Type = tp match {
621+
case tp: TypeRef if tp.underlying.isInstanceOf[TypeBounds] =>
619622
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
623+
expose(tp)
624+
625+
case AppliedType(tycon: TypeRef, args) if tycon.underlying.isInstanceOf[TypeBounds] =>
626+
val args2 = args.map(this)
627+
val lo = this(tycon.info.loBound).applyIfParameterized(args2)
628+
val hi = this(tycon.info.hiBound).applyIfParameterized(args2)
620629
val exposed =
621-
if (variance == 0) newTypeVar(tp.underlying.bounds)
622-
else if (variance == 1) mapOver(tp.underlying.hiBound)
623-
else mapOver(tp.underlying.loBound)
630+
if (variance == 0)
631+
newTypeVar(TypeBounds(lo, hi))
632+
else if (variance == 1)
633+
if (maximize) hi else lo
634+
else
635+
if (maximize) lo else hi
624636

625637
debug.println(s"$tp exposed to =====> $exposed")
626638
exposed
639+
627640
case _ =>
628-
mapOver(t)
641+
super.mapOver(tp)
629642
}
630643
}
631644

645+
// We are checking the possibility of `tp1 <:< tp2`, thus we should
646+
// minimize `tp1` while maximizing `tp2`. See tests/patmat/3645b.scala
647+
def childTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = false) {
648+
def apply(t: Type): Type = t.dealias match {
649+
// map `ThisType` of `tp1` to a type variable
650+
// precondition: `tp1` should have the same shape as `path.Child`, thus `ThisType` is always covariant
651+
case tp @ ThisType(tref) if !tref.symbol.isStaticOwner =>
652+
if (tref.symbol.is(Module)) this(tref)
653+
else newTypeVar(TypeBounds.upper(tp.underlying))
654+
655+
case tp =>
656+
mapOver(tp)
657+
}
658+
}
659+
660+
// replace type parameter references with bounds
661+
def parentTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = true) {
662+
def apply(tp: Type): Type = mapOver(tp.dealias)
663+
}
664+
632665
// replace uninstantiated type vars with WildcardType, check tests/patmat/3333.scala
633-
val instUndetMap = new TypeMap {
666+
def instUndetMap(implicit ctx: Context) = new TypeMap {
634667
def apply(t: Type): Type = t match {
635668
case tvar: TypeVar if !tvar.isInstantiated => WildcardType(tvar.origin.underlying.bounds)
636669
case _ => mapOver(t)
@@ -643,17 +676,27 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
643676
)
644677

645678
val tvars = tp1.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
646-
val protoTp1 = thisTypeMap(tp1.appliedTo(tvars))
679+
val protoTp1 = childTypeMap.apply(tp1.appliedTo(tvars))
680+
681+
// If parent contains a reference to an abstract type, then we should
682+
// refine subtype checking to eliminate abstract types according to
683+
// variance. As this logic is only needed in exhaustivity check,
684+
// we manually patch subtyping check instead of changing TypeComparer.
685+
// See tests/patmat/3645b.scala
686+
def parentQualify = tp1.widen.classSymbol.info.parents.exists { parent =>
687+
implicit val ictx = ctx.fresh.setNewTyperState()
688+
parent.argInfos.nonEmpty && childTypeMap.apply(parent) <:< parentTypeMap.apply(tp2)
689+
}
647690

648691
if (protoTp1 <:< tp2) {
649692
if (isFullyDefined(protoTp1, force)) protoTp1
650-
else instUndetMap(protoTp1)
693+
else instUndetMap.apply(protoTp1)
651694
}
652695
else {
653-
val protoTp2 = typeParamMap(tp2)
654-
if (protoTp1 <:< protoTp2) {
696+
val protoTp2 = parentTypeMap.apply(tp2)
697+
if (protoTp1 <:< protoTp2 || parentQualify) {
655698
if (isFullyDefined(AndType(protoTp1, protoTp2), force)) protoTp1
656-
else instUndetMap(protoTp1)
699+
else instUndetMap.apply(protoTp1)
657700
}
658701
else {
659702
debug.println(s"$protoTp1 <:< $protoTp2 = false")

tests/patmat/i3645.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
20: Pattern Match Exhaustivity: KInt

tests/patmat/i3645.scala

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
object App {
2+
def main(args: Array[String]): Unit = {
3+
trait AgeT {
4+
type T
5+
def subst[F[_]](fa: F[Int]): F[T]
6+
}
7+
8+
type Age = Age.T
9+
10+
val Age: AgeT = new AgeT {
11+
type T = Int
12+
def subst[F[_]](fa: F[Int]): F[T] = fa
13+
}
14+
15+
sealed abstract class K[A]
16+
final case object KAge extends K[Age]
17+
final case object KInt extends K[Int]
18+
19+
val kint: K[Age] = Age.subst[K](KInt)
20+
def get(k: K[Age]): String = k match {
21+
case KAge => "Age"
22+
}
23+
24+
get(kint)
25+
}
26+
}

tests/patmat/i3645b.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
21: Pattern Match Exhaustivity: K3, K2

tests/patmat/i3645b.scala

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
object App {
2+
def main(args: Array[String]): Unit = {
3+
trait FooT {
4+
type T
5+
def subst[F[_]](fa: F[T]): F[Int]
6+
}
7+
val Foo: FooT = new FooT {
8+
type T = Int
9+
10+
def subst[F[_]](fa: F[T]): F[Int] = fa
11+
}
12+
type Foo = Foo.T
13+
type Bar = Foo
14+
15+
sealed abstract class K[A]
16+
final case object K1 extends K[Int]
17+
final case object K2 extends K[Foo]
18+
final case object K3 extends K[Bar]
19+
20+
val foo: K[Int] = Foo.subst[K](K2)
21+
def get(k: K[Int]): Unit = k match {
22+
case K1 => ()
23+
// case K2 => ()
24+
// case K3 => ()
25+
}
26+
27+
get(foo)
28+
}
29+
}

tests/patmat/i3645c.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
21: Pattern Match Exhaustivity: K3, K2

tests/patmat/i3645c.scala

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
object App {
2+
def main(args: Array[String]): Unit = {
3+
trait FooT {
4+
type T
5+
def subst[F[_]](fa: F[T]): F[Int]
6+
}
7+
val Foo: FooT = new FooT {
8+
type T = Int
9+
10+
def subst[F[_]](fa: F[T]): F[Int] = fa
11+
}
12+
type Foo = Foo.T
13+
type Bar = Foo
14+
15+
sealed abstract class K[+A]
16+
final case object K1 extends K[Int]
17+
final case object K2 extends K[Foo]
18+
final case object K3 extends K[Bar]
19+
20+
val foo: K[Int] = Foo.subst[K](K2)
21+
def get(k: K[Int]): Unit = k match {
22+
case K1 => ()
23+
// case K2 => ()
24+
// case K3 => ()
25+
}
26+
27+
get(foo)
28+
}
29+
}

tests/patmat/i3645d.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
21: Pattern Match Exhaustivity: K3, K2

tests/patmat/i3645d.scala

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
object App {
2+
def main(args: Array[String]): Unit = {
3+
trait FooT {
4+
type T
5+
def subst[F[_]](fa: F[T]): F[Int]
6+
}
7+
val Foo: FooT = new FooT {
8+
type T = Int
9+
10+
def subst[F[_]](fa: F[T]): F[Int] = fa
11+
}
12+
type Foo = Foo.T
13+
type Bar = Foo
14+
15+
sealed abstract class K[-A]
16+
final case object K1 extends K[Int]
17+
final case object K2 extends K[Foo]
18+
final case object K3 extends K[Bar]
19+
20+
val foo: K[Int] = Foo.subst[K](K2)
21+
def get(k: K[Int]): Unit = k match {
22+
case K1 => ()
23+
// case K2 => ()
24+
// case K3 => ()
25+
}
26+
27+
get(foo)
28+
}
29+
}

tests/patmat/i3645e.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
29: Pattern Match Exhaustivity: K1

tests/patmat/i3645e.scala

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
object App {
2+
def main(args: Array[String]): Unit = {
3+
trait ModuleSig {
4+
type Upper
5+
6+
trait FooSig {
7+
type Type <: Upper
8+
def subst[F[_]](fa: F[Int]): F[Type]
9+
}
10+
11+
val Foo: FooSig
12+
}
13+
val Module: ModuleSig = new ModuleSig {
14+
type Upper = Int
15+
16+
val Foo: FooSig = new FooSig {
17+
type Type = Int
18+
def subst[F[_]](fa: F[Int]): F[Type] = fa
19+
}
20+
}
21+
type Upper = Module.Upper
22+
type Foo = Module.Foo.Type
23+
24+
sealed abstract class K[F]
25+
final case object K1 extends K[Int]
26+
final case object K2 extends K[Foo]
27+
28+
val kv: K[Foo] = Module.Foo.subst[K](K1)
29+
def test(k: K[Foo]): Unit = k match {
30+
case K2 => ()
31+
}
32+
33+
test(kv)
34+
}
35+
}

tests/patmat/i3645f.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
30: Pattern Match Exhaustivity: K1

tests/patmat/i3645f.scala

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
object App {
2+
def main(args: Array[String]): Unit = {
3+
trait ModuleSig {
4+
type U2
5+
type U1
6+
7+
trait FooSig {
8+
type Type = (U1 & U2)
9+
def subst[F[_]](fa: F[Int]): F[Type]
10+
}
11+
12+
val Foo: FooSig
13+
}
14+
val Module: ModuleSig = new ModuleSig {
15+
type U1 = Int
16+
type U2 = Int
17+
18+
val Foo: FooSig = new FooSig {
19+
// type Type = Int
20+
def subst[F[_]](fa: F[Int]): F[Type] = fa
21+
}
22+
}
23+
type Foo = Module.Foo.Type
24+
25+
sealed abstract class K[F]
26+
final case object K1 extends K[Int]
27+
final case object K2 extends K[Foo]
28+
29+
val kv: K[Foo] = Module.Foo.subst[K](K1)
30+
def test(k: K[Foo]): Unit = k match {
31+
case K2 => ()
32+
}
33+
34+
test(kv)
35+
}
36+
}

tests/patmat/i3645g.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
29: Pattern Match Exhaustivity: K1

tests/patmat/i3645g.scala

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
object App {
2+
def main(args: Array[String]): Unit = {
3+
trait ModuleSig {
4+
type F[_]
5+
type U
6+
7+
trait FooSig {
8+
type Type = F[U]
9+
def subst[F[_]](fa: F[Int]): F[Type]
10+
}
11+
12+
val Foo: FooSig
13+
}
14+
val Module: ModuleSig = new ModuleSig {
15+
type F[A] = Int
16+
17+
val Foo: FooSig = new FooSig {
18+
// type Type = Int
19+
def subst[F[_]](fa: F[Int]): F[Type] = fa
20+
}
21+
}
22+
type Foo = Module.Foo.Type
23+
24+
sealed abstract class K[F]
25+
final case object K1 extends K[Int]
26+
final case object K2 extends K[Foo]
27+
28+
val kv: K[Foo] = Module.Foo.subst[K](K1)
29+
def test(k: K[Foo]): Unit = k match {
30+
case K2 => ()
31+
}
32+
33+
test(kv)
34+
}
35+
}

0 commit comments

Comments
 (0)