Skip to content

Commit aeb7f43

Browse files
committed
Fix #4561: Refactor overloading resolution
refactor overloading resolution to make handling of type arguments more regular and systematic.
1 parent 0b4871d commit aeb7f43

File tree

3 files changed

+68
-54
lines changed

3 files changed

+68
-54
lines changed

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 52 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,7 +1544,7 @@ trait Applications extends Compatibility {
15441544
* Two trials: First, without implicits or SAM conversions enabled. Then,
15451545
* if the first finds no eligible candidates, with implicits and SAM conversions enabled.
15461546
*/
1547-
def resolveOverloaded(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] = {
1547+
def resolveOverloaded(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] =
15481548
record("resolveOverloaded")
15491549

15501550
/** Is `alt` a method or polytype whose result type after the first value parameter
@@ -1590,15 +1590,26 @@ trait Applications extends Compatibility {
15901590
case _ => chosen
15911591
}
15921592

1593-
def resolve(alts: List[TermRef]) = {
1594-
var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled))
1595-
if (found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled))
1596-
found = resolveOverloaded(alts, pt, Nil)
1597-
found match {
1593+
def resolve(alts: List[TermRef]): List[TermRef] =
1594+
pt match
1595+
case pt: FunProto =>
1596+
if pt.isUsingApply then
1597+
val alts0 = alts.filterConserve { alt =>
1598+
val mt = alt.widen.stripPoly
1599+
mt.isImplicitMethod || mt.isContextualMethod
1600+
}
1601+
if alts0 ne alts then return resolve(alts0)
1602+
else if alts.exists(_.widen.stripPoly.isContextualMethod) then
1603+
return resolveMapped(alts, alt => stripImplicit(alt.widen), pt)
1604+
case _ =>
1605+
1606+
var found = resolveOverloaded1(alts, pt)(using ctx.retractMode(Mode.ImplicitsEnabled))
1607+
if found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled) then
1608+
found = resolveOverloaded1(alts, pt)
1609+
found match
15981610
case alt :: Nil => adaptByResult(alt, alts) :: Nil
15991611
case _ => found
1600-
}
1601-
}
1612+
end resolve
16021613

16031614
/** Try an apply method, if
16041615
* - the result is applied to value arguments and alternative is not a method, or
@@ -1634,15 +1645,16 @@ trait Applications extends Compatibility {
16341645
resolve(expanded).map(retract)
16351646
}
16361647
else resolve(alts)
1637-
}
1648+
end resolveOverloaded
16381649

16391650
/** This private version of `resolveOverloaded` does the bulk of the work of
1640-
* overloading resolution, but does not do result adaptation. It might be
1641-
* called twice from the public `resolveOverloaded` method, once with
1651+
* overloading resolution, but does neither result adaptation nor apply insertion.
1652+
* It might be called twice from the public `resolveOverloaded` method, once with
16421653
* implicits and SAM conversions enabled, and once without.
16431654
*/
1644-
private def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type])(implicit ctx: Context): List[TermRef] =
1645-
record("resolveOverloaded/2")
1655+
private def resolveOverloaded1(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] =
1656+
trace(i"resolve over $alts%, %, pt = $pt", typr, show = true) {
1657+
record("resolveOverloaded1")
16461658

16471659
def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty
16481660

@@ -1707,22 +1719,6 @@ trait Applications extends Compatibility {
17071719
case _ => arg
17081720
end normArg
17091721

1710-
/** Resolve overloading by mapping to a different problem where each alternative's
1711-
* type is mapped with `f`, alternatives with non-existing types are dropped, and the
1712-
* expected type is `pt`. Map the results back to the original alternatives.
1713-
*/
1714-
def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type): List[TermRef] =
1715-
val reverseMapping = alts.flatMap { alt =>
1716-
val t = f(alt)
1717-
if t.exists then
1718-
Some((TermRef(NoPrefix, alt.symbol.asTerm.copy(info = t)), alt))
1719-
else
1720-
None
1721-
}
1722-
val mapped = reverseMapping.map(_._1)
1723-
overload.println(i"resolve mapped: $mapped")
1724-
resolveOverloaded(mapped, pt, targs).map(reverseMapping.toMap)
1725-
17261722
val candidates = pt match {
17271723
case pt @ FunProto(args, resultType) =>
17281724
val numArgs = args.length
@@ -1753,25 +1749,16 @@ trait Applications extends Compatibility {
17531749

17541750
def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] = {
17551751
val alts2 = alts.filter(alt =>
1756-
isDirectlyApplicableMethodRef(alt, targs, args, resultType)
1752+
isDirectlyApplicableMethodRef(alt, Nil, args, resultType)
17571753
)
17581754
if (alts2.isEmpty && !ctx.isAfterTyper)
17591755
alts.filter(alt =>
1760-
isApplicableMethodRef(alt, targs, args, resultType, keepConstraint = false)
1756+
isApplicableMethodRef(alt, Nil, args, resultType, keepConstraint = false)
17611757
)
17621758
else
17631759
alts2
17641760
}
17651761

1766-
if pt.isUsingApply then
1767-
val alts0 = alts.filterConserve { alt =>
1768-
val mt = alt.widen.stripPoly
1769-
mt.isImplicitMethod || mt.isContextualMethod
1770-
}
1771-
if alts0 ne alts then return resolveOverloaded(alts0, pt, targs)
1772-
else if alts.exists(_.widen.stripPoly.isContextualMethod) then
1773-
return resolveMapped(alts, alt => stripImplicit(alt.widen), pt)
1774-
17751762
val alts1 = narrowBySize(alts)
17761763
//ctx.log(i"narrowed by size: ${alts1.map(_.symbol.showDcl)}%, %")
17771764
if isDetermined(alts1) then alts1
@@ -1783,9 +1770,10 @@ trait Applications extends Compatibility {
17831770
pretypeArgs(alts2, pt)
17841771
narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType)
17851772

1786-
case pt @ PolyProto(targs1, pt1) if targs.isEmpty =>
1787-
val alts1 = alts.filter(pt.isMatchedBy(_))
1788-
resolveOverloaded(alts1, pt1, targs1.tpes)
1773+
case pt @ PolyProto(targs1, pt1) =>
1774+
val alts1 = alts.filter(pt.canInstantiate)
1775+
if isDetermined(alts1) then alts1
1776+
else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1)
17891777

17901778
case defn.FunctionOf(args, resultType, _, _) =>
17911779
narrowByTypes(alts, args, resultType)
@@ -1842,21 +1830,38 @@ trait Applications extends Compatibility {
18421830
if noCurriedCount == 1 then
18431831
noCurried
18441832
else if noCurriedCount > 1 && noCurriedCount < alts.length then
1845-
resolveOverloaded(noCurried, pt, targs)
1833+
resolveOverloaded1(noCurried, pt)
18461834
else
18471835
// prefer alternatves that match without default parameters
18481836
val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
18491837
val noDefaultsCount = noDefaults.length
18501838
if noDefaultsCount == 1 then
18511839
noDefaults
18521840
else if noDefaultsCount > 1 && noDefaultsCount < alts.length then
1853-
resolveOverloaded(noDefaults, pt, targs)
1841+
resolveOverloaded1(noDefaults, pt)
18541842
else if deepPt ne pt then
18551843
// try again with a deeper known expected type
1856-
resolveOverloaded(alts, deepPt, targs)
1844+
resolveOverloaded1(alts, deepPt)
18571845
else
18581846
candidates
1859-
end resolveOverloaded
1847+
}
1848+
end resolveOverloaded1
1849+
1850+
/** Resolve overloading by mapping to a different problem where each alternative's
1851+
* type is mapped with `f`, alternatives with non-existing types are dropped, and the
1852+
* expected type is `pt`. Map the results back to the original alternatives.
1853+
*/
1854+
def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type)(using Context): List[TermRef] =
1855+
val reverseMapping = alts.flatMap { alt =>
1856+
val t = f(alt)
1857+
if t.exists then
1858+
Some((TermRef(NoPrefix, alt.symbol.asTerm.copy(info = t)), alt))
1859+
else
1860+
None
1861+
}
1862+
val mapped = reverseMapping.map(_._1)
1863+
overload.println(i"resolve mapped: $mapped")
1864+
resolveOverloaded(mapped, pt).map(reverseMapping.toMap)
18601865

