Skip to content

Commit 8c35a6e

Browse files
committed
Better scheme for handling overloaded applys
- expand before resolving, retract afterwards. - that way, we avoid a lot of fragile and incomplete logic in the resolver.
1 parent 17fa245 commit 8c35a6e

File tree

4 files changed

+118
-123
lines changed

4 files changed

+118
-123
lines changed

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

Lines changed: 98 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
11691169
if (keepConstraint) isApp else ctx.test(implicit ctx => isApp)
11701170
}
11711171

1172+
/** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views?
1173+
* @param resultType The expected result type of the application
1174+
*/
1175+
def isDirectlyApplicableMethodRef(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
1176+
ctx.test(implicit ctx => new ApplicableToTreesDirectly(methRef, targs, args, resultType).success)
1177+
11721178
/** Is given method reference applicable to argument types `args`?
11731179
* @param resultType The expected result type of the application
11741180
*/
@@ -1192,27 +1198,15 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
11921198
isApplicableMethodRef(_, args, resultType)
11931199
}
11941200

1195-
/** Is given method type applicable to type arguments `targs` and argument trees `args` without inferring views,
1196-
* possibly after inserting an `apply`?
1197-
* @param resultType The expected result type of the application
1198-
*/
1199-
def isDirectlyApplicableType(tp: Type, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
1200-
onMethod(tp, targs.nonEmpty || args.nonEmpty) { methRef =>
1201-
ctx.test(implicit ctx => new ApplicableToTreesDirectly(methRef, targs, args, resultType).success)
1202-
}
1203-
12041201
private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(implicit ctx: Context): Boolean = tp match {
12051202
case methRef: TermRef if methRef.widenSingleton.isInstanceOf[MethodicType] =>
12061203
p(methRef)
12071204
case mt: MethodicType =>
12081205
p(mt.narrow)
12091206
case _ =>
1210-
followApply && hasApplyWith(tp)(p)
1207+
followApply && tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d)))
12111208
}
12121209

