Skip to content

Prefer parameterless alternatives during ambiguous overload resolution #16315

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 1 commit into from
Nov 17, 2022
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
70 changes: 49 additions & 21 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3427,42 +3427,59 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
ErrorReporting.missingArgs(tree, mt)
tree.withType(mt.resultType)

def adaptOverloaded(ref: TermRef) = {
def adaptOverloaded(ref: TermRef) =
// get all the alternatives
val altDenots =
val allDenots = ref.denot.alternatives
if pt.isExtensionApplyProto then allDenots.filter(_.symbol.is(ExtensionMethod))
else allDenots

typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%\n\n %")

/** Search for an alternative that does not take parameters.
* If there is one, return it, otherwise emit an error.
*/
def tryParameterless(alts: List[TermRef])(error: => tpd.Tree): Tree =
alts.filter(_.info.isParameterless) match
case alt :: Nil => readaptSimplified(tree.withType(alt))
case _ =>
if altDenots.exists(_.info.paramInfoss == ListOfNil) then
typed(untpd.Apply(untpd.TypedSplice(tree), Nil), pt, locked)
else
error

def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt)
val alts = altDenots.map(altRef)
resolveOverloaded(alts, pt) match {

resolveOverloaded(alts, pt) match
case alt :: Nil =>
readaptSimplified(tree.withType(alt))
case Nil =>
// If alternative matches, there are still two ways to recover:
// If no alternative matches, there are still two ways to recover:
// 1. If context is an application, try to insert an apply or implicit
// 2. If context is not an application, pick a alternative that does
// not take parameters.
def noMatches =
errorTree(tree, NoMatchingOverload(altDenots, pt))
def hasEmptyParams(denot: SingleDenotation) = denot.info.paramInfoss == ListOfNil
pt match {

def errorNoMatch = errorTree(tree, NoMatchingOverload(altDenots, pt))

pt match
case pt: FunOrPolyProto if pt.applyKind != ApplyKind.Using =>
// insert apply or convert qualifier, but only for a regular application
tryInsertApplyOrImplicit(tree, pt, locked)(noMatches)
tryInsertApplyOrImplicit(tree, pt, locked)(errorNoMatch)
case _ =>
alts.filter(_.info.isParameterless) match {
case alt :: Nil => readaptSimplified(tree.withType(alt))
case _ =>
if (altDenots exists (_.info.paramInfoss == ListOfNil))
typed(untpd.Apply(untpd.TypedSplice(tree), Nil), pt, locked)
else
noMatches
}
}
tryParameterless(alts)(errorNoMatch)

case ambiAlts =>
if tree.tpe.isErroneous || pt.isErroneous then tree.withType(UnspecifiedErrorType)
else
// If there are ambiguous alternatives, and:
// 1. the types aren't erroneous
// 2. the expected type is not a function type
// 3. there exist a parameterless alternative
//
// Then, pick the parameterless alternative.
// See tests/pos/i10715-scala and tests/pos/i10715-java.

/** Constructs an "ambiguous overload" error */
def errorAmbiguous =
val remainingDenots = altDenots.filter(denot => ambiAlts.contains(altRef(denot)))
val addendum =
if ambiAlts.exists(!_.symbol.exists) then
Expand All @@ -3471,8 +3488,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
|Note: Overloaded definitions introduced by refinements cannot be resolved"""
else ""
errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt, addendum))
}
}
end errorAmbiguous

if tree.tpe.isErroneous || pt.isErroneous then
tree.withType(UnspecifiedErrorType)
else
pt match
case _: FunProto =>
errorAmbiguous
case _ =>
tryParameterless(alts)(errorAmbiguous)

end match
end adaptOverloaded

def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match {
case wtp: MethodOrPoly =>
Expand Down
22 changes: 22 additions & 0 deletions tests/neg/i10715a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class Parent:
def f(x: Int): Parent = ???
def f: Int = 0

def g[A](x: Int): Parent = ???
def g[A]: Int = 0

class Sub extends Parent:
override def f(x: Int): Parent = ???
override def g[A](x: Int): Parent = ???

def bad(c: Sub): Unit =
c.f: String // error
c.g: String // error
c.f.bad // error
c.g.bad // error

c.f("") // error
c.g("") // error
c.g[Int]("") // error
c.g[Int]: (String => String) // error
c.g[Int]: (Int => Parent) // ok
10 changes: 10 additions & 0 deletions tests/neg/i10715b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Parent:
def f(x: Int): Unit = ()
def f: Int = 0

class Sub extends Parent:
override def f(x: Int): Unit = ()
def f(x: Int)(using String): Unit = ()

def bad(c: Sub): Unit =
c.f(1) // error: ambiguous overload
16 changes: 16 additions & 0 deletions tests/pos/i10715-java/C_1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class C_1 {

public int f() {
return 0;
}
public C_1 f(int x) {
return null;
}
}

class Child extends C_1 {
@Override
public C_1 f(int x) {
return null;
}
}
11 changes: 11 additions & 0 deletions tests/pos/i10715-java/caller_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
def test(c: Child): Unit =
c.f() // always ok
c.f // should work too
c.f(1)
c.f.toString

// The issue was first detected on NIO buffers,
// (on Java 11+), so these should pass now.
def buffer(c: java.nio.ByteBuffer): Unit =
c.position
c.position(10).position.toString
26 changes: 26 additions & 0 deletions tests/pos/i10715-scala/test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class Parent:
def f(x: Int): Parent = ???
def f: Int = 0

def g[A](x: Int): Parent = ???
def g[A]: Int = 0

// For the issue to show up, there must be a subclass that overrides
// one of the two methods.
class Sub extends Parent:
override def f(x: Int): Parent = ???
override def g[A](x: Int): Parent = ???

def test(c: Sub): Unit =
c.f(1) // already worked
c.f
c.f.+(0)
c.f.toString

c.g(0) // already worked
c.g
c.g[Int]
c.g.+(0)
c.g.toString
c.g[Int].+(0)
c.g.toString