From e10d8a794f16a8de946e5f07392dbcbc0847a603 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 21 Jul 2020 19:45:50 +0200 Subject: [PATCH 1/8] Localize GADTused variable --- .../dotty/tools/dotc/core/TypeComparer.scala | 11 +++++++++- .../src/dotty/tools/dotc/typer/Typer.scala | 22 +++++++++---------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index aac7562c31d9..1e7782d65db1 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -133,6 +133,12 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi } } + def testSubType(tp1: Type, tp2: Type): CompareResult = + GADTused = false + if !topLevelSubType(tp1, tp2) then CompareResult.Fail + else if GADTused then CompareResult.OKwithGADTUsed + else CompareResult.OK + /** The current approximation state. See `ApproxState`. */ private var approx: ApproxState = FreshApprox protected def approxState: ApproxState = approx @@ -141,7 +147,7 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi * every time we compare components of the previous pair of types. * This type is used for capture conversion in `isSubArgs`. */ - private [this] var leftRoot: Type = _ + private [this] var leftRoot: Type = null /** Are we forbidden from recording GADT constraints? */ private var frozenGadt = false @@ -2493,6 +2499,9 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi object TypeComparer { + enum CompareResult: + case OK, Fail, OKwithGADTUsed + /** Class for unification variables used in `natValue`. */ private class AnyConstantType extends UncachedGroundType with ValueType { var tpe: Type = NoType diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6d074f5a2664..4c0b95ba0920 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -26,6 +26,7 @@ import ErrorReporting._ import Checking._ import Inferencing._ import EtaExpansion.etaExpand +import TypeComparer.CompareResult import util.Spans._ import util.common._ import util.{Property, SimpleIdentityMap, SrcPos} @@ -3282,23 +3283,22 @@ class Typer extends Namer |To turn this error into a warning, pass -Xignore-scala2-macros to the compiler""".stripMargin, tree.srcPos.startPos) tree } - else if (tree.tpe.widenExpr <:< pt) { - if (ctx.typeComparer.GADTused && pt.isValueType) + else ctx.typeComparer.testSubType(tree.tpe.widenExpr, pt) match + case CompareResult.Fail => + wtp match + case wtp: MethodType => missingArgs(wtp) + case _ => + typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt") + //typr.println(TypeComparer.explained(tree.tpe <:< pt)) + adaptToSubType(wtp) + case CompareResult.OKwithGADTUsed if pt.isValueType => // Insert an explicit cast, so that -Ycheck in later phases succeeds. // I suspect, but am not 100% sure that this might affect inferred types, // if the expected type is a supertype of the GADT bound. It would be good to come // up with a test case for this. tree.cast(pt) - else - tree - } - else wtp match { - case wtp: MethodType => missingArgs(wtp) case _ => - typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt") - //typr.println(TypeComparer.explained(tree.tpe <:< pt)) - adaptToSubType(wtp) - } + tree } // Follow proxies and approximate type paramrefs by their upper bound From 82e8d5e76d21480b435c86a9eb15ae213ca35e22 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 21 Jul 2020 20:12:23 +0200 Subject: [PATCH 2/8] Move typeComparer out of context Have a separate stack of type comparers instead. --- .../src/dotty/tools/dotc/config/Config.scala | 3 + .../tools/dotc/core/ConstraintHandling.scala | 15 +- .../src/dotty/tools/dotc/core/Contexts.scala | 63 ++-- .../dotty/tools/dotc/core/Definitions.scala | 2 +- .../dotty/tools/dotc/core/Denotations.scala | 2 +- .../tools/dotc/core/GadtConstraint.scala | 4 +- .../tools/dotc/core/SymDenotations.scala | 2 +- .../dotty/tools/dotc/core/TypeComparer.scala | 278 ++++++++++++------ .../dotty/tools/dotc/core/TypeErasure.scala | 2 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 8 +- .../dotty/tools/dotc/core/TyperState.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 50 ++-- .../tools/dotc/printing/Formatting.scala | 2 +- .../tools/dotc/printing/PlainPrinter.scala | 2 +- .../dotty/tools/dotc/reporting/messages.scala | 4 +- .../tools/dotc/transform/TreeChecker.scala | 2 +- .../tools/dotc/transform/TypeTestsCasts.scala | 2 +- .../tools/dotc/transform/patmat/Space.scala | 6 +- .../dotty/tools/dotc/typer/Applications.scala | 2 +- .../tools/dotc/typer/ErrorReporting.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 4 +- .../dotty/tools/dotc/typer/Inferencing.scala | 10 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- .../dotty/tools/dotc/typer/Synthesizer.scala | 2 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 6 +- .../src/dotty/tools/dotc/typer/Typer.scala | 25 +- .../dotty/tools/dotc/CompilationTests.scala | 1 - .../matchtype-loop2.scala | 3 +- 30 files changed, 311 insertions(+), 199 deletions(-) rename tests/{neg => neg-custom-args/allow-deep-subtypes}/matchtype-loop2.scala (60%) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 6bad889890d4..a16f55c12d3e 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -49,6 +49,9 @@ object Config { */ final val checkBackendNames = false + /** Check that re-used type comparers are in their initialization state */ + final val checkTypeComparerReset = true + /** Type comparer will fail with an assert if the upper bound * of a constrained parameter becomes Nothing. This should be turned * on only for specific debugging as normally instantiation to Nothing diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 9b1990b981fa..27949f3fb899 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -54,11 +54,18 @@ trait ConstraintHandling { */ protected var comparedTypeLambdas: Set[TypeLambda] = Set.empty + def checkReset() = + assert(addConstraintInvocations == 0) + assert(frozenConstraint == false) + assert(caseLambda == NoType) + assert(homogenizeArgs == false) + assert(comparedTypeLambdas == Set.empty) + /** Gives for each instantiated type var that does not yet have its `inst` field - * set, the instance value stored in the constraint. Storing instances in constraints - * is done only in a temporary way for contexts that may be retracted - * without also retracting the type var as a whole. - */ + * set, the instance value stored in the constraint. Storing instances in constraints + * is done only in a temporary way for contexts that may be retracted + * without also retracting the type var as a whole. + */ def instType(tvar: TypeVar): Type = constraint.entry(tvar.origin) match { case _: TypeBounds => NoType case tp: TypeParamRef => diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 941078a0d82e..73ab4b3a16a6 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -172,17 +172,6 @@ object Contexts { protected def searchHistory_= (searchHistory: SearchHistory): Unit = _searchHistory = searchHistory final def searchHistory: SearchHistory = _searchHistory - /** The current type comparer. This ones updates itself automatically for - * each new context. - */ - private var _typeComparer: TypeComparer = _ - protected def typeComparer_=(typeComparer: TypeComparer): Unit = _typeComparer = typeComparer - def typeComparer: TypeComparer = { - if (_typeComparer.comparerCtx ne this) - _typeComparer = _typeComparer.copyIn(this) - _typeComparer - } - /** The current source file */ private var _source: SourceFile = _ protected def source_=(source: SourceFile): Unit = _source = source @@ -479,7 +468,6 @@ object Contexts { _typeAssigner = origin.typeAssigner _gadt = origin.gadt _searchHistory = origin.searchHistory - _typeComparer = origin.typeComparer _source = origin.source _moreProperties = origin.moreProperties _store = origin.store @@ -610,7 +598,6 @@ object Contexts { util.Stats.record("Context.setSource") this.source = source this - def setTypeComparerFn(tcfn: Context => TypeComparer): this.type = { this.typeComparer = tcfn(this); this } private def setMoreProperties(moreProperties: Map[Key[Any], Any]): this.type = util.Stats.record("Context.setMoreProperties") this.moreProperties = moreProperties @@ -699,26 +686,57 @@ object Contexts { val base = ctx.base import base._ val nestedCtx = - if testsInUse < testContexts.size then - testContexts(testsInUse).reuseIn(ctx) + if exploresInUse < exploreContexts.size then + exploreContexts(exploresInUse).reuseIn(ctx) else val ts = TyperState() .setReporter(ExploringReporter()) .setCommittable(false) val c = FreshContext(ctx.base).init(ctx, ctx).setTyperState(ts) - testContexts += c + exploreContexts += c c - testsInUse += 1 + exploresInUse += 1 val nestedTS = nestedCtx.typerState nestedTS.init(ctx.typerState, ctx.typerState.constraint) val result = try op(using nestedCtx) finally nestedTS.reporter.asInstanceOf[ExploringReporter].reset() - testsInUse -= 1 + exploresInUse -= 1 result end explore + /** The type comparer of the kind created by `maker` to be used. + * This is the currently active type comparer CMP if + * - CMP is associated with the current context, and + * - CMP is of the kind created by maker or maker creates a plain type comparer. + * Note: plain TypeComparers always take on the kind of the outer comparer if they are in the same context. + * In other words: tracking or explaining is a sticky property in the same context. + */ + private def comparer(using Context): TypeComparer = + val base = ctx.base + if base.comparersInUse > 0 + && (base.comparers(base.comparersInUse - 1).comparerContext eq ctx) + then + base.comparers(base.comparersInUse - 1).currentInstance + else + val result = + if base.comparersInUse < base.comparers.size then + base.comparers(base.comparersInUse) + else + val result = TypeComparer(ctx) + base.comparers += result + result + base.comparersInUse += 1 + result.init(ctx) + result + + def comparing[T](op: TypeComparer => T)(using Context): T = + val saved = ctx.base.comparersInUse + try op(comparer) + finally ctx.base.comparersInUse = saved + end comparing + /** A class defining the initial context with given context base * and set of possible settings. */ @@ -735,7 +753,6 @@ object Contexts { store = initialStore .updated(settingsStateLoc, settingsGroup.defaultState) .updated(notNullInfosLoc, Nil) - typeComparer = new TypeComparer(using this) searchHistory = new SearchRoot gadt = EmptyGadtConstraint } @@ -871,14 +888,18 @@ object Contexts { protected[dotc] val indentTab: String = " " - private[dotc] val testContexts = new mutable.ArrayBuffer[FreshContext] - private[dotc] var testsInUse: Int = 0 + private[Contexts] val exploreContexts = new mutable.ArrayBuffer[FreshContext] + private[Contexts] var exploresInUse: Int = 0 + + private[Contexts] val comparers = new mutable.ArrayBuffer[TypeComparer] + private[Contexts] var comparersInUse: Int = 0 def reset(): Unit = { for ((_, set) <- uniqueSets) set.clear() errorTypeMsg.clear() sources.clear() sourceNamed.clear() + comparers.clear() // forces re-evaluation of top and bottom classes in TypeComparer } // Test that access is single threaded diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index bcc5e47fd09e..418d7f3027c9 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1324,7 +1324,7 @@ class Definitions { def asContextFunctionType(tp: Type)(using Context): Type = tp.stripTypeVar.dealias match { case tp1: TypeParamRef if ctx.typerState.constraint.contains(tp1) => - asContextFunctionType(ctx.typeComparer.bounds(tp1).hiBound) + asContextFunctionType(TypeComparer.bounds(tp1).hiBound) case tp1 => if (isFunctionType(tp1) && tp1.typeSymbol.name.isContextFunction) tp1 else NoType diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 4404ae0abf31..2e3bde2ee62c 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -539,7 +539,7 @@ object Denotations { case tp1: MethodType => tp2 match case tp2: MethodType - if ctx.typeComparer.matchingMethodParams(tp1, tp2) + if TypeComparer.matchingMethodParams(tp1, tp2) && tp1.isImplicitMethod == tp2.isImplicitMethod && tp1.isErasedMethod == tp2.isErasedMethod => val resType = infoMeet(tp1.resType, tp2.resType.subst(tp2, tp1), safeIntersection) diff --git a/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala b/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala index d95647593ef7..e33e634c5e9e 100644 --- a/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/GadtConstraint.scala @@ -220,8 +220,8 @@ final class ProperGadtConstraint private( override protected def constraint = myConstraint override protected def constraint_=(c: Constraint) = myConstraint = c - override protected def isSub(tp1: Type, tp2: Type)(using Context): Boolean = ctx.typeComparer.isSubType(tp1, tp2) - override protected def isSame(tp1: Type, tp2: Type)(using Context): Boolean = ctx.typeComparer.isSameType(tp1, tp2) + override protected def isSub(tp1: Type, tp2: Type)(using Context): Boolean = TypeComparer.isSubType(tp1, tp2) + override protected def isSame(tp1: Type, tp2: Type)(using Context): Boolean = TypeComparer.isSameType(tp1, tp2) override def nonParamBounds(param: TypeParamRef)(using Context): TypeBounds = constraint.nonParamBounds(param) match { diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 6b3e570cdc05..951c5e9e15dd 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1991,7 +1991,7 @@ object SymDenotations { computeApplied case tp: TypeParamRef => // uncachable, since baseType depends on context bounds - recur(ctx.typeComparer.bounds(tp).hi) + recur(TypeComparer.bounds(tp).hi) case tp: TypeProxy => def computeTypeProxy = { diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 1e7782d65db1..2cdc00d8cefe 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -23,16 +23,32 @@ import typer.ProtoTypes.constrained import typer.Applications.productSelectorTypes import reporting.trace import NullOpsDecorator._ +import annotation.constructorOnly /** Provides methods to compare types. */ -class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling with PatternTypeConstrainer { +class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling, PatternTypeConstrainer { import TypeComparer._ + Stats.record("TypeComparer") - val state = ctx.typerState + private var myContext: Context = initctx + def comparerContext: Context = myContext + + protected given [DummySoItsADef] as Context = myContext + + protected var state: TyperState = null def constraint: Constraint = state.constraint def constraint_=(c: Constraint): Unit = state.constraint = c + def init(c: Context): Unit = + myContext = c + state = c.typerState + monitored = false + GADTused = false + recCount = 0 + needsGc = false + if Config.checkTypeComparerReset then checkReset() + private var pendingSubTypes: mutable.Set[(Type, Type)] = null private var recCount = 0 private var monitored = false @@ -41,6 +57,12 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi private var canCompareAtoms: Boolean = true // used for internal consistency checking + /** Indicates whether the subtype check used GADT bounds */ + private var GADTused: Boolean = false + + private var myInstance: TypeComparer = this + def currentInstance: TypeComparer = myInstance + /** Is a subtype check in progress? In that case we may not * permanently instantiate type variables, because the corresponding * constraint might still be retracted and the instantiation should @@ -59,50 +81,24 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi private var successCount = 0 private var totalCount = 0 - private var myAnyClass: ClassSymbol = null - private var myAnyKindClass: ClassSymbol = null - private var myNothingClass: ClassSymbol = null - private var myNullClass: ClassSymbol = null - private var myObjectClass: ClassSymbol = null - private var myAnyType: TypeRef = null - private var myAnyKindType: TypeRef = null - private var myNothingType: TypeRef = null - - def AnyClass: ClassSymbol = { - if (myAnyClass == null) myAnyClass = defn.AnyClass - myAnyClass - } - def AnyKindClass: ClassSymbol = { - if (myAnyKindClass == null) myAnyKindClass = defn.AnyKindClass - myAnyKindClass - } - def NothingClass: ClassSymbol = { - if (myNothingClass == null) myNothingClass = defn.NothingClass - myNothingClass - } - def NullClass: ClassSymbol = { - if (myNullClass == null) myNullClass = defn.NullClass - myNullClass - } - def ObjectClass: ClassSymbol = { - if (myObjectClass == null) myObjectClass = defn.ObjectClass - myObjectClass - } - def AnyType: TypeRef = { - if (myAnyType == null) myAnyType = AnyClass.typeRef - myAnyType - } - def AnyKindType: TypeRef = { - if (myAnyKindType == null) myAnyKindType = AnyKindClass.typeRef - myAnyKindType - } - def NothingType: TypeRef = { - if (myNothingType == null) myNothingType = NothingClass.typeRef - myNothingType - } - - /** Indicates whether a previous subtype check used GADT bounds */ - var GADTused: Boolean = false + protected val AnyClass = defn.AnyClass + protected val AnyKindClass = defn.AnyKindClass + protected val NothingClass = defn.NothingClass + protected val NullClass = defn.NullClass + protected val ObjectClass = defn.ObjectClass + protected val AnyType = AnyClass.typeRef + protected val AnyKindType = AnyKindClass.typeRef + protected val NothingType = NothingClass.typeRef + + override def checkReset() = + super.checkReset() + assert(pendingSubTypes == null || pendingSubTypes.isEmpty) + assert(canCompareAtoms == true) + assert(successCount == 0) + assert(totalCount == 0) + assert(approx == FreshApprox) + assert(leftRoot == null) + assert(frozenGadt == false) /** Record that GADT bounds of `sym` were used in a subtype check. * But exclude constructor type parameters, as these are aliased @@ -206,7 +202,7 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi //} assert(!ctx.settings.YnoDeepSubtypes.value) if (Config.traceDeepSubTypeRecursions && !this.isInstanceOf[ExplainingTypeComparer]) - report.log(TypeComparer.explained(summon[Context].typeComparer.isSubType(tp1, tp2, approx))) + report.log(explained(_.isSubType(tp1, tp2, approx))) } // Eliminate LazyRefs before checking whether we have seen a type before val normalize = new TypeMap { @@ -240,7 +236,7 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi def firstTry: Boolean = tp2 match { case tp2: NamedType => def compareNamed(tp1: Type, tp2: NamedType): Boolean = - val ctx = comparerCtx + val ctx = comparerContext given Context = ctx // optimization for performance val info2 = tp2.info info2 match @@ -1193,16 +1189,16 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi } // begin recur - if (tp2 eq NoType) false - else if (tp1 eq tp2) true - else { + if tp2 eq NoType then false + else if tp1 eq tp2 then true + else val saved = constraint val savedSuccessCount = successCount - try { - recCount = recCount + 1 - if (recCount >= Config.LogPendingSubTypesThreshold) monitored = true - val result = if (monitored) monitoredIsSubType else firstTry - recCount = recCount - 1 + try + recCount += 1 + if recCount >= Config.LogPendingSubTypesThreshold then monitored = true + val result = if monitored then monitoredIsSubType else firstTry + recCount -= 1 if !result then state.constraint = saved else if recCount == 0 && needsGc then @@ -1210,16 +1206,12 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi needsGc = false if (Stats.monitored) recordStatistics(result, savedSuccessCount) result - } - catch { - case NonFatal(ex) => - if (ex.isInstanceOf[AssertionError]) showGoal(tp1, tp2) - recCount -= 1 - state.constraint = saved - successCount = savedSuccessCount - throw ex - } - } + catch case NonFatal(ex) => + if ex.isInstanceOf[AssertionError] then showGoal(tp1, tp2) + recCount -= 1 + state.constraint = saved + successCount = savedSuccessCount + throw ex } private def nonExprBaseType(tp: Type, cls: Symbol)(using Context): Type = @@ -1929,9 +1921,6 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi } } - /** The greatest lower bound of a list types */ - final def glb(tps: List[Type]): Type = tps.foldLeft(AnyType: Type)(glb) - def widenInUnions(using Context): Boolean = migrateTo3 || ctx.erasedTypes @@ -1966,16 +1955,12 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi def widen(tp: Type) = if (widenInUnions) tp.widen else tp.widenIfUnstable val tp1w = widen(tp1) val tp2w = widen(tp2) - if ((tp1 ne tp1w) || (tp2 ne tp2w)) lub(tp1w, tp2w) + if ((tp1 ne tp1w) || (tp2 ne tp2w)) lub(tp1w, tp2w, canConstrain) else orType(tp1w, tp2w) // no need to check subtypes again } mergedLub(tp1.stripLazyRef, tp2.stripLazyRef) } - /** The least upper bound of a list of types */ - final def lub(tps: List[Type]): Type = - tps.foldLeft(NothingType: Type)(lub(_,_, canConstrain = false)) - /** Try to produce joint arguments for a lub `A[T_1, ..., T_n] | A[T_1', ..., T_n']` using * the following strategies: * @@ -2285,9 +2270,6 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi false } - /** A new type comparer of the same type as this one, using the given context. */ - def copyIn(ctx: Context): TypeComparer = new TypeComparer(using ctx) - // ----------- Diagnostics -------------------------------------------------- /** A hook for showing subtype traces. Overridden in ExplainingTypeComparer */ @@ -2333,9 +2315,6 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi } } - /** Returns last check's debug mode, if explicitly enabled. */ - def lastTrace(): String = "" - /** Does `tycon` have a field with type `tparam`? Special cased for `scala.*:` * as that type is artificially added to tuples. */ private def typeparamCorrespondsToField(tycon: Type, tparam: TypeParamInfo): Boolean = @@ -2495,6 +2474,24 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi false } } + + protected def explainingTypeComparer = ExplainingTypeComparer(comparerContext) + protected def trackingTypeComparer = TrackingTypeComparer(comparerContext) + + private def inSubComparer[T, Cmp <: TypeComparer](comparer: Cmp)(op: Cmp => T): T = + val saved = myInstance + myInstance = comparer + try op(comparer) + finally myInstance = saved + + /** The trace of comparison operations when performing `op` */ + def explained[T](op: ExplainingTypeComparer => T)(using Context): String = + val cmp = explainingTypeComparer + inSubComparer(cmp)(op) + cmp.lastTrace() + + def tracked[T](op: TrackingTypeComparer => T)(using Context): T = + inSubComparer(trackingTypeComparer)(op) } object TypeComparer { @@ -2541,23 +2538,106 @@ object TypeComparer { */ val FreshApprox: ApproxState = new ApproxState(4) - /** Show trace of comparison operations when performing `op` */ - def explaining[T](say: String => Unit)(op: Context ?=> T)(using Context): T = { - val nestedCtx = ctx.fresh.setTypeComparerFn(new ExplainingTypeComparer(using _)) - val res = try { op(using nestedCtx) } finally { say(nestedCtx.typeComparer.lastTrace()) } - res - } + def topLevelSubType(tp1: Type, tp2: Type)(using Context): Boolean = + comparing(_.topLevelSubType(tp1, tp2)) - /** Like [[explaining]], but returns the trace instead */ - def explained[T](op: Context ?=> T)(using Context): String = { - var trace: String = null - try { explaining(trace = _)(op) } catch { case ex: Throwable => ex.printStackTrace } - trace - } + def isSubType(tp1: Type, tp2: Type)(using Context): Boolean = + comparing(_.isSubType(tp1, tp2)) + + def isSameType(tp1: Type, tp2: Type)(using Context): Boolean = + comparing(_.isSameType(tp1, tp2)) + + def isSubTypeWhenFrozen(tp1: Type, tp2: Type)(using Context): Boolean = + comparing(_.isSubTypeWhenFrozen(tp1, tp2)) + + def testSubType(tp1: Type, tp2: Type)(using Context): CompareResult = + comparing(_.testSubType(tp1, tp2)) + + def isSameTypeWhenFrozen(tp1: Type, tp2: Type)(using Context): Boolean = + comparing(_.isSameTypeWhenFrozen(tp1, tp2)) + + def isSameRef(tp1: Type, tp2: Type)(using Context): Boolean = + comparing(_.isSameRef(tp1, tp2)) + + def matchesType(tp1: Type, tp2: Type, relaxed: Boolean)(using Context): Boolean = + comparing(_.matchesType(tp1, tp2, relaxed)) + + def matchingMethodParams(tp1: MethodType, tp2: MethodType)(using Context): Boolean = + comparing(_.matchingMethodParams(tp1, tp2)) + + def lub(tp1: Type, tp2: Type, canConstrain: Boolean = false)(using Context): Type = + comparing(_.lub(tp1, tp2, canConstrain)) + + /** The least upper bound of a list of types */ + final def lub(tps: List[Type])(using Context): Type = + tps.foldLeft(defn.NothingType: Type)(lub(_,_)) + + def lubArgs(args1: List[Type], args2: List[Type], tparams: List[TypeParamInfo], canConstrain: Boolean = false)(using Context): List[Type] = + comparing(_.lubArgs(args1, args2, tparams, canConstrain)) + + def glb(tp1: Type, tp2: Type)(using Context): Type = + comparing(_.glb(tp1, tp2)) + + /** The greatest lower bound of a list types */ + def glb(tps: List[Type])(using Context): Type = + tps.foldLeft(defn.AnyType: Type)(glb) + + def orType(using Context)(tp1: Type, tp2: Type, isErased: Boolean = ctx.erasedTypes): Type = + comparing(_.orType(tp1, tp2, isErased)) + + def andType(using Context)(tp1: Type, tp2: Type, isErased: Boolean = ctx.erasedTypes): Type = + comparing(_.andType(tp1, tp2, isErased)) + + def provablyDisjoint(tp1: Type, tp2: Type)(using Context): Boolean = + comparing(_.provablyDisjoint(tp1, tp2)) + + def constValue(tp: Type)(using Context): Option[Constant] = + comparing(_.constValue(tp)) + + def subtypeCheckInProgress(using Context): Boolean = + comparing(_.subtypeCheckInProgress) + + def instType(tvar: TypeVar)(using Context): Type = + comparing(_.instType(tvar)) + + def instanceType(param: TypeParamRef, fromBelow: Boolean)(using Context): Type = + comparing(_.instanceType(param, fromBelow)) + + def approximation(param: TypeParamRef, fromBelow: Boolean)(using Context): Type = + comparing(_.approximation(param, fromBelow)) + + def bounds(param: TypeParamRef)(using Context): TypeBounds = + comparing(_.bounds(param)) + + def fullBounds(param: TypeParamRef)(using Context): TypeBounds = + comparing(_.fullBounds(param)) + + def fullLowerBound(param: TypeParamRef)(using Context): Type = + comparing(_.fullLowerBound(param)) + + def fullUpperBound(param: TypeParamRef)(using Context): Type = + comparing(_.fullUpperBound(param)) + + def addToConstraint(tl: TypeLambda, tvars: List[TypeVar])(using Context): Boolean = + comparing(_.addToConstraint(tl, tvars)) + + def widenInferred(inst: Type, bound: Type)(using Context): Type = + comparing(_.widenInferred(inst, bound)) + + def constrainPatternType(pat: Type, scrut: Type)(using Context): Boolean = + comparing(_.constrainPatternType(pat, scrut)) + + def explained[T](op: ExplainingTypeComparer => T)(using Context): String = + comparing(_.explained(op)) + + def tracked[T](op: TrackingTypeComparer => T)(using Context): T = + comparing(_.tracked(op)) } -class TrackingTypeComparer(using Context) extends TypeComparer { - import state.constraint +class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { + init(initctx) + + override def trackingTypeComparer = this val footprint: mutable.Set[Type] = mutable.Set[Type]() @@ -2704,9 +2784,13 @@ class TrackingTypeComparer(using Context) extends TypeComparer { } /** A type comparer that can record traces of subtype operations */ -class ExplainingTypeComparer(using Context) extends TypeComparer { +class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { import TypeComparer._ + init(initctx) + + override def explainingTypeComparer = this + private var indent = 0 private val b = new StringBuilder @@ -2757,7 +2841,5 @@ class ExplainingTypeComparer(using Context) extends TypeComparer { super.addConstraint(param, bound, fromBelow) } - override def copyIn(using Context): ExplainingTypeComparer = new ExplainingTypeComparer - - override def lastTrace(): String = "Subtype trace:" + { try b.toString finally b.clear() } + def lastTrace(): String = "Subtype trace:" + { try b.toString finally b.clear() } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index ecc5b14a2536..64f97f8a7c1f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -468,7 +468,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean case AndType(tp1, tp2) => erasedGlb(this(tp1), this(tp2), isJava) case OrType(tp1, tp2) => - ctx.typeComparer.orType(this(tp1), this(tp2), isErased = true) + TypeComparer.orType(this(tp1), this(tp2), isErased = true) case tp: MethodType => def paramErasure(tpToErase: Type) = erasureFn(tp.isJavaMethod, semiEraseVCs, isConstructor, wildcardOK)(tpToErase) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index c15af0461811..318e4419e295 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -216,7 +216,7 @@ object TypeOps: case AppliedType(tycon2, args2) => tp1.derivedAppliedType( mergeRefinedOrApplied(tycon1, tycon2), - ctx.typeComparer.lubArgs(args1, args2, tycon1.typeParams)) + TypeComparer.lubArgs(args1, args2, tycon1.typeParams)) case _ => fallback } case tp1 @ TypeRef(pre1, _) => @@ -334,7 +334,7 @@ object TypeOps: */ def classBound(info: ClassInfo)(using Context): Type = { val cls = info.cls - val parentType = info.parents.reduceLeft(ctx.typeComparer.andType(_, _)) + val parentType = info.parents.reduceLeft(TypeComparer.andType(_, _)) def addRefinement(parent: Type, decl: Symbol) = { val inherited = @@ -420,8 +420,8 @@ object TypeOps: case tp: SkolemType if partsToAvoid(mutable.Set.empty, tp.info).nonEmpty => range(defn.NothingType, apply(tp.info)) case tp: TypeVar if mapCtx.typerState.constraint.contains(tp) => - val lo = mapCtx.typeComparer.instanceType( - tp.origin, fromBelow = variance > 0 || variance == 0 && tp.hasLowerBound) + val lo = TypeComparer.instanceType( + tp.origin, fromBelow = variance > 0 || variance == 0 && tp.hasLowerBound)(using mapCtx) val lo1 = apply(lo) if (lo1 ne lo) lo1 else tp case _ => diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index ccb1cfd29648..a1fba01be312 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -141,7 +141,7 @@ class TyperState() { val toCollect = new mutable.ListBuffer[TypeLambda] constraint foreachTypeVar { tvar => if (!tvar.inst.exists) { - val inst = ctx.typeComparer.instType(tvar) + val inst = TypeComparer.instType(tvar) if (inst.exists && (tvar.owningState.get eq this)) { tvar.inst = inst val lam = tvar.origin.binder diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 67b2f2eb00a3..5c247f5b7dc8 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -915,13 +915,13 @@ object Types { /** Is this type a subtype of that type? */ final def <:<(that: Type)(using Context): Boolean = { record("<:<") - ctx.typeComparer.topLevelSubType(this, that) + TypeComparer.topLevelSubType(this, that) } /** Is this type a subtype of that type? */ final def frozen_<:<(that: Type)(using Context): Boolean = { record("frozen_<:<") - ctx.typeComparer.isSubTypeWhenFrozen(this, that) + TypeComparer.isSubTypeWhenFrozen(this, that) } /** Is this type the same as that type? @@ -929,11 +929,11 @@ object Types { */ final def =:=(that: Type)(using Context): Boolean = { record("=:=") - ctx.typeComparer.isSameType(this, that) + TypeComparer.isSameType(this, that) } final def frozen_=:=(that: Type)(using Context): Boolean = - ctx.typeComparer.isSameTypeWhenFrozen(this, that) + TypeComparer.isSameTypeWhenFrozen(this, that) /** Is this type a primitive value type which can be widened to the primitive value type `that`? */ def isValueSubType(that: Type)(using Context): Boolean = widen match { @@ -988,7 +988,7 @@ object Types { */ def matches(that: Type)(using Context): Boolean = { record("matches") - ctx.typeComparer.matchesType(this, that, relaxed = !ctx.phase.erasedTypes) + TypeComparer.matchesType(this, that, relaxed = !ctx.phase.erasedTypes) } /** This is the same as `matches` except that it also matches => T with T and @@ -1012,7 +1012,7 @@ object Types { def & (that: Type)(using Context): Type = { record("&") - ctx.typeComparer.glb(this, that) + TypeComparer.glb(this, that) } /** Safer version of `&`. @@ -1046,7 +1046,7 @@ object Types { def | (that: Type)(using Context): Type = { record("|") - ctx.typeComparer.lub(this, that) + TypeComparer.lub(this, that) } // ----- Unwrapping types ----------------------------------------------- @@ -1156,7 +1156,7 @@ object Types { def widenUnionWithoutNull(using Context): Type = widen match { case tp @ OrType(lhs, rhs) => - ctx.typeComparer.lub(lhs.widenUnionWithoutNull, rhs.widenUnionWithoutNull, canConstrain = true) match { + TypeComparer.lub(lhs.widenUnionWithoutNull, rhs.widenUnionWithoutNull, canConstrain = true) match { case union: OrType => union.join case res => res } @@ -4184,7 +4184,7 @@ object Types { * uninstantiated */ def instanceOpt(using Context): Type = - if (inst.exists) inst else ctx.typeComparer.instType(this) + if (inst.exists) inst else TypeComparer.instType(this) /** Is the variable already instantiated? */ def isInstantiated(using Context): Boolean = instanceOpt.exists @@ -4208,7 +4208,7 @@ object Types { val atp = TypeOps.avoid(tp, problems.toList) def msg = i"Inaccessible variables captured in instantation of type variable $this.\n$tp was fixed to $atp" typr.println(msg) - val bound = ctx.typeComparer.fullUpperBound(origin) + val bound = TypeComparer.fullUpperBound(origin) if !(atp <:< bound) then throw new TypeError(s"$msg,\nbut the latter type does not conform to the upper bound $bound") atp @@ -4223,7 +4223,7 @@ object Types { def instantiateWith(tp: Type)(using Context): Type = { assert(tp ne this, s"self instantiation of ${tp.show}, constraint = ${ctx.typerState.constraint.show}") typr.println(s"instantiating ${this.show} with ${tp.show}") - if ((ctx.typerState eq owningState.get) && !ctx.typeComparer.subtypeCheckInProgress) + if ((ctx.typerState eq owningState.get) && !TypeComparer.subtypeCheckInProgress) inst = tp ctx.typerState.constraint = ctx.typerState.constraint.replace(origin, tp) tp @@ -4237,7 +4237,7 @@ object Types { * is also a singleton type. */ def instantiate(fromBelow: Boolean)(using Context): Type = - instantiateWith(avoidCaptures(ctx.typeComparer.instanceType(origin, fromBelow))) + instantiateWith(avoidCaptures(TypeComparer.instanceType(origin, fromBelow))) /** For uninstantiated type variables: Is the lower bound different from Nothing? */ def hasLowerBound(using Context): Boolean = @@ -4304,13 +4304,11 @@ object Types { override def tryNormalize(using Context): Type = reduced.normalized def reduced(using Context): Type = { - val trackingCtx = ctx.fresh.setTypeComparerFn(new TrackingTypeComparer(using _)) - val typeComparer = trackingCtx.typeComparer.asInstanceOf[TrackingTypeComparer] def contextInfo(tp: Type): Type = tp match { case tp: TypeParamRef => val constraint = ctx.typerState.constraint - if (constraint.entry(tp).exists) ctx.typeComparer.fullBounds(tp) + if (constraint.entry(tp).exists) TypeComparer.fullBounds(tp) else NoType case tp: TypeRef => val bounds = ctx.gadt.fullBounds(tp.symbol) @@ -4319,12 +4317,11 @@ object Types { tp.underlying } - def updateReductionContext(): Unit = { + def updateReductionContext(footprint: collection.Set[Type]): Unit = reductionContext = new mutable.HashMap - for (tp <- typeComparer.footprint) + for (tp <- footprint) reductionContext(tp) = contextInfo(tp) - typr.println(i"footprint for $this $hashCode: ${typeComparer.footprint.toList.map(x => (x, contextInfo(x)))}%, %") - } + typr.println(i"footprint for $this $hashCode: ${footprint.toList.map(x => (x, contextInfo(x)))}%, %") def isUpToDate: Boolean = reductionContext.keysIterator.forall { tp => @@ -4337,14 +4334,13 @@ object Types { if (myReduced != null) record("MatchType.reduce cache miss") myReduced = trace(i"reduce match type $this $hashCode", typr, show = true) { - try - typeComparer.matchCases(scrutinee.normalized, cases)(using trackingCtx) - catch { - case ex: Throwable => + def matchCases(cmp: TrackingTypeComparer): Type = + try cmp.matchCases(scrutinee.normalized, cases) + catch case ex: Throwable => handleRecursive("reduce type ", i"$scrutinee match ...", ex) - } + finally updateReductionContext(cmp.footprint) + TypeComparer.tracked(matchCases) } - updateReductionContext() } myReduced } @@ -5553,7 +5549,7 @@ object Types { case tp: TypeRef if tp.info.isTypeAlias => apply(n, tp.superType) case tp: TypeParamRef => - apply(n, ctx.typeComparer.bounds(tp)) + apply(n, TypeComparer.bounds(tp)) case _ => foldOver(n, tp) } @@ -5581,7 +5577,7 @@ object Types { val tsym = if (tp.termSymbol.is(Param)) tp.underlying.typeSymbol else tp.termSymbol foldOver(cs + tsym, tp) case tp: TypeParamRef => - apply(cs, ctx.typeComparer.bounds(tp)) + apply(cs, TypeComparer.bounds(tp)) case other => foldOver(cs, tp) } diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index b4a2af039b5c..be25469c549d 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -199,7 +199,7 @@ object Formatting { entry match { case param: TypeParamRef => - s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}" + s"is a type variable${addendum("constraint", TypeComparer.bounds(param))}" case param: TermParamRef => s"is a reference to a value parameter" case sym: Symbol => diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 8b41983bad27..84d422c99207 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -216,7 +216,7 @@ class PlainPrinter(_ctx: Context) extends Printer { val constr = ctx.typerState.constraint val bounds = if constr.contains(tp) then - withMode(Mode.Printing)(ctx.typeComparer.fullBounds(tp.origin)) + withMode(Mode.Printing)(TypeComparer.fullBounds(tp.origin)) else TypeBounds.empty if (bounds.isTypeAlias) toText(bounds.lo) ~ (Str("^") provided printDebug) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index d875a930b063..ab389eebc33c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -248,8 +248,8 @@ import ast.tpd case tp: TypeParamRef => constraint.entry(tp) match case bounds: TypeBounds => - if variance < 0 then apply(mapCtx.typeComparer.fullUpperBound(tp)) - else if variance > 0 then apply(mapCtx.typeComparer.fullLowerBound(tp)) + if variance < 0 then apply(TypeComparer.fullUpperBound(tp)) + else if variance > 0 then apply(TypeComparer.fullLowerBound(tp)) else tp case NoType => tp case instType => apply(instType) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index ed1232c0e213..334161f582ed 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -341,7 +341,7 @@ class TreeChecker extends Phase with SymTransformer { |Original tree : ${tree.show} |After checking: ${tree1.show} |Why different : - """.stripMargin + core.TypeComparer.explained(tp1 <:< tp2) + """.stripMargin + core.TypeComparer.explained(_.isSubType(tp1, tp2)) if (tree.hasType) // it might not be typed because Typer sometimes constructs new untyped trees and resubmits them to typedUnadapted assert(isSubType(tree1.tpe, tree.typeOpt), divergenceMsg(tree1.tpe, tree.typeOpt)) tree1 diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index bc4c066fb065..d5c0df04c778 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -120,7 +120,7 @@ object TypeTestsCasts { val res = P1 <:< P - debug.println(TypeComparer.explained(P1 <:< P)) + debug.println(TypeComparer.explained(_.isSubType(P1, P))) debug.println("P1 : " + P1.show) debug.println("P1 <:< P = " + res) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 4de45c371047..c292a79b9cae 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -345,7 +345,7 @@ class SpaceEngine(using Context) extends SpaceLogic { Empty } else { - val res = ctx.typeComparer.provablyDisjoint(tp1, tp2) + val res = TypeComparer.provablyDisjoint(tp1, tp2) if (res) Empty else if (tp1.isSingleton) Typ(tp1, true) @@ -499,7 +499,7 @@ class SpaceEngine(using Context) extends SpaceLogic { /** Is `tp1` a subtype of `tp2`? */ def isSubType(tp1: Type, tp2: Type): Boolean = { - debug.println(TypeComparer.explained(tp1 <:< tp2)) + debug.println(TypeComparer.explained(_.isSubType(tp1, tp2))) val res = if (ctx.explicitNulls) { tp1 <:< tp2 } else { @@ -612,7 +612,7 @@ class SpaceEngine(using Context) extends SpaceLogic { def inhabited(tp: Type): Boolean = tp.dealias match { - case AndType(tp1, tp2) => !ctx.typeComparer.provablyDisjoint(tp1, tp2) + case AndType(tp1, tp2) => !TypeComparer.provablyDisjoint(tp1, tp2) case OrType(tp1, tp2) => inhabited(tp1) || inhabited(tp2) case tp: RefinedType => inhabited(tp.parent) case tp: TypeRef => inhabited(tp.prefix) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 38f5dbaf4e00..daf6dc58c1b9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1242,7 +1242,7 @@ trait Applications extends Compatibility { // We ignore whether constraining the pattern succeeded. // Constraining only fails if the pattern cannot possibly match, // but useless pattern checks detect more such cases, so we simply rely on them instead. - withMode(Mode.GadtConstraintInference)(ctx.typeComparer.constrainPatternType(unapplyArgType, selType)) + withMode(Mode.GadtConstraintInference)(TypeComparer.constrainPatternType(unapplyArgType, selType)) val patternBound = maximizeType(unapplyArgType, tree.span, fromScala2x) if (patternBound.nonEmpty) unapplyFn = addBinders(unapplyFn, patternBound) unapp.println(i"case 2 $unapplyArgType ${ctx.typerState.constraint}") diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 0026cf005fe5..03c5b4dd32cf 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -122,7 +122,7 @@ object ErrorReporting { else if (ctx.settings.explainTypes.value) i""" |${ctx.typerState.constraint} - |${TypeComparer.explained(found <:< expected)}""" + |${TypeComparer.explained(_.isSubType(found, expected))}""" else "" } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 607e891852d5..cba3af104944 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -443,7 +443,7 @@ object Implicits: case t: TypeParamRef => constraint.entry(t) match { case NoType => t - case bounds: TypeBounds => mapCtx.typeComparer.fullBounds(t) + case bounds: TypeBounds => TypeComparer.fullBounds(t) case t1 => t1 } case t: TypeVar => @@ -760,7 +760,7 @@ trait Implicits: case ex: AssertionError => implicits.println(s"view $from ==> $to") implicits.println(ctx.typerState.constraint.show) - implicits.println(TypeComparer.explained(from.tpe <:< to)) + implicits.println(TypeComparer.explained(_.isSubType(from.tpe, to))) throw ex } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index e11c14363c3f..39cc1ffa10ac 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -69,7 +69,7 @@ object Inferencing { def apply(tvars: Set[TypeVar], tp: Type) = tp match { case tp: TypeVar if !tp.isInstantiated && - accCtx.typeComparer.bounds(tp.origin) + TypeComparer.bounds(tp.origin) .namedPartsWith(ref => params.contains(ref.symbol)) .nonEmpty => tvars + tp @@ -242,7 +242,7 @@ object Inferencing { constraint.entry(param) match { case TypeBounds(lo, hi) if (hi frozen_<:< lo) => - val inst = accCtx.typeComparer.approximation(param, fromBelow = true) + val inst = TypeComparer.approximation(param, fromBelow = true) typr.println(i"replace singleton $param := $inst") accCtx.typerState.constraint = constraint.replace(param, inst) case _ => @@ -319,9 +319,9 @@ object Inferencing { * 0 if unconstrained, or constraint is from below and above. */ private def instDirection(param: TypeParamRef)(using Context): Int = { - val constrained = ctx.typeComparer.fullBounds(param) + val constrained = TypeComparer.fullBounds(param) val original = param.binder.paramInfos(param.paramNum) - val cmp = ctx.typeComparer + val cmp = TypeComparer val approxBelow = if (!cmp.isSubTypeWhenFrozen(constrained.lo, original.lo)) 1 else 0 val approxAbove = @@ -355,7 +355,7 @@ object Inferencing { if (v == 1) tvar.instantiate(fromBelow = false) else if (v == -1) tvar.instantiate(fromBelow = true) else { - val bounds = ctx.typeComparer.fullBounds(tvar.origin) + val bounds = TypeComparer.fullBounds(tvar.origin) if (bounds.hi <:< bounds.lo || bounds.hi.classSymbol.is(Final) || fromScala2x) tvar.instantiate(fromBelow = false) else { diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index e05bc4cd8421..9af8f9ac06c9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -611,7 +611,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { tree.changeOwner(originalOwner, ctx.owner) def tryConstValue: Tree = - ctx.typeComparer.constValue(callTypeArgs.head.tpe) match { + TypeComparer.constValue(callTypeArgs.head.tpe) match { case Some(c) => Literal(c).withSpan(call.span) case _ => EmptyTree } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 0366636ec43d..798e132626bf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1343,7 +1343,7 @@ class Namer { typer: Typer => def widenRhs(tp: Type): Type = tp.widenTermRefExpr.simplified match case ctp: ConstantType if isInlineVal => ctp - case tp => ctx.typeComparer.widenInferred(tp, rhsProto) + case tp => TypeComparer.widenInferred(tp, rhsProto) // Replace aliases to Unit by Unit itself. If we leave the alias in // it would be erased to BoxedUnit. diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 2cb455ad8e2d..6920d972b387 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -512,7 +512,7 @@ object ProtoTypes { val added = state.constraint.ensureFresh(tl) val tvars = if (addTypeVars) newTypeVars(added) else Nil - ctx.typeComparer.addToConstraint(added, tvars.tpes.asInstanceOf[List[TypeVar]]) + TypeComparer.addToConstraint(added, tvars.tpes.asInstanceOf[List[TypeVar]]) (added, tvars) } diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index d6c47380e438..bb34a230bf49 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -307,7 +307,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val tparams = poly.paramRefs val variances = caseClass.typeParams.map(_.paramVarianceSign) val instanceTypes = tparams.lazyZip(variances).map((tparam, variance) => - ctx.typeComparer.instanceType(tparam, fromBelow = variance < 0)) + TypeComparer.instanceType(tparam, fromBelow = variance < 0)) resType.substParams(poly, instanceTypes) instantiate(using ctx.fresh.setExploreTyperState().setOwner(caseClass)) case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 28b3ddeb2fbf..402d8b567d22 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -413,7 +413,7 @@ trait TypeAssigner { } def assignType(tree: untpd.Match, scrutinee: Tree, cases: List[CaseDef])(using Context): Match = - tree.withType(ctx.typeComparer.lub(cases.tpes)) + tree.withType(TypeComparer.lub(cases.tpes)) def assignType(tree: untpd.Labeled)(using Context): Labeled = tree.withType(tree.bind.symbol.info) @@ -426,7 +426,7 @@ trait TypeAssigner { def assignType(tree: untpd.Try, expr: Tree, cases: List[CaseDef])(using Context): Try = if (cases.isEmpty) tree.withType(expr.tpe) - else tree.withType(ctx.typeComparer.lub(expr.tpe :: cases.tpes)) + else tree.withType(TypeComparer.lub(expr.tpe :: cases.tpes)) def assignType(tree: untpd.SeqLiteral, elems: List[Tree], elemtpt: Tree)(using Context): SeqLiteral = { val ownType = tree match { @@ -487,7 +487,7 @@ trait TypeAssigner { tree.withType(NamedType(NoPrefix, sym)) def assignType(tree: untpd.Alternative, trees: List[Tree])(using Context): Alternative = - tree.withType(ctx.typeComparer.lub(trees.tpes)) + tree.withType(TypeComparer.lub(trees.tpes)) def assignType(tree: untpd.UnApply, proto: Type)(using Context): UnApply = tree.withType(proto) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4c0b95ba0920..84938132bd49 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -168,7 +168,7 @@ class Typer extends Namer * the previous (inner) definition. This models what scalac does. */ def checkNewOrShadowed(found: Type, newPrec: BindingPrec, scala2pkg: Boolean = false)(using Context): Type = - if !previous.exists || ctx.typeComparer.isSameRef(previous, found) then + if !previous.exists || TypeComparer.isSameRef(previous, found) then found else if (prevCtx.scope eq ctx.scope) && (newPrec == Definition || newPrec == NamedImport && prevPrec == WildImport) @@ -768,7 +768,9 @@ class Typer extends Namer def handlePattern: Tree = { val tpt1 = typedTpt if (!ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef) - withMode(Mode.GadtConstraintInference)(ctx.typeComparer.constrainPatternType(tpt1.tpe, pt)) + withMode(Mode.GadtConstraintInference) { + TypeComparer.constrainPatternType(tpt1.tpe, pt) + } // special case for an abstract type that comes with a class tag tryWithClassTag(ascription(tpt1, isWildcard = true), pt) } @@ -1616,7 +1618,7 @@ class Typer extends Namer else if (tree.elems.isEmpty && tree.isInstanceOf[Trees.JavaSeqLiteral[?]]) defn.ObjectType // generic empty Java varargs are of type Object[] else - ctx.typeComparer.lub(elems1.tpes) + TypeComparer.lub(elems1.tpes) val elemtpt1 = typed(tree.elemtpt, elemtptType) assign(elems1, elemtpt1) } @@ -2195,7 +2197,7 @@ class Typer extends Namer case _ => val pcls = parents.foldLeft(defn.ObjectClass)(improve) typr.println(i"ensure first is class $parents%, % --> ${parents map (_ baseType pcls)}%, %") - val first = ctx.typeComparer.glb(defn.ObjectType :: parents.map(_.baseType(pcls))) + val first = TypeComparer.glb(defn.ObjectType :: parents.map(_.baseType(pcls))) checkFeasibleParent(first, ctx.source.atSpan(span), em" in inferred superclass $first") :: parents } } @@ -2384,7 +2386,7 @@ class Typer extends Namer if (ctx.mode.is(Mode.Pattern)) app1 else { val elemTpes = elems.lazyZip(pts).map((elem, pt) => - ctx.typeComparer.widenInferred(elem.tpe, pt)) + TypeComparer.widenInferred(elem.tpe, pt)) val resTpe = TypeOps.nestedPairs(elemTpes) app1.cast(resTpe) } @@ -3228,7 +3230,6 @@ class Typer extends Namer } def adaptNoArgsOther(wtp: Type): Tree = { - ctx.typeComparer.GADTused = false if (isContextFunctionRef(wtp) && !untpd.isContextualClosure(tree) && !isApplyProto(pt) && @@ -3283,7 +3284,7 @@ class Typer extends Namer |To turn this error into a warning, pass -Xignore-scala2-macros to the compiler""".stripMargin, tree.srcPos.startPos) tree } - else ctx.typeComparer.testSubType(tree.tpe.widenExpr, pt) match + else TypeComparer.testSubType(tree.tpe.widenExpr, pt) match case CompareResult.Fail => wtp match case wtp: MethodType => missingArgs(wtp) @@ -3307,7 +3308,7 @@ class Typer extends Namer def underlyingApplied(tp: Type): Type = tp.stripTypeVar match { case tp: RefinedType => tp case tp: AppliedType => tp - case tp: TypeParamRef => underlyingApplied(ctx.typeComparer.bounds(tp).hi) + case tp: TypeParamRef => underlyingApplied(TypeComparer.bounds(tp).hi) case tp: TypeProxy => underlyingApplied(tp.superType) case _ => tp } @@ -3615,9 +3616,11 @@ class Typer extends Namer protected def checkEqualityEvidence(tree: tpd.Tree, pt: Type)(using Context) : Unit = tree match { case _: RefTree | _: Literal - if !isVarPattern(tree) && - !(pt <:< tree.tpe) && - !withMode(Mode.GadtConstraintInference)(ctx.typeComparer.constrainPatternType(tree.tpe, pt)) => + if !isVarPattern(tree) + && !(pt <:< tree.tpe) + && !withMode(Mode.GadtConstraintInference) { + TypeComparer.constrainPatternType(tree.tpe, pt) + } => val cmp = untpd.Apply( untpd.Select(untpd.TypedSplice(tree), nme.EQ), diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 7b3fd7c6f04a..610467f705cf 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -153,7 +153,6 @@ class CompilationTests extends ParallelTesting { compileFile("tests/neg-custom-args/conditionalWarnings.scala", allowDeepSubtypes.and("-deprecation").and("-Xfatal-warnings")), compileFilesInDir("tests/neg-custom-args/isInstanceOf", allowDeepSubtypes and "-Xfatal-warnings"), compileFile("tests/neg-custom-args/i3627.scala", allowDeepSubtypes), - compileFile("tests/neg-custom-args/matchtype-loop.scala", allowDeepSubtypes), compileFile("tests/neg-custom-args/sourcepath/outer/nested/Test1.scala", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath")), compileDir("tests/neg-custom-args/sourcepath2/hi", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath2", "-Xfatal-warnings")), compileList("duplicate source", List( diff --git a/tests/neg/matchtype-loop2.scala b/tests/neg-custom-args/allow-deep-subtypes/matchtype-loop2.scala similarity index 60% rename from tests/neg/matchtype-loop2.scala rename to tests/neg-custom-args/allow-deep-subtypes/matchtype-loop2.scala index 4ea55fc2928f..3a97d54a2820 100644 --- a/tests/neg/matchtype-loop2.scala +++ b/tests/neg-custom-args/allow-deep-subtypes/matchtype-loop2.scala @@ -2,7 +2,8 @@ object Test { type L[X] = X match { case Int => L[X] } - type LL[X] = X match { // error: recursion limit exceeded + type LL[X] = X match { case Int => LL[LL[X]] } + val x: LL[Int] = 2 // error } From 2ec5dffc4725754ad25ada5b1f7bfe95f55f7fbf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 21 Jul 2020 22:02:03 +0200 Subject: [PATCH 3/8] Make comparing an inline method --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 73ab4b3a16a6..fdba7003a9d1 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -731,7 +731,7 @@ object Contexts { result.init(ctx) result - def comparing[T](op: TypeComparer => T)(using Context): T = + inline def comparing[T](inline op: TypeComparer => T)(using Context): T = val saved = ctx.base.comparersInUse try op(comparer) finally ctx.base.comparersInUse = saved From 5e0dc9c7f78b90b382eca33bd314ea786aad7f4c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 22 Jul 2020 09:43:33 +0200 Subject: [PATCH 4/8] Some improvements for dotc.Bench --- compiler/src/dotty/tools/dotc/Bench.scala | 40 +++++++++++++------ .../src/dotty/tools/dotc/config/Config.scala | 2 +- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Bench.scala b/compiler/src/dotty/tools/dotc/Bench.scala index ceb945004844..3d264141eb71 100644 --- a/compiler/src/dotty/tools/dotc/Bench.scala +++ b/compiler/src/dotty/tools/dotc/Bench.scala @@ -10,25 +10,28 @@ import scala.annotation.internal.sharable * number of compilers and run each (sequentially) a given number of times * on the same sources. */ -object Bench extends Driver { +object Bench extends Driver: @sharable private var numRuns = 1 private def ntimes(n: Int)(op: => Reporter): Reporter = (0 until n).foldLeft(emptyReporter)((_, _) => op) + @sharable private var times: Array[Int] = _ + override def doCompile(compiler: Compiler, fileNames: List[String])(using Context): Reporter = - ntimes(numRuns) { + times = new Array[Int](numRuns) + var reporter: Reporter = emptyReporter + for i <- 0 until numRuns do val start = System.nanoTime() - val r = super.doCompile(compiler, fileNames) - println(s"time elapsed: ${(System.nanoTime - start) / 1000000}ms") - if (ctx.settings.Xprompt.value) { + reporter = super.doCompile(compiler, fileNames) + times(i) = ((System.nanoTime - start) / 1000000).toInt + println(s"time elapsed: ${times(i)}ms") + if ctx.settings.Xprompt.value then print("hit to continue >") System.in.read() println() - } - r - } + reporter def extractNumArg(args: Array[String], name: String, default: Int = 1): (Int, Array[String]) = { val pos = args indexOf name @@ -36,12 +39,25 @@ object Bench extends Driver { else (args(pos + 1).toInt, (args take pos) ++ (args drop (pos + 2))) } - override def process(args: Array[String], rootCtx: Context): Reporter = { + def reportTimes() = + val best = times.sorted + val measured = numRuns / 3 + val avgBest = best.take(measured).sum / measured + val avgLast = times.reverse.take(measured).sum / measured + println(s"best out of $numRuns runs: ${best(0)}") + println(s"average out of best $measured: $avgBest") + println(s"average out of last $measured: $avgLast") + + override def process(args: Array[String], rootCtx: Context): Reporter = val (numCompilers, args1) = extractNumArg(args, "#compilers") val (numRuns, args2) = extractNumArg(args1, "#runs") this.numRuns = numRuns - ntimes(numCompilers)(super.process(args2, rootCtx)) - } -} + var reporter: Reporter = emptyReporter + for i <- 0 until numCompilers do + reporter = super.process(args2, rootCtx) + reportTimes() + reporter + +end Bench diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index a16f55c12d3e..cbdc40996a91 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -50,7 +50,7 @@ object Config { final val checkBackendNames = false /** Check that re-used type comparers are in their initialization state */ - final val checkTypeComparerReset = true + final val checkTypeComparerReset = false /** Type comparer will fail with an assert if the upper bound * of a constrained parameter becomes Nothing. This should be turned From 7c64b9ff98958aa650acb9e43693fe8b9bb896b0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 18 Aug 2020 11:31:44 +0200 Subject: [PATCH 5/8] Fix rebase breakage --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 5 +++++ compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 2cdc00d8cefe..333975223168 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2591,6 +2591,11 @@ object TypeComparer { def provablyDisjoint(tp1: Type, tp2: Type)(using Context): Boolean = comparing(_.provablyDisjoint(tp1, tp2)) + def liftIfHK(tp1: Type, tp2: Type, + op: (Type, Type) => Type, original: (Type, Type) => Type, + combineVariance: (Variance, Variance) => Variance)(using Context): Type = + comparing(_.liftIfHK(tp1, tp2, op, original, combineVariance)) + def constValue(tp: Type)(using Context): Option[Constant] = comparing(_.constValue(tp)) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5c247f5b7dc8..df248eb2987e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2948,7 +2948,7 @@ object Types { /** Like `make`, but also supports higher-kinded types as argument */ def makeHk(tp1: Type, tp2: Type)(using Context): Type = - ctx.typeComparer.liftIfHK(tp1, tp2, AndType.make(_, _, checkValid = false), makeHk, _ | _) + TypeComparer.liftIfHK(tp1, tp2, AndType.make(_, _, checkValid = false), makeHk, _ | _) } abstract case class OrType(tp1: Type, tp2: Type) extends AndOrType { @@ -3038,7 +3038,7 @@ object Types { /** Like `make`, but also supports higher-kinded types as argument */ def makeHk(tp1: Type, tp2: Type)(using Context): Type = - ctx.typeComparer.liftIfHK(tp1, tp2, OrType(_, _), makeHk, _ & _) + TypeComparer.liftIfHK(tp1, tp2, OrType(_, _), makeHk, _ & _) } /** An extractor object to pattern match against a nullable union. From 072585f0a064b28cc03b9d50661d270b9d218bcf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 22 Jul 2020 14:30:16 +0200 Subject: [PATCH 6/8] Update period in place when transforming denotations --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 6 ++++++ compiler/src/dotty/tools/dotc/core/Denotations.scala | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index fdba7003a9d1..e752a3de0f17 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -313,6 +313,12 @@ object Contexts { final def withPhase(phase: Phase): Context = withPhase(phase.id) + inline def evalAt[T](phase: Phase)(inline op: Context ?=> T): T = + val saved = period + this.asInstanceOf[FreshContext].period = Period(runId, phase.id) + try op(using this) + finally period = saved + // `creationTrace`-related code. To enable, uncomment the code below and the // call to `setCreationTrace()` in this file. /* diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 2e3bde2ee62c..827cd319c0ba 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -797,7 +797,13 @@ object Denotations { val transformer = ctx.base.denotTransformers(nextTransformerId) //println(s"transforming $this with $transformer") try - next = atPhase(transformer)(transformer.transform(cur)) + util.Stats.record("denot transform") + next = ctx.evalAt(transformer)(transformer.transform(cur)) + // We temporarily update the context with the new phase instead of creating a + // new one. This is done for performance. We cut down on about 30% of context + // creations that way, and also avoid phase caches in contexts to get large. + // To work correctly, we need to demand that the context with the new phase + // is not retained in the result. catch { case ex: CyclicReference => println(s"error while transforming $this") // DEBUG From e2eb66a8ef22e5e0dfe45c77580277f69652e7be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 22 Jul 2020 15:31:25 +0200 Subject: [PATCH 7/8] Optimize Denotation#current Split into smaller parts --- .../src/dotty/tools/dotc/core/Contexts.scala | 6 - .../dotty/tools/dotc/core/Denotations.scala | 177 +++++++++--------- 2 files changed, 92 insertions(+), 91 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index e752a3de0f17..fdba7003a9d1 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -313,12 +313,6 @@ object Contexts { final def withPhase(phase: Phase): Context = withPhase(phase.id) - inline def evalAt[T](phase: Phase)(inline op: Context ?=> T): T = - val saved = period - this.asInstanceOf[FreshContext].period = Period(runId, phase.id) - try op(using this) - finally period = saved - // `creationTrace`-related code. To enable, uncomment the code below and the // call to `setCreationTrace()` in this file. /* diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 827cd319c0ba..a07287b5ce93 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -757,95 +757,102 @@ object Denotations { * is brought forward to be valid in the new runId. Otherwise * the symbol is stale, which constitutes an internal error. */ - def current(using Context): SingleDenotation = { + def current(using Context): SingleDenotation = + util.Stats.record("current") val currentPeriod = ctx.period val valid = myValidFor - if (valid.code <= 0) { - // can happen if we sit on a stale denotation which has been replaced - // wholesale by an installAfter; in this case, proceed to the next - // denotation and try again. - val nxt = nextDefined - if (nxt.validFor != Nowhere) return nxt - assert(false, this) - } - if (valid.runId != currentPeriod.runId) - if (exists) initial.bringForward().current - else this - else { + def signalError() = println(s"error while transforming $this") + + def assertNotPackage(d: SingleDenotation, transformer: DenotTransformer) = d match + case d: ClassDenotation => + assert(!d.is(Package), s"illegal transformation of package denotation by transformer $transformer") + case _ => + + def escapeToNext = nextDefined.ensuring(_.validFor != Nowhere) + + def toNewRun = + util.Stats.record("current.bringForward") + if exists then initial.bringForward().current else this + + def goForward = var cur = this - if (currentPeriod.code > valid.code) { - // search for containing period as long as nextInRun increases. - var next = nextInRun - while (next.validFor.code > valid.code && !(next.validFor contains currentPeriod)) { - cur = next - next = next.nextInRun - } - if (next.validFor.code > valid.code) { - // in this case, next.validFor contains currentPeriod - cur = next - cur - } - else { - //println(s"might need new denot for $cur, valid for ${cur.validFor} at $currentPeriod") - // not found, cur points to highest existing variant - val nextTransformerId = ctx.base.nextDenotTransformerId(cur.validFor.lastPhaseId) - if (currentPeriod.lastPhaseId <= nextTransformerId) - cur.validFor = Period(currentPeriod.runId, cur.validFor.firstPhaseId, nextTransformerId) - else { - var startPid = nextTransformerId + 1 - val transformer = ctx.base.denotTransformers(nextTransformerId) - //println(s"transforming $this with $transformer") - try - util.Stats.record("denot transform") - next = ctx.evalAt(transformer)(transformer.transform(cur)) - // We temporarily update the context with the new phase instead of creating a - // new one. This is done for performance. We cut down on about 30% of context - // creations that way, and also avoid phase caches in contexts to get large. - // To work correctly, we need to demand that the context with the new phase - // is not retained in the result. - catch { - case ex: CyclicReference => - println(s"error while transforming $this") // DEBUG - throw ex - } - if (next eq cur) - startPid = cur.validFor.firstPhaseId - else { - next match { - case next: ClassDenotation => - assert(!next.is(Package), s"illegal transformation of package denotation by transformer $transformer") - case _ => - } - next.insertAfter(cur) - cur = next - } - cur.validFor = Period(currentPeriod.runId, startPid, transformer.lastPhaseId) - //printPeriods(cur) - //println(s"new denot: $cur, valid for ${cur.validFor}") - } - cur.current // multiple transformations could be required - } - } - else { - // currentPeriod < end of valid; in this case a version must exist - // but to be defensive we check for infinite loop anyway - var cnt = 0 - while (!(cur.validFor contains currentPeriod)) { - //println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}") - cur = cur.nextInRun - // Note: One might be tempted to add a `prev` field to get to the new denotation - // more directly here. I tried that, but it degrades rather than improves - // performance: Test setup: Compile everything in dotc and immediate subdirectories - // 10 times. Best out of 10: 18154ms with `prev` field, 17777ms without. - cnt += 1 - if (cnt > MaxPossiblePhaseId) - return atPhase(coveredInterval.firstPhaseId)(current) - } + // search for containing period as long as nextInRun increases. + var next = nextInRun + while next.validFor.code > valid.code && !(next.validFor contains currentPeriod) do + cur = next + next = next.nextInRun + if next.validFor.code > valid.code then + // in this case, next.validFor contains currentPeriod + cur = next cur - } - } - } + else + //println(s"might need new denot for $cur, valid for ${cur.validFor} at $currentPeriod") + // not found, cur points to highest existing variant + val nextTransformerId = ctx.base.nextDenotTransformerId(cur.validFor.lastPhaseId) + if (currentPeriod.lastPhaseId <= nextTransformerId) + cur.validFor = Period(currentPeriod.runId, cur.validFor.firstPhaseId, nextTransformerId) + else + var startPid = nextTransformerId + 1 + val transformer = ctx.base.denotTransformers(nextTransformerId) + //println(s"transforming $this with $transformer") + val savedPeriod = ctx.period + val mutCtx = ctx.asInstanceOf[FreshContext] + try + mutCtx.setPhase(transformer) + next = transformer.transform(cur) + // We temporarily update the context with the new phase instead of creating a + // new one. This is done for performance. We cut down on about 30% of context + // creations that way, and also avoid phase caches in contexts to get large. + // To work correctly, we need to demand that the context with the new phase + // is not retained in the result. + catch case ex: CyclicReference => + signalError() + throw ex + finally + mutCtx.setPeriod(savedPeriod) + if next eq cur then + startPid = cur.validFor.firstPhaseId + else + assertNotPackage(next, transformer) + next.insertAfter(cur) + cur = next + cur.validFor = Period(currentPeriod.runId, startPid, transformer.lastPhaseId) + //printPeriods(cur) + //println(s"new denot: $cur, valid for ${cur.validFor}") + cur.current // multiple transformations could be required + end goForward + + def goBack: SingleDenotation = + // currentPeriod < end of valid; in this case a version must exist + // but to be defensive we check for infinite loop anyway + var cur = this + var cnt = 0 + while !(cur.validFor contains currentPeriod) do + //println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}") + cur = cur.nextInRun + // Note: One might be tempted to add a `prev` field to get to the new denotation + // more directly here. I tried that, but it degrades rather than improves + // performance: Test setup: Compile everything in dotc and immediate subdirectories + // 10 times. Best out of 10: 18154ms with `prev` field, 17777ms without. + cnt += 1 + if cnt > MaxPossiblePhaseId then + return atPhase(coveredInterval.firstPhaseId)(current) + cur + end goBack + + if valid.code <= 0 then + // can happen if we sit on a stale denotation which has been replaced + // wholesale by an installAfter; in this case, proceed to the next + // denotation and try again. + escapeToNext + else if valid.runId != currentPeriod.runId then + toNewRun + else if currentPeriod.code > valid.code then + goForward + else + goBack + end current private def demandOutsideDefinedMsg(using Context): String = s"demanding denotation of $this at phase ${ctx.phase}(${ctx.phaseId}) outside defined interval: defined periods are${definedPeriodsString}" @@ -900,7 +907,7 @@ object Denotations { /** Insert this denotation instead of `old`. * Also ensure that `old` refers with `nextInRun` to this denotation - * and set its `validFor` field to `NoWhere`. This is necessary so that + * and set its `validFor` field to `Nowhere`. This is necessary so that * references to the old denotation can be brought forward via `current` * to a valid denotation. * From e9b70549853155e8ea877803898362435607939e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 22 Jul 2020 18:47:22 +0200 Subject: [PATCH 8/8] Don't bring symbols forward to non-existing phases. This was observed in TabCompleterTests. The problem in general is that we might see symbols in runs that have fewer phases than the phase at which the symbol was created. Previously, we updated the symbol anyway. But this is problematic since it means the symbol has a validity period that does not correspond to a phase in the current run. We now treat those symbols as stale without a way to recover instead. --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 2 ++ compiler/src/dotty/tools/dotc/core/Denotations.scala | 7 +++++-- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 7 +++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index fdba7003a9d1..d75a60255003 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -343,6 +343,8 @@ object Contexts { final def runId = period.runId final def phaseId = period.phaseId + final def lastPhaseId = base.phases.length - 1 + /** Does current phase use an erased types interpretation? */ final def erasedTypes = phase.erasedTypes diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index a07287b5ce93..c20be8ee252f 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -716,7 +716,10 @@ object Denotations { this match { case symd: SymDenotation => if (stillValid(symd)) return updateValidity() - if (acceptStale(symd)) return symd.currentSymbol.denot.orElse(symd).updateValidity() + if acceptStale(symd) && symd.initial.validFor.firstPhaseId <= ctx.lastPhaseId then + // New run might have fewer phases than old, so symbol might no longer be + // visible at all. TabCompleteTests have examples where this happens. + return symd.currentSymbol.denot.orElse(symd).updateValidity() case _ => } if (!symbol.exists) return updateValidity() @@ -790,7 +793,7 @@ object Denotations { //println(s"might need new denot for $cur, valid for ${cur.validFor} at $currentPeriod") // not found, cur points to highest existing variant val nextTransformerId = ctx.base.nextDenotTransformerId(cur.validFor.lastPhaseId) - if (currentPeriod.lastPhaseId <= nextTransformerId) + if currentPeriod.lastPhaseId <= nextTransformerId then cur.validFor = Period(currentPeriod.runId, cur.validFor.firstPhaseId, nextTransformerId) else var startPid = nextTransformerId + 1 diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 951c5e9e15dd..e3c0d4df023e 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2351,8 +2351,11 @@ object SymDenotations { if (denot.isOneOf(ValidForeverFlags) || denot.isRefinementClass || denot.isImport) true else { val initial = denot.initial - val firstPhaseId = initial.validFor.firstPhaseId.max(typerPhase.id) - if ((initial ne denot) || ctx.phaseId != firstPhaseId) + val firstPhaseId = + initial.validFor.firstPhaseId.max(typerPhase.id) + if firstPhaseId > ctx.lastPhaseId then + false + else if (initial ne denot) || ctx.phaseId != firstPhaseId then atPhase(firstPhaseId)(stillValidInOwner(initial)) else stillValidInOwner(denot)