1213-
private def hasApplyWith(tp: Type)(p: TermRef => Boolean)(implicit ctx: Context): Boolean =
1214-
tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d)))
1215-
12161210
/** Does `tp` have an extension method named `name` with this-argument `argType` and
12171211
* result matching `resultType`?
12181212
*/
@@ -1257,10 +1251,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
12571251
* If that tournament yields a draw, a tiebreak is applied where
12581252
* an alternative that takes more implicit parameters wins over one
12591253
* that takes fewer.
1260-
*
1261-
* @param followApply if true consider `apply` members when comparing with a method reference
12621254
*/
1263-
def compare(alt1: TermRef, alt2: TermRef, followApply: Boolean)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) {
1255+
def compare(alt1: TermRef, alt2: TermRef)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) {
12641256

12651257
assert(alt1 ne alt2)
12661258

@@ -1275,78 +1267,44 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
12751267
* assumption that for i = 1,...,n each ai is an abstract type name bounded
12761268
* from below by Li and from above by Ui.
12771269
* 3. A member of any other type `tp1` is:
1278-
* a. always as specific as a method or a polymorphic method
1270+
* a. always as specific as a method or a polymorphic method.
12791271
* b. as specific as a member of any other type `tp2` if `tp1` is compatible
12801272
* with `tp2`.
1281-
*
1282-
* If followApply is true, and one of the alternatives is not a method, we test instead
1283-
* whether isAsSpecific is true for all apply methods in that alternative.
1284-
* Note that this is errs on the side of not being comparable in the following case:
1285-
*
1286-
* Alternatives
1287-
*
1288-
* val x: A
1289-
* def x(y: S): B
1290-
*
1291-
* where A has members
1292-
*
1293-
* def apply(y: S1): B1
1294-
* def apply(y: S2): B2
1295-
*
1296-
* and only one of the two `apply` methods (say the first) is applicable. If the first `apply`
1297-
* is as specific as the method `x`, but not the second, we still judge the two `x`'s
1298-
* as incomparable. If we had used an "exists an apply method" instead of the "forall"
1299-
* then value `x` would be picked over method `x` instead. On the other hand, if
1300-
* the first `apply` was NOT applicable but the second one was, then we would still pick
1301-
* pick value `x` over method `x` even though the applicable second apply method was is not
1302-
* more specific than the `x` method. So in going with "forall" instead of "exists" we
1303-
* err on the side of treating alternatives as incomparable, instead of potentially
1304-
* picking the wrong one.
13051273
*/
1306-
def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type, followApply: Boolean): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) {
1307-
if (followApply) {
1308-
val isMethod1 = tp1.stripPoly.isInstanceOf[MethodType]
1309-
val isMethod2 = tp2.stripPoly.isInstanceOf[MethodType]
1310-
if (!isMethod1 && isMethod2)
1311-
return !hasApplyWith(tp1)(alt1app => !isAsSpecific(alt1app, alt1app.widen, alt2, tp2, false))
1312-
if (!isMethod2 && isMethod1)
1313-
return !hasApplyWith(tp2)(alt2app => !isAsSpecific(alt1, tp1, alt2app, alt2app.widen, false))
1314-
}
1315-
tp1 match {
1316-
case tp1: MethodType => // (1)
1317-
val formals1 =
1318-
if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle)
1319-
else tp1.paramInfos
1320-
val isAsSpecificMethod = isApplicableMethodRef(alt2, formals1, WildcardType)
1321-
isAsSpecificMethod || tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType]
1322-
case tp1: PolyType => // (2)
1323-
val nestedCtx = ctx.fresh.setExploreTyperState()
1324-
1325-
{
1326-
implicit val ctx = nestedCtx
1327-
1328-
// Fully define the PolyType parameters so that the infos of the
1329-
// tparams created below never contain TypeRefs whose underling types
1330-
// contain uninstantiated TypeVars, this could lead to cycles in
1331-
// `isSubType` as a TypeVar might get constrained by a TypeRef it's
1332-
// part of.
1333-
val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType)
1334-
fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span)
1335-
1336-
val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_))
1337-
isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2, followApply)
1338-
}
1339-
case _ => // (3)
1340-
tp2 match {
1341-
case tp2: MethodType => true
1342-
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true
1343-
case tp2: PolyType => // (3b)
1344-
ctx.test(implicit ctx => isAsSpecificValueType(tp1, constrained(tp2).resultType))
1345-
case _ => // (3b)
1346-
isAsSpecificValueType(tp1, tp2)
1347-
}
1348-
}
1349-
}
1274+
def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) { tp1 match {
1275+
case tp1: MethodType => // (1)
1276+
val formals1 =
1277+
if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle)
1278+
else tp1.paramInfos
1279+
isApplicableMethodRef(alt2, formals1, WildcardType) ||
1280+
tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType]
1281+
case tp1: PolyType => // (2)
1282+
val nestedCtx = ctx.fresh.setExploreTyperState()
1283+
1284+
{
1285+
implicit val ctx = nestedCtx
1286+
1287+
// Fully define the PolyType parameters so that the infos of the
1288+
// tparams created below never contain TypeRefs whose underling types
1289+
// contain uninstantiated TypeVars, this could lead to cycles in
1290+
// `isSubType` as a TypeVar might get constrained by a TypeRef it's
1291+
// part of.
1292+
val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType)
1293+
fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span)
1294+
1295+
val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_))
1296+
isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
1297+
}
1298+
case _ => // (3)
1299+
tp2 match {
1300+
case tp2: MethodType => true // (3a)
1301+
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
1302+
case tp2: PolyType => // (3b)
1303+
ctx.test(implicit ctx => isAsSpecificValueType(tp1, constrained(tp2).resultType))
1304+
case _ => // (3b)
1305+
isAsSpecificValueType(tp1, tp2)
1306+
}
1307+
}}
13501308

