Skip to content

Changes to overloading #1389

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 4 commits into from
Jul 18, 2016
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
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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could avoid the var here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you propose to avoid it?

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)
*/
9 changes: 9 additions & 0 deletions tests/pos/hkgadt.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
object HKGADT {
sealed trait Foo[F[_]]
final case class Bar() extends Foo[List]

def frob[F[_]](foo: Foo[F]) =
foo match {
case Bar() => ()
}
}
3 changes: 3 additions & 0 deletions tests/pos/i618.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class C(val f: Any*)

class D(override val f: Nothing) extends C(f)
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
7 changes: 7 additions & 0 deletions tests/run/t1381.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
4
3
2
A
B
frA
frB
59 changes: 59 additions & 0 deletions tests/run/t1381.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
object Test {
def main(args: Array[String]): Unit = {
Test1.test()
Test2.test()
Test3.test()
}
}

object Test1 {
class Bar[T](n: Int) {
println(n)
}
implicit def const[T](x: T): Bar[T] = new Bar[T](1)

def bar[T](e: T): Any = new Bar[T](2)
def bar[T](e: Bar[T]): Any = new Bar[T](3)

val b: Bar[Int] = new Bar[Int](4)

def test(): Unit = {
bar(b)
bar(5)
}
}

object Test2 {
trait A; trait B
class C1 {
def f(x: A): Unit = println("A")
}
class C2 extends C1 {
def f(x: B): Unit = println("B")
}
object Test extends C2 with App {
implicit def a2b(x: A): B = new B {}
def test(): Unit = {
f(new A {})
f(new B {})
}
}
def test(): Unit = Test.test()
}

object Test3 {
trait A; trait B
class C extends A with B
def fr(x: A): A = {
println("frA")
x
}
def fr(x: B): B = {
println("frB")
x
}
def test(): Unit = {
val a: A = fr(new C)
val b: B = fr(new C)
}
}