Skip to content

Commit 671b1f5

Browse files
authored
Prefer parameterless alternatives during ambiguous overload resolution (#16315)
Fixes #10715. Pair-programmed with @smarter this morning 😃 Parameterless alternatives were already preferred when there was no matching alternativ (#6955). With this PR, they are also preferred when the alternatives are "ambiguous".
2 parents a30e54b + 6226567 commit 671b1f5

File tree

6 files changed

+134
-21
lines changed

6 files changed

+134
-21
lines changed

compiler/src/dotty/tools/dotc/typer/Typer.scala

+49-21
Original file line numberDiff line numberDiff line change
@@ -3424,42 +3424,59 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
34243424
ErrorReporting.missingArgs(tree, mt)
34253425
tree.withType(mt.resultType)
34263426

3427-
def adaptOverloaded(ref: TermRef) = {
3427+
def adaptOverloaded(ref: TermRef) =
3428+
// get all the alternatives
34283429
val altDenots =
34293430
val allDenots = ref.denot.alternatives
34303431
if pt.isExtensionApplyProto then allDenots.filter(_.symbol.is(ExtensionMethod))
34313432
else allDenots
3433+
34323434
typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%\n\n %")
3435+
3436+
/** Search for an alternative that does not take parameters.
3437+
* If there is one, return it, otherwise emit an error.
3438+
*/
3439+
def tryParameterless(alts: List[TermRef])(error: => tpd.Tree): Tree =
3440+
alts.filter(_.info.isParameterless) match
3441+
case alt :: Nil => readaptSimplified(tree.withType(alt))
3442+
case _ =>
3443+
if altDenots.exists(_.info.paramInfoss == ListOfNil) then
3444+
typed(untpd.Apply(untpd.TypedSplice(tree), Nil), pt, locked)
3445+
else
3446+
error
3447+
34333448
def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt)
34343449
val alts = altDenots.map(altRef)
3435-
resolveOverloaded(alts, pt) match {
3450+
3451+
resolveOverloaded(alts, pt) match
34363452
case alt :: Nil =>
34373453
readaptSimplified(tree.withType(alt))
34383454
case Nil =>
3439-
// If alternative matches, there are still two ways to recover:
3455+
// If no alternative matches, there are still two ways to recover:
34403456
// 1. If context is an application, try to insert an apply or implicit
34413457
// 2. If context is not an application, pick a alternative that does
34423458
// not take parameters.
3443-
def noMatches =
3444-
errorTree(tree, NoMatchingOverload(altDenots, pt))
3445-
def hasEmptyParams(denot: SingleDenotation) = denot.info.paramInfoss == ListOfNil
3446-
pt match {
3459+
3460+
def errorNoMatch = errorTree(tree, NoMatchingOverload(altDenots, pt))
3461+
3462+
pt match
34473463
case pt: FunOrPolyProto if pt.applyKind != ApplyKind.Using =>
34483464
// insert apply or convert qualifier, but only for a regular application
3449-
tryInsertApplyOrImplicit(tree, pt, locked)(noMatches)
3465+
tryInsertApplyOrImplicit(tree, pt, locked)(errorNoMatch)
34503466
case _ =>
3451-
alts.filter(_.info.isParameterless) match {
3452-
case alt :: Nil => readaptSimplified(tree.withType(alt))
3453-
case _ =>
3454-
if (altDenots exists (_.info.paramInfoss == ListOfNil))
3455-
typed(untpd.Apply(untpd.TypedSplice(tree), Nil), pt, locked)
3456-
else
3457-
noMatches
3458-
}
3459-
}
3467+
tryParameterless(alts)(errorNoMatch)
3468+
34603469
case ambiAlts =>
3461-
if tree.tpe.isErroneous || pt.isErroneous then tree.withType(UnspecifiedErrorType)
3462-
else
3470+
// If there are ambiguous alternatives, and:
3471+
// 1. the types aren't erroneous
3472+
// 2. the expected type is not a function type
3473+
// 3. there exist a parameterless alternative
3474+
//
3475+
// Then, pick the parameterless alternative.
3476+
// See tests/pos/i10715-scala and tests/pos/i10715-java.
3477+
3478+
/** Constructs an "ambiguous overload" error */
3479+
def errorAmbiguous =
34633480
val remainingDenots = altDenots.filter(denot => ambiAlts.contains(altRef(denot)))
34643481
val addendum =
34653482
if ambiAlts.exists(!_.symbol.exists) then
@@ -3468,8 +3485,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
34683485
|Note: Overloaded definitions introduced by refinements cannot be resolved"""
34693486
else ""
34703487
errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt, addendum))
3471-
}
3472-
}
3488+
end errorAmbiguous
3489+
3490+
if tree.tpe.isErroneous || pt.isErroneous then
3491+
tree.withType(UnspecifiedErrorType)
3492+
else
3493+
pt match
3494+
case _: FunProto =>
3495+
errorAmbiguous
3496+
case _ =>
3497+
tryParameterless(alts)(errorAmbiguous)
3498+
3499+
end match
3500+
end adaptOverloaded
34733501

34743502
def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match {
34753503
case wtp: MethodOrPoly =>

tests/neg/i10715a.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class Parent:
2+
def f(x: Int): Parent = ???
3+
def f: Int = 0
4+
5+
def g[A](x: Int): Parent = ???
6+
def g[A]: Int = 0
7+
8+
class Sub extends Parent:
9+
override def f(x: Int): Parent = ???
10+
override def g[A](x: Int): Parent = ???
11+
12+
def bad(c: Sub): Unit =
13+
c.f: String // error
14+
c.g: String // error
15+
c.f.bad // error
16+
c.g.bad // error
17+
18+
c.f("") // error
19+
c.g("") // error
20+
c.g[Int]("") // error
21+
c.g[Int]: (String => String) // error
22+
c.g[Int]: (Int => Parent) // ok

tests/neg/i10715b.scala

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Parent:
2+
def f(x: Int): Unit = ()
3+
def f: Int = 0
4+
5+
class Sub extends Parent:
6+
override def f(x: Int): Unit = ()
7+
def f(x: Int)(using String): Unit = ()
8+
9+
def bad(c: Sub): Unit =
10+
c.f(1) // error: ambiguous overload

tests/pos/i10715-java/C_1.java

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class C_1 {
2+
3+
public int f() {
4+
return 0;
5+
}
6+
public C_1 f(int x) {
7+
return null;
8+
}
9+
}
10+
11+
class Child extends C_1 {
12+
@Override
13+
public C_1 f(int x) {
14+
return null;
15+
}
16+
}

tests/pos/i10715-java/caller_2.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
def test(c: Child): Unit =
2+
c.f() // always ok
3+
c.f // should work too
4+
c.f(1)
5+
c.f.toString
6+
7+
// The issue was first detected on NIO buffers,
8+
// (on Java 11+), so these should pass now.
9+
def buffer(c: java.nio.ByteBuffer): Unit =
10+
c.position
11+
c.position(10).position.toString

tests/pos/i10715-scala/test.scala

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class Parent:
2+
def f(x: Int): Parent = ???
3+
def f: Int = 0
4+
5+
def g[A](x: Int): Parent = ???
6+
def g[A]: Int = 0
7+
8+
// For the issue to show up, there must be a subclass that overrides
9+
// one of the two methods.
10+
class Sub extends Parent:
11+
override def f(x: Int): Parent = ???
12+
override def g[A](x: Int): Parent = ???
13+
14+
def test(c: Sub): Unit =
15+
c.f(1) // already worked
16+
c.f
17+
c.f.+(0)
18+
c.f.toString
19+
20+
c.g(0) // already worked
21+
c.g
22+
c.g[Int]
23+
c.g.+(0)
24+
c.g.toString
25+
c.g[Int].+(0)
26+
c.g.toString

0 commit comments

Comments
 (0)