diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 10ccc59a3e5f..6513de4ccdd0 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -49,7 +49,7 @@ class Run(comp: Compiler, ictx: Context) { .setOwner(defn.RootClass) .setTyper(new Typer) .addMode(Mode.ImplicitsEnabled) - .setTyperState(new MutableTyperState(ctx.typerState, ctx.typerState.reporter, isCommittable = true)) + .setTyperState(new TyperState(ctx.typerState)) .setFreshNames(new FreshNameCreator.Default) ctx.initialize()(start) // re-initialize the base context with start def addImport(ctx: Context, refFn: () => TermRef) = diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index bdc0be6e6ca3..85dc5e75012e 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -455,9 +455,9 @@ object Contexts { def setCompilerCallback(callback: CompilerCallback): this.type = { this.compilerCallback = callback; this } def setSbtCallback(callback: AnalysisCallback): this.type = { this.sbtCallback = callback; this } def setTyperState(typerState: TyperState): this.type = { this.typerState = typerState; this } - def setReporter(reporter: Reporter): this.type = setTyperState(typerState.withReporter(reporter)) - def setNewTyperState: this.type = setTyperState(typerState.fresh(isCommittable = true)) - def setExploreTyperState: this.type = setTyperState(typerState.fresh(isCommittable = false)) + def setReporter(reporter: Reporter): this.type = setTyperState(typerState.fresh().setReporter(reporter)) + def setNewTyperState(): this.type = setTyperState(typerState.fresh().setCommittable(true)) + def setExploreTyperState(): this.type = setTyperState(typerState.fresh().setCommittable(false)) def setPrinterFn(printer: Context => Printer): this.type = { this.printerFn = printer; this } def setOwner(owner: Symbol): this.type = { assert(owner != NoSymbol); this.owner = owner; this } def setSettings(sstate: SettingsState): this.type = { this.sstate = sstate; this } @@ -516,7 +516,7 @@ object Contexts { outer = NoContext period = InitialPeriod mode = Mode.None - typerState = new TyperState(new ConsoleReporter()) + typerState = new TyperState(null) printerFn = new RefinedPrinter(_) owner = NoSymbol sstate = settings.defaultState diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 051fb20ee02c..df7c9e087423 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -13,18 +13,31 @@ import config.Config import collection.mutable import java.lang.ref.WeakReference -class TyperState(r: Reporter) extends DotClass with Showable { +class TyperState(previous: TyperState /* | Null */) extends DotClass with Showable { - /** The current reporter */ - def reporter = r + private var myReporter = + if (previous == null) new ConsoleReporter() else previous.reporter - /** The current constraint set */ - def constraint: Constraint = - new OrderingConstraint(SimpleMap.Empty, SimpleMap.Empty, SimpleMap.Empty) - def constraint_=(c: Constraint)(implicit ctx: Context): Unit = {} + def reporter: Reporter = myReporter - /** The uninstantiated variables */ - def uninstVars = constraint.uninstVars + /** A fresh type state with the same constraint as this one and the given reporter */ + def setReporter(reporter: Reporter): this.type = { myReporter = reporter; this } + + private var myConstraint: Constraint = + if (previous == null) new OrderingConstraint(SimpleMap.Empty, SimpleMap.Empty, SimpleMap.Empty) + else previous.constraint + + def constraint = myConstraint + def constraint_=(c: Constraint)(implicit ctx: Context) = { + if (Config.debugCheckConstraintsClosed && isGlobalCommittable) c.checkClosed() + myConstraint = c + } + + private val previousConstraint = + if (previous == null) constraint else previous.constraint + + private var myEphemeral: Boolean = + if (previous == null) false else previous.ephemeral /** The ephemeral flag is set as a side effect if an operation accesses * the underlying type of a type variable. The reason we need this flag is @@ -33,8 +46,26 @@ class TyperState(r: Reporter) extends DotClass with Showable { * check the ephemeral flag; If the flag is set during an operation, the result * of that operation should not be cached. */ - def ephemeral: Boolean = false - def ephemeral_=(x: Boolean): Unit = () + def ephemeral = myEphemeral + def ephemeral_=(x: Boolean): Unit = { myEphemeral = x } + + private var myIsCommittable = true + + def isCommittable = myIsCommittable + + def setCommittable(committable: Boolean): this.type = { this.myIsCommittable = committable; this } + + def isGlobalCommittable: Boolean = + isCommittable && (previous == null || previous.isGlobalCommittable) + + private var isCommitted = false + + /** A fresh typer state with the same constraint as this one. */ + def fresh(): TyperState = + new TyperState(this).setReporter(new StoreReporter(reporter)).setCommittable(isCommittable) + + /** The uninstantiated variables */ + def uninstVars = constraint.uninstVars /** 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 @@ -49,76 +80,36 @@ class TyperState(r: Reporter) extends DotClass with Showable { case tp => tp } - /** A fresh typer state with the same constraint as this one. - * @param isCommittable The constraint can be committed to an enclosing context. - */ - def fresh(isCommittable: Boolean): TyperState = this - - /** A fresh type state with the same constraint as this one and the given reporter */ - def withReporter(reporter: Reporter) = new TyperState(reporter) - - /** Commit state so that it gets propagated to enclosing context */ - def commit()(implicit ctx: Context): Unit = unsupported("commit") - /** The closest ancestor of this typer state (including possibly this typer state itself) * which is not yet committed, or which does not have a parent. */ - def uncommittedAncestor: TyperState = this - - /** Make type variable instances permanent by assigning to `inst` field if - * type variable instantiation cannot be retracted anymore. Then, remove - * no-longer needed constraint entries. - */ - def gc()(implicit ctx: Context): Unit = () - - /** Is it allowed to commit this state? */ - def isCommittable: Boolean = false - - /** Can this state be transitively committed until the top-level? */ - def isGlobalCommittable: Boolean = false - - override def toText(printer: Printer): Text = "ImmutableTyperState" - - /** A string showing the hashes of all nested mutable typerstates */ - def hashesStr: String = "" -} - -class MutableTyperState(previous: TyperState, r: Reporter, override val isCommittable: Boolean) -extends TyperState(r) { - - private var myReporter = r - - override def reporter = myReporter - - private val previousConstraint = previous.constraint - private var myConstraint: Constraint = previousConstraint + def uncommittedAncestor: TyperState = + if (isCommitted) previous.uncommittedAncestor else this - override def constraint = myConstraint - override def constraint_=(c: Constraint)(implicit ctx: Context) = { - if (Config.debugCheckConstraintsClosed && isGlobalCommittable) c.checkClosed() - myConstraint = c + private var testReporter: StoreReporter = null + + /** Test using `op`, restoring typerState to previous state afterwards */ + def test(op: => Boolean): Boolean = { + val savedReporter = myReporter + val savedConstraint = myConstraint + val savedCommittable = myIsCommittable + val savedCommitted = isCommitted + myIsCommittable = false + myReporter = + if (testReporter == null) new StoreReporter(reporter) + else { + testReporter.reset() + testReporter + } + try op + finally { + myReporter = savedReporter + myConstraint = savedConstraint + myIsCommittable = savedCommittable + isCommitted = savedCommitted + } } - private var myEphemeral: Boolean = previous.ephemeral - - override def ephemeral = myEphemeral - override def ephemeral_=(x: Boolean): Unit = { myEphemeral = x } - - override def fresh(isCommittable: Boolean): TyperState = - new MutableTyperState(this, new StoreReporter(reporter), isCommittable) - - override def withReporter(reporter: Reporter) = - new MutableTyperState(this, reporter, isCommittable) - - override val isGlobalCommittable = - isCommittable && - (!previous.isInstanceOf[MutableTyperState] || previous.isGlobalCommittable) - - private var isCommitted = false - - override def uncommittedAncestor: TyperState = - if (isCommitted) previous.uncommittedAncestor else this - /** Commit typer state so that its information is copied into current typer state * In addition (1) the owning state of undetermined or temporarily instantiated * type variables changes from this typer state to the current one. (2) Variables @@ -137,7 +128,7 @@ extends TyperState(r) { * isApplicableSafe but also for (e.g. erased-lubs.scala) as well as * many parts of dotty itself. */ - override def commit()(implicit ctx: Context) = { + def commit()(implicit ctx: Context) = { val targetState = ctx.typerState assert(isCommittable) targetState.constraint = @@ -152,7 +143,11 @@ extends TyperState(r) { isCommitted = true } - override def gc()(implicit ctx: Context): Unit = { + /** Make type variable instances permanent by assigning to `inst` field if + * type variable instantiation cannot be retracted anymore. Then, remove + * no-longer needed constraint entries. + */ + def gc()(implicit ctx: Context): Unit = { val toCollect = new mutable.ListBuffer[TypeLambda] constraint foreachTypeVar { tvar => if (!tvar.inst.exists) { @@ -170,6 +165,5 @@ extends TyperState(r) { override def toText(printer: Printer): Text = constraint.toText(printer) - override def hashesStr: String = hashCode.toString + " -> " + previous.hashesStr - + def hashesStr: String = hashCode.toString + " -> " + previous.hashesStr } diff --git a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala index 343e6a517712..ac60124ae86e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -22,6 +22,8 @@ class StoreReporter(outer: Reporter) extends Reporter { private var infos: mutable.ListBuffer[MessageContainer] = null + def reset() = infos = null + def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { typr.println(s">>>> StoredError: ${m.message}") // !!! DEBUG if (infos == null) infos = new mutable.ListBuffer diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 548824ce2684..b0a6d12f27a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -847,7 +847,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def followTypeAlias(tree: untpd.Tree): untpd.Tree = { tree match { case tree: untpd.RefTree => - val nestedCtx = ctx.fresh.setNewTyperState + val nestedCtx = ctx.fresh.setNewTyperState() val ttree = typedType(untpd.rename(tree, tree.name.toTypeName))(nestedCtx) ttree.tpe match { @@ -1002,26 +1002,20 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Is given method reference applicable to type arguments `targs` and argument trees `args`? * @param resultType The expected result type of the application */ - def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = { - val nestedContext = ctx.fresh.setExploreTyperState - new ApplicableToTrees(methRef, targs, args, resultType)(nestedContext).success - } + def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = + ctx.typerState.test(new ApplicableToTrees(methRef, targs, args, resultType).success) /** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views? * @param resultType The expected result type of the application */ - def isDirectlyApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = { - val nestedContext = ctx.fresh.setExploreTyperState - new ApplicableToTreesDirectly(methRef, targs, args, resultType)(nestedContext).success - } + def isDirectlyApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = + ctx.typerState.test(new ApplicableToTreesDirectly(methRef, targs, args, resultType).success) /** Is given method reference applicable to argument types `args`? * @param resultType The expected result type of the application */ - def isApplicable(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = { - val nestedContext = ctx.fresh.setExploreTyperState - new ApplicableToTypes(methRef, args, resultType)(nestedContext).success - } + def isApplicable(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = + ctx.typerState.test(new ApplicableToTypes(methRef, args, resultType).success) /** Is given type applicable to type arguments `targs` and argument trees `args`, * possibly after inserting an `apply`? @@ -1102,12 +1096,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case tp2: MethodType => true // (3a) case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a) case tp2: PolyType => // (3b) - val nestedCtx = ctx.fresh.setExploreTyperState - - { - implicit val ctx = nestedCtx - isAsSpecificValueType(tp1, constrained(tp2).resultType) - } + ctx.typerState.test(isAsSpecificValueType(tp1, constrained(tp2).resultType)) case _ => // (3b) isAsSpecificValueType(tp1, tp2) } @@ -1253,22 +1242,20 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * probability of pruning the search. result type comparisons are neither cheap nor * do they prune much, on average. */ - def adaptByResult(chosen: TermRef) = { - def nestedCtx = ctx.fresh.setExploreTyperState - pt match { - case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) => - alts.filter(alt => - (alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match { - case Nil => chosen - case alt2 :: Nil => alt2 - case alts2 => - resolveOverloaded(alts2, pt) match { - case alt2 :: Nil => alt2 - case _ => chosen - } - } - case _ => chosen - } + def adaptByResult(chosen: TermRef) = pt match { + case pt: FunProto if !ctx.typerState.test(resultConforms(chosen, pt.resultType)) => + val conformingAlts = alts.filter(alt => + (alt ne chosen) && ctx.typerState.test(resultConforms(alt, pt.resultType))) + conformingAlts match { + case Nil => chosen + case alt2 :: Nil => alt2 + case alts2 => + resolveOverloaded(alts2, pt) match { + case alt2 :: Nil => alt2 + case _ => chosen + } + } + case _ => chosen } var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled)) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 10cfcf87cf15..ca1c8425245d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -67,7 +67,7 @@ object Implicits { case mt: MethodType => mt.isImplicit || mt.paramInfos.length != 1 || - !(argType relaxed_<:< mt.paramInfos.head)(ctx.fresh.setExploreTyperState) + !ctx.typerState.test(argType relaxed_<:< mt.paramInfos.head) case poly: PolyType => // We do not need to call ProtoTypes#constrained on `poly` because // `refMatches` is always called with mode TypevarsMissContext enabled. @@ -75,7 +75,7 @@ object Implicits { case mt: MethodType => mt.isImplicit || mt.paramInfos.length != 1 || - !(argType relaxed_<:< wildApprox(mt.paramInfos.head, null, Set.empty)(ctx.fresh.setExploreTyperState)) + !ctx.typerState.test(argType relaxed_<:< wildApprox(mt.paramInfos.head, null, Set.empty)) case rtp => discardForView(wildApprox(rtp, null, Set.empty), argType) } @@ -131,8 +131,12 @@ object Implicits { } if (refs.isEmpty) Nil - else refs.filter(refMatches(_)(ctx.fresh.addMode(Mode.TypevarsMissContext).setExploreTyperState)) // create a defensive copy of ctx to avoid constraint pollution - .map(Candidate(_, level)) + else { + val nestedCtx = ctx.fresh.addMode(Mode.TypevarsMissContext) + refs + .filter(ref => nestedCtx.typerState.test(refMatches(ref)(nestedCtx))) + .map(Candidate(_, level)) + } } } @@ -505,8 +509,9 @@ trait Implicits { self: Typer => && from.isValueType && ( from.isValueSubType(to) || inferView(dummyTreeOfType(from), to) - (ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState) + (ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState()) .isInstanceOf[SearchSuccess] + // TODO: investigate why we can't TyperState#test here ) ) @@ -578,7 +583,7 @@ trait Implicits { self: Typer => formal.argTypes match { case args @ (arg1 :: arg2 :: Nil) if !ctx.featureEnabled(defn.LanguageModuleClass, nme.strictEquality) && - validEqAnyArgs(arg1, arg2)(ctx.fresh.setExploreTyperState) => + ctx.typerState.test(validEqAnyArgs(arg1, arg2)) => ref(defn.Eq_eqAny).appliedToTypes(args).withPos(pos) case _ => EmptyTree @@ -782,7 +787,7 @@ trait Implicits { self: Typer => val generated1 = adapt(generated, pt) lazy val shadowing = typed(untpd.Ident(ref.name) withPos pos.toSynthetic, funProto)( - nestedContext.addMode(Mode.ImplicitShadowing).setExploreTyperState) + nestedContext.addMode(Mode.ImplicitShadowing).setExploreTyperState()) def refSameAs(shadowing: Tree): Boolean = ref.symbol == closureBody(shadowing).symbol || { shadowing match { @@ -814,7 +819,7 @@ trait Implicits { self: Typer => val history = ctx.searchHistory nest wildProto val result = if (history eq ctx.searchHistory) divergingImplicit(cand.ref) - else typedImplicit(cand)(nestedContext.setNewTyperState.setSearchHistory(history)) + else typedImplicit(cand)(nestedContext.setNewTyperState().setSearchHistory(history)) result match { case fail: SearchFailure => rankImplicits(pending1, acc) @@ -822,7 +827,7 @@ trait Implicits { self: Typer => if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent) best :: Nil else { val newPending = pending1.filter(cand1 => - isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext.setExploreTyperState)) + ctx.typerState.test(isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext))) rankImplicits(newPending, best :: acc) } } @@ -851,7 +856,8 @@ trait Implicits { self: Typer => /** Convert a (possibly empty) list of search successes into a single search result */ def condense(hits: List[SearchSuccess]): SearchResult = hits match { case best :: alts => - alts find (alt => isAsGood(alt.ref, best.ref, alt.level, best.level)(ctx.fresh.setExploreTyperState)) match { + alts.find(alt => + ctx.typerState.test(isAsGood(alt.ref, best.ref, alt.level, best.level))) match { case Some(alt) => typr.println(i"ambiguous implicits for $pt: ${best.ref} @ ${best.level}, ${alt.ref} @ ${alt.level}") /* !!! DEBUG diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 99a6e5373fa9..a78a9582597f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -31,7 +31,7 @@ object Inferencing { * Variables that are successfully minimized do not count as uninstantiated. */ def isFullyDefined(tp: Type, force: ForceDegree.Value)(implicit ctx: Context): Boolean = { - val nestedCtx = ctx.fresh.setNewTyperState + val nestedCtx = ctx.fresh.setNewTyperState() val result = new IsFullyDefinedAccumulator(force)(nestedCtx).process(tp) if (result) nestedCtx.typerState.commit() result diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 4906a26966a2..2b372640f51f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -39,11 +39,9 @@ object ProtoTypes { (tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt) /** Test compatibility after normalization in a fresh typerstate. */ - def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context) = { - val nestedCtx = ctx.fresh.setExploreTyperState - val normTp = normalize(tp, pt)(nestedCtx) - isCompatible(normTp, pt)(nestedCtx) || - pt.isRef(defn.UnitClass) && normTp.isParameterless + def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context) = ctx.typerState.test { + val normTp = normalize(tp, pt) + isCompatible(normTp, pt) || pt.isRef(defn.UnitClass) && normTp.isParameterless } private def disregardProto(pt: Type)(implicit ctx: Context): Boolean = pt.dealias match { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bd5ff738ff2d..8d258927173c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1755,7 +1755,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit typed(tree, selType)(ctx addMode Mode.Pattern) def tryEither[T](op: Context => T)(fallBack: (T, TyperState) => T)(implicit ctx: Context) = { - val nestedCtx = ctx.fresh.setNewTyperState + val nestedCtx = ctx.fresh.setNewTyperState() val result = op(nestedCtx) if (nestedCtx.reporter.hasErrors) fallBack(result, nestedCtx.typerState) @@ -1977,7 +1977,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val constraint = ctx.typerState.constraint def inst(tp: Type): Type = tp match { case TypeBounds(lo, hi) - if (lo eq hi) || (hi <:< lo)(ctx.fresh.setExploreTyperState) => + if (lo eq hi) || ctx.typerState.test(hi <:< lo) => inst(lo) case tp: TypeParamRef => constraint.typeVarOfParam(tp).orElse(tp)