Skip to content

Take expected result type into account for overloading resolution #665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 60 additions & 8 deletions src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,9 @@ trait Applications extends Compatibility { self: Typer =>
}}

def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") {
(alts: @unchecked) match {
alts match {
case Nil => alts
case _ :: Nil => alts
case alt :: alts1 =>
def winner(bestSoFar: TermRef, alts: List[TermRef]): TermRef = alts match {
case alt :: alts1 =>
Expand Down Expand Up @@ -966,6 +968,51 @@ trait Applications extends Compatibility { self: Typer =>
def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
alts filter (isApplicable(_, argTypes, resultType))

/** Is `alt` a method or polytype whose result type after the first value parameter
* section conforms to the expected type `resultType`? If `resultType`
* is a `IgnoredProto`, pick the underlying type instead.
*/
def resultConforms(alt: Type, resultType: Type)(implicit ctx: Context): Boolean = resultType match {
case IgnoredProto(ignored) => resultConforms(alt, ignored)
case _: ValueType =>
alt.widen match {
case tp: PolyType => resultConforms(constrained(tp).resultType, resultType)
case tp: MethodType => constrainResult(tp.resultType, resultType)
case _ => true
}
case _ => true
}

/** If the `chosen` alternative has a result type incompatible with the expected result
* type `pt`, run overloading resolution again on all alternatives that do match `pt`.
* If the latter succeeds with a single alternative, return it, otherwise
* fallback to `chosen`.
*
* Note this order of events is done for speed. One might be tempted to
* preselect alternatives by result type. But is slower, because it discriminates
* less. The idea is when searching for a best solution, as is the case in overloading
* resolution, we should first try criteria which are cheap and which have a high
* probability of pruning the search. result type comparisons are neither cheap nor
* do they prune much, on average.
*/
def adaptByResult(alts: List[TermRef], chosen: TermRef) = {
def nestedCtx = ctx.fresh.setExploreTyperState
pt match {
case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) =>
alts.filter(alt =>
(alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match {
case Nil => chosen
case alt2 :: Nil => alt2
case alts2 =>
resolveOverloaded(alts2, pt) match {
case alt2 :: Nil => alt2
case _ => chosen
}
}
case _ => chosen
}
}

val candidates = pt match {
case pt @ FunProto(args, resultType, _) =>
val numArgs = args.length
Expand Down Expand Up @@ -1027,15 +1074,20 @@ trait Applications extends Compatibility { self: Typer =>
case pt =>
alts filter (normalizedCompatible(_, pt))
}
if (isDetermined(candidates)) candidates
else narrowMostSpecific(candidates) match {
case result @ (alt1 :: alt2 :: _) =>
// overload.println(i"ambiguous $alt1 $alt2")
narrowMostSpecific(candidates) match {
case Nil => Nil
case alt :: Nil =>
adaptByResult(alts, alt) :: Nil
// why `alts` and not `candidates`? pos/array-overload.scala gives a test case.
// Here, only the Int-apply is a candidate, but it is not compatible with the result
// type. Picking the Byte-apply as the only result-compatible solution then forces
// the arguments (which are constants) to be adapted to Byte. If we had picked
// `candidates` instead, no solution would have been found.
case alts =>
// overload.println(i"ambiguous $alts%, %")
val deepPt = pt.deepenProto
if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs)
else result
case result =>
result
else alts
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ object ProtoTypes {
* fits the given expected result type.
*/
def constrainResult(mt: Type, pt: Type)(implicit ctx: Context): Boolean = pt match {
case _: FunProto =>
case pt: FunProto =>
mt match {
case mt: MethodType =>
mt.isDependent || constrainResult(mt.resultType, pt.resultType)
Expand Down
26 changes: 26 additions & 0 deletions tests/pending/pos/t8230a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
trait Arr[T]
object Arr {
def apply[T](xs: T): Arr[T] = null
def apply(x: Long) : Arr[Long] = null
}

object I {
implicit def arrToTrav[T] (a: Arr[T]) : Traversable[T] = null
implicit def longArrToTrav(a: Arr[Long]): Traversable[Long] = null
}

object Test {
def foo(t: Traversable[Any]) = {}

object Okay {
Arr("1")

import I.{ arrToTrav, longArrToTrav }
foo(Arr("2"))
}

object Fail {
import I.arrToTrav
foo(Arr("3")) // found String, expected Long
}
}
8 changes: 8 additions & 0 deletions tests/pos/array-overload.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Test {
object A {
def apply(x: Byte, xs: Byte*): Array[Byte] = ???
def apply(x: Int, xs: Int*): Array[Int] = ???
}

val x: Array[Byte] = A.apply(1, 2)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import scala.reflect.{ClassTag, classTag}
import scala.reflect.ClassTag

object Test extends dotty.runtime.LegacyApp{
BooleanArrayClone;
Expand Down Expand Up @@ -106,3 +106,4 @@ object PolymorphicArrayClone{

testIt(mangled.it, 0, 1);
}