Skip to content

Commit

Permalink
Refactor TermRef widening logic; cache widening results during overlo…
Browse files Browse the repository at this point in the history
…ading resolving.
  • Loading branch information
noti0na1 committed Jul 26, 2024
1 parent 3dfd762 commit c5852e4
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 21 deletions.
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
48 changes: 28 additions & 20 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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 _ =>
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -2359,18 +2367,18 @@ 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)
val noDefaultsCount = noDefaults.length
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
}
Expand Down

0 comments on commit c5852e4

Please sign in to comment.