Skip to content

Commit

Permalink
Changes to overloading
Browse files Browse the repository at this point in the history
Fix scala#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.
  • Loading branch information
odersky committed Jul 15, 2016
1 parent 409c6c3 commit 6253125
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 76 deletions.
3 changes: 1 addition & 2 deletions src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -854,8 +854,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
var allAlts = denot.alternatives
.map(_.termRef).filter(tr => typeParamCount(tr) == targs.length)
if (targs.isEmpty) allAlts = allAlts.filterNot(_.widen.isInstanceOf[PolyType])
val alternatives =
ctx.typer.resolveOverloaded(allAlts, proto, Nil)
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
assert(alternatives.size == 1,
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
i"$method on ${receiver.tpe.widenDealias} with targs: $targs%, %; args: $args%, % of types ${args.tpes}%, %; expectedType: $expectedType." +
Expand Down
138 changes: 70 additions & 68 deletions src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1039,31 +1039,7 @@ trait Applications extends Compatibility { self: Typer =>
* to form the method type.
* todo: use techniques like for implicits to pick candidates quickly?
*/
def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type] = Nil)(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") {

def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty

/** The shape of given tree as a type; cannot handle named arguments. */
def typeShape(tree: untpd.Tree): Type = tree match {
case untpd.Function(args, body) =>
defn.FunctionOf(args map Function.const(defn.AnyType), typeShape(body))
case _ =>
defn.NothingType
}

/** The shape of given tree as a type; is more expensive than
* typeShape but can can handle named arguments.
*/
def treeShape(tree: untpd.Tree): Tree = tree match {
case NamedArg(name, arg) =>
val argShape = treeShape(arg)
cpy.NamedArg(tree)(name, argShape).withType(argShape.tpe)
case _ =>
dummyTreeOfType(typeShape(tree))
}

def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
alts filter (isApplicable(_, argTypes, resultType))
def resolveOverloaded(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") {

/** Is `alt` a method or polytype whose result type after the first value parameter
* section conforms to the expected type `resultType`? If `resultType`
Expand Down Expand Up @@ -1092,23 +1068,63 @@ trait Applications extends Compatibility { self: Typer =>
* 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
}
def adaptByResult(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
}
}

var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled))
if (found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled))
found = resolveOverloaded(alts, pt, Nil)
found match {
case alt :: Nil => adaptByResult(alt) :: Nil
case _ => found
}
}

/** This private version of `resolveOverloaded` does the bulk of the work of
* overloading resolution, but does not do result adaptation. It might be
* called twice from the public `resolveOverloaded` method, once with
* implicits enabled, and once without.
*/
private def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type])(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") {

def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty

/** The shape of given tree as a type; cannot handle named arguments. */
def typeShape(tree: untpd.Tree): Type = tree match {
case untpd.Function(args, body) =>
defn.FunctionOf(args map Function.const(defn.AnyType), typeShape(body))
case _ =>
defn.NothingType
}

/** The shape of given tree as a type; is more expensive than
* typeShape but can can handle named arguments.
*/
def treeShape(tree: untpd.Tree): Tree = tree match {
case NamedArg(name, arg) =>
val argShape = treeShape(arg)
cpy.NamedArg(tree)(name, argShape).withType(argShape.tpe)
case _ =>
dummyTreeOfType(typeShape(tree))
}

def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
alts filter (isApplicable(_, argTypes, resultType))

val candidates = pt match {
case pt @ FunProto(args, resultType, _) =>
Expand Down Expand Up @@ -1168,33 +1184,27 @@ trait Applications extends Compatibility { self: Typer =>
}
}

case pt @ PolyProto(targs, pt1) =>
case pt @ PolyProto(targs1, pt1) =>
assert(targs.isEmpty)
val alts1 = alts filter pt.isMatchedBy
resolveOverloaded(alts1, pt1, targs)
resolveOverloaded(alts1, pt1, targs1)

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

case pt =>
alts filter (normalizedCompatible(_, pt))
}
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 =>
val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
if (noDefaults.length == 1) noDefaults // return unique alternative without default parameters if it exists
else {
val deepPt = pt.deepenProto
if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs)
else alts
}
val found = narrowMostSpecific(candidates)
if (found.length <= 1) found
else {
val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
if (noDefaults.length == 1) noDefaults // return unique alternative without default parameters if it exists
else {
val deepPt = pt.deepenProto
if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs)
else alts
}
}
}

Expand Down Expand Up @@ -1297,11 +1307,3 @@ trait Applications extends Compatibility { self: Typer =>
harmonizeWith(tpes)(identity, (tp, pt) => pt)
}

/*
def typedApply(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Tree = track("typedApply") {
new ApplyToTyped(app, fun, methRef, args, resultType).result
}
def typedApply(fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Tree =
typedApply(untpd.Apply(untpd.TypedSplice(fun), args), fun, methRef, args, resultType)
*/
8 changes: 2 additions & 6 deletions tests/neg/t2660.scala → tests/pos/t2660.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// Dotty deviation. The calls here now are classified as ambiguous.

package hoho

class G
Expand All @@ -22,9 +20,7 @@ class A[T](x: T) {
object T {
def main(args: Array[String]): Unit = {
implicit def g2h(g: G): H = new H
new A[Int](new H, 23) // error
// in the context here, either secondary constructor is applicable
// to the other, due to the implicit in scope. So the call is ambiguous.
new A[Int](new H, 23)
}
}

Expand All @@ -40,7 +36,7 @@ object X {
object T2 {
def main(args: Array[String]): Unit = {
implicit def g2h(g: G): H = new H
X.f(new H, 23) // error
X.f(new H, 23)
}
}

Expand Down

0 comments on commit 6253125

Please sign in to comment.