From a3c12b4c84a9c5589829ebc4212b7069074c233c Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Mon, 25 Sep 2017 14:03:04 +0200 Subject: [PATCH] Revert "Avoid exploring TyperState creations" --- compiler/src/dotty/tools/dotc/Run.scala | 2 +- .../src/dotty/tools/dotc/core/Contexts.scala | 8 +- .../dotty/tools/dotc/core/TyperState.scala | 152 +++++++++--------- .../tools/dotc/reporting/StoreReporter.scala | 2 - .../dotty/tools/dotc/typer/Applications.scala | 57 ++++--- .../dotty/tools/dotc/typer/Implicits.scala | 26 ++- .../dotty/tools/dotc/typer/Inferencing.scala | 2 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 8 +- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +- 9 files changed, 137 insertions(+), 124 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 28b74b1b38c6..ebf16bb9eb14 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 TyperState(ctx.typerState)) + .setTyperState(new MutableTyperState(ctx.typerState, ctx.typerState.reporter, isCommittable = true)) .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 f430a083c7c2..df330c4da0b5 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -459,9 +459,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.fresh().setReporter(reporter)) - def setNewTyperState(): this.type = setTyperState(typerState.fresh().setCommittable(true)) - def setExploreTyperState(): this.type = setTyperState(typerState.fresh().setCommittable(false)) + 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 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 } @@ -520,7 +520,7 @@ object Contexts { outer = NoContext period = InitialPeriod mode = Mode.None - typerState = new TyperState(null) + typerState = new TyperState(new ConsoleReporter()) 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 df7c9e087423..051fb20ee02c 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -13,31 +13,18 @@ import config.Config import collection.mutable import java.lang.ref.WeakReference -class TyperState(previous: TyperState /* | Null */) extends DotClass with Showable { +class TyperState(r: Reporter) extends DotClass with Showable { - private var myReporter = - if (previous == null) new ConsoleReporter() else previous.reporter + /** The current reporter */ + def reporter = r - def reporter: Reporter = myReporter + /** The current constraint set */ + def constraint: Constraint = + new OrderingConstraint(SimpleMap.Empty, SimpleMap.Empty, SimpleMap.Empty) + def constraint_=(c: Constraint)(implicit ctx: Context): Unit = {} - /** 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 uninstantiated variables */ + def uninstVars = constraint.uninstVars /** 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 @@ -46,26 +33,8 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab * check the ephemeral flag; If the flag is set during an operation, the result * of that operation should not be cached. */ - 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 + def ephemeral: Boolean = false + def ephemeral_=(x: Boolean): Unit = () /** 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 @@ -80,36 +49,76 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab 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 = - if (isCommitted) previous.uncommittedAncestor else this + def uncommittedAncestor: TyperState = this - 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 - } + /** 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 + + override def constraint = myConstraint + override def constraint_=(c: Constraint)(implicit ctx: Context) = { + if (Config.debugCheckConstraintsClosed && isGlobalCommittable) c.checkClosed() + myConstraint = c } + 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 @@ -128,7 +137,7 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab * isApplicableSafe but also for (e.g. erased-lubs.scala) as well as * many parts of dotty itself. */ - def commit()(implicit ctx: Context) = { + override def commit()(implicit ctx: Context) = { val targetState = ctx.typerState assert(isCommittable) targetState.constraint = @@ -143,11 +152,7 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab isCommitted = true } - /** 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 = { + override def gc()(implicit ctx: Context): Unit = { val toCollect = new mutable.ListBuffer[TypeLambda] constraint foreachTypeVar { tvar => if (!tvar.inst.exists) { @@ -165,5 +170,6 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab override def toText(printer: Printer): Text = constraint.toText(printer) - def hashesStr: String = hashCode.toString + " -> " + previous.hashesStr + override 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 ac60124ae86e..343e6a517712 100644 --- a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -22,8 +22,6 @@ 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 4bb4b7820c98..934c2236b014 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,20 +1002,26 @@ 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 = - ctx.typerState.test(new ApplicableToTrees(methRef, targs, args, resultType).success) + 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 + } /** 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 = - ctx.typerState.test(new ApplicableToTreesDirectly(methRef, targs, args, resultType).success) + 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 + } /** 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 = - ctx.typerState.test(new ApplicableToTypes(methRef, args, resultType).success) + 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 + } /** Is given type applicable to type arguments `targs` and argument trees `args`, * possibly after inserting an `apply`? @@ -1096,7 +1102,12 @@ 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) - ctx.typerState.test(isAsSpecificValueType(tp1, constrained(tp2).resultType)) + val nestedCtx = ctx.fresh.setExploreTyperState + + { + implicit val ctx = nestedCtx + isAsSpecificValueType(tp1, constrained(tp2).resultType) + } case _ => // (3b) isAsSpecificValueType(tp1, tp2) } @@ -1246,20 +1257,22 @@ 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) = 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 + 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 + } } 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 c75b1897e5ad..11ac5b3b98fa 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 || - !ctx.typerState.test(argType relaxed_<:< mt.paramInfos.head) + !(argType relaxed_<:< mt.paramInfos.head)(ctx.fresh.setExploreTyperState) 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 || - !ctx.typerState.test(argType relaxed_<:< wildApprox(mt.paramInfos.head, null, Set.empty)) + !(argType relaxed_<:< wildApprox(mt.paramInfos.head, null, Set.empty)(ctx.fresh.setExploreTyperState)) case rtp => discardForView(wildApprox(rtp, null, Set.empty), argType) } @@ -132,12 +132,8 @@ object Implicits { } if (refs.isEmpty) Nil - else { - val nestedCtx = ctx.fresh.addMode(Mode.TypevarsMissContext) - refs - .filter(ref => nestedCtx.typerState.test(refMatches(ref)(nestedCtx))) - .map(Candidate(_, level)) - } + else refs.filter(refMatches(_)(ctx.fresh.addMode(Mode.TypevarsMissContext).setExploreTyperState)) // create a defensive copy of ctx to avoid constraint pollution + .map(Candidate(_, level)) } } @@ -507,9 +503,8 @@ 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 ) ) @@ -581,7 +576,7 @@ trait Implicits { self: Typer => formal.argTypes match { case args @ (arg1 :: arg2 :: Nil) if !ctx.featureEnabled(defn.LanguageModuleClass, nme.strictEquality) && - ctx.typerState.test(validEqAnyArgs(arg1, arg2)) => + validEqAnyArgs(arg1, arg2)(ctx.fresh.setExploreTyperState) => ref(defn.Eq_eqAny).appliedToTypes(args).withPos(pos) case _ => EmptyTree @@ -787,7 +782,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 { @@ -819,7 +814,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) @@ -827,7 +822,7 @@ trait Implicits { self: Typer => if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent) best :: Nil else { val newPending = pending1.filter(cand1 => - ctx.typerState.test(isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext))) + isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext.setExploreTyperState)) rankImplicits(newPending, best :: acc) } } @@ -856,8 +851,7 @@ 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 => - ctx.typerState.test(isAsGood(alt.ref, best.ref, alt.level, best.level))) match { + alts find (alt => isAsGood(alt.ref, best.ref, alt.level, best.level)(ctx.fresh.setExploreTyperState)) 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 ee5995d3f0ee..01f4dd7fb895 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 e7fe53bff0c6..9f36fbed9d34 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -39,9 +39,11 @@ 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) = ctx.typerState.test { - val normTp = normalize(tp, pt) - isCompatible(normTp, pt) || pt.isRef(defn.UnitClass) && normTp.isParameterless + 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 } 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 2ca985bb0cfd..d94a5851574e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1751,7 +1751,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) @@ -1972,7 +1972,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) || ctx.typerState.test(hi <:< lo) => + if (lo eq hi) || (hi <:< lo)(ctx.fresh.setExploreTyperState) => inst(lo) case tp: TypeParamRef => constraint.typeVarOfParam(tp).orElse(tp)