18611866
/** Try to typecheck any arguments in `pt` that are function values missing a
18621867
* parameter type. If the formal parameter types corresponding to a closure argument

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -454,13 +454,12 @@ object ProtoTypes {
454454

455455
override def resultType(implicit ctx: Context): Type = resType
456456

457-
override def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
458-
def isInstantiatable(tp: Type) = tp.widen match {
459-
case tp: PolyType => tp.paramNames.length == targs.length
460-
case _ => false
461-
}
462-
isInstantiatable(tp) || tp.member(nme.apply).hasAltWith(d => isInstantiatable(d.info))
463-
}
457+
def canInstantiate(tp: Type)(using Context) = tp.widen match
458+
case tp: PolyType => tp.paramNames.length == targs.length
459+
case _ => false
460+
461+
override def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean =
462+
canInstantiate(tp) || tp.member(nme.apply).hasAltWith(d => canInstantiate(d.info))
464463

465464
def derivedPolyProto(targs: List[Tree], resultType: Type): PolyProto =
466465
if ((targs eq this.targs) && (resType eq this.resType)) this

tests/pos/i4561.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
object abc {
2+
trait Test0
3+
trait Test1 { def apply(f: Int => Int): Unit }
4+
5+
def v: Test0 = ???
6+
def v[T]: Test1 = ???
7+
//def v[T](x: String): Test0 = ???
8+
9+
v[Int]{ v => v }
10+
}

0 commit comments

Comments
 (0)