diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 50cd6a8d6ae4..dacd6dae12df 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 @@ -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 => diff --git a/tests/neg/i10715a.scala b/tests/neg/i10715a.scala new file mode 100644 index 000000000000..b5794c46d22c --- /dev/null +++ b/tests/neg/i10715a.scala @@ -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 diff --git a/tests/neg/i10715b.scala b/tests/neg/i10715b.scala new file mode 100644 index 000000000000..922b80cf727b --- /dev/null +++ b/tests/neg/i10715b.scala @@ -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 diff --git a/tests/pos/i10715-java/C_1.java b/tests/pos/i10715-java/C_1.java new file mode 100644 index 000000000000..8973027a0c3c --- /dev/null +++ b/tests/pos/i10715-java/C_1.java @@ -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; + } +} diff --git a/tests/pos/i10715-java/caller_2.scala b/tests/pos/i10715-java/caller_2.scala new file mode 100644 index 000000000000..43e616ee5818 --- /dev/null +++ b/tests/pos/i10715-java/caller_2.scala @@ -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 diff --git a/tests/pos/i10715-scala/test.scala b/tests/pos/i10715-scala/test.scala new file mode 100644 index 000000000000..ca80a2270927 --- /dev/null +++ b/tests/pos/i10715-scala/test.scala @@ -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