Skip to content

Commit 6253125

Browse files
committed
Changes to overloading
Fix #1381: Overloading is now changed so that we first try without implicit searches. Only if that leaves no applicable alternatives we try again with implicit search turned on. This also fixes test case t2660, which got moved from neg to pos.
1 parent 409c6c3 commit 6253125

File tree

3 files changed

+73
-76
lines changed

3 files changed

+73
-76
lines changed

src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -854,8 +854,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
854854
var allAlts = denot.alternatives
855855
.map(_.termRef).filter(tr => typeParamCount(tr) == targs.length)
856856
if (targs.isEmpty) allAlts = allAlts.filterNot(_.widen.isInstanceOf[PolyType])
857-
val alternatives =
858-
ctx.typer.resolveOverloaded(allAlts, proto, Nil)
857+
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
859858
assert(alternatives.size == 1,
860859
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
861860
i"$method on ${receiver.tpe.widenDealias} with targs: $targs%, %; args: $args%, % of types ${args.tpes}%, %; expectedType: $expectedType." +

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

Lines changed: 70 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,31 +1039,7 @@ trait Applications extends Compatibility { self: Typer =>
10391039
* to form the method type.
10401040
* todo: use techniques like for implicits to pick candidates quickly?
10411041
*/
1042-
def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type] = Nil)(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") {
1043-
1044-
def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty
1045-
1046-
/** The shape of given tree as a type; cannot handle named arguments. */
1047-
def typeShape(tree: untpd.Tree): Type = tree match {
1048-
case untpd.Function(args, body) =>
1049-
defn.FunctionOf(args map Function.const(defn.AnyType), typeShape(body))
1050-
case _ =>
1051-
defn.NothingType
1052-
}
1053-
1054-
/** The shape of given tree as a type; is more expensive than
1055-
* typeShape but can can handle named arguments.
1056-
*/
1057-
def treeShape(tree: untpd.Tree): Tree = tree match {
1058-
case NamedArg(name, arg) =>
1059-
val argShape = treeShape(arg)
1060-
cpy.NamedArg(tree)(name, argShape).withType(argShape.tpe)
1061-
case _ =>
1062-
dummyTreeOfType(typeShape(tree))
1063-
}
1064-
1065-
def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
1066-
alts filter (isApplicable(_, argTypes, resultType))
1042+
def resolveOverloaded(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") {
10671043

10681044
/** Is `alt` a method or polytype whose result type after the first value parameter
10691045
* section conforms to the expected type `resultType`? If `resultType`
@@ -1092,23 +1068,63 @@ trait Applications extends Compatibility { self: Typer =>
10921068
* probability of pruning the search. result type comparisons are neither cheap nor
10931069
* do they prune much, on average.
10941070
*/
1095-
def adaptByResult(alts: List[TermRef], chosen: TermRef) = {
1096-
def nestedCtx = ctx.fresh.setExploreTyperState
1097-
pt match {
1098-
case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) =>
1099-
alts.filter(alt =>
1100-
(alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match {
1101-
case Nil => chosen
1102-
case alt2 :: Nil => alt2
1103-
case alts2 =>
1104-
resolveOverloaded(alts2, pt) match {
1105-
case alt2 :: Nil => alt2
1106-
case _ => chosen
1107-
}
1108-
}
1109-
case _ => chosen
1110-
}
1071+
def adaptByResult(chosen: TermRef) = {
1072+
def nestedCtx = ctx.fresh.setExploreTyperState
1073+
pt match {
1074+
case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) =>
1075+
alts.filter(alt =>
1076+
(alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match {
1077+
case Nil => chosen
1078+
case alt2 :: Nil => alt2
1079+
case alts2 =>
1080+
resolveOverloaded(alts2, pt) match {
1081+
case alt2 :: Nil => alt2
1082+
case _ => chosen
1083+
}
1084+
}
1085+
case _ => chosen
11111086
}
1087+
}
1088+
1089+
var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled))
1090+
if (found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled))
1091+
found = resolveOverloaded(alts, pt, Nil)
1092+
found match {
1093+
case alt :: Nil => adaptByResult(alt) :: Nil
1094+
case _ => found
1095+
}
1096+
}
1097+
1098+
/** This private version of `resolveOverloaded` does the bulk of the work of
1099+
* overloading resolution, but does not do result adaptation. It might be
1100+
* called twice from the public `resolveOverloaded` method, once with
1101+
* implicits enabled, and once without.
1102+
*/
1103+
private def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type])(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") {
1104+
1105+
def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty
1106+
1107+
/** The shape of given tree as a type; cannot handle named arguments. */
1108+
def typeShape(tree: untpd.Tree): Type = tree match {
1109+
case untpd.Function(args, body) =>
1110+
defn.FunctionOf(args map Function.const(defn.AnyType), typeShape(body))
1111+
case _ =>
1112+
defn.NothingType
1113+
}
1114+
1115+
/** The shape of given tree as a type; is more expensive than
1116+
* typeShape but can can handle named arguments.
1117+
*/
1118+
def treeShape(tree: untpd.Tree): Tree = tree match {
1119+
case NamedArg(name, arg) =>
1120+
val argShape = treeShape(arg)
1121+
cpy.NamedArg(tree)(name, argShape).withType(argShape.tpe)
1122+
case _ =>
1123+
dummyTreeOfType(typeShape(tree))
1124+
}
1125+
1126+
def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
1127+
alts filter (isApplicable(_, argTypes, resultType))
11121128

11131129
val candidates = pt match {
11141130
case pt @ FunProto(args, resultType, _) =>
@@ -1168,33 +1184,27 @@ trait Applications extends Compatibility { self: Typer =>
11681184
}
11691185
}
11701186

1171-
case pt @ PolyProto(targs, pt1) =>
1187+
case pt @ PolyProto(targs1, pt1) =>
1188+
assert(targs.isEmpty)
11721189
val alts1 = alts filter pt.isMatchedBy
1173-
resolveOverloaded(alts1, pt1, targs)
1190+
resolveOverloaded(alts1, pt1, targs1)
11741191

11751192
case defn.FunctionOf(args, resultType) =>
11761193
narrowByTypes(alts, args, resultType)
11771194

11781195
case pt =>
11791196
alts filter (normalizedCompatible(_, pt))
11801197
}
1181-
narrowMostSpecific(candidates) match {
1182-
case Nil => Nil
1183-
case alt :: Nil =>
1184-
adaptByResult(alts, alt) :: Nil
1185-
// why `alts` and not `candidates`? pos/array-overload.scala gives a test case.
1186-
// Here, only the Int-apply is a candidate, but it is not compatible with the result
1187-
// type. Picking the Byte-apply as the only result-compatible solution then forces
1188-
// the arguments (which are constants) to be adapted to Byte. If we had picked
1189-
// `candidates` instead, no solution would have been found.
1190-
case alts =>
1191-
val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
1192-
if (noDefaults.length == 1) noDefaults // return unique alternative without default parameters if it exists
1193-
else {
1194-
val deepPt = pt.deepenProto
1195-
if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs)
1196-
else alts
1197-
}
1198+
val found = narrowMostSpecific(candidates)
1199+
if (found.length <= 1) found
1200+
else {
1201+
val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
1202+
if (noDefaults.length == 1) noDefaults // return unique alternative without default parameters if it exists
1203+
else {
1204+
val deepPt = pt.deepenProto
1205+
if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs)
1206+
else alts
1207+
}
11981208
}
11991209
}
12001210

