From 06e18c6e7761c458b33af3471f013a4dd3cee3f1 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 30 Dec 2015 20:45:21 +0100 Subject: Avoid infinite subtyping checks when intersecting denotations This allows us to run compileStdLib without deep subtypes again. --- src/dotty/tools/dotc/core/Denotations.scala | 11 +++++++--- src/dotty/tools/dotc/core/Types.scala | 33 +++++++++++++++++++++-------- test/dotc/tests.scala | 2 +- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index fcd60ef16..00ad90354 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -259,11 +259,12 @@ object Denotations { * * If there is no preferred accessible denotation, return a JointRefDenotation * with one of the operand symbols (unspecified which one), and an info which - * is intersection (&) of the infos of the operand denotations. + * is the intersection (using `&` or `safe_&` if `safeIntersection` is true) + * of the infos of the operand denotations. * * If SingleDenotations with different signatures are joined, return NoDenotation. */ - def & (that: Denotation, pre: Type)(implicit ctx: Context): Denotation = { + def & (that: Denotation, pre: Type, safeIntersection: Boolean = false)(implicit ctx: Context): Denotation = { /** Try to merge denot1 and denot2 without adding a new signature. */ def mergeDenot(denot1: Denotation, denot2: SingleDenotation): Denotation = denot1 match { @@ -317,7 +318,11 @@ object Denotations { else if (preferSym(sym2, sym1)) sym2 else sym1 val jointInfo = - try info1 & info2 + try + if (safeIntersection) + info1 safe_& info2 + else + info1 & info2 catch { case ex: MergeError => if (pre.widen.classSymbol.is(Scala2x) || ctx.scala2Mode) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 43276d254..cc81b9558 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -459,7 +459,7 @@ object Types { val jointInfo = if (rinfo.isAlias) rinfo else if (pdenot.info.isAlias) pdenot.info - else if (ctx.pendingMemberSearches.contains(name)) safeAnd(pdenot.info, rinfo) + else if (ctx.pendingMemberSearches.contains(name)) pdenot.info safe_& rinfo else try pdenot.info & rinfo catch { @@ -470,11 +470,15 @@ object Types { // the special shortcut for Any in derivesFrom was as yet absent. To reproduce, // remove the special treatment of Any in derivesFrom and compile // sets.scala. - safeAnd(pdenot.info, rinfo) + pdenot.info safe_& rinfo } pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) - } else - pdenot & (new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId)), pre) + } else { + pdenot & ( + new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId)), + pre, + safeIntersection = ctx.pendingMemberSearches.contains(name)) + } } def goThis(tp: ThisType) = { val d = go(tp.underlying) @@ -501,12 +505,10 @@ object Types { go(next) } } - def goAnd(l: Type, r: Type) = go(l) & (go(r), pre) - def goOr(l: Type, r: Type) = go(l) | (go(r), pre) - def safeAnd(tp1: Type, tp2: Type): Type = (tp1, tp2) match { - case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) => TypeBounds(lo1 | lo2, AndType(hi1, hi2)) - case _ => tp1 & tp2 + def goAnd(l: Type, r: Type) = { + go(l) & (go(r), pre, safeIntersection = ctx.pendingMemberSearches.contains(name)) } + def goOr(l: Type, r: Type) = go(l) | (go(r), pre) { val recCount = ctx.findMemberCount + 1 ctx.findMemberCount = recCount @@ -705,6 +707,19 @@ object Types { ctx.typeComparer.glb(this, that) } + /** Safer version of `&`. + * + * This version does not simplify the upper bound of the intersection of + * two TypeBounds. The simplification done by `&` requires subtyping checks + * which may end up calling `&` again, in most cases this should be safe + * but because of F-bounded types, this can result in an infinite loop + * (which will be masked unless `-Yno-deep-subtypes` is enabled). + */ + def safe_& (that: Type)(implicit ctx: Context): Type = (this, that) match { + case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) => TypeBounds(lo1 | lo2, AndType(hi1, hi2)) + case _ => this & that + } + def | (that: Type)(implicit ctx: Context): Type = track("|") { ctx.typeComparer.lub(this, that) } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index bc212bdca..029387333 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -186,7 +186,7 @@ class tests extends CompilerTest { .filter(_.nonEmpty) .toList - @Test def compileStdLib = compileList("compileStdLib", stdlibFiles, "-migration" :: scala2mode)(allowDeepSubtypes) + @Test def compileStdLib = compileList("compileStdLib", stdlibFiles, "-migration" :: scala2mode) @Test def compileMixed = compileLine( """tests/pos/B.scala |./scala-scala/src/library/scala/collection/immutable/Seq.scala -- cgit v1.2.3