From d8bcc16e1f20933fb8d26ff1f38831fd1b7a0972 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jan 2020 09:09:37 +0100 Subject: [PATCH 1/4] Fix #7401: Refine overloading resolution If we are left with several alternatives after normal overloadiong resolution and the expected result type is not a FunProto, prefer the alternatives that do not have a method result type. This makes i2378 compile, so it got moved to a pos test. --- .../dotty/tools/dotc/typer/Applications.scala | 52 +++++++++++-------- tests/neg/i2378.scala | 30 ----------- tests/pos/i2378.scala | 9 ++-- tests/pos/i7401.scala | 11 ++++ 4 files changed, 47 insertions(+), 55 deletions(-) delete mode 100644 tests/neg/i2378.scala create mode 100644 tests/pos/i7401.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 4ecdaeb83b5a..01ac671bc8fd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1616,7 +1616,7 @@ trait Applications extends Compatibility { * called twice from the public `resolveOverloaded` method, once with * implicits and SAM conversions enabled, and once without. */ - private def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type])(implicit ctx: Context): List[TermRef] = { + private def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type])(implicit ctx: Context): List[TermRef] = record("resolveOverloaded/2") def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty @@ -1785,28 +1785,38 @@ trait Applications extends Compatibility { candidates.flatMap(cloneCandidate) } + def resultIsMethod(tp: Type): Boolean = tp.widen.stripPoly match + case tp: MethodType => tp.resultType.isInstanceOf[MethodType] + case _ => false + val found = narrowMostSpecific(candidates) if (found.length <= 1) found - else pt match { - case pt @ FunProto(_, resType: FunProto) => - // try to narrow further with snd argument list - val advanced = advanceCandidates(pt.typedArgs().tpes) - resolveOverloaded(advanced.map(_._1), resType, Nil) // resolve with candidates where first params are stripped - .map(advanced.toMap) // map surviving result(s) back to original candidates - case _ => - val noDefaults = alts.filter(!_.symbol.hasDefaultParams) - val noDefaultsCount = noDefaults.length - if (noDefaultsCount == 1) - noDefaults // return unique alternative without default parameters if it exists - else if (noDefaultsCount > 1 && noDefaultsCount < alts.length) - resolveOverloaded(noDefaults, pt, targs) // try again, dropping defult arguments - else { - val deepPt = pt.deepenProto - if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs) - else candidates - } - } - } + else + val deepPt = pt.deepenProto + deepPt match + case pt @ FunProto(_, resType: FunProto) => + // try to narrow further with snd argument list + val advanced = advanceCandidates(pt.typedArgs().tpes) + resolveOverloaded(advanced.map(_._1), resType, Nil) // resolve with candidates where first params are stripped + .map(advanced.toMap) // map surviving result(s) back to original candidates + case _ => + // prefer alternatives that need no eta expansion + val noCurried = alts.filter(!resultIsMethod(_)) + if noCurried.length == 1 then + noCurried + else + // prefer alternatves that match without default parameters + val noDefaults = noCurried.filter(!_.symbol.hasDefaultParams) + val noDefaultsCount = noDefaults.length + if noDefaultsCount == 1 then + noDefaults // return unique alternative without default parameters if it exists + else if noDefaultsCount > 1 && noDefaultsCount < alts.length then + resolveOverloaded(noDefaults, pt, targs) // try again, dropping defult arguments + else if deepPt ne pt then + // try again with a deeper known expected type + resolveOverloaded(alts, deepPt, targs) + else candidates + end resolveOverloaded /** Try to typecheck any arguments in `pt` that are function values missing a * parameter type. If the formal parameter types corresponding to a closure argument diff --git a/tests/neg/i2378.scala b/tests/neg/i2378.scala deleted file mode 100644 index 1ec5c3c648a8..000000000000 --- a/tests/neg/i2378.scala +++ /dev/null @@ -1,30 +0,0 @@ -trait Cap - -trait Toolbox { - type Tree - - val tpd: TypedTrees - trait TypedTrees { - type Tree - } - - val Apply: ApplyImpl - - trait ApplyImpl { - def unapply(tree: Tree): Option[(Tree, Seq[Tree])] - def unapply(tree: tpd.Tree)(implicit c: Cap): Option[(tpd.Tree, Seq[tpd.Tree])] - } -} - -class Test(val tb: Toolbox) { - import tb._ - implicit val cap: Cap = null - - def foo(tree: Tree): Int = (tree: Any) match { - case tb.Apply(fun, args) => 3 // error, but error message is wrong - } - - def bar(tree: tpd.Tree): Int = (tree: Any) match { - case Apply(fun, args) => 3 // error, but error message is wrong - } -} diff --git a/tests/pos/i2378.scala b/tests/pos/i2378.scala index 26e95207c270..0dba1b951b3f 100644 --- a/tests/pos/i2378.scala +++ b/tests/pos/i2378.scala @@ -9,6 +9,7 @@ trait Toolbox { } val Apply: ApplyImpl + trait ApplyImpl { def unapply(tree: Tree): Option[(Tree, Seq[Tree])] def unapply(tree: tpd.Tree)(implicit c: Cap): Option[(tpd.Tree, Seq[tpd.Tree])] @@ -19,11 +20,11 @@ class Test(val tb: Toolbox) { import tb._ implicit val cap: Cap = null - def foo(tree: Tree): Int = tree match { - case Apply(fun, args) => 3 + def foo(tree: Tree): Int = (tree: Any) match { + case tb.Apply(fun, args) => 3 } - def bar(tree: tpd.Tree): Int = tree match { + def bar(tree: tpd.Tree): Int = (tree: Any) match { case Apply(fun, args) => 3 } -} \ No newline at end of file +} diff --git a/tests/pos/i7401.scala b/tests/pos/i7401.scala new file mode 100644 index 000000000000..fea4bc18e04b --- /dev/null +++ b/tests/pos/i7401.scala @@ -0,0 +1,11 @@ +object Test { + given ops: (a: Int) extended with { + def foo(i: Int): Unit = () + def foo: Unit = () + } + val x: Int = 5 + x.foo(4) + x.foo + ops.foo(x)(4) + ops.foo(x) +} \ No newline at end of file From 14e38e5b640da91cbf496febfb49b86d7d23bd78 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jan 2020 09:36:56 +0100 Subject: [PATCH 2/4] Drop replTest The error that was checked for in the test is no more. --- compiler/test-resources/repl/i4536 | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 compiler/test-resources/repl/i4536 diff --git a/compiler/test-resources/repl/i4536 b/compiler/test-resources/repl/i4536 deleted file mode 100644 index 97326fb45852..000000000000 --- a/compiler/test-resources/repl/i4536 +++ /dev/null @@ -1,14 +0,0 @@ -scala> object Foo { def apply() = 1; def apply()(implicit ord: Ordering[Int]) = 2; Foo() } -1 | object Foo { def apply() = 1; def apply()(implicit ord: Ordering[Int]) = 2; Foo() } - | ^^^ - |Ambiguous overload. The overloaded alternatives of method apply in object Foo with types - | ()(implicit ord: Ordering[Int]): Int - | (): Int - |both match arguments () -scala> object Foo { def apply() = 1; def apply()(implicit ord: Ordering[Int]) = 2; Foo.apply() } -1 | object Foo { def apply() = 1; def apply()(implicit ord: Ordering[Int]) = 2; Foo.apply() } - | ^^^^^^^^^ - |Ambiguous overload. The overloaded alternatives of method apply in object Foo with types - | ()(implicit ord: Ordering[Int]): Int - | (): Int - |both match arguments () From 5fda17a988245721d18df0edfd50446ab0650648 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Jan 2020 09:59:46 +0100 Subject: [PATCH 3/4] Fix "prefer not curried" criterion embedding The previous code did not compose correctly with the "no defaults" criterion that followed. --- .../src/dotty/tools/dotc/typer/Applications.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 01ac671bc8fd..747906e70735 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1802,20 +1802,24 @@ trait Applications extends Compatibility { case _ => // prefer alternatives that need no eta expansion val noCurried = alts.filter(!resultIsMethod(_)) + val noCurriedCount = noCurried.length if noCurried.length == 1 then noCurried + else if noCurriedCount > 1 && noCurriedCount < alts.length then + resolveOverloaded(noCurried, pt, targs) else // prefer alternatves that match without default parameters - val noDefaults = noCurried.filter(!_.symbol.hasDefaultParams) + val noDefaults = alts.filter(!_.symbol.hasDefaultParams) val noDefaultsCount = noDefaults.length if noDefaultsCount == 1 then - noDefaults // return unique alternative without default parameters if it exists + noDefaults else if noDefaultsCount > 1 && noDefaultsCount < alts.length then - resolveOverloaded(noDefaults, pt, targs) // try again, dropping defult arguments + resolveOverloaded(noDefaults, pt, targs) else if deepPt ne pt then // try again with a deeper known expected type resolveOverloaded(alts, deepPt, targs) - else candidates + else + candidates end resolveOverloaded /** Try to typecheck any arguments in `pt` that are function values missing a From 8058592c46abe10035187e6f8cb84ccf477516b7 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 15 Jan 2020 16:54:10 +0100 Subject: [PATCH 4/4] Update compiler/src/dotty/tools/dotc/typer/Applications.scala Co-Authored-By: Fengyun Liu --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 747906e70735..b8c5db87f855 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1803,7 +1803,7 @@ trait Applications extends Compatibility { // prefer alternatives that need no eta expansion val noCurried = alts.filter(!resultIsMethod(_)) val noCurriedCount = noCurried.length - if noCurried.length == 1 then + if noCurriedCount == 1 then noCurried else if noCurriedCount > 1 && noCurriedCount < alts.length then resolveOverloaded(noCurried, pt, targs)