Skip to content

Improve multi argument list overload resolution #22740

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1908,7 +1908,7 @@ object Trees {
case MethodTpe(_, _, x: MethodType) => !x.isImplicitMethod
case _ => true
}}
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto, receiver.srcPos)
assert(alternatives.size == 1,
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
i"$method on ${receiver.tpe.widenDealiasKeepAnnots} with targs: $targs%, %; args: $args%, %; expectedType: $expectedType." +
Expand Down
261 changes: 178 additions & 83 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ object ProtoTypes {
* [](args): resultType
*
* @param args The untyped arguments to which the function is applied
* @param resType The expeected result type
* @param resType The expected result type
* @param typer The typer to use for typing the arguments
* @param applyKind The kind of application (regular/using/tupled infix operand)
* @param state The state object to use for tracking the changes to this prototype
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4108,7 +4108,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt)
val alts = altDenots.map(altRef)

resolveOverloaded(alts, pt) match
resolveOverloaded(alts, pt, tree.srcPos) match
case alt :: Nil =>
readaptSimplified(tree.withType(alt))
case Nil =>
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotc/pos-test-pickling.excludelist
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ i7445b.scala
i15525.scala
i19955a.scala
i19955b.scala
i20053.scala
i20053b.scala

# alias types at different levels of dereferencing
Expand Down
36 changes: 0 additions & 36 deletions tests/neg/i10901.check
Original file line number Diff line number Diff line change
@@ -1,39 +1,3 @@
-- [E008] Not Found Error: tests/neg/i10901.scala:45:38 ----------------------------------------------------------------
45 | val pos1: Point2D[Int,Double] = x º y // error
| ^^^
| value º is not a member of object BugExp4Point2D.IntT.
| An extension method was tried, but could not be fully constructed:
|
| º(x)
|
| failed with:
|
| Ambiguous overload. The overloaded alternatives of method º in object dsl with types
| [T1, T2]
| (x: BugExp4Point2D.ColumnType[T1])
| (y: BugExp4Point2D.ColumnType[T2])
| (using evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
| [T1, T2]
| (x: T1)
| (y: BugExp4Point2D.ColumnType[T2])
| (using evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
| both match arguments ((x : BugExp4Point2D.IntT.type))((y : BugExp4Point2D.DoubleT.type))
-- [E008] Not Found Error: tests/neg/i10901.scala:48:38 ----------------------------------------------------------------
48 | val pos4: Point2D[Int,Double] = x º 201.1 // error
| ^^^
|value º is not a member of object BugExp4Point2D.IntT.
|An extension method was tried, but could not be fully constructed:
|
| º(x)
|
| failed with:
|
| Ambiguous overload. The overloaded alternatives of method º in object dsl with types
| [T1, T2]
| (x: BugExp4Point2D.ColumnType[T1])
| (y: T2)(using evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
| [T1, T2](x: T1)(y: T2)(using evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
| both match arguments ((x : BugExp4Point2D.IntT.type))((201.1d : Double))
-- [E008] Not Found Error: tests/neg/i10901.scala:62:16 ----------------------------------------------------------------
62 | val y = "abc".foo // error
| ^^^^^^^^^
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i10901.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ object BugExp4Point2D {
val x = IntT
val y = DoubleT

val pos1: Point2D[Int,Double] = x º y // error
val pos1: Point2D[Int,Double] = x º y // ok
val pos2: Point2D[Int,Double] = 100 º 200.1 // ok
val pos3: Point2D[Int,Double] = 101 º y // ok
val pos4: Point2D[Int,Double] = x º 201.1 // error
val pos4: Point2D[Int,Double] = x º 201.1 // ok

}
}
Expand Down
7 changes: 7 additions & 0 deletions tests/neg/multiparamlist-overload-3.6.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- [E007] Type Mismatch Error: tests/neg/multiparamlist-overload-3.6.scala:41:21 ---------------------------------------
41 | val r = f(new B)(new A) // error: resolves to: R2 in 3.6, R1 in 3.7
| ^^^^^
| Found: A
| Required: B
|
| longer explanation available when compiling with `-explain`
44 changes: 44 additions & 0 deletions tests/neg/multiparamlist-overload-3.6.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import scala.language.`3.6`

class A
class B extends A
class C extends B

class R1
class R2
class R3

// In each test, the alternatives are defined from most genereal to most specific,
// with respect to a lexicographic ordering by parameter list.
// In each of the cases tested here, scala-3.7 resolves to the most specific applicable alternative.
// See tests/pos/multiparamlist-overload-3.7.scala for the comparison.

object Test1:
def f(x: A)(y: C) = new R1
def f(x: B)(y: A) = new R2
def f(x: B)(y: B) = new R3

val r = f(new B)(new C) // all alternatives are applicable; resolves to: R1 in 3.6, R3 in 3.7
val _: R1 = r
end Test1

object Test2:
// R1 is the only applicable alternative in both parts
// but (in 3.7) it is only resolved to in Part1 by adding (an unapplicable) R3

object Part1:
def f(x: A)(y: A) = new R1
def f(x: B)(y: B) = new R2
def f(x: B)(y: C) = new R3

val r = f(new B)(new A) // resolves to: R1 in 3.6, R1 in 3.7
val _: R1 = r

object Part2:
def f(x: A)(y: A) = new R1
def f(x: B)(y: B) = new R2

val r = f(new B)(new A) // error: resolves to: R2 in 3.6, R1 in 3.7
val _: R2 = r

end Test2
73 changes: 73 additions & 0 deletions tests/pos/i20053.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

trait Summon[R, T <: R]:
type Out
object Summon:
given [R, T <: R]: Summon[R, T] with
type Out = R

sealed class Modifier[+A, +P]
type ModifierAny = Modifier[Any, Any]
sealed trait ISCONST[T <: Boolean]
type CONST = ISCONST[true]

trait DFTypeAny
trait DFBits[W <: Int] extends DFTypeAny
trait DFVal[+T <: DFTypeAny, +M <: ModifierAny]
type DFValAny = DFVal[DFTypeAny, ModifierAny]
type DFValTP[+T <: DFTypeAny, +P] = DFVal[T, Modifier[Any, P]]
type DFConstOf[+T <: DFTypeAny] = DFVal[T, Modifier[Any, CONST]]

trait Candidate[R]:
type OutW <: Int
type OutP
object Candidate:
given [W <: Int, P, R <: DFValTP[DFBits[W], P]]: Candidate[R] with
type OutW = W
type OutP = P

extension [L <: DFValAny](lhs: L)(using icL: Candidate[L])
def ^^^[R](rhs: R)(using
icR: Candidate[R]
): DFValTP[DFBits[icL.OutW], icL.OutP | icR.OutP] = ???
def ^^^ : Unit = ???
extension [L](lhs: L)
def ^^^[RW <: Int, RP](
rhs: DFValTP[DFBits[RW], RP]
)(using es: Summon[L, lhs.type])(using
c: Candidate[L]
)(using check: c.OutW =:= c.OutW): DFValTP[DFBits[c.OutW], c.OutP | RP] = ???

val x: DFConstOf[DFBits[8]] = ???
val zzz = x ^^^ x ^^^ x


object Minimized:

trait Sub[T, R >: T]
given [T, R >: T]: Sub[T, R] with {}

trait Candidate[-R]:
type OutP
given [P]: Candidate[Option[P]] with
type OutP = P

extension [L <: Option[Any]](lhs: L)(using icL: Candidate[L])
def ^^^[R](rhs: R)(using icR: Candidate[R]): Option[icL.OutP | icR.OutP] = ???
def ^^^ : Unit = ???

extension [L](lhs: L)
def ^^^[R](rhs: Option[R])
(using es: Sub[lhs.type, L])
(using c: Candidate[L])
(using check: c.OutP =:= c.OutP): Option[c.OutP | R] = ???

val x: Option[true] = ???
val z1 = x ^^^ x // Ok
val z2 = z1 ^^^ x // Ok
val zzz = x ^^^ x ^^^ x // Error before changes

/* Before the changes, when `def ^^^ : Unit = ???` is present,
* all of z1, z2, zzz attempt to use the last `def ^^^`,
* despite it being less specific than the 1st one.
*/
end Minimized
22 changes: 22 additions & 0 deletions tests/pos/scalatest-overload-3.6.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import scala.language.`3.6`

class TestBody1
class TestBody2

class StartWithWord
class EndWithWord

class Matchers:
extension (leftSideString: String)
def should(body: TestBody1): Unit = ()
def should(body: TestBody2): Unit = ()

extension [T](leftSideValue: T)
def should(word: StartWithWord)(using T <:< String): Unit = ()
def should(word: EndWithWord)(using T <:< String): Unit = ()

def endWith(rightSideString: String): EndWithWord = new EndWithWord

class Test extends Matchers:
def test(): Unit =
"hello world" should endWith ("world") // ok, error in 3.7
23 changes: 23 additions & 0 deletions tests/pos/scalatest-overload-3.7.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import scala.language.`3.7`

class TestBody1
class TestBody2

class StartWithWord
class EndWithWord

class Matchers:
extension (leftSideString: String)
def should(body: TestBody1): Unit = ()
def should(body: TestBody2): Unit = ()

extension [T](leftSideValue: T)
def should(word: StartWithWord)(using T <:< String): Unit = ()
def should(word: EndWithWord)(using T <:< String): Unit = ()

def endWith(rightSideString: String): EndWithWord = new EndWithWord

class Test extends Matchers:
def test(): Unit =
"hello world" should endWith ("world") // no overloading resolution change
// related to tests/warn/multiparamlist-overload-3.7.scala
19 changes: 19 additions & 0 deletions tests/warn/multiparamlist-overload-3.7.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- Warning: tests/warn/multiparamlist-overload-3.7.scala:21:10 ---------------------------------------------------------
21 | val r = f(new B)(new C) // warn: all alternatives are applicable; resolves to: R1 in 3.6, R3 in 3.7
| ^
| Overloading resolution for arguments (B)(C) between alternatives
| (x: B)(y: B): R3
| (x: B)(y: A): R2
| (x: A)(y: C): R1
| has changed.
| Previous choice : (x: A)(y: C): R1
| New choice from Scala 3.7: (x: B)(y: B): R3
-- Warning: tests/warn/multiparamlist-overload-3.7.scala:41:12 ---------------------------------------------------------
41 | val r = f(new B)(new A) // warn: resolves to: R2 in 3.6, R1 in 3.7
| ^
| Overloading resolution for arguments (B)(A) between alternatives
| (x: B)(y: B): R2
| (x: A)(y: A): R1
| has changed.
| Previous choice : (x: B)(y: B): R2
| New choice from Scala 3.7: (x: A)(y: A): R1
44 changes: 44 additions & 0 deletions tests/warn/multiparamlist-overload-3.7.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import scala.language.`3.7-migration`

class A
class B extends A
class C extends B

class R1
class R2
class R3

// In each test, the alternatives are defined from most genereal to most specific,
// with respect to a lexicographic ordering by parameter list.
// In each of the cases tested here, scala-3.7 resolves to the most specific applicable alternative.
// See tests/neg/multiparamlist-overload-3.6.scala for the comparison.

object Test1:
def f(x: A)(y: C) = new R1
def f(x: B)(y: A) = new R2
def f(x: B)(y: B) = new R3

val r = f(new B)(new C) // warn: all alternatives are applicable; resolves to: R1 in 3.6, R3 in 3.7
val _: R3 = r
end Test1

object Test2:
// R1 is the only applicable alternative in both parts
// but (in 3.7) it is only resolved to in Part1 by adding (an unapplicable) R3

object Part1:
def f(x: A)(y: A) = new R1
def f(x: B)(y: B) = new R2
def f(x: B)(y: C) = new R3

val r = f(new B)(new A) // resolves to: R1 in 3.6, R1 in 3.7
val _: R1 = r

object Part2:
def f(x: A)(y: A) = new R1
def f(x: B)(y: B) = new R2

val r = f(new B)(new A) // warn: resolves to: R2 in 3.6, R1 in 3.7
val _: R1 = r

end Test2
Loading