13511309
/** Test whether value type `tp1` is as specific as value type `tp2`.
13521310
* Let's abbreviate this to `tp1 <:s tp2`.
@@ -1437,8 +1395,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14371395

14381396
def compareWithTypes(tp1: Type, tp2: Type) = {
14391397
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
1440-
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2, followApply)
1441-
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1, followApply)
1398+
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
1399+
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
14421400

14431401
overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
14441402
if (ownerScore == 1)
@@ -1465,20 +1423,20 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14651423
else compareWithTypes(fullType1, fullType2) // continue by comparing implicits parameters
14661424
}}
14671425

1468-
def narrowMostSpecific(alts: List[TermRef], followApply: Boolean)(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") {
1426+
def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") {
14691427
alts match {
14701428
case Nil => alts
14711429
case _ :: Nil => alts
14721430
case alt1 :: alt2 :: Nil =>
1473-
compare(alt1, alt2, followApply) match {
1431+
compare(alt1, alt2) match {
14741432
case 1 => alt1 :: Nil
14751433
case -1 => alt2 :: Nil
14761434
case 0 => alts
14771435
}
14781436
case alt :: alts1 =>
14791437
def survivors(previous: List[TermRef], alts: List[TermRef]): List[TermRef] = alts match {
14801438
case alt :: alts1 =>
1481-
compare(previous.head, alt, followApply) match {
1439+
compare(previous.head, alt) match {
14821440
case 1 => survivors(previous, alts1)
14831441
case -1 => survivors(alt :: previous.tail, alts1)
14841442
case 0 => survivors(alt :: previous, alts1)
@@ -1488,7 +1446,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14881446
val best :: rest = survivors(alt :: Nil, alts1)
14891447
def asGood(alts: List[TermRef]): List[TermRef] = alts match {
14901448
case alt :: alts1 =>
1491-
if (compare(alt, best, followApply) < 0) asGood(alts1) else alt :: asGood(alts1)
1449+
if (compare(alt, best) < 0) asGood(alts1) else alt :: asGood(alts1)
14921450
case nil =>
14931451
Nil
14941452
}
@@ -1507,21 +1465,17 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
15071465
/** Is `alt` a method or polytype whose result type after the first value parameter
15081466
* section conforms to the expected type `resultType`? If `resultType`
15091467
* is a `IgnoredProto`, pick the underlying type instead.
1510-
* If `alt`does not have method or poly type, and `followApply` is true, consider
1511-
* all apply members instead. In all other cases return `true`.
15121468
*/
1513-
def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = {
1514-
def recur(altType: Type, followApply: Boolean): Boolean = resultType.revealIgnored match {
1469+
def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean =
1470+
resultType.revealIgnored match {
15151471
case resultType: ValueType =>
15161472
altType.widen match {
1517-
case tp: PolyType => recur(constrained(tp).resultType, followApply)
1473+
case tp: PolyType => resultConforms(altSym, constrained(tp).resultType, resultType)
15181474
case tp: MethodType => constrainResult(altSym, tp.resultType, resultType)
1519-
case _ => !followApply || hasApplyWith(altType)(recur(_, followApply = false))
1475+
case _ => true
15201476
}
15211477
case _ => true
15221478
}
1523-
recur(altType, followApply = true)
1524-
}
15251479

15261480
/** If the `chosen` alternative has a result type incompatible with the expected result
15271481
* type `pt`, run overloading resolution again on all alternatives that do match `pt`.
@@ -1535,7 +1489,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
15351489
* probability of pruning the search. result type comparisons are neither cheap nor
15361490
* do they prune much, on average.
15371491
*/
1538-
def adaptByResult(chosen: TermRef) = pt match {
1492+
def adaptByResult(chosen: TermRef, alts: List[TermRef]) = pt match {
15391493
case pt: FunProto if !ctx.test(implicit ctx => resultConforms(chosen.symbol, chosen, pt.resultType)) =>
15401494
val conformingAlts = alts.filter(alt =>
15411495
(alt ne chosen) && ctx.test(implicit ctx => resultConforms(alt.symbol, alt, pt.resultType)))
@@ -1551,13 +1505,42 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
15511505
case _ => chosen
15521506
}
15531507

1554-
var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled))
1555-
if (found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled))
1556-
found = resolveOverloaded(alts, pt, Nil)
1557-
found match {
1558-
case alt :: Nil => adaptByResult(alt) :: Nil
1559-
case _ => found
1508+
def resolve(alts: List[TermRef]) = {
1509+
var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled))
1510+
if (found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled))
1511+
found = resolveOverloaded(alts, pt, Nil)
1512+
found match {
1513+
case alt :: Nil => adaptByResult(alt, alts) :: Nil
1514+
case _ => found
1515+
}
1516+
}
1517+
1518+
/** Try an apply method, if
1519+
* - the result is applied to value arguments and alternative is not a method, or
1520+
* - the result is applied to type arguments and alternatuve is not polymorphic
1521+
*/
1522+
val tryApply: Type => Boolean = alt => pt match {
1523+
case pt: FunProto => !alt.widen.stripPoly.isInstanceOf[MethodType]
1524+
case pt: PolyProto => !alt.widen.isInstanceOf[PolyType]
1525+
case _ => false
15601526
}
1527+
1528+
/** Replace each alternative by its apply members where necesssary */
1529+
def applyMembers(alt: TermRef): List[TermRef] =
1530+
if (tryApply(alt)) alt.member(nme.apply).alternatives.map(TermRef(alt, nme.apply, _))
1531+
else alt :: Nil
1532+
1533+
/** Fall back from an apply method to its original alternative */
1534+
def retract(alt: TermRef): TermRef =
1535+
if (alt.name == nme.apply && !alts.contains(alt))
1536+
alts.find(_.symbol == alt.prefix.termSymbol).getOrElse(alt)
1537+
else alt
1538+
1539+
if (alts.exists(tryApply)) {
1540+
val expanded = alts.flatMap(applyMembers)
1541+
resolve(expanded).map(retract)
1542+
}
1543+
else resolve(alts)
15611544
}
15621545

