Skip to content

Fix #3627: Avoid looping when inferring types with recursive lower bounds #3714

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ import reporting.trace
/** Provides methods to compare types.
*/
class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
import TypeComparer.show

implicit val ctx = initctx

val state = ctx.typerState
import state.constraint

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

private[this] var needsGc = false

Expand Down Expand Up @@ -101,9 +104,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
if (tp2 eq NoType) return false
if ((tp2 eq tp1) || (tp2 eq WildcardType)) return true
try isSubType(tp1, tp2)
finally
finally {
monitored = false
if (Config.checkConstraintsSatisfiable)
assert(isSatisfiable, constraint.show)
}
}

protected def isSubType(tp1: Type, tp2: Type): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) {
Expand All @@ -114,9 +119,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
val savedSuccessCount = successCount
try {
recCount = recCount + 1
val result =
if (recCount < Config.LogPendingSubTypesThreshold) firstTry(tp1, tp2)
else monitoredIsSubType(tp1, tp2)
if (recCount >= Config.LogPendingSubTypesThreshold) monitored = true
val result = if (monitored) monitoredIsSubType(tp1, tp2) else firstTry(tp1, tp2)
recCount = recCount - 1
if (!result) state.resetConstraintTo(saved)
else if (recCount == 0 && needsGc) {
Expand Down Expand Up @@ -284,6 +288,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
if (isSubType(info1.alias, tp2)) return true
if (tp1.prefix.isStable) return false
case _ =>
if (tp1 eq NothingType) return tp1 == tp2.bottomType
}
thirdTry(tp1, tp2)
case tp1: TypeParamRef =>
Expand Down Expand Up @@ -1571,7 +1576,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {

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

object TypeComparer {

private[core] def show(res: Any)(implicit ctx: Context) = res match {
case res: printing.Showable if !ctx.settings.YexplainLowlevel.value => res.show
case _ => String.valueOf(res)
}

/** Show trace of comparison operations when performing `op` as result string */
def explained[T](op: Context => T)(implicit ctx: Context): String = {
val nestedCtx = ctx.fresh.setTypeComparerFn(new ExplainingTypeComparer(_))
Expand All @@ -1613,6 +1623,8 @@ object TypeComparer {

/** A type comparer that can record traces of subtype operations */
class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
import TypeComparer.show

private[this] var indent = 0
private val b = new StringBuilder

Expand All @@ -1629,11 +1641,6 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
res
}

private def show(res: Any) = res match {
case res: printing.Showable if !ctx.settings.YexplainLowlevel.value => res.show
case _ => String.valueOf(res)
}

override def isSubType(tp1: Type, tp2: Type) =
traceIndented(s"${show(tp1)} <:< ${show(tp2)}${if (Config.verboseExplainSubtype) s" ${tp1.getClass} ${tp2.getClass}" else ""}${if (frozenConstraint) " frozen" else ""}") {
super.isSubType(tp1, tp2)
Expand Down
7 changes: 3 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,7 @@ object ProtoTypes {
tp.derivedTypeAlias(wildApprox(tp.alias, theMap, seen))
case tp @ TypeParamRef(poly, pnum) =>
def wildApproxBounds(bounds: TypeBounds) =
if (bounds.lo.isInstanceOf[NamedType] && bounds.hi.isInstanceOf[NamedType])
WildcardType(wildApprox(bounds, theMap, seen).bounds)
else if (seen.contains(tp)) WildcardType
if (seen.contains(tp)) WildcardType
else WildcardType(wildApprox(bounds, theMap, seen + tp).bounds)
def unconstrainedApprox = wildApproxBounds(poly.paramInfos(pnum))
def approxPoly =
Expand Down Expand Up @@ -544,7 +542,8 @@ object ProtoTypes {
case _: ThisType | _: BoundType | NoPrefix => // default case, inlined for speed
tp
case _ =>
(if (theMap != null) theMap else new WildApproxMap(seen)).mapOver(tp)
(if (theMap != null && seen.eq(theMap.seen)) theMap else new WildApproxMap(seen))
.mapOver(tp)
}

@sharable object AssignProto extends UncachedGroundType with MatchAlways
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class CompilationTests extends ParallelTesting {
compileFilesInDir("../tests/neg-tailcall", defaultOptions) +
compileFilesInDir("../tests/neg-no-optimise", defaultOptions) +
compileFile("../tests/neg-custom-args/i3246.scala", scala2Mode) +
compileFile("../tests/neg-custom-args/i3627.scala", allowDeepSubtypes) +
compileFile("../tests/neg-custom-args/typers.scala", allowDoubleBindings) +
compileFile("../tests/neg-custom-args/overrideClass.scala", scala2Mode) +
compileFile("../tests/neg-custom-args/autoTuplingTest.scala", defaultOptions.and("-language:noAutoTupling")) +
Expand Down
9 changes: 9 additions & 0 deletions tests/neg-custom-args/i3627.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
trait Comparinator[T] {
def sort[T](x: Comparinator[_ >: T]) = ()
sort((a: Int) => true) // error
}

trait Comparinator2[T >: U, U] {
def sort[TT](x: Comparinator2[_ >: TT, U]) = ()
sort((a: Int) => true) // error
}