Skip to content

Commit a307a90

Browse files
authored
Merge pull request #1389 from dotty-staging/fix-#1381
Changes to overloading
2 parents 9da40cb + 762375c commit a307a90

File tree

7 files changed

+151
-76
lines changed

7 files changed

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

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

11211137
val candidates = pt match {
11221138
case pt @ FunProto(args, resultType, _) =>
@@ -1176,33 +1192,27 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
11761192
}
11771193
}
11781194

1179-
case pt @ PolyProto(targs, pt1) =>
1195+
case pt @ PolyProto(targs1, pt1) =>
1196+
assert(targs.isEmpty)
11801197
val alts1 = alts filter pt.isMatchedBy
1181-
resolveOverloaded(alts1, pt1, targs)
1198+
resolveOverloaded(alts1, pt1, targs1)
11821199

11831200
case defn.FunctionOf(args, resultType) =>
11841201
narrowByTypes(alts, args, resultType)
11851202

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

@@ -1305,11 +1315,3 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
13051315
harmonizeWith(tpes)(identity, (tp, pt) => pt)
13061316
}
13071317

1308-
/*
1309-
def typedApply(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Tree = track("typedApply") {
1310-
new ApplyToTyped(app, fun, methRef, args, resultType).result
1311-
}
1312-
1313-
def typedApply(fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Tree =
1314-
typedApply(untpd.Apply(untpd.TypedSplice(fun), args), fun, methRef, args, resultType)
1315-
*/

tests/pos/hkgadt.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object HKGADT {
2+
sealed trait Foo[F[_]]
3+
final case class Bar() extends Foo[List]
4+
5+
def frob[F[_]](foo: Foo[F]) =
6+
foo match {
7+
case Bar() => ()
8+
}
9+
}

tests/pos/i618.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class C(val f: Any*)
2+
3+
class D(override val f: Nothing) extends C(f)

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

tests/run/t1381.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
4
2+
3
3+
2
4+
A
5+
B
6+
frA
7+
frB

tests/run/t1381.scala

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
object Test {
2+
def main(args: Array[String]): Unit = {
3+
Test1.test()
4+
Test2.test()
5+
Test3.test()
6+
}
7+
}
8+
9+
object Test1 {
10+
class Bar[T](n: Int) {
11+
println(n)
12+
}
13+
implicit def const[T](x: T): Bar[T] = new Bar[T](1)
14+
15+
def bar[T](e: T): Any = new Bar[T](2)
16+
def bar[T](e: Bar[T]): Any = new Bar[T](3)
17+
18+
val b: Bar[Int] = new Bar[Int](4)
19+
20+
def test(): Unit = {
21+
bar(b)
22+
bar(5)
23+
}
24+
}
25+
26+
object Test2 {
27+
trait A; trait B
28+
class C1 {
29+
def f(x: A): Unit = println("A")
30+
}
31+
class C2 extends C1 {
32+
def f(x: B): Unit = println("B")
33+
}
34+
object Test extends C2 with App {
35+
implicit def a2b(x: A): B = new B {}
36+
def test(): Unit = {
37+
f(new A {})
38+
f(new B {})
39+
}
40+
}
41+
def test(): Unit = Test.test()
42+
}
43+
44+
object Test3 {
45+
trait A; trait B
46+
class C extends A with B
47+
def fr(x: A): A = {
48+
println("frA")
49+
x
50+
}
51+
def fr(x: B): B = {
52+
println("frB")
53+
x
54+
}
55+
def test(): Unit = {
56+
val a: A = fr(new C)
57+
val b: B = fr(new C)
58+
}
59+
}

0 commit comments

Comments
 (0)