Skip to content

Commit b6db86f

Browse files
committed
Improve handling of AndTypes on the LHS of subtype comparisons
Fixes #18226 Might fix some other reported issues with AndTypes as well.
1 parent ca19b46 commit b6db86f

File tree

3 files changed

+42
-12
lines changed

3 files changed

+42
-12
lines changed

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

Lines changed: 29 additions & 9 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,8 +898,27 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
898898

899899
canWidenAbstract && acc(true, tp)
900900

901-
def tryBaseType(cls2: Symbol) = {
902-
val base = nonExprBaseType(tp1, cls2).boxedIfTypeParam(tp1.typeSymbol)
901+
def tryBaseType(cls2: Symbol) =
902+
903+
def computeBase(tp: Type): Type = tp.widenDealias match
904+
case tp @ AndType(tp1, tp2) =>
905+
// We have to treat AndTypes specially, since the normal treatment
906+
// of `(T1 & T2).baseType(C)` combines the base types of T1 and T2 via glb
907+
// which drops any types that don't exist. That forgets possible solutions.
908+
// For instance, in i18266.scala, we get to a subgoal `R & Row[Int] <: Row[String]`
909+
// where R is an uninstantiated type variable. The base type computation
910+
// of the LHS drops the non-existing base type of R and results in
911+
// `Row[Int]`, which leads to a subtype failure since `Row[Int] <: Row[String]`
912+
// does not hold. The new strategy is to declare that the base type computation
913+
// failed since R does not have a base type, and to proceed to fourthTry instead,
914+
// where we try both sides of an AndType individually.
915+
val b1 = computeBase(tp1)
916+
val b2 = computeBase(tp2)
917+
if b1.exists && b2.exists then tp.derivedAndType(b1, b2) else NoType
918+
case _ =>
919+
nonExprBaseType(tp, cls2).boxedIfTypeParam(tp.typeSymbol)
920+
921+
val base = computeBase(tp1)
903922
if base.exists && (base ne tp1)
904923
&& (!caseLambda.exists
905924
|| widenAbstractOKFor(tp2)
@@ -912,7 +931,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
912931
// expands to a match type. In this case, we should try to reduce the type
913932
// and compare the redux. This is done in fourthTry
914933
else fourthTry
915-
}
934+
end tryBaseType
916935

917936
def fourthTry: Boolean = tp1 match {
918937
case tp1: TypeRef =>
@@ -989,7 +1008,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
9891008
}
9901009
}
9911010
compareHKLambda
992-
case AndType(tp11, tp12) =>
1011+
case tp1 @ AndType(tp11, tp12) =>
9931012
val tp2a = tp2.dealiasKeepRefiningAnnots
9941013
if (tp2a ne tp2) // Follow the alias; this might avoid truncating the search space in the either below
9951014
return recur(tp1, tp2a)
@@ -1009,8 +1028,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
10091028
return recur(AndType(tp11, tp121), tp2) && recur(AndType(tp11, tp122), tp2)
10101029
case _ =>
10111030
}
1012-
val tp1norm = simplifyAndTypeWithFallback(tp11, tp12, tp1)
1013-
if (tp1 ne tp1norm) recur(tp1norm, tp2)
1031+
val tp1norm = trySimplify(tp1)
1032+
if tp1 ne tp1norm then recur(tp1norm, tp2)
10141033
else either(recur(tp11, tp2), recur(tp12, tp2))
10151034
case tp1: MatchType =>
10161035
def compareMatch = tp2 match {
@@ -2506,8 +2525,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
25062525
final def andType(tp1: Type, tp2: Type, isErased: Boolean = ctx.erasedTypes): Type =
25072526
andTypeGen(tp1, tp2, AndType.balanced(_, _), isErased = isErased)
25082527

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

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

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ object Main {
2020

2121
def testHasThisType(): Unit = {
2222
def testSelf[PThis <: HasThisType[_ <: PThis]](that: HasThisType[PThis]): Unit = {
23-
val thatSelf = that.self()
23+
val thatSelf = that.self() // error: recursion limit exceeded
2424
// that.self().type <: that.This
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/i18226.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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) // error!

0 commit comments

Comments
 (0)