Skip to content

Commit 816bd5e

Browse files
authored
Improve handling of AndTypes on the LHS of subtype comparisons (#18235)
Fixes #18226 Fixes #12077 Might fix some other reported issues with AndTypes as well.
2 parents b21241b + 5ecb8c0 commit 816bd5e

File tree

6 files changed

+72
-15
lines changed

6 files changed

+72
-15
lines changed

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

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
478478
tp2.isRef(AnyClass, skipRefined = false)
479479
|| !tp1.evaluating && recur(tp1.ref, tp2)
480480
case AndType(tp11, tp12) =>
481-
if (tp11.stripTypeVar eq tp12.stripTypeVar) recur(tp11, tp2)
481+
if tp11.stripTypeVar eq tp12.stripTypeVar then recur(tp11, tp2)
482482
else thirdTry
483483
case tp1 @ OrType(tp11, tp12) =>
484484
compareAtoms(tp1, tp2) match
@@ -898,21 +898,29 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
898898

899899
canWidenAbstract && acc(true, tp)
900900

901-
def tryBaseType(cls2: Symbol) = {
901+
def tryBaseType(cls2: Symbol) =
902902
val base = nonExprBaseType(tp1, cls2).boxedIfTypeParam(tp1.typeSymbol)
903903
if base.exists && (base ne tp1)
904904
&& (!caseLambda.exists
905905
|| widenAbstractOKFor(tp2)
906906
|| tp1.widen.underlyingClassRef(refinementOK = true).exists)
907907
then
908-
isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx.addLow)
909-
&& recordGadtUsageIf { MatchType.thatReducesUsingGadt(tp1) }
910-
|| base.isInstanceOf[OrType] && fourthTry
911-
// if base is a disjunction, this might have come from a tp1 type that
908+
def checkBase =
909+
isSubType(base, tp2, if tp1.isRef(cls2) then approx else approx.addLow)
910+
&& recordGadtUsageIf { MatchType.thatReducesUsingGadt(tp1) }
911+
if tp1.widenDealias.isInstanceOf[AndType] || base.isInstanceOf[OrType] then
912+
// If tp1 is a intersection, it could be that one of the original
913+
// branches of the AndType tp1 conforms to tp2, but its base type does
914+
// not, or else that its base type for cls2 does not exist, in which case
915+
// it would not show up in `base`. In either case, we need to also fall back
916+
// to fourthTry. Test cases are i18266.scala and i18226a.scala.
917+
// If base is a disjunction, this might have come from a tp1 type that
912918
// expands to a match type. In this case, we should try to reduce the type
913919
// and compare the redux. This is done in fourthTry
920+
either(checkBase, fourthTry)
921+
else
922+
checkBase
914923
else fourthTry
915-
}
916924

917925
def fourthTry: Boolean = tp1 match {
918926
case tp1: TypeRef =>
@@ -989,7 +997,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
989997
}
990998
}
991999
compareHKLambda
992-
case AndType(tp11, tp12) =>
1000+
case tp1 @ AndType(tp11, tp12) =>
9931001
val tp2a = tp2.dealiasKeepRefiningAnnots
9941002
if (tp2a ne tp2) // Follow the alias; this might avoid truncating the search space in the either below
9951003
return recur(tp1, tp2a)
@@ -1009,8 +1017,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
10091017
return recur(AndType(tp11, tp121), tp2) && recur(AndType(tp11, tp122), tp2)
10101018
case _ =>
10111019
}
1012-
val tp1norm = simplifyAndTypeWithFallback(tp11, tp12, tp1)
1013-
if (tp1 ne tp1norm) recur(tp1norm, tp2)
1020+
val tp1norm = trySimplify(tp1)
1021+
if tp1 ne tp1norm then recur(tp1norm, tp2)
10141022
else either(recur(tp11, tp2), recur(tp12, tp2))
10151023
case tp1: MatchType =>
10161024
def compareMatch = tp2 match {
@@ -2506,8 +2514,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
25062514
final def andType(tp1: Type, tp2: Type, isErased: Boolean = ctx.erasedTypes): Type =
25072515
andTypeGen(tp1, tp2, AndType.balanced(_, _), isErased = isErased)
25082516

2509-
final def simplifyAndTypeWithFallback(tp1: Type, tp2: Type, fallback: Type): Type =
2510-
andTypeGen(tp1, tp2, (_, _) => fallback)
2517+
/** Try to simplify AndType, or return the type itself if no simplifiying opportunities exist. */
2518+
private def trySimplify(tp: AndType): Type =
2519+
andTypeGen(tp.tp1, tp.tp2, (_, _) => tp)
25112520

25122521
/** Form a normalized conjunction of two types.
25132522
* Note: For certain types, `|` is distributed inside the type. This holds for

tests/neg-custom-args/allow-deep-subtypes/i5877.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ object Main {
2525
assert(implicitly[thatSelf.type <:< that.This] != null)
2626
}
2727
val that: HasThisType[_] = Foo() // null.asInstanceOf
28-
testSelf(that) // error
28+
testSelf(that) // error: recursion limit exceeded
2929
}
3030

3131

@@ -36,7 +36,7 @@ object Main {
3636
}
3737
val that: HasThisType[_] = Foo() // null.asInstanceOf
3838
// this line of code makes Dotty compiler infinite recursion (stopped only by overflow) - comment it to make it compilable again
39-
testSelf(that) // error
39+
testSelf(that) // error: recursion limit exceeded
4040
}
4141

4242
// ---- ---- ---- ----

tests/pos/i12077.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
trait Wrapper[K]
2+
trait Has0[T]
3+
4+
def test[R](v: Wrapper[Has0[String] with R]):R = ???
5+
6+
val zz:Wrapper[Has0[String] with Has0[Int]] = ???
7+
val _ = test(zz)

tests/pos/i18226.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
trait F[-R]
2+
3+
trait Row[A]
4+
5+
def eliminateInt[R](f: F[R & Row[Int]]): F[R] = new F[R] {}
6+
7+
val x = new F[Row[Int] & Row[String]] {}
8+
9+
val _ = eliminateInt[Row[String]](x) // compiles OK when given explicit type
10+
val y = eliminateInt(x) // was error
11+
val _: F[Row[String]] = y

tests/pos/i18226a.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class Has[A]
2+
trait Foo
3+
4+
class TestAspect[+LowerR, -UpperR]
5+
6+
class Spec[-R] {
7+
def foo[R1 <: R](aspect: TestAspect[R1, R1]): Unit = {}
8+
}
9+
10+
class SuiteBuilder[R <: Has[_]] {
11+
def toSpec(
12+
spec: Spec[R & Has[Foo]],
13+
aspect: TestAspect[
14+
R & Has[Foo],
15+
R & Has[Foo]
16+
]
17+
) =
18+
spec.foo(aspect)
19+
}

tests/pos/intersection.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,15 @@ object Test {
4040
def fooAB1: Int = fooAB
4141
def fooBA = (??? : B with A).f
4242
def fooBA1: Int = fooBA
43-
}
43+
}
44+
45+
object Test2:
46+
class Row[+X]
47+
class A
48+
class B
49+
class C extends Row[A]
50+
class D extends Row[B]
51+
val x: C & D = ???
52+
val y: Row[A & B] = x
53+
54+

0 commit comments

Comments
 (0)