Skip to content

Commit 3ca1171

Browse files
authored
Merge pull request #3714 from dotty-staging/fix-#3627
Fix #3627: Avoid looping when inferring types with recursive lower bounds
2 parents 6e6c271 + 56ec720 commit 3ca1171

File tree

4 files changed

+30
-14
lines changed

4 files changed

+30
-14
lines changed

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

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@ import reporting.trace
1717
/** Provides methods to compare types.
1818
*/
1919
class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
20+
import TypeComparer.show
21+
2022
implicit val ctx = initctx
2123

2224
val state = ctx.typerState
2325
import state.constraint
2426

2527
private[this] var pendingSubTypes: mutable.Set[(Type, Type)] = null
2628
private[this] var recCount = 0
29+
private[this] var monitored = false
2730

2831
private[this] var needsGc = false
2932

@@ -101,9 +104,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
101104
if (tp2 eq NoType) return false
102105
if ((tp2 eq tp1) || (tp2 eq WildcardType)) return true
103106
try isSubType(tp1, tp2)
104-
finally
107+
finally {
108+
monitored = false
105109
if (Config.checkConstraintsSatisfiable)
106110
assert(isSatisfiable, constraint.show)
111+
}
107112
}
108113

109114
protected def isSubType(tp1: Type, tp2: Type): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) {
@@ -114,9 +119,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
114119
val savedSuccessCount = successCount
115120
try {
116121
recCount = recCount + 1
117-
val result =
118-
if (recCount < Config.LogPendingSubTypesThreshold) firstTry(tp1, tp2)
119-
else monitoredIsSubType(tp1, tp2)
122+
if (recCount >= Config.LogPendingSubTypesThreshold) monitored = true
123+
val result = if (monitored) monitoredIsSubType(tp1, tp2) else firstTry(tp1, tp2)
120124
recCount = recCount - 1
121125
if (!result) state.resetConstraintTo(saved)
122126
else if (recCount == 0 && needsGc) {
@@ -300,6 +304,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
300304
if (isSubType(info1.alias, tp2)) return true
301305
if (tp1.prefix.isStable) return false
302306
case _ =>
307+
if (tp1 eq NothingType) return tp1 == tp2.bottomType
303308
}
304309
thirdTry(tp1, tp2)
305310
case tp1: TypeParamRef =>
@@ -1577,7 +1582,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
15771582

15781583
/** Show subtype goal that led to an assertion failure */
15791584
def showGoal(tp1: Type, tp2: Type)(implicit ctx: Context) = {
1580-
println(i"assertion failure for $tp1 <:< $tp2, frozen = $frozenConstraint")
1585+
println(i"assertion failure for ${show(tp1)} <:< ${show(tp2)}, frozen = $frozenConstraint")
15811586
def explainPoly(tp: Type) = tp match {
15821587
case tp: TypeParamRef => ctx.echo(s"TypeParamRef ${tp.show} found in ${tp.binder.show}")
15831588
case tp: TypeRef if tp.symbol.exists => ctx.echo(s"typeref ${tp.show} found in ${tp.symbol.owner.show}")
@@ -1609,6 +1614,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
16091614

16101615
object TypeComparer {
16111616

1617+
private[core] def show(res: Any)(implicit ctx: Context) = res match {
1618+
case res: printing.Showable if !ctx.settings.YexplainLowlevel.value => res.show
1619+
case _ => String.valueOf(res)
1620+
}
1621+
16121622
/** Show trace of comparison operations when performing `op` as result string */
16131623
def explained[T](op: Context => T)(implicit ctx: Context): String = {
16141624
val nestedCtx = ctx.fresh.setTypeComparerFn(new ExplainingTypeComparer(_))
@@ -1619,6 +1629,8 @@ object TypeComparer {
16191629

16201630
/** A type comparer that can record traces of subtype operations */
16211631
class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
1632+
import TypeComparer.show
1633+
16221634
private[this] var indent = 0
16231635
private val b = new StringBuilder
16241636

@@ -1635,11 +1647,6 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
16351647
res
16361648
}
16371649

1638-
private def show(res: Any) = res match {
1639-
case res: printing.Showable if !ctx.settings.YexplainLowlevel.value => res.show
1640-
case _ => String.valueOf(res)
1641-
}
1642-
16431650
override def isSubType(tp1: Type, tp2: Type) =
16441651
traceIndented(s"${show(tp1)} <:< ${show(tp2)}${if (Config.verboseExplainSubtype) s" ${tp1.getClass} ${tp2.getClass}" else ""}${if (frozenConstraint) " frozen" else ""}") {
16451652
super.isSubType(tp1, tp2)

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -496,9 +496,7 @@ object ProtoTypes {
496496
tp.derivedTypeAlias(wildApprox(tp.alias, theMap, seen))
497497
case tp @ TypeParamRef(poly, pnum) =>
498498
def wildApproxBounds(bounds: TypeBounds) =
499-
if (bounds.lo.isInstanceOf[NamedType] && bounds.hi.isInstanceOf[NamedType])
500-
WildcardType(wildApprox(bounds, theMap, seen).bounds)
501-
else if (seen.contains(tp)) WildcardType
499+
if (seen.contains(tp)) WildcardType
502500
else WildcardType(wildApprox(bounds, theMap, seen + tp).bounds)
503501
def unconstrainedApprox = wildApproxBounds(poly.paramInfos(pnum))
504502
def approxPoly =
@@ -545,7 +543,8 @@ object ProtoTypes {
545543
case _: ThisType | _: BoundType | NoPrefix => // default case, inlined for speed
546544
tp
547545
case _ =>
548-
(if (theMap != null) theMap else new WildApproxMap(seen)).mapOver(tp)
546+
(if (theMap != null && seen.eq(theMap.seen)) theMap else new WildApproxMap(seen))
547+
.mapOver(tp)
549548
}
550549

551550
@sharable object AssignProto extends UncachedGroundType with MatchAlways

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class CompilationTests extends ParallelTesting {
176176
compileFilesInDir("../tests/neg-tailcall", defaultOptions) +
177177
compileFilesInDir("../tests/neg-no-optimise", defaultOptions) +
178178
compileFile("../tests/neg-custom-args/i3246.scala", scala2Mode) +
179+
compileFile("../tests/neg-custom-args/i3627.scala", allowDeepSubtypes) +
179180
compileFile("../tests/neg-custom-args/typers.scala", allowDoubleBindings) +
180181
compileFile("../tests/neg-custom-args/overrideClass.scala", scala2Mode) +
181182
compileFile("../tests/neg-custom-args/autoTuplingTest.scala", defaultOptions.and("-language:noAutoTupling")) +

tests/neg-custom-args/i3627.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
trait Comparinator[T] {
2+
def sort[T](x: Comparinator[_ >: T]) = ()
3+
sort((a: Int) => true) // error
4+
}
5+
6+
trait Comparinator2[T >: U, U] {
7+
def sort[TT](x: Comparinator2[_ >: TT, U]) = ()
8+
sort((a: Int) => true) // error
9+
}

0 commit comments

Comments
 (0)