diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 9e9acf97a2a1..c45db4ccc827 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -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 => @@ -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 @@ -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 } } diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index c7efe45b7b23..9a012c30e0b0 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -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) diff --git a/tests/pending/pos/t8230a.scala b/tests/pending/pos/t8230a.scala new file mode 100644 index 000000000000..405aa86f552c --- /dev/null +++ b/tests/pending/pos/t8230a.scala @@ -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 + } +} diff --git a/tests/pos/array-overload.scala b/tests/pos/array-overload.scala new file mode 100644 index 000000000000..737ea0ec735a --- /dev/null +++ b/tests/pos/array-overload.scala @@ -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) +} diff --git a/tests/pending/run/arrayclone-new.scala b/tests/run/arrayclone-new.scala similarity index 98% rename from tests/pending/run/arrayclone-new.scala rename to tests/run/arrayclone-new.scala index 5f09cd73a1e4..8f66d1b31243 100644 --- a/tests/pending/run/arrayclone-new.scala +++ b/tests/run/arrayclone-new.scala @@ -1,4 +1,4 @@ -import scala.reflect.{ClassTag, classTag} +import scala.reflect.ClassTag object Test extends dotty.runtime.LegacyApp{ BooleanArrayClone; @@ -106,3 +106,4 @@ object PolymorphicArrayClone{ testIt(mangled.it, 0, 1); } +