15631546
/** This private version of `resolveOverloaded` does the bulk of the work of
@@ -1591,21 +1574,17 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
15911574
}
15921575

15931576
def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
1594-
alts filter (isApplicableType(_, argTypes, resultType))
1595-
1596-
val numArgs = pt match {
1597-
case pt @ FunProto(args, resultType) => args.length
1598-
case _ => 0
1599-
}
1577+
alts filter (isApplicableMethodRef(_, argTypes, resultType))
16001578

16011579
val candidates = pt match {
16021580
case pt @ FunProto(args, resultType) =>
1581+
val numArgs = args.length
16031582
val normArgs = args.mapConserve {
16041583
case Block(Nil, expr) => expr
16051584
case x => x
16061585
}
16071586

1608-
def sizeFits(alt: TermRef, followApply: Boolean): Boolean = alt.widen.stripPoly match {
1587+
def sizeFits(alt: TermRef): Boolean = alt.widen.stripPoly match {
16091588
case tp: MethodType =>
16101589
val ptypes = tp.paramInfos
16111590
val numParams = ptypes.length
@@ -1615,13 +1594,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
16151594
else if (numParams < numArgs) isVarArgs
16161595
else if (numParams > numArgs + 1) hasDefault
16171596
else isVarArgs || hasDefault
1618-
case tp =>
1619-
numArgs == 0 ||
1620-
followApply && hasApplyWith(tp)(sizeFits(_, followApply = false))
1597+
case _ =>
1598+
numArgs == 0
16211599
}
16221600

16231601
def narrowBySize(alts: List[TermRef]): List[TermRef] =
1624-
alts.filter(sizeFits(_, followApply = true))
1602+
alts.filter(sizeFits(_))
16251603

16261604
def narrowByShapes(alts: List[TermRef]): List[TermRef] = {
16271605
if (normArgs exists untpd.isFunctionWithUnknownParamType)
@@ -1633,11 +1611,11 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
16331611

16341612
def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] = {
16351613
val alts2 = alts.filter(alt =>
1636-
isDirectlyApplicableType(alt, targs, args, resultType)
1614+
isDirectlyApplicableMethodRef(alt, targs, args, resultType)
16371615
)
16381616
if (alts2.isEmpty && !ctx.isAfterTyper)
16391617
alts.filter(alt =>
1640-
isApplicableType(alt, targs, args, resultType, keepConstraint = false)
1618+
isApplicableMethodRef(alt, targs, args, resultType, keepConstraint = false)
16411619
)
16421620
else
16431621
alts2
@@ -1708,7 +1686,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
17081686
candidates.flatMap(cloneCandidate)
17091687
}
17101688

1711-
val found = narrowMostSpecific(candidates, followApply = numArgs != 0)
1689+
val found = narrowMostSpecific(candidates)
17121690
if (found.length <= 1) found
17131691
else pt match {
17141692
case pt @ FunProto(_, resType: FunProto) =>
@@ -1880,4 +1858,3 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
18801858
app
18811859
}
18821860
}
1883-

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,7 @@ trait Implicits { self: Typer =>
11421142
def compareCandidate(prev: SearchSuccess, ref: TermRef, level: Int): Int =
11431143
if (prev.ref eq ref) 0
11441144
else if (prev.level != level) prev.level - level
1145-
else nestedContext().test(implicit ctx => compare(prev.ref, ref, followApply = false))
1145+
else nestedContext().test(implicit ctx => compare(prev.ref, ref))
11461146

11471147
/** If `alt1` is also a search success, try to disambiguate as follows:
11481148
* - If alt2 is preferred over alt1, pick alt2, otherwise return an

tests/pos/i2774.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ object Test {
66
val i1: Int = a given (new T{})
77
implied for T = new T {}
88
val i2: Int = a
9+
val i3: Int = a2
910

10-
def a given (t: T) given (q: Q): Int = 1
11+
def a2 given (t: T) given (q: Q): Int = 1
1112

1213
}

0 commit comments

Comments
 (0)