@@ -1297,11 +1307,3 @@ trait Applications extends Compatibility { self: Typer =>
12971307
harmonizeWith(tpes)(identity, (tp, pt) => pt)
12981308
}
12991309

1300-
/*
1301-
def typedApply(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Tree = track("typedApply") {
1302-
new ApplyToTyped(app, fun, methRef, args, resultType).result
1303-
}
1304-
1305-
def typedApply(fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Tree =
1306-
typedApply(untpd.Apply(untpd.TypedSplice(fun), args), fun, methRef, args, resultType)
1307-
*/

tests/neg/t2660.scala renamed to tests/pos/t2660.scala

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// Dotty deviation. The calls here now are classified as ambiguous.
2-
31
package hoho
42

53
class G
@@ -22,9 +20,7 @@ class A[T](x: T) {
2220
object T {
2321
def main(args: Array[String]): Unit = {
2422
implicit def g2h(g: G): H = new H
25-
new A[Int](new H, 23) // error
26-
// in the context here, either secondary constructor is applicable
27-
// to the other, due to the implicit in scope. So the call is ambiguous.
23+
new A[Int](new H, 23)
2824
}
2925
}
3026

@@ -40,7 +36,7 @@ object X {
4036
object T2 {
4137
def main(args: Array[String]): Unit = {
4238
implicit def g2h(g: G): H = new H
43-
X.f(new H, 23) // error
39+
X.f(new H, 23)
4440
}
4541
}
4642

0 commit comments

Comments
 (0)