Skip to content

Commit eaa9319

Browse files
committed
Avoid loop in subtype checking of applied types
`isMatchingApply(tp1)` compares the applied type `tp1` with `tp2`. Before this commit, we called it recursively incorrectly by passing it a type constructor instead of an applied type. The result was that it returned false when it shouldn't. This lead to infinite loops in some cases as demonstrated by tests/pos/factory-conversion.scala. Note: even before this commit, the added test case used to compile, but only when using a non-bootstrapped compiler. This puzzling behavior was due to the logic in `monitoredIsSubType` detecting the loop and breaking out of it before it lead to a stackoverflow. Of course this kind of detection is fragile since it depends on the stack size as well as the size of each stack frame, so we just happened to get lucky with the non-bootstrapped compiler but not with the bootstrapped one. As always, when debugging subtyping issues, it's a good idea to run the compiler with `-Yno-deep-subtypes` to replace the recovery logic in `monitoredIsSubType` by an immediate crash.
1 parent fffc3c4 commit eaa9319

File tree

2 files changed

+11
-3
lines changed

2 files changed

+11
-3
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
822822
*/
823823
def isMatchingApply(tp1: Type): Boolean = tp1 match {
824824
case AppliedType(tycon1, args1) =>
825-
tycon1.dealiasKeepRefiningAnnots match {
825+
def loop(tycon1: Type, args1: List[Type]): Boolean = tycon1.dealiasKeepRefiningAnnots match {
826826
case tycon1: TypeParamRef =>
827827
(tycon1 == tycon2 ||
828828
canConstrain(tycon1) && isSubType(tycon1, tycon2)) &&
@@ -865,12 +865,13 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
865865
false
866866
}
867867
case tycon1: TypeVar =>
868-
isMatchingApply(tycon1.underlying)
868+
loop(tycon1.underlying, args1)
869869
case tycon1: AnnotatedType if !tycon1.isRefining =>
870-
isMatchingApply(tycon1.underlying)
870+
loop(tycon1.underlying, args1)
871871
case _ =>
872872
false
873873
}
874+
loop(tycon1, args1)
874875
case _ =>
875876
false
876877
}

tests/pos/factory-conversion.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.annotation.unchecked.uncheckedVariance
2+
3+
object Test {
4+
def to[Col[_]](factory: collection.Factory[Int, Col[Int @uncheckedVariance]]) = ???
5+
6+
val test = to(List)
7+
}

0 commit comments

Comments
 (0)