Skip to content

Commit c052d65

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 bef40b4 commit c052d65

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

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

11161132
val candidates = pt match {
11171133
case pt @ FunProto(args, resultType, _) =>
@@ -1171,33 +1187,27 @@ trait Applications extends Compatibility { self: Typer =>
11711187
}
11721188
}
11731189

1174-
case pt @ PolyProto(targs, pt1) =>
1190+
case pt @ PolyProto(targs1, pt1) =>
1191+
assert(targs.isEmpty)
11751192
val alts1 = alts filter pt.isMatchedBy
1176-
resolveOverloaded(alts1, pt1, targs)
1193+
resolveOverloaded(alts1, pt1, targs1)
11771194

11781195
case defn.FunctionOf(args, resultType) =>
11791196
narrowByTypes(alts, args, resultType)
11801197

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

@@ -1300,11 +1310,3 @@ trait Applications extends Compatibility { self: Typer =>
13001310
harmonizeWith(tpes)(identity, (tp, pt) => pt)
13011311
}
13021312

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

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)