Skip to content

Commit 9e1f8a2

Browse files
Merge pull request #8484 from dotty-staging/fix-#4561
Fix #4561: Refactor overloading resolution
2 parents 95eeacc + c075f8b commit 9e1f8a2

File tree

4 files changed

+95
-73
lines changed

4 files changed

+95
-73
lines changed

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

Lines changed: 67 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -650,8 +650,8 @@ trait Applications extends Compatibility {
650650
/** Subclass of Application for applicability tests with type arguments and value
651651
* argument trees.
652652
*/
653-
class ApplicableToTrees(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context)
654-
extends TestApplication(methRef, methRef.widen.appliedTo(targs), args, resultType) {
653+
class ApplicableToTrees(methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context)
654+
extends TestApplication(methRef, methRef.widen, args, resultType) {
655655
def argType(arg: Tree, formal: Type): Type = normalize(arg.tpe, formal)
656656
def treeToArg(arg: Tree): Tree = arg
657657
def isVarArg(arg: Tree): Boolean = tpd.isWildcardStarArg(arg)
@@ -662,7 +662,8 @@ trait Applications extends Compatibility {
662662
/** Subclass of Application for applicability tests with type arguments and value
663663
* argument trees.
664664
*/
665-
class ApplicableToTreesDirectly(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context) extends ApplicableToTrees(methRef, targs, args, resultType)(ctx) {
665+
class ApplicableToTreesDirectly(methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context)
666+
extends ApplicableToTrees(methRef, args, resultType)(ctx) {
666667
override def argOK(arg: TypedArg, formal: Type): Boolean =
667668
argType(arg, formal) relaxed_<:< formal.widenExpr
668669
}
@@ -1240,34 +1241,33 @@ trait Applications extends Compatibility {
12401241
def typedUnApply(tree: untpd.UnApply, selType: Type)(implicit ctx: Context): UnApply =
12411242
throw new UnsupportedOperationException("cannot type check an UnApply node")
12421243

1243-
/** Is given method reference applicable to type arguments `targs` and argument trees `args`?
1244+
/** Is given method reference applicable to argument trees `args`?
12441245
* @param resultType The expected result type of the application
12451246
*/
1246-
def isApplicableMethodRef(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
1247+
def isApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
12471248
def isApp(implicit ctx: Context): Boolean =
1248-
new ApplicableToTrees(methRef, targs, args, resultType).success
1249+
new ApplicableToTrees(methRef, args, resultType).success
12491250
if (keepConstraint) isApp else ctx.test(isApp)
12501251
}
12511252

1252-
/** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views?
1253+
/** Is given method reference applicable to argument trees `args` without inferring views?
12531254
* @param resultType The expected result type of the application
12541255
*/
1255-
def isDirectlyApplicableMethodRef(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
1256-
ctx.test(new ApplicableToTreesDirectly(methRef, targs, args, resultType).success)
1256+
def isDirectlyApplicableMethodRef(methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
1257+
ctx.test(new ApplicableToTreesDirectly(methRef, args, resultType).success)
12571258

12581259
/** Is given method reference applicable to argument types `args`?
12591260
* @param resultType The expected result type of the application
12601261
*/
12611262
def isApplicableMethodRef(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean =
12621263
ctx.test(new ApplicableToTypes(methRef, args, resultType).success)
12631264

1264-
/** Is given type applicable to type arguments `targs` and argument trees `args`,
1265-
* possibly after inserting an `apply`?
1265+
/** Is given type applicable to argument trees `args`, possibly after inserting an `apply`?
12661266
* @param resultType The expected result type of the application
12671267
*/
1268-
def isApplicableType(tp: Type, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean =
1269-
onMethod(tp, targs.nonEmpty || args.nonEmpty) {
1270-
isApplicableMethodRef(_, targs, args, resultType, keepConstraint)
1268+
def isApplicableType(tp: Type, args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean =
1269+
onMethod(tp, args.nonEmpty) {
1270+
isApplicableMethodRef(_, args, resultType, keepConstraint)
12711271
}
12721272

12731273
/** Is given type applicable to argument types `args`, possibly after inserting an `apply`?
@@ -1538,13 +1538,11 @@ trait Applications extends Compatibility {
15381538
}
15391539
}
15401540

1541-
/** Resolve overloaded alternative `alts`, given expected type `pt` and
1542-
* possibly also type argument `targs` that need to be applied to each alternative
1543-
* to form the method type.
1541+
/** Resolve overloaded alternative `alts`, given expected type `pt`.
15441542
* Two trials: First, without implicits or SAM conversions enabled. Then,
15451543
* if the first finds no eligible candidates, with implicits and SAM conversions enabled.
15461544
*/
1547-
def resolveOverloaded(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] = {
1545+
def resolveOverloaded(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] =
15481546
record("resolveOverloaded")
15491547

15501548
/** Is `alt` a method or polytype whose result type after the first value parameter
@@ -1590,15 +1588,26 @@ trait Applications extends Compatibility {
15901588
case _ => chosen
15911589
}
15921590

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

16031612
/** Try an apply method, if
16041613
* - the result is applied to value arguments and alternative is not a method, or
@@ -1634,15 +1643,16 @@ trait Applications extends Compatibility {
16341643
resolve(expanded).map(retract)
16351644
}
16361645
else resolve(alts)
1637-
}
1646+
end resolveOverloaded
16381647

16391648
/** 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
1649+
* overloading resolution, but does neither result adaptation nor apply insertion.
1650+
* It might be called twice from the public `resolveOverloaded` method, once with
16421651
* implicits and SAM conversions enabled, and once without.
16431652
*/
1644-
private def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type])(implicit ctx: Context): List[TermRef] =
1645-
record("resolveOverloaded/2")
1653+
private def resolveOverloaded1(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] =
1654+
trace(i"resolve over $alts%, %, pt = $pt", typr, show = true) {
1655+
record("resolveOverloaded1")
16461656

16471657
def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty
16481658

@@ -1707,22 +1717,6 @@ trait Applications extends Compatibility {
17071717
case _ => arg
17081718
end normArg
17091719

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-
17261720
val candidates = pt match {
17271721
case pt @ FunProto(args, resultType) =>
17281722
val numArgs = args.length
@@ -1753,25 +1747,16 @@ trait Applications extends Compatibility {
17531747

17541748
def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] = {
17551749
val alts2 = alts.filter(alt =>
1756-
isDirectlyApplicableMethodRef(alt, targs, args, resultType)
1750+
isDirectlyApplicableMethodRef(alt, args, resultType)
17571751
)
17581752
if (alts2.isEmpty && !ctx.isAfterTyper)
17591753
alts.filter(alt =>
1760-
isApplicableMethodRef(alt, targs, args, resultType, keepConstraint = false)
1754+
isApplicableMethodRef(alt, args, resultType, keepConstraint = false)
17611755
)
17621756
else
17631757
alts2
17641758
}
17651759

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-
17751760
val alts1 = narrowBySize(alts)
17761761
//ctx.log(i"narrowed by size: ${alts1.map(_.symbol.showDcl)}%, %")
17771762
if isDetermined(alts1) then alts1
@@ -1783,9 +1768,10 @@ trait Applications extends Compatibility {
17831768
pretypeArgs(alts2, pt)
17841769
narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType)
17851770

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

17901776
case defn.FunctionOf(args, resultType, _, _) =>
17911777
narrowByTypes(alts, args, resultType)
@@ -1842,21 +1828,38 @@ trait Applications extends Compatibility {
18421828
if noCurriedCount == 1 then
18431829
noCurried
18441830
else if noCurriedCount > 1 && noCurriedCount < alts.length then
1845-
resolveOverloaded(noCurried, pt, targs)
1831+
resolveOverloaded1(noCurried, pt)
18461832
else
18471833
// prefer alternatves that match without default parameters
18481834
val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
18491835
val noDefaultsCount = noDefaults.length
18501836
if noDefaultsCount == 1 then
18511837
noDefaults
18521838
else if noDefaultsCount > 1 && noDefaultsCount < alts.length then
1853-
resolveOverloaded(noDefaults, pt, targs)
1839+
resolveOverloaded1(noDefaults, pt)
18541840
else if deepPt ne pt then
18551841
// try again with a deeper known expected type
1856-
resolveOverloaded(alts, deepPt, targs)
1842+
resolveOverloaded1(alts, deepPt)
18571843
else
18581844
candidates
1859-
end resolveOverloaded
1845+
}
1846+
end resolveOverloaded1
1847+
1848+
/** Resolve overloading by mapping to a different problem where each alternative's
1849+
* type is mapped with `f`, alternatives with non-existing types are dropped, and the
1850+
* expected type is `pt`. Map the results back to the original alternatives.
1851+
*/
1852+
def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type)(using Context): List[TermRef] =
1853+
val reverseMapping = alts.flatMap { alt =>
1854+
val t = f(alt)
1855+
if t.exists then
1856+
Some((TermRef(NoPrefix, alt.symbol.asTerm.copy(info = t)), alt))
1857+
else
1858+
None
1859+
}
1860+
val mapped = reverseMapping.map(_._1)
1861+
overload.println(i"resolve mapped: $mapped")
1862+
resolveOverloaded(mapped, pt).map(reverseMapping.toMap)
18601863

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

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ object ProtoTypes {
260260
def isPoly(tree: Tree) = tree.tpe.widenSingleton.isInstanceOf[PolyType]
261261
// See remark in normalizedCompatible for why we can't keep the constraint
262262
// if one of the arguments has a PolyType.
263-
typer.isApplicableType(tp, Nil, args, resultType, keepConstraint && !args.exists(isPoly))
263+
typer.isApplicableType(tp, args, resultType, keepConstraint && !args.exists(isPoly))
264264
}
265265

266266
def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer): FunProto =
@@ -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

language-server/test/dotty/tools/languageserver/SignatureHelpTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ class SignatureHelpTest {
159159
}""".withSource
160160
.signatureHelp(m1, List(sig0, sig1), None, 0)
161161
.signatureHelp(m2, List(sig0, sig1), None, 0)
162-
.signatureHelp(m3, List(), Some(1), 1) // TODO: investigate we do not get help at $m3
162+
.signatureHelp(m3, List(sig0, sig1), Some(1), 1)
163163
}
164164

165165
@Test def multipleParameterLists: Unit = {

tests/pos/i4561.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
object abc:
2+
trait Test0
3+
trait Test1[T]:
4+
def apply(f: T => T): Unit
5+
def apply(s: String): Unit
6+
7+
def v: Test0 = ???
8+
def v[T]: Test1[Int] = ???
9+
10+
def w: Test0 = ???
11+
def w[T]: Test1[T] = ???
12+
def w[T](x: String): Test1[T] = ???
13+
14+
v[Any](x => x)
15+
val v1: Test0 = v
16+
v(x => x + 1)
17+
18+
w[Int](_ + 1)
19+
// w(_ + 1) // error: `+` is not a member of Any
20+
w[Int]("x")(_ + 1)

0 commit comments

Comments
 (0)