From c5852e4db72d8da8dc4a58c5b798d77ca6fe26cc Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 26 Jul 2024 04:16:12 +0200 Subject: [PATCH] Refactor TermRef widening logic; cache widening results during overloading resolving. --- .../src/dotty/tools/dotc/core/Types.scala | 5 +- .../dotty/tools/dotc/typer/Applications.scala | 48 +++++++++++-------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ca4f8687dc3c..7fbdbafad0b8 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1309,7 +1309,10 @@ object Types extends TypeUtils { final def widen(using Context): Type = this match case _: TypeRef | _: MethodOrPoly => this // fast path for most frequent cases case tp: TermRef => // fast path for next most frequent case - if tp.isOverloaded then tp else tp.underlying.widen + // Don't call `isOverloaded` and `underlying` on `tp` directly, + // because `denot` will be computed twice if the type is provisional. + val denot = tp.denot + if denot.isOverloaded then tp else denot.info.widen case tp: SingletonType => tp.underlying.widen case tp: ExprType => tp.resultType.widen case tp => diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 42765cd6c0bf..0edefff765b3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2048,6 +2048,13 @@ trait Applications extends Compatibility { def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = record("resolveOverloaded") + // A local cache for widenings of alternatives. + // If a type is provisonal, its denotation will not cached. + // Hence, using `widen` on provisional types everywhere will recompute denotations repeatedly. + // Given the denotation will not change in this part, we can safely cache the result. + val altsWidenMap = mutable.HashMap.empty[TermRef, Type] + def widen(ref: TermRef): Type = altsWidenMap.getOrElseUpdate(ref, ref.widen) + /** Is `alt` a method or polytype whose result type after the first value parameter * section conforms to the expected type `resultType`? If `resultType` * is a `IgnoredProto`, pick the underlying type instead. @@ -2095,15 +2102,15 @@ trait Applications extends Compatibility { pt match case pt: FunProto => if pt.applyKind == ApplyKind.Using then - val alts0 = alts.filterConserve(_.widen.stripPoly.isImplicitMethod) + val alts0 = alts.filterConserve(alt => widen(alt).stripPoly.isImplicitMethod) if alts0 ne alts then return resolve(alts0) - else if alts.exists(_.widen.stripPoly.isContextualMethod) then - return resolveMapped(alts, alt => stripImplicit(alt.widen), pt) + else if alts.exists(alt => widen(alt).stripPoly.isContextualMethod) then + return resolveMapped(alts, alt => stripImplicit(widen(alt)), pt) case _ => - var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt)) + var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt, altsWidenMap)) if found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled) then - found = resolveOverloaded1(alts, pt) + found = resolveOverloaded1(alts, pt, altsWidenMap) found match case alt :: Nil => adaptByResult(alt, alts) :: Nil case _ => found @@ -2113,16 +2120,15 @@ trait Applications extends Compatibility { * - the result is applied to value arguments and alternative is not a method, or * - the result is applied to type arguments and alternative is not polymorphic */ - val tryApply: Type => Boolean = alt => pt match { - case pt: FunProto => !alt.widen.stripPoly.isInstanceOf[MethodType] - case pt: PolyProto => !alt.widen.isInstanceOf[PolyType] + def tryApply(alt: TermRef): Boolean = pt match + case pt: FunProto => !widen(alt).stripPoly.isInstanceOf[MethodType] + case pt: PolyProto => !widen(alt).isInstanceOf[PolyType] case _ => false - } /** Replace each alternative by its apply members where necessary */ def applyMembers(alt: TermRef): List[TermRef] = if (tryApply(alt)) { - val qual = alt.widen match { + val qual = widen(alt) match { case pt: PolyType => wildApprox(pt.resultType) case _ => @@ -2150,10 +2156,12 @@ trait Applications extends Compatibility { * It might be called twice from the public `resolveOverloaded` method, once with * implicits and SAM conversions enabled, and once without. */ - private def resolveOverloaded1(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = + private def resolveOverloaded1(alts: List[TermRef], pt: Type, altsWidenMap: mutable.HashMap[TermRef, Type])(using Context): List[TermRef] = trace(i"resolve over $alts%, %, pt = $pt", typr, show = true) { record(s"resolveOverloaded1", alts.length) + def widen(ref: TermRef): Type = altsWidenMap.getOrElseUpdate(ref, ref.widen) + def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty /** The shape of given tree as a type; cannot handle named arguments. */ @@ -2197,7 +2205,7 @@ trait Applications extends Compatibility { // If ref refers to a method whose parameter at index `idx` is a function type, // the arity of that function, otherise -1. def paramCount(ref: TermRef) = - val formals = ref.widen.firstParamTypes + val formals = widen(ref).firstParamTypes if formals.length > idx then formals(idx).dealias match case defn.FunctionNOf(args, _, _) => args.length @@ -2222,7 +2230,7 @@ trait Applications extends Compatibility { val candidates = pt match { case pt @ FunProto(args, resultType) => val numArgs = args.length - def sizeFits(alt: TermRef): Boolean = alt.widen.stripPoly match { + def sizeFits(alt: TermRef): Boolean = widen(alt).stripPoly match { case tp: MethodType => val ptypes = tp.paramInfos val numParams = ptypes.length @@ -2277,12 +2285,12 @@ trait Applications extends Compatibility { val alts1 = alts.filterConserve(pt.canInstantiate) if isDetermined(alts1) then alts1 else - def withinBounds(alt: TermRef) = alt.widen match + def withinBounds(alt: TermRef) = widen(alt) match case tp: PolyType => TypeOps.boundsViolations(targs1, tp.paramInfos, _.substParams(tp, _), NoType).isEmpty val alts2 = alts1.filter(withinBounds) if isDetermined(alts2) then alts2 - else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1) + else resolveMapped(alts1, alt => widen(alt).appliedTo(targs1.tpes), pt1) case pt => val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false)) @@ -2327,9 +2335,9 @@ trait Applications extends Compatibility { case _ => NoType } - skip(alt.widen) + skip(widen(alt)) - def resultIsMethod(tp: Type): Boolean = tp.widen.stripPoly match + def resultIsMethod(tp: TermRef): Boolean = widen(tp).stripPoly match case tp: MethodType => stripInferrable(tp.resultType).isInstanceOf[MethodType] case _ => false @@ -2359,7 +2367,7 @@ trait Applications extends Compatibility { if noCurriedCount == 1 then noCurried else if noCurriedCount > 1 && noCurriedCount < alts.length then - resolveOverloaded1(noCurried, pt) + resolveOverloaded1(noCurried, pt, altsWidenMap) else // prefer alternatves that match without default parameters val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams) @@ -2367,10 +2375,10 @@ trait Applications extends Compatibility { if noDefaultsCount == 1 then noDefaults else if noDefaultsCount > 1 && noDefaultsCount < alts.length then - resolveOverloaded1(noDefaults, pt) + resolveOverloaded1(noDefaults, pt, altsWidenMap) else if deepPt ne pt then // try again with a deeper known expected type - resolveOverloaded1(alts, deepPt) + resolveOverloaded1(alts, deepPt, altsWidenMap) else candidates }