Skip to content

Commit c54debe

Browse files
committed
Merge pull request #665 from dotty-staging/fix/#647-expected-type-overloading
Take expected result type into account for overloading resolution
2 parents 1b31f06 + 0bbc858 commit c54debe

File tree

5 files changed

+97
-10
lines changed

5 files changed

+97
-10
lines changed

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

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,9 @@ trait Applications extends Compatibility { self: Typer =>
915915
}}
916916

917917
def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") {
918-
(alts: @unchecked) match {
918+
alts match {
919+
case Nil => alts
920+
case _ :: Nil => alts
919921
case alt :: alts1 =>
920922
def winner(bestSoFar: TermRef, alts: List[TermRef]): TermRef = alts match {
921923
case alt :: alts1 =>
@@ -966,6 +968,51 @@ trait Applications extends Compatibility { self: Typer =>
966968
def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
967969
alts filter (isApplicable(_, argTypes, resultType))
968970

971+
/** Is `alt` a method or polytype whose result type after the first value parameter
972+
* section conforms to the expected type `resultType`? If `resultType`
973+
* is a `IgnoredProto`, pick the underlying type instead.
974+
*/
975+
def resultConforms(alt: Type, resultType: Type)(implicit ctx: Context): Boolean = resultType match {
976+
case IgnoredProto(ignored) => resultConforms(alt, ignored)
977+
case _: ValueType =>
978+
alt.widen match {
979+
case tp: PolyType => resultConforms(constrained(tp).resultType, resultType)
980+
case tp: MethodType => constrainResult(tp.resultType, resultType)
981+
case _ => true
982+
}
983+
case _ => true
984+
}
985+
986+
/** If the `chosen` alternative has a result type incompatible with the expected result
987+
* type `pt`, run overloading resolution again on all alternatives that do match `pt`.
988+
* If the latter succeeds with a single alternative, return it, otherwise
989+
* fallback to `chosen`.
990+
*
991+
* Note this order of events is done for speed. One might be tempted to
992+
* preselect alternatives by result type. But is slower, because it discriminates
993+
* less. The idea is when searching for a best solution, as is the case in overloading
994+
* resolution, we should first try criteria which are cheap and which have a high
995+
* probability of pruning the search. result type comparisons are neither cheap nor
996+
* do they prune much, on average.
997+
*/
998+
def adaptByResult(alts: List[TermRef], chosen: TermRef) = {
999+
def nestedCtx = ctx.fresh.setExploreTyperState
1000+
pt match {
1001+
case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) =>
1002+
alts.filter(alt =>
1003+
(alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match {
1004+
case Nil => chosen
1005+
case alt2 :: Nil => alt2
1006+
case alts2 =>
1007+
resolveOverloaded(alts2, pt) match {
1008+
case alt2 :: Nil => alt2
1009+
case _ => chosen
1010+
}
1011+
}
1012+
case _ => chosen
1013+
}
1014+
}
1015+
9691016
val candidates = pt match {
9701017
case pt @ FunProto(args, resultType, _) =>
9711018
val numArgs = args.length
@@ -1027,15 +1074,20 @@ trait Applications extends Compatibility { self: Typer =>
10271074
case pt =>
10281075
alts filter (normalizedCompatible(_, pt))
10291076
}
1030-
if (isDetermined(candidates)) candidates
1031-
else narrowMostSpecific(candidates) match {
1032-
case result @ (alt1 :: alt2 :: _) =>
1033-
// overload.println(i"ambiguous $alt1 $alt2")
1077+
narrowMostSpecific(candidates) match {
1078+
case Nil => Nil
1079+
case alt :: Nil =>
1080+
adaptByResult(alts, alt) :: Nil
1081+
// why `alts` and not `candidates`? pos/array-overload.scala gives a test case.
1082+
// Here, only the Int-apply is a candidate, but it is not compatible with the result
1083+
// type. Picking the Byte-apply as the only result-compatible solution then forces
1084+
// the arguments (which are constants) to be adapted to Byte. If we had picked
1085+
// `candidates` instead, no solution would have been found.
1086+
case alts =>
1087+
// overload.println(i"ambiguous $alts%, %")
10341088
val deepPt = pt.deepenProto
10351089
if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs)
1036-
else result
1037-
case result =>
1038-
result
1090+
else alts
10391091
}
10401092
}
10411093

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ object ProtoTypes {
4646
* fits the given expected result type.
4747
*/
4848
def constrainResult(mt: Type, pt: Type)(implicit ctx: Context): Boolean = pt match {
49-
case _: FunProto =>
49+
case pt: FunProto =>
5050
mt match {
5151
case mt: MethodType =>
5252
mt.isDependent || constrainResult(mt.resultType, pt.resultType)

tests/pending/pos/t8230a.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
trait Arr[T]
2+
object Arr {
3+
def apply[T](xs: T): Arr[T] = null
4+
def apply(x: Long) : Arr[Long] = null
5+
}
6+
7+
object I {
8+
implicit def arrToTrav[T] (a: Arr[T]) : Traversable[T] = null
9+
implicit def longArrToTrav(a: Arr[Long]): Traversable[Long] = null
10+
}
11+
12+
object Test {
13+
def foo(t: Traversable[Any]) = {}
14+
15+
object Okay {
16+
Arr("1")
17+
18+
import I.{ arrToTrav, longArrToTrav }
19+
foo(Arr("2"))
20+
}
21+
22+
object Fail {
23+
import I.arrToTrav
24+
foo(Arr("3")) // found String, expected Long
25+
}
26+
}

tests/pos/array-overload.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Test {
2+
object A {
3+
def apply(x: Byte, xs: Byte*): Array[Byte] = ???
4+
def apply(x: Int, xs: Int*): Array[Int] = ???
5+
}
6+
7+
val x: Array[Byte] = A.apply(1, 2)
8+
}

tests/pending/run/arrayclone-new.scala renamed to tests/run/arrayclone-new.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import scala.reflect.{ClassTag, classTag}
1+
import scala.reflect.ClassTag
22

33
object Test extends dotty.runtime.LegacyApp{
44
BooleanArrayClone;
@@ -106,3 +106,4 @@ object PolymorphicArrayClone{
106106

107107
testIt(mangled.it, 0, 1);
108108
}
109+

0 commit comments

Comments
 (0)