From 9840abb4ec986a407efbbe04eda4d911cd1b229f Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Sun, 31 Mar 2024 12:50:04 +0200 Subject: [PATCH 01/14] Improve overload resolution `resolveOverloaded1` starts with `narrowMostSpecific(candidates)` which compares the alternatives based on the 1st argument list. If more than one result if found, we can continue with the 2nd argument list. But we do so with the original candidates, rather than with the result of `narrowMostSpecific`. This can lead to choosing an alternative which is not the most specific, by now disregarding the 1st argument list. In i120053, the 1st pass correctly eliminated the 3rd `def ^^^`, but could not resolve between the 1st two (having the same argument list). The 2nd pass then disregarded this and restarted the comparison based on the 2nd argument list alone, which incorrectly yielded the 3rd option. The change is simply using the initial result of `narrowMostSpecific` in the recursive resolution based on subsequent argument lists. I'm not sure however if the same changes should apply to the rest of the cases attempting further narrowing ? --- .../dotty/tools/dotc/typer/Applications.scala | 4 +- .../test/dotc/pos-test-pickling.excludelist | 1 + tests/neg/i10901.check | 36 --------- tests/neg/i10901.scala | 4 +- tests/pos/i20053.scala | 79 +++++++++++++++++++ 5 files changed, 84 insertions(+), 40 deletions(-) create mode 100644 tests/pos/i20053.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 98bfbe69ff8c..a50cd90cc776 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2436,11 +2436,11 @@ trait Applications extends Compatibility { deepPt match case pt @ FunProto(_, PolyProto(targs, resType)) => // try to narrow further with snd argument list and following type params - resolveMapped(candidates, + resolveMapped(found, skipParamClause(pt.typedArgs().tpes, targs.tpes), resType) case pt @ FunProto(_, resType: FunOrPolyProto) => // try to narrow further with snd argument list - resolveMapped(candidates, + resolveMapped(found, skipParamClause(pt.typedArgs().tpes, Nil), resType) case _ => // prefer alternatives that need no eta expansion diff --git a/compiler/test/dotc/pos-test-pickling.excludelist b/compiler/test/dotc/pos-test-pickling.excludelist index 23c79affada0..960d3431d8ea 100644 --- a/compiler/test/dotc/pos-test-pickling.excludelist +++ b/compiler/test/dotc/pos-test-pickling.excludelist @@ -126,6 +126,7 @@ i7445b.scala i15525.scala i19955a.scala i19955b.scala +i20053.scala i20053b.scala # alias types at different levels of dereferencing diff --git a/tests/neg/i10901.check b/tests/neg/i10901.check index 325cdccc6aab..45e2e7f763dd 100644 --- a/tests/neg/i10901.check +++ b/tests/neg/i10901.check @@ -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 | ^^^^^^^^^ diff --git a/tests/neg/i10901.scala b/tests/neg/i10901.scala index 996a0753c2e7..da9b09916268 100644 --- a/tests/neg/i10901.scala +++ b/tests/neg/i10901.scala @@ -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 } } diff --git a/tests/pos/i20053.scala b/tests/pos/i20053.scala new file mode 100644 index 000000000000..6c0a3390e0bb --- /dev/null +++ b/tests/pos/i20053.scala @@ -0,0 +1,79 @@ + +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 DFVal[+T <: Int, +P] + + trait Summon[R, T <: R] + given [R, T <: R]: Summon[R, T] with {} + + trait Candidate[R]: + type OutW <: Int + type OutP + given [W <: Int, P, R <: DFVal[W, P]]: Candidate[R] with + type OutW = W + type OutP = P + + extension [L <: DFVal[Int, Any]](lhs: L)(using icL: Candidate[L]) + def ^^^[R](rhs: R) + (using icR: Candidate[R]) + : DFVal[icL.OutW, icL.OutP | icR.OutP] = ??? + def ^^^ : Unit = ??? + + extension [L](lhs: L) + def ^^^[RW <: Int, RP](rhs: DFVal[RW, RP]) + (using es: Summon[L, lhs.type]) + (using c: Candidate[L]) + (using check: c.OutW =:= c.OutW) + : DFVal[c.OutW, c.OutP | RP] = ??? + + val x: DFVal[8, 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 From d4d8cb4ad19e4bd32078e7ac4aff65808e915795 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Sun, 31 Mar 2024 17:39:37 +0200 Subject: [PATCH 02/14] Minimize test case more --- tests/pos/i20053.scala | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/tests/pos/i20053.scala b/tests/pos/i20053.scala index 6c0a3390e0bb..bae639c2a83c 100644 --- a/tests/pos/i20053.scala +++ b/tests/pos/i20053.scala @@ -42,32 +42,26 @@ val zzz = x ^^^ x ^^^ x object Minimized: - trait DFVal[+T <: Int, +P] - trait Summon[R, T <: R] - given [R, T <: R]: Summon[R, T] with {} + trait Sub[T, R >: T] + given [T, R >: T]: Sub[T, R] with {} - trait Candidate[R]: - type OutW <: Int + trait Candidate[-R]: type OutP - given [W <: Int, P, R <: DFVal[W, P]]: Candidate[R] with - type OutW = W + given [P]: Candidate[Option[P]] with type OutP = P - extension [L <: DFVal[Int, Any]](lhs: L)(using icL: Candidate[L]) - def ^^^[R](rhs: R) - (using icR: Candidate[R]) - : DFVal[icL.OutW, icL.OutP | icR.OutP] = ??? + 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 ^^^[RW <: Int, RP](rhs: DFVal[RW, RP]) - (using es: Summon[L, lhs.type]) - (using c: Candidate[L]) - (using check: c.OutW =:= c.OutW) - : DFVal[c.OutW, c.OutP | RP] = ??? + 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: DFVal[8, true] = ??? + val x: Option[true] = ??? val z1 = x ^^^ x // Ok val z2 = z1 ^^^ x // Ok val zzz = x ^^^ x ^^^ x // Error before changes From 94521ebf8dc9429a27be1c9497c0f3e8b1d71bd8 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Tue, 20 Aug 2024 17:30:50 +0200 Subject: [PATCH 03/14] Make overloading resolution changes apply in 3.7 and report warnings in 3.6 This is similar to the warnings for changes in given preference. In this case, however, the changes only affect a part of disambiguation used relatively late in the process, as a "last resort" disambiguation mechanism. We can therefore accept running resolution independently with both schemes in these cases to detect and report changes. Having a source position for the warning messages requires passing the tree source position to resolveOverloaded from the Typer. It could previously be avoided this since any potential error in overloading could be determined from its result. Clearly, this cannot be done for the new warnings, although I am open to an alternative design. --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 68 ++++++++++++++----- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index fdefc14aadd6..4e7761cff98a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -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." + diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index a50cd90cc776..70ece55c6f2c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2116,7 +2116,7 @@ trait Applications extends Compatibility { * Two trials: First, without implicits or SAM conversions enabled. Then, * if the first finds no eligible candidates, with implicits and SAM conversions enabled. */ - def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = + def resolveOverloaded(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = record("resolveOverloaded") /** Is `alt` a method or polytype whose result type after the first value parameter @@ -2154,7 +2154,7 @@ trait Applications extends Compatibility { case Nil => chosen case alt2 :: Nil => alt2 case alts2 => - resolveOverloaded(alts2, pt) match { + resolveOverloaded(alts2, pt, srcPos) match { case alt2 :: Nil => alt2 case _ => chosen } @@ -2169,12 +2169,12 @@ trait Applications extends Compatibility { val alts0 = alts.filterConserve(_.widen.stripPoly.isImplicitMethod) if alts0 ne alts then return resolve(alts0) else if alts.exists(_.widen.stripPoly.isContextualMethod) then - return resolveMapped(alts, alt => stripImplicit(alt.widen), pt) + return resolveMapped(alts, alt => stripImplicit(alt.widen), pt, srcPos) case _ => - var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt)) + var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt, srcPos)) if found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled) then - found = resolveOverloaded1(alts, pt) + found = resolveOverloaded1(alts, pt, srcPos) found match case alt :: Nil => adaptByResult(alt, alts) :: Nil case _ => found @@ -2221,10 +2221,44 @@ trait Applications extends Compatibility { * It might be called twice from the public `resolveOverloaded` method, once with * implicits and SAM conversions enabled, and once without. */ - private def resolveOverloaded1(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = + private def resolveOverloaded1(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = trace(i"resolve over $alts%, %, pt = $pt", typr, show = true) { record(s"resolveOverloaded1", alts.length) + val sv = Feature.sourceVersion + val isOldPriorityVersion: Boolean = sv.isAtMost(SourceVersion.`3.6`) + val isWarnPriorityChangeVersion = sv == SourceVersion.`3.6` || sv == SourceVersion.`3.7-migration` + + inline def warnOnPriorityChange(oldCands: List[TermRef], newCands: List[TermRef])(f: List[TermRef] => List[TermRef]): List[TermRef] = + + def doWarn(oldChoice: String, newChoice: String): Unit = + val (change, whichChoice) = + if isOldPriorityVersion + then ("will change", "Current choice ") + else ("has changed", "Previous choice") + + val msg = // uses oldCands as the list of alternatives since they should be a superset of newCands + em"""Overloading resolution for ${err.expectedTypeStr(pt)} between alternatives + | ${oldCands map (_.info)}%\n % + |$change. + |$whichChoice : $oldChoice + |New choice from Scala 3.7: $newChoice""" + + report.warning(msg, srcPos) + end doWarn + + lazy val oldRes = f(oldCands) + val newRes = f(newCands) + + if isWarnPriorityChangeVersion then (oldRes, newRes) match + case (oldAlt :: Nil, newAlt :: Nil) if oldAlt != newAlt => doWarn(oldAlt.info.show, newAlt.info.show) + case (oldAlt :: Nil, Nil) => doWarn(oldAlt.info.show, "none") + case (Nil, newAlt :: Nil) => doWarn("none", newAlt.info.show) + case _ => // neither scheme has determined an alternative + + if isOldPriorityVersion then oldRes else newRes + end warnOnPriorityChange + def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty /** The shape of given tree as a type; cannot handle named arguments. */ @@ -2372,7 +2406,7 @@ trait Applications extends Compatibility { TypeOps.boundsViolations(targs1, tp.paramInfos, _.substParams(tp, _), NoType).isEmpty val alts2 = alts1.filter(withinBounds) if isDetermined(alts2) then alts2 - else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1) + else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1, srcPos) case pt => val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false)) @@ -2430,18 +2464,18 @@ trait Applications extends Compatibility { candidates else val found = narrowMostSpecific(candidates) - if found.length <= 1 then found + if isDetermined(found) then found else val deepPt = pt.deepenProto deepPt match case pt @ FunProto(_, PolyProto(targs, resType)) => // try to narrow further with snd argument list and following type params - resolveMapped(found, - skipParamClause(pt.typedArgs().tpes, targs.tpes), resType) + warnOnPriorityChange(candidates, found): + resolveMapped(_, skipParamClause(pt.typedArgs().tpes, targs.tpes), resType, srcPos) case pt @ FunProto(_, resType: FunOrPolyProto) => // try to narrow further with snd argument list - resolveMapped(found, - skipParamClause(pt.typedArgs().tpes, Nil), resType) + warnOnPriorityChange(candidates, found): + resolveMapped(_, skipParamClause(pt.typedArgs().tpes, Nil), resType, srcPos) case _ => // prefer alternatives that need no eta expansion val noCurried = alts.filterConserve(!resultIsMethod(_)) @@ -2449,7 +2483,7 @@ trait Applications extends Compatibility { if noCurriedCount == 1 then noCurried else if noCurriedCount > 1 && noCurriedCount < alts.length then - resolveOverloaded1(noCurried, pt) + resolveOverloaded1(noCurried, pt, srcPos) else // prefer alternatves that match without default parameters val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams) @@ -2457,10 +2491,10 @@ trait Applications extends Compatibility { if noDefaultsCount == 1 then noDefaults else if noDefaultsCount > 1 && noDefaultsCount < alts.length then - resolveOverloaded1(noDefaults, pt) + resolveOverloaded1(noDefaults, pt, srcPos) else if deepPt ne pt then // try again with a deeper known expected type - resolveOverloaded1(alts, deepPt) + resolveOverloaded1(alts, deepPt, srcPos) else candidates } @@ -2494,7 +2528,7 @@ trait Applications extends Compatibility { * type is mapped with `f`, alternatives with non-existing types or symbols are dropped, and the * expected type is `pt`. Map the results back to the original alternatives. */ - def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type)(using Context): List[TermRef] = + def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = val reverseMapping = alts.flatMap { alt => val t = f(alt) if t.exists && alt.symbol.exists then @@ -2517,7 +2551,7 @@ trait Applications extends Compatibility { } val mapped = reverseMapping.map(_._1) overload.println(i"resolve mapped: ${mapped.map(_.widen)}%, % with $pt") - resolveOverloaded(mapped, pt)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver)) + resolveOverloaded(mapped, pt, srcPos)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver)) .map(reverseMapping.toMap) /** Try to typecheck any arguments in `pt` that are function values missing a diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8ba63dfc1e67..eb26f58358b2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 => From 6d0548592b4d9733fa57db3516f8ba21e338f609 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Tue, 20 Aug 2024 19:58:59 +0200 Subject: [PATCH 04/14] Test overloading changes and reported warning messages Note that in tests/neg/multiparamlist-overload-3.7 Test2 we now get an error in both Parts as intended. But they are, however, different ones: Part1 is a TypeMismatch Error, whereas Part2 is a NoMatchingOverload Error. This is due to the fact that `resolveOverloaded` will first find the candidate alternatives by considering only the 1st parameter list and commit early if there is a single one, e.g. Test2.Part1. If not, we recursively continue with the found alternatives and recompute the candidate alternatives based on the 2nd parameter list, which may rule out all of them, and hence lead to a different message. --- tests/neg/i10901.check | 36 +++++++++++++++++ tests/neg/i10901.scala | 4 +- tests/neg/multiparamlist-overload-3.6.check | 27 +++++++++++++ tests/neg/multiparamlist-overload-3.6.scala | 43 +++++++++++++++++++++ tests/neg/multiparamlist-overload-3.7.check | 25 ++++++++++++ tests/neg/multiparamlist-overload-3.7.scala | 42 ++++++++++++++++++++ tests/neg/scalatest-overload-3.7.scala | 22 +++++++++++ tests/pos/scalatest-overload-3.6.scala | 22 +++++++++++ 8 files changed, 219 insertions(+), 2 deletions(-) create mode 100755 tests/neg/multiparamlist-overload-3.6.check create mode 100755 tests/neg/multiparamlist-overload-3.6.scala create mode 100755 tests/neg/multiparamlist-overload-3.7.check create mode 100755 tests/neg/multiparamlist-overload-3.7.scala create mode 100644 tests/neg/scalatest-overload-3.7.scala create mode 100644 tests/pos/scalatest-overload-3.6.scala diff --git a/tests/neg/i10901.check b/tests/neg/i10901.check index 45e2e7f763dd..325cdccc6aab 100644 --- a/tests/neg/i10901.check +++ b/tests/neg/i10901.check @@ -1,3 +1,39 @@ +-- [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 | ^^^^^^^^^ diff --git a/tests/neg/i10901.scala b/tests/neg/i10901.scala index da9b09916268..996a0753c2e7 100644 --- a/tests/neg/i10901.scala +++ b/tests/neg/i10901.scala @@ -42,10 +42,10 @@ object BugExp4Point2D { val x = IntT val y = DoubleT - val pos1: Point2D[Int,Double] = x º y // ok + val pos1: Point2D[Int,Double] = x º y // error 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 // ok + val pos4: Point2D[Int,Double] = x º 201.1 // error } } diff --git a/tests/neg/multiparamlist-overload-3.6.check b/tests/neg/multiparamlist-overload-3.6.check new file mode 100755 index 000000000000..ff57b249a7b1 --- /dev/null +++ b/tests/neg/multiparamlist-overload-3.6.check @@ -0,0 +1,27 @@ +-- [E007] Type Mismatch Error: tests/neg/multiparamlist-overload-3.6.scala:33:21 --------------------------------------- +33 | val r = f(new B)(new A) // error since resolves to R2 in 3.6 (and 3.7) as expected + | ^^^^^ + | Found: A + | Required: B + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/neg/multiparamlist-overload-3.6.scala:20:10 ---------------------------------------------------------- +20 | val r = f(new B)(new C) // resolves to R1 in 3.6 + | ^ + | Overloading resolution for arguments (B)(C) between alternatives + | (x: B)(y: B): R3 + | (x: B)(y: A): R2 + | (x: A)(y: C): R1 + | will change. + | Current choice : (x: A)(y: C): R1 + | New choice from Scala 3.7: (x: B)(y: B): R3 +-- Warning: tests/neg/multiparamlist-overload-3.6.scala:40:12 ---------------------------------------------------------- +40 | val r = f(new B)(new A) // resolves to R1 in 3.6 + | ^ + | Overloading resolution for arguments (B)(A) between alternatives + | (x: B)(y: C): R3 + | (x: B)(y: B): R2 + | (x: A)(y: A): R1 + | will change. + | Current choice : (x: A)(y: A): R1 + | New choice from Scala 3.7: none diff --git a/tests/neg/multiparamlist-overload-3.6.scala b/tests/neg/multiparamlist-overload-3.6.scala new file mode 100755 index 000000000000..a54b41398c28 --- /dev/null +++ b/tests/neg/multiparamlist-overload-3.6.scala @@ -0,0 +1,43 @@ +import scala.language.`3.6` + +class A +class B extends A +class C extends B + +class R1 +class R2 +class R3 + +// The alternatives are ordered from most genereal to most specific in each test, +// with respect to a lexicographic ordering by parameter list. + + +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) // resolves to R1 in 3.6 + val _: R1 = r +end Test1 + + +object Test2: + // R1 is the only applicable alternative in both parts + // but it is only resolved to in Part2 by adding (an unapplicable) R3 + + object Part1: + def f(x: A)(y: A) = new R1 + def f(x: B)(y: B) = new R2 + + val r = f(new B)(new A) // error since resolves to R2 in 3.6 (and 3.7) as expected + + object Part2: + 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 + val _: R1 = r + +end Test2 diff --git a/tests/neg/multiparamlist-overload-3.7.check b/tests/neg/multiparamlist-overload-3.7.check new file mode 100755 index 000000000000..af1b502fa26f --- /dev/null +++ b/tests/neg/multiparamlist-overload-3.7.check @@ -0,0 +1,25 @@ +-- [E007] Type Mismatch Error: tests/neg/multiparamlist-overload-3.7.scala:33:21 --------------------------------------- +33 | val r = f(new B)(new A) // error since resolves to R2 in 3.7 (and 3.6), as expected + | ^^^^^ + | Found: A + | Required: B + | + | longer explanation available when compiling with `-explain` +-- [E134] Type Error: tests/neg/multiparamlist-overload-3.7.scala:40:12 ------------------------------------------------ +40 | val r = f(new B)(new A) // error since resolves to R2 in 3.7, as in Part1 + | ^ + | None of the overloaded alternatives of method f in object Part2 with types + | (x: B)(y: C): R3 + | (x: B)(y: B): R2 + | (x: A)(y: A): R1 + | match arguments (B)(A) +-- Warning: tests/neg/multiparamlist-overload-3.7.scala:20:10 ---------------------------------------------------------- +20 | val r = f(new B)(new C) // resolves to 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 diff --git a/tests/neg/multiparamlist-overload-3.7.scala b/tests/neg/multiparamlist-overload-3.7.scala new file mode 100755 index 000000000000..22a1c6ec1b57 --- /dev/null +++ b/tests/neg/multiparamlist-overload-3.7.scala @@ -0,0 +1,42 @@ +import scala.language.`3.7-migration` + +class A +class B extends A +class C extends B + +class R1 +class R2 +class R3 + +// The alternatives are ordered from most genereal to most specific in each test, +// with respect to a lexicographic ordering by parameter list. + + +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) // resolves to R3 in 3.7 + val _: R3 = r +end Test1 + + +object Test2: + // R1 is the only applicable alternative in both parts + // but it is never resolved to since R2 has a more specific 1st parameter list + + object Part1: + def f(x: A)(y: A) = new R1 + def f(x: B)(y: B) = new R2 + + val r = f(new B)(new A) // error since resolves to R2 in 3.7 (and 3.6), as expected + + object Part2: + 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) // error since resolves to R2 in 3.7, as in Part1 + +end Test2 diff --git a/tests/neg/scalatest-overload-3.7.scala b/tests/neg/scalatest-overload-3.7.scala new file mode 100644 index 000000000000..648a9eccaf90 --- /dev/null +++ b/tests/neg/scalatest-overload-3.7.scala @@ -0,0 +1,22 @@ +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") // error diff --git a/tests/pos/scalatest-overload-3.6.scala b/tests/pos/scalatest-overload-3.6.scala new file mode 100644 index 000000000000..9e3bf343867c --- /dev/null +++ b/tests/pos/scalatest-overload-3.6.scala @@ -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 From e8b9cee5c099d905e5464333100fb5ef5a205222 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 21 Aug 2024 17:49:33 +0200 Subject: [PATCH 05/14] Delay overloading priority changes to 3.8 With the warnings about the changes enabled in 3.7 and 3.8-migration --- .../src/dotty/tools/dotc/typer/Applications.scala | 13 ++++++------- tests/neg/multiparamlist-overload-3.6.check | 6 +++--- tests/neg/multiparamlist-overload-3.6.scala | 8 ++++---- tests/neg/multiparamlist-overload-3.7.check | 6 +++--- tests/neg/multiparamlist-overload-3.7.scala | 8 ++++---- tests/neg/scalatest-overload-3.7.scala | 2 +- 6 files changed, 21 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 70ece55c6f2c..33fda876f652 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2226,10 +2226,12 @@ trait Applications extends Compatibility { record(s"resolveOverloaded1", alts.length) val sv = Feature.sourceVersion - val isOldPriorityVersion: Boolean = sv.isAtMost(SourceVersion.`3.6`) - val isWarnPriorityChangeVersion = sv == SourceVersion.`3.6` || sv == SourceVersion.`3.7-migration` + val isOldPriorityVersion: Boolean = sv.isAtMost(SourceVersion.`3.7`) + val isWarnPriorityChangeVersion = sv == SourceVersion.`3.7` || sv == SourceVersion.`3.8-migration` - inline def warnOnPriorityChange(oldCands: List[TermRef], newCands: List[TermRef])(f: List[TermRef] => List[TermRef]): List[TermRef] = + def warnOnPriorityChange(oldCands: List[TermRef], newCands: List[TermRef])(f: List[TermRef] => List[TermRef]): List[TermRef] = + lazy val oldRes = f(oldCands) + val newRes = f(newCands) def doWarn(oldChoice: String, newChoice: String): Unit = val (change, whichChoice) = @@ -2237,7 +2239,7 @@ trait Applications extends Compatibility { then ("will change", "Current choice ") else ("has changed", "Previous choice") - val msg = // uses oldCands as the list of alternatives since they should be a superset of newCands + val msg = // using oldCands to list the alternatives as they should be a superset of newCands em"""Overloading resolution for ${err.expectedTypeStr(pt)} between alternatives | ${oldCands map (_.info)}%\n % |$change. @@ -2247,9 +2249,6 @@ trait Applications extends Compatibility { report.warning(msg, srcPos) end doWarn - lazy val oldRes = f(oldCands) - val newRes = f(newCands) - if isWarnPriorityChangeVersion then (oldRes, newRes) match case (oldAlt :: Nil, newAlt :: Nil) if oldAlt != newAlt => doWarn(oldAlt.info.show, newAlt.info.show) case (oldAlt :: Nil, Nil) => doWarn(oldAlt.info.show, "none") diff --git a/tests/neg/multiparamlist-overload-3.6.check b/tests/neg/multiparamlist-overload-3.6.check index ff57b249a7b1..24854e7cb132 100755 --- a/tests/neg/multiparamlist-overload-3.6.check +++ b/tests/neg/multiparamlist-overload-3.6.check @@ -1,12 +1,12 @@ -- [E007] Type Mismatch Error: tests/neg/multiparamlist-overload-3.6.scala:33:21 --------------------------------------- -33 | val r = f(new B)(new A) // error since resolves to R2 in 3.6 (and 3.7) as expected +33 | val r = f(new B)(new A) // error since resolves to R2 in 3.7 (and 3.8) as expected | ^^^^^ | Found: A | Required: B | | longer explanation available when compiling with `-explain` -- Warning: tests/neg/multiparamlist-overload-3.6.scala:20:10 ---------------------------------------------------------- -20 | val r = f(new B)(new C) // resolves to R1 in 3.6 +20 | val r = f(new B)(new C) // resolves to: R1 in 3.7, R3 in 3.8 | ^ | Overloading resolution for arguments (B)(C) between alternatives | (x: B)(y: B): R3 @@ -16,7 +16,7 @@ | Current choice : (x: A)(y: C): R1 | New choice from Scala 3.7: (x: B)(y: B): R3 -- Warning: tests/neg/multiparamlist-overload-3.6.scala:40:12 ---------------------------------------------------------- -40 | val r = f(new B)(new A) // resolves to R1 in 3.6 +40 | val r = f(new B)(new A) // resolves to: R1 in 3.7, R2 in 3.8 as in Part1 | ^ | Overloading resolution for arguments (B)(A) between alternatives | (x: B)(y: C): R3 diff --git a/tests/neg/multiparamlist-overload-3.6.scala b/tests/neg/multiparamlist-overload-3.6.scala index a54b41398c28..e61d932c18c8 100755 --- a/tests/neg/multiparamlist-overload-3.6.scala +++ b/tests/neg/multiparamlist-overload-3.6.scala @@ -1,4 +1,4 @@ -import scala.language.`3.6` +import scala.language.`3.7` class A class B extends A @@ -17,7 +17,7 @@ object Test1: def f(x: B)(y: A) = new R2 def f(x: B)(y: B) = new R3 - val r = f(new B)(new C) // resolves to R1 in 3.6 + val r = f(new B)(new C) // resolves to: R1 in 3.7, R3 in 3.8 val _: R1 = r end Test1 @@ -30,14 +30,14 @@ object Test2: def f(x: A)(y: A) = new R1 def f(x: B)(y: B) = new R2 - val r = f(new B)(new A) // error since resolves to R2 in 3.6 (and 3.7) as expected + val r = f(new B)(new A) // error since resolves to R2 in 3.7 (and 3.8) as expected object Part2: 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 + val r = f(new B)(new A) // resolves to: R1 in 3.7, R2 in 3.8 as in Part1 val _: R1 = r end Test2 diff --git a/tests/neg/multiparamlist-overload-3.7.check b/tests/neg/multiparamlist-overload-3.7.check index af1b502fa26f..f9508f736889 100755 --- a/tests/neg/multiparamlist-overload-3.7.check +++ b/tests/neg/multiparamlist-overload-3.7.check @@ -1,12 +1,12 @@ -- [E007] Type Mismatch Error: tests/neg/multiparamlist-overload-3.7.scala:33:21 --------------------------------------- -33 | val r = f(new B)(new A) // error since resolves to R2 in 3.7 (and 3.6), as expected +33 | val r = f(new B)(new A) // error, since resolves to R2 in both 3.7 and 3.8, as expected | ^^^^^ | Found: A | Required: B | | longer explanation available when compiling with `-explain` -- [E134] Type Error: tests/neg/multiparamlist-overload-3.7.scala:40:12 ------------------------------------------------ -40 | val r = f(new B)(new A) // error since resolves to R2 in 3.7, as in Part1 +40 | val r = f(new B)(new A) // error since resolves to R2 in 3.8 as in Part1 (was R1 in 3.7) | ^ | None of the overloaded alternatives of method f in object Part2 with types | (x: B)(y: C): R3 @@ -14,7 +14,7 @@ | (x: A)(y: A): R1 | match arguments (B)(A) -- Warning: tests/neg/multiparamlist-overload-3.7.scala:20:10 ---------------------------------------------------------- -20 | val r = f(new B)(new C) // resolves to R3 in 3.7 +20 | val r = f(new B)(new C) // resolves to: R1 in 3.7, R3 in 3.8 | ^ | Overloading resolution for arguments (B)(C) between alternatives | (x: B)(y: B): R3 diff --git a/tests/neg/multiparamlist-overload-3.7.scala b/tests/neg/multiparamlist-overload-3.7.scala index 22a1c6ec1b57..d59b67682cb8 100755 --- a/tests/neg/multiparamlist-overload-3.7.scala +++ b/tests/neg/multiparamlist-overload-3.7.scala @@ -1,4 +1,4 @@ -import scala.language.`3.7-migration` +import scala.language.`3.8-migration` class A class B extends A @@ -17,7 +17,7 @@ object Test1: def f(x: B)(y: A) = new R2 def f(x: B)(y: B) = new R3 - val r = f(new B)(new C) // resolves to R3 in 3.7 + val r = f(new B)(new C) // resolves to: R1 in 3.7, R3 in 3.8 val _: R3 = r end Test1 @@ -30,13 +30,13 @@ object Test2: def f(x: A)(y: A) = new R1 def f(x: B)(y: B) = new R2 - val r = f(new B)(new A) // error since resolves to R2 in 3.7 (and 3.6), as expected + val r = f(new B)(new A) // error, since resolves to R2 in both 3.7 and 3.8, as expected object Part2: 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) // error since resolves to R2 in 3.7, as in Part1 + val r = f(new B)(new A) // error since resolves to R2 in 3.8 as in Part1 (was R1 in 3.7) end Test2 diff --git a/tests/neg/scalatest-overload-3.7.scala b/tests/neg/scalatest-overload-3.7.scala index 648a9eccaf90..9796b526ea48 100644 --- a/tests/neg/scalatest-overload-3.7.scala +++ b/tests/neg/scalatest-overload-3.7.scala @@ -1,4 +1,4 @@ -import scala.language.`3.7` +import scala.language.`3.8` class TestBody1 class TestBody2 From 111b8d04ba2421315bc089ca1c3a2fdf0c90ddb9 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 14 Feb 2025 17:43:30 +0100 Subject: [PATCH 06/14] Make scalatest-overload-3.7.scala a warn test --- tests/warn/scalatest-overload-3.7.check | 13 +++++++++++++ tests/{neg => warn}/scalatest-overload-3.7.scala | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 tests/warn/scalatest-overload-3.7.check rename tests/{neg => warn}/scalatest-overload-3.7.scala (81%) diff --git a/tests/warn/scalatest-overload-3.7.check b/tests/warn/scalatest-overload-3.7.check new file mode 100644 index 000000000000..91874f913f42 --- /dev/null +++ b/tests/warn/scalatest-overload-3.7.check @@ -0,0 +1,13 @@ +-- Warning: tests/warn/scalatest-overload-3.7.scala:22:18 -------------------------------------------------------------- +22 | "hello world" should endWith ("world") // warn: overloading resolution change + | ^^^^^^ + | Overloading resolution for arguments (("hello world" : String))(EndWithWord) between alternatives + | [T](leftSideValue: T)(word: EndWithWord)(using x$3: T <:< String): Unit + | [T](leftSideValue: T)(word: StartWithWord)(using x$3: T <:< String): Unit + | (leftSideString: String)(body: TestBody2): Unit + | (leftSideString: String)(body: TestBody1): Unit + | will change. + | Current choice : [T](leftSideValue: T)(word: EndWithWord)(using x$3: T <:< String): Unit + | New choice from Scala 3.7: none + | + | where: T is a type variable with constraint >: ("hello world" : String) diff --git a/tests/neg/scalatest-overload-3.7.scala b/tests/warn/scalatest-overload-3.7.scala similarity index 81% rename from tests/neg/scalatest-overload-3.7.scala rename to tests/warn/scalatest-overload-3.7.scala index 9796b526ea48..ad0e38b2b692 100644 --- a/tests/neg/scalatest-overload-3.7.scala +++ b/tests/warn/scalatest-overload-3.7.scala @@ -1,4 +1,4 @@ -import scala.language.`3.8` +import scala.language.`3.7` class TestBody1 class TestBody2 @@ -19,4 +19,4 @@ class Matchers: class Test extends Matchers: def test(): Unit = - "hello world" should endWith ("world") // error + "hello world" should endWith ("world") // warn: overloading resolution change From eedd309970f9f8dac81f763bdab4da224f5ea2cd Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 5 Mar 2025 10:47:51 +0100 Subject: [PATCH 07/14] Parametrize `resolveMapped` by the resolution function --- .../dotty/tools/dotc/typer/Applications.scala | 21 +++++++++++-------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 33fda876f652..e6f306cac232 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2169,7 +2169,7 @@ trait Applications extends Compatibility { val alts0 = alts.filterConserve(_.widen.stripPoly.isImplicitMethod) if alts0 ne alts then return resolve(alts0) else if alts.exists(_.widen.stripPoly.isContextualMethod) then - return resolveMapped(alts, alt => stripImplicit(alt.widen), pt, srcPos) + return resolveMapped(alt => stripImplicit(alt.widen))(alts, pt, srcPos) case _ => var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt, srcPos)) @@ -2405,7 +2405,7 @@ trait Applications extends Compatibility { TypeOps.boundsViolations(targs1, tp.paramInfos, _.substParams(tp, _), NoType).isEmpty val alts2 = alts1.filter(withinBounds) if isDetermined(alts2) then alts2 - else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1, srcPos) + else resolveMapped(_.widen.appliedTo(targs1.tpes))(alts1, pt1, srcPos) case pt => val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false)) @@ -2470,11 +2470,11 @@ trait Applications extends Compatibility { case pt @ FunProto(_, PolyProto(targs, resType)) => // try to narrow further with snd argument list and following type params warnOnPriorityChange(candidates, found): - resolveMapped(_, skipParamClause(pt.typedArgs().tpes, targs.tpes), resType, srcPos) + resolveMapped(skipParamClause(pt.typedArgs().tpes, targs.tpes))(_, resType, srcPos) case pt @ FunProto(_, resType: FunOrPolyProto) => // try to narrow further with snd argument list warnOnPriorityChange(candidates, found): - resolveMapped(_, skipParamClause(pt.typedArgs().tpes, Nil), resType, srcPos) + resolveMapped(skipParamClause(pt.typedArgs().tpes, Nil))(_, resType, srcPos) case _ => // prefer alternatives that need no eta expansion val noCurried = alts.filterConserve(!resultIsMethod(_)) @@ -2523,11 +2523,13 @@ trait Applications extends Compatibility { recur(paramss, 0) case _ => (Nil, 0) - /** Resolve overloading by mapping to a different problem where each alternative's - * type is mapped with `f`, alternatives with non-existing types or symbols are dropped, and the - * expected type is `pt`. Map the results back to the original alternatives. + /** Resolve with `g` by mapping to a different problem where each alternative's type + * is mapped with `f`, alternatives with non-existing types or symbols are dropped, + * and the expected type is `pt`. Map the results back to the original alternatives. */ - def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = + def resolveMapped + (f: TermRef => Type, g: (List[TermRef], Type, SrcPos) => Context ?=> List[TermRef] = resolveOverloaded) + (alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = val reverseMapping = alts.flatMap { alt => val t = f(alt) if t.exists && alt.symbol.exists then @@ -2550,8 +2552,9 @@ trait Applications extends Compatibility { } val mapped = reverseMapping.map(_._1) overload.println(i"resolve mapped: ${mapped.map(_.widen)}%, % with $pt") - resolveOverloaded(mapped, pt, srcPos)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver)) + g(mapped, pt, srcPos)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver)) .map(reverseMapping.toMap) + end resolveMapped /** 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/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 85f44ead5f28..ab0807284d18 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -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 From 3f8ef668d460947b7f06fd33405177050e07025d Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 5 Mar 2025 11:19:39 +0100 Subject: [PATCH 08/14] Extract narrowByNextParamClause logic into local def --- .../dotty/tools/dotc/typer/Applications.scala | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index e6f306cac232..ba5d12b0007f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2286,6 +2286,37 @@ trait Applications extends Compatibility { def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] = alts.filterConserve(isApplicableMethodRef(_, argTypes, resultType, ArgMatch.CompatibleCAP)) + def narrowByNextParamClause(alts: List[TermRef], args: List[Tree], resultType: FunOrPolyProto): List[TermRef] = + + /** The type of alternative `alt` after instantiating its first parameter + * clause with `argTypes`. In addition, if the resulting type is a PolyType + * and `typeArgs` matches its parameter list, instantiate the result with `typeArgs`. + */ + def skipParamClause(typeArgs: List[Type])(alt: TermRef): Type = + def skip(tp: Type): Type = tp match + case tp: PolyType => + skip(tp.resultType) match + case NoType => + NoType + case rt: PolyType if typeArgs.length == rt.paramInfos.length => + tp.derivedLambdaType(resType = rt.instantiate(typeArgs)) + case rt => + tp.derivedLambdaType(resType = rt).asInstanceOf[PolyType].flatten + case tp: MethodType => + tp.instantiate(args.tpes) + case _ => + NoType + skip(alt.widen) + + resultType match + case PolyProto(targs, resType) => + // try to narrow further with snd argument list and following type params + resolveMapped(skipParamClause(targs.tpes))(alts, resType, srcPos) + case resType => + // try to narrow further with snd argument list + resolveMapped(skipParamClause(Nil))(alts, resType, srcPos) + end narrowByNextParamClause + /** Normalization steps before checking arguments: * * { expr } --> expr @@ -2431,27 +2462,6 @@ trait Applications extends Compatibility { else compat } - /** The type of alternative `alt` after instantiating its first parameter - * clause with `argTypes`. In addition, if the resulting type is a PolyType - * and `typeArgs` matches its parameter list, instantiate the result with `typeArgs`. - */ - def skipParamClause(argTypes: List[Type], typeArgs: List[Type])(alt: TermRef): Type = - def skip(tp: Type): Type = tp match { - case tp: PolyType => - skip(tp.resultType) match - case NoType => - NoType - case rt: PolyType if typeArgs.length == rt.paramInfos.length => - tp.derivedLambdaType(resType = rt.instantiate(typeArgs)) - case rt => - tp.derivedLambdaType(resType = rt).asInstanceOf[PolyType].flatten - case tp: MethodType => - tp.instantiate(argTypes) - case _ => - NoType - } - skip(alt.widen) - def resultIsMethod(tp: Type): Boolean = tp.widen.stripPoly match case tp: MethodType => stripInferrable(tp.resultType).isInstanceOf[MethodType] case _ => false @@ -2467,14 +2477,9 @@ trait Applications extends Compatibility { else val deepPt = pt.deepenProto deepPt match - case pt @ FunProto(_, PolyProto(targs, resType)) => - // try to narrow further with snd argument list and following type params - warnOnPriorityChange(candidates, found): - resolveMapped(skipParamClause(pt.typedArgs().tpes, targs.tpes))(_, resType, srcPos) case pt @ FunProto(_, resType: FunOrPolyProto) => - // try to narrow further with snd argument list warnOnPriorityChange(candidates, found): - resolveMapped(skipParamClause(pt.typedArgs().tpes, Nil))(_, resType, srcPos) + narrowByNextParamClause(_, pt.typedArgs(), resType) case _ => // prefer alternatives that need no eta expansion val noCurried = alts.filterConserve(!resultIsMethod(_)) From 8c7e8bd4d500ecae855f6d5d5df69da701e089fb Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 5 Mar 2025 12:48:38 +0100 Subject: [PATCH 09/14] Parametrize `resolveOverloaded` by the inner resolve function used for the two trials: without and with implicits and conversions and analogously parametrize `narrowByNextParamClause` --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 41 +++++++++++-------- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 4e7761cff98a..53848cb0b77d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1908,7 +1908,7 @@ object Trees { case MethodTpe(_, _, x: MethodType) => !x.isImplicitMethod case _ => true }} - val alternatives = ctx.typer.resolveOverloaded(allAlts, proto, receiver.srcPos) + 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." + diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index ba5d12b0007f..a43a5e65420c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2115,8 +2115,11 @@ trait Applications extends Compatibility { /** Resolve overloaded alternative `alts`, given expected type `pt`. * Two trials: First, without implicits or SAM conversions enabled. Then, * if the first finds no eligible candidates, with implicits and SAM conversions enabled. + * Each trial applies the `resolve` parameter. */ - def resolveOverloaded(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = + def resolveOverloaded + (resolve: (List[TermRef], Type, SrcPos) => Context ?=> List[TermRef] = resolveOverloaded1) + (alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = record("resolveOverloaded") /** Is `alt` a method or polytype whose result type after the first value parameter @@ -2154,7 +2157,7 @@ trait Applications extends Compatibility { case Nil => chosen case alt2 :: Nil => alt2 case alts2 => - resolveOverloaded(alts2, pt, srcPos) match { + resolveOverloaded(resolve)(alts2, pt, srcPos) match { case alt2 :: Nil => alt2 case _ => chosen } @@ -2162,23 +2165,23 @@ trait Applications extends Compatibility { case _ => chosen } - def resolve(alts: List[TermRef]): List[TermRef] = + def resolve1(alts: List[TermRef]): List[TermRef] = pt match case pt: FunProto => if pt.applyKind == ApplyKind.Using then val alts0 = alts.filterConserve(_.widen.stripPoly.isImplicitMethod) - if alts0 ne alts then return resolve(alts0) + if alts0 ne alts then return resolve1(alts0) else if alts.exists(_.widen.stripPoly.isContextualMethod) then - return resolveMapped(alt => stripImplicit(alt.widen))(alts, pt, srcPos) + return resolveMapped(alt => stripImplicit(alt.widen), resolve)(alts, pt, srcPos) case _ => - var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt, srcPos)) + var found = withoutMode(Mode.ImplicitsEnabled)(resolve(alts, pt, srcPos)) if found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled) then - found = resolveOverloaded1(alts, pt, srcPos) + found = resolve(alts, pt, srcPos) found match case alt :: Nil => adaptByResult(alt, alts) :: Nil case _ => found - end resolve + end resolve1 /** Try an apply method, if * - the result is applied to value arguments and alternative is not a method, or @@ -2211,9 +2214,9 @@ trait Applications extends Compatibility { if (alts.exists(tryApply)) { val expanded = alts.flatMap(applyMembers) - resolve(expanded).map(retract) + resolve1(expanded).map(retract) } - else resolve(alts) + else resolve1(alts) end resolveOverloaded /** This private version of `resolveOverloaded` does the bulk of the work of @@ -2286,7 +2289,9 @@ trait Applications extends Compatibility { def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] = alts.filterConserve(isApplicableMethodRef(_, argTypes, resultType, ArgMatch.CompatibleCAP)) - def narrowByNextParamClause(alts: List[TermRef], args: List[Tree], resultType: FunOrPolyProto): List[TermRef] = + def narrowByNextParamClause + (resolve: (List[TermRef], Type, SrcPos) => Context ?=> List[TermRef]) + (alts: List[TermRef], args: List[Tree], resultType: FunOrPolyProto): List[TermRef] = /** The type of alternative `alt` after instantiating its first parameter * clause with `argTypes`. In addition, if the resulting type is a PolyType @@ -2311,10 +2316,10 @@ trait Applications extends Compatibility { resultType match case PolyProto(targs, resType) => // try to narrow further with snd argument list and following type params - resolveMapped(skipParamClause(targs.tpes))(alts, resType, srcPos) + resolveMapped(skipParamClause(targs.tpes), resolve)(alts, resType, srcPos) case resType => // try to narrow further with snd argument list - resolveMapped(skipParamClause(Nil))(alts, resType, srcPos) + resolveMapped(skipParamClause(Nil), resolve)(alts, resType, srcPos) end narrowByNextParamClause /** Normalization steps before checking arguments: @@ -2479,7 +2484,7 @@ trait Applications extends Compatibility { deepPt match case pt @ FunProto(_, resType: FunOrPolyProto) => warnOnPriorityChange(candidates, found): - narrowByNextParamClause(_, pt.typedArgs(), resType) + narrowByNextParamClause(resolveOverloaded1)(_, pt.typedArgs(), resType) case _ => // prefer alternatives that need no eta expansion val noCurried = alts.filterConserve(!resultIsMethod(_)) @@ -2528,12 +2533,12 @@ trait Applications extends Compatibility { recur(paramss, 0) case _ => (Nil, 0) - /** Resolve with `g` by mapping to a different problem where each alternative's type - * is mapped with `f`, alternatives with non-existing types or symbols are dropped, + /** ResolveOverloaded using `resolve` by mapping to a different problem where each alternative's + * type is mapped with `f`, alternatives with non-existing types or symbols are dropped, * and the expected type is `pt`. Map the results back to the original alternatives. */ def resolveMapped - (f: TermRef => Type, g: (List[TermRef], Type, SrcPos) => Context ?=> List[TermRef] = resolveOverloaded) + (f: TermRef => Type, resolve: (List[TermRef], Type, SrcPos) => Context ?=> List[TermRef]) (alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = val reverseMapping = alts.flatMap { alt => val t = f(alt) @@ -2557,7 +2562,7 @@ trait Applications extends Compatibility { } val mapped = reverseMapping.map(_._1) overload.println(i"resolve mapped: ${mapped.map(_.widen)}%, % with $pt") - g(mapped, pt, srcPos)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver)) + resolveOverloaded(resolve)(mapped, pt, srcPos)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver)) .map(reverseMapping.toMap) end resolveMapped diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index eb26f58358b2..af4aa0d92419 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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, tree.srcPos) match + resolveOverloaded()(alts, pt, tree.srcPos) match case alt :: Nil => readaptSimplified(tree.withType(alt)) case Nil => From b2a2c12f3c10b432c0eae6cd1a7b7433b5160669 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 5 Mar 2025 13:03:09 +0100 Subject: [PATCH 10/14] Resolve candidates by considering all argument lists before starting to narrow most specific --- .../dotty/tools/dotc/typer/Applications.scala | 97 +++++++++++-------- 1 file changed, 58 insertions(+), 39 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index a43a5e65420c..2f9137f2ac2a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2225,7 +2225,7 @@ trait Applications extends Compatibility { * implicits and SAM conversions enabled, and once without. */ private def resolveOverloaded1(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = - trace(i"resolve over $alts%, %, pt = $pt", typr, show = true) { + trace(i"resolve over $alts%, %, pt = $pt", typr, show = true): record(s"resolveOverloaded1", alts.length) val sv = Feature.sourceVersion @@ -2378,7 +2378,10 @@ trait Applications extends Compatibility { case _ => arg end normArg - val candidates = pt match { + // Note it is important not to capture the outer ctx, for when it is passed to resolveMapped + // (through narrowByNextParamClause) which retracts the Mode.SynthesizeExtMethodReceiver from the ctx + // before continuing to `resolveCandidates`. + def resolveCandidates(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = pt match case pt @ FunProto(args, resultType) => val numArgs = args.length def sizeFits(alt: TermRef): Boolean = alt.widen.stripPoly match { @@ -2430,7 +2433,13 @@ trait Applications extends Compatibility { else record("resolveOverloaded.narrowedByShape", alts2.length) pretypeArgs(alts2, pt) - narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType) + val alts3 = narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType) + + resultType.deepenProto match + case resultType: FunOrPolyProto => + narrowByNextParamClause(resolveCandidates)(alts3, pt.typedArgs(), resultType) + case _ => + alts3 case pt @ PolyProto(targs1, pt1) => val alts1 = alts.filterConserve(pt.canInstantiate) @@ -2441,7 +2450,7 @@ trait Applications extends Compatibility { TypeOps.boundsViolations(targs1, tp.paramInfos, _.substParams(tp, _), NoType).isEmpty val alts2 = alts1.filter(withinBounds) if isDetermined(alts2) then alts2 - else resolveMapped(_.widen.appliedTo(targs1.tpes))(alts1, pt1, srcPos) + else resolveMapped(_.widen.appliedTo(targs1.tpes), resolveCandidates)(alts1, pt1, srcPos) case pt => val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false)) @@ -2465,48 +2474,58 @@ trait Applications extends Compatibility { normalize(alt, IgnoredProto(pt)).widenSingleton.isInstanceOf[MethodType]) if convertible.length == 1 then convertible else compat else compat - } + end resolveCandidates def resultIsMethod(tp: Type): Boolean = tp.widen.stripPoly match case tp: MethodType => stripInferrable(tp.resultType).isInstanceOf[MethodType] case _ => false - record("resolveOverloaded.narrowedApplicable", candidates.length) - if pt.unusableForInference then - // `pt` might have become erroneous by typing arguments of FunProtos. - // If `pt` is erroneous, don't try to go further; report the error in `pt` instead. - candidates - else - val found = narrowMostSpecific(candidates) - if isDetermined(found) then found + // Like resolveCandidates, we should not capture the outer ctx parameter. + def resolveOverloaded2(candidates: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = + if pt.unusableForInference then + // `pt` might have become erroneous by typing arguments of FunProtos. + // If `pt` is erroneous, don't try to go further; report the error in `pt` instead. + candidates else - val deepPt = pt.deepenProto - deepPt match - case pt @ FunProto(_, resType: FunOrPolyProto) => - warnOnPriorityChange(candidates, found): - narrowByNextParamClause(resolveOverloaded1)(_, pt.typedArgs(), resType) - case _ => - // prefer alternatives that need no eta expansion - val noCurried = alts.filterConserve(!resultIsMethod(_)) - val noCurriedCount = noCurried.length - if noCurriedCount == 1 then - noCurried - else if noCurriedCount > 1 && noCurriedCount < alts.length then - resolveOverloaded1(noCurried, pt, srcPos) - else - // prefer alternatves that match without default parameters - val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams) - val noDefaultsCount = noDefaults.length - if noDefaultsCount == 1 then - noDefaults - else if noDefaultsCount > 1 && noDefaultsCount < alts.length then - resolveOverloaded1(noDefaults, pt, srcPos) - else if deepPt ne pt then - // try again with a deeper known expected type - resolveOverloaded1(alts, deepPt, srcPos) + val found = narrowMostSpecific(candidates) + if isDetermined(found) then found + else + val deepPt = pt.deepenProto + deepPt match + case pt @ FunProto(_, resType: FunOrPolyProto) => + warnOnPriorityChange(candidates, found): + narrowByNextParamClause(resolveOverloaded1)(_, pt.typedArgs(), resType) + case _ => + // prefer alternatives that need no eta expansion + val noCurried = alts.filterConserve(!resultIsMethod(_)) + val noCurriedCount = noCurried.length + if noCurriedCount == 1 then + noCurried + else if noCurriedCount > 1 && noCurriedCount < alts.length then + resolveOverloaded1(noCurried, pt, srcPos) else - candidates - } + // prefer alternatves that match without default parameters + val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams) + val noDefaultsCount = noDefaults.length + if noDefaultsCount == 1 then + noDefaults + else if noDefaultsCount > 1 && noDefaultsCount < alts.length then + resolveOverloaded1(noDefaults, pt, srcPos) + else if deepPt ne pt then + // try again with a deeper known expected type + resolveOverloaded1(alts, deepPt, srcPos) + else + candidates + end resolveOverloaded2 + + // First, we find the candidates by considering all parameter clauses. + // Second, we determine the most specific again by considering all parameter clauses; + // but restarting from the 1st argument list. + // In both cases, considering subsequent argument lists only narrows the set of alternatives + // (i.e. we do retry from the complete list of alternative mapped onto there next param clause). + val candidates = resolveCandidates(alts, pt, srcPos) + record("resolveOverloaded.narrowedApplicable", candidates.length) + resolveOverloaded2(candidates, pt, srcPos) end resolveOverloaded1 /** Is `formal` a product type which is elementwise compatible with `params`? */ From fab9bbf23baa27987a17836185acfdb805df020d Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 7 Mar 2025 11:24:00 +0100 Subject: [PATCH 11/14] Drop warnings and threading `srcPos` argument through resolution methods It will need to be at the top level with the new proposal anyway --- .../dotty/tools/dotc/typer/Applications.scala | 76 +++++-------------- 1 file changed, 21 insertions(+), 55 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 2f9137f2ac2a..f9e67b155dcf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2118,8 +2118,8 @@ trait Applications extends Compatibility { * Each trial applies the `resolve` parameter. */ def resolveOverloaded - (resolve: (List[TermRef], Type, SrcPos) => Context ?=> List[TermRef] = resolveOverloaded1) - (alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = + (resolve: (List[TermRef], Type) => Context ?=> List[TermRef] = resolveOverloaded1) + (alts: List[TermRef], pt: Type, srcPos: SrcPos = NoSourcePosition)(using Context): List[TermRef] = record("resolveOverloaded") /** Is `alt` a method or polytype whose result type after the first value parameter @@ -2172,12 +2172,12 @@ trait Applications extends Compatibility { val alts0 = alts.filterConserve(_.widen.stripPoly.isImplicitMethod) if alts0 ne alts then return resolve1(alts0) else if alts.exists(_.widen.stripPoly.isContextualMethod) then - return resolveMapped(alt => stripImplicit(alt.widen), resolve)(alts, pt, srcPos) + return resolveMapped(alt => stripImplicit(alt.widen), resolve)(alts, pt) case _ => - var found = withoutMode(Mode.ImplicitsEnabled)(resolve(alts, pt, srcPos)) + var found = withoutMode(Mode.ImplicitsEnabled)(resolve(alts, pt)) if found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled) then - found = resolve(alts, pt, srcPos) + found = resolve(alts, pt) found match case alt :: Nil => adaptByResult(alt, alts) :: Nil case _ => found @@ -2224,43 +2224,10 @@ trait Applications extends Compatibility { * It might be called twice from the public `resolveOverloaded` method, once with * implicits and SAM conversions enabled, and once without. */ - private def resolveOverloaded1(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = + private def resolveOverloaded1(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = trace(i"resolve over $alts%, %, pt = $pt", typr, show = true): record(s"resolveOverloaded1", alts.length) - val sv = Feature.sourceVersion - val isOldPriorityVersion: Boolean = sv.isAtMost(SourceVersion.`3.7`) - val isWarnPriorityChangeVersion = sv == SourceVersion.`3.7` || sv == SourceVersion.`3.8-migration` - - def warnOnPriorityChange(oldCands: List[TermRef], newCands: List[TermRef])(f: List[TermRef] => List[TermRef]): List[TermRef] = - lazy val oldRes = f(oldCands) - val newRes = f(newCands) - - def doWarn(oldChoice: String, newChoice: String): Unit = - val (change, whichChoice) = - if isOldPriorityVersion - then ("will change", "Current choice ") - else ("has changed", "Previous choice") - - val msg = // using oldCands to list the alternatives as they should be a superset of newCands - em"""Overloading resolution for ${err.expectedTypeStr(pt)} between alternatives - | ${oldCands map (_.info)}%\n % - |$change. - |$whichChoice : $oldChoice - |New choice from Scala 3.7: $newChoice""" - - report.warning(msg, srcPos) - end doWarn - - if isWarnPriorityChangeVersion then (oldRes, newRes) match - case (oldAlt :: Nil, newAlt :: Nil) if oldAlt != newAlt => doWarn(oldAlt.info.show, newAlt.info.show) - case (oldAlt :: Nil, Nil) => doWarn(oldAlt.info.show, "none") - case (Nil, newAlt :: Nil) => doWarn("none", newAlt.info.show) - case _ => // neither scheme has determined an alternative - - if isOldPriorityVersion then oldRes else newRes - end warnOnPriorityChange - def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty /** The shape of given tree as a type; cannot handle named arguments. */ @@ -2290,7 +2257,7 @@ trait Applications extends Compatibility { alts.filterConserve(isApplicableMethodRef(_, argTypes, resultType, ArgMatch.CompatibleCAP)) def narrowByNextParamClause - (resolve: (List[TermRef], Type, SrcPos) => Context ?=> List[TermRef]) + (resolve: (List[TermRef], Type) => Context ?=> List[TermRef]) (alts: List[TermRef], args: List[Tree], resultType: FunOrPolyProto): List[TermRef] = /** The type of alternative `alt` after instantiating its first parameter @@ -2316,10 +2283,10 @@ trait Applications extends Compatibility { resultType match case PolyProto(targs, resType) => // try to narrow further with snd argument list and following type params - resolveMapped(skipParamClause(targs.tpes), resolve)(alts, resType, srcPos) + resolveMapped(skipParamClause(targs.tpes), resolve)(alts, resType) case resType => // try to narrow further with snd argument list - resolveMapped(skipParamClause(Nil), resolve)(alts, resType, srcPos) + resolveMapped(skipParamClause(Nil), resolve)(alts, resType) end narrowByNextParamClause /** Normalization steps before checking arguments: @@ -2381,7 +2348,7 @@ trait Applications extends Compatibility { // Note it is important not to capture the outer ctx, for when it is passed to resolveMapped // (through narrowByNextParamClause) which retracts the Mode.SynthesizeExtMethodReceiver from the ctx // before continuing to `resolveCandidates`. - def resolveCandidates(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = pt match + def resolveCandidates(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = pt match case pt @ FunProto(args, resultType) => val numArgs = args.length def sizeFits(alt: TermRef): Boolean = alt.widen.stripPoly match { @@ -2450,7 +2417,7 @@ trait Applications extends Compatibility { TypeOps.boundsViolations(targs1, tp.paramInfos, _.substParams(tp, _), NoType).isEmpty val alts2 = alts1.filter(withinBounds) if isDetermined(alts2) then alts2 - else resolveMapped(_.widen.appliedTo(targs1.tpes), resolveCandidates)(alts1, pt1, srcPos) + else resolveMapped(_.widen.appliedTo(targs1.tpes), resolveCandidates)(alts1, pt1) case pt => val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false)) @@ -2481,7 +2448,7 @@ trait Applications extends Compatibility { case _ => false // Like resolveCandidates, we should not capture the outer ctx parameter. - def resolveOverloaded2(candidates: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = + def resolveOverloaded2(candidates: List[TermRef], pt: Type)(using Context): List[TermRef] = if pt.unusableForInference then // `pt` might have become erroneous by typing arguments of FunProtos. // If `pt` is erroneous, don't try to go further; report the error in `pt` instead. @@ -2493,8 +2460,7 @@ trait Applications extends Compatibility { val deepPt = pt.deepenProto deepPt match case pt @ FunProto(_, resType: FunOrPolyProto) => - warnOnPriorityChange(candidates, found): - narrowByNextParamClause(resolveOverloaded1)(_, pt.typedArgs(), resType) + narrowByNextParamClause(resolveOverloaded1)(found, pt.typedArgs(), resType) case _ => // prefer alternatives that need no eta expansion val noCurried = alts.filterConserve(!resultIsMethod(_)) @@ -2502,7 +2468,7 @@ trait Applications extends Compatibility { if noCurriedCount == 1 then noCurried else if noCurriedCount > 1 && noCurriedCount < alts.length then - resolveOverloaded1(noCurried, pt, srcPos) + resolveOverloaded1(noCurried, pt) else // prefer alternatves that match without default parameters val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams) @@ -2510,10 +2476,10 @@ trait Applications extends Compatibility { if noDefaultsCount == 1 then noDefaults else if noDefaultsCount > 1 && noDefaultsCount < alts.length then - resolveOverloaded1(noDefaults, pt, srcPos) + resolveOverloaded1(noDefaults, pt) else if deepPt ne pt then // try again with a deeper known expected type - resolveOverloaded1(alts, deepPt, srcPos) + resolveOverloaded1(alts, deepPt) else candidates end resolveOverloaded2 @@ -2523,9 +2489,9 @@ trait Applications extends Compatibility { // but restarting from the 1st argument list. // In both cases, considering subsequent argument lists only narrows the set of alternatives // (i.e. we do retry from the complete list of alternative mapped onto there next param clause). - val candidates = resolveCandidates(alts, pt, srcPos) + val candidates = resolveCandidates(alts, pt) record("resolveOverloaded.narrowedApplicable", candidates.length) - resolveOverloaded2(candidates, pt, srcPos) + resolveOverloaded2(candidates, pt) end resolveOverloaded1 /** Is `formal` a product type which is elementwise compatible with `params`? */ @@ -2557,8 +2523,8 @@ trait Applications extends Compatibility { * and the expected type is `pt`. Map the results back to the original alternatives. */ def resolveMapped - (f: TermRef => Type, resolve: (List[TermRef], Type, SrcPos) => Context ?=> List[TermRef]) - (alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = + (f: TermRef => Type, resolve: (List[TermRef], Type) => Context ?=> List[TermRef]) + (alts: List[TermRef], pt: Type)(using Context): List[TermRef] = val reverseMapping = alts.flatMap { alt => val t = f(alt) if t.exists && alt.symbol.exists then @@ -2581,7 +2547,7 @@ trait Applications extends Compatibility { } val mapped = reverseMapping.map(_._1) overload.println(i"resolve mapped: ${mapped.map(_.widen)}%, % with $pt") - resolveOverloaded(resolve)(mapped, pt, srcPos)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver)) + resolveOverloaded(resolve)(mapped, pt)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver)) .map(reverseMapping.toMap) end resolveMapped From 6ebb4c2397419d8b1039130465945d910380649c Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 7 Mar 2025 11:52:23 +0100 Subject: [PATCH 12/14] Fallback to alts before skipParamClause if empty in resolveCandidates `resolvedMapped` applies `resolveOverloaded(resolve)`, _not_ `resolve` directly. This benefits from the insertion of implicit parameters, apply methods, etc. But there are still some adaptations, e.g. auto-tupling, that are not performed at this stage. In those cases, it is possible that we find that no alternatives are applicable. So we fallback to the `alts` we had before considering the next parameter clause. Resolution will succeed (only) if narrowMostSpecific finds an unambiguous alternative by considering (only) the prior argument lists, after which adaptation can be performed. See tests/run/tupled-function-extension-method.scala for an example. We only do this for resolveCandidates and not resolveOverloaded2, because in the latter causes some cases where there are indeed no good alternatives to be reported as ambiguous instead, which is unideal for error messages. --- .../src/dotty/tools/dotc/typer/Applications.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index f9e67b155dcf..433d018b9c71 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2228,7 +2228,9 @@ trait Applications extends Compatibility { trace(i"resolve over $alts%, %, pt = $pt", typr, show = true): record(s"resolveOverloaded1", alts.length) - def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty + extension (self: List[TermRef]) + def isDetermined: Boolean = self.isEmpty || self.tail.isEmpty + inline def fallbackTo(inline alts: List[TermRef]) = if self.nonEmpty then self else alts /** The shape of given tree as a type; cannot handle named arguments. */ def typeShape(tree: untpd.Tree): Type = tree match { @@ -2287,6 +2289,15 @@ trait Applications extends Compatibility { case resType => // try to narrow further with snd argument list resolveMapped(skipParamClause(Nil), resolve)(alts, resType) + + // Note that `resolvedMapped` applies `resolveOverloaded(resolve)`, _not_ `resolve` directly. + // This benefits from the insertion of implicit parameters, apply methods, etc. + // But there are still some adaptations, e.g. auto-tupling, that are not performed at this stage. + // In those cases, it is possible that we find that no alternatives are applicable. + // So, in resolveCandidates, we fallback to the `alts` we had before considering the next parameter clause. + // Resolution will succeed (only) if narrowMostSpecific finds an unambiguous alternative + // by considering (only) the prior argument lists, after which adaptation can be performed. + // See tests/run/tupled-function-extension-method.scala for an example. end narrowByNextParamClause /** Normalization steps before checking arguments: @@ -2405,6 +2416,7 @@ trait Applications extends Compatibility { resultType.deepenProto match case resultType: FunOrPolyProto => narrowByNextParamClause(resolveCandidates)(alts3, pt.typedArgs(), resultType) + .fallbackTo(alts3) // see comment in narrowByNextParamClause case _ => alts3 From 304156eff44ca197b93643715084a08ae0143f8c Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 7 Mar 2025 14:22:00 +0100 Subject: [PATCH 13/14] Update test error annotations and check-file For cases which are now detected as unambiguous! --- tests/neg/i10901.check | 36 ------------------------------------ tests/neg/i10901.scala | 4 ++-- 2 files changed, 2 insertions(+), 38 deletions(-) diff --git a/tests/neg/i10901.check b/tests/neg/i10901.check index 325cdccc6aab..45e2e7f763dd 100644 --- a/tests/neg/i10901.check +++ b/tests/neg/i10901.check @@ -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 | ^^^^^^^^^ diff --git a/tests/neg/i10901.scala b/tests/neg/i10901.scala index 996a0753c2e7..da9b09916268 100644 --- a/tests/neg/i10901.scala +++ b/tests/neg/i10901.scala @@ -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 } } From 9cbbee62a7e5db8e61a1940c84c3045eb4502434 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 7 Mar 2025 14:29:29 +0100 Subject: [PATCH 14/14] Add overloading priority change warnings and documentation --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 82 +++++++++++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg/multiparamlist-overload-3.6.check | 24 +----- tests/neg/multiparamlist-overload-3.6.scala | 21 ++--- tests/neg/multiparamlist-overload-3.7.check | 25 ------ tests/neg/multiparamlist-overload-3.7.scala | 42 ---------- .../scalatest-overload-3.7.scala | 3 +- tests/warn/multiparamlist-overload-3.7.check | 19 +++++ tests/warn/multiparamlist-overload-3.7.scala | 44 ++++++++++ tests/warn/scalatest-overload-3.7.check | 13 --- 11 files changed, 147 insertions(+), 130 deletions(-) delete mode 100755 tests/neg/multiparamlist-overload-3.7.check delete mode 100755 tests/neg/multiparamlist-overload-3.7.scala rename tests/{warn => pos}/scalatest-overload-3.7.scala (78%) create mode 100755 tests/warn/multiparamlist-overload-3.7.check create mode 100755 tests/warn/multiparamlist-overload-3.7.scala delete mode 100644 tests/warn/scalatest-overload-3.7.check diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 53848cb0b77d..4e7761cff98a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1908,7 +1908,7 @@ object Trees { case MethodTpe(_, _, x: MethodType) => !x.isImplicitMethod case _ => true }} - val alternatives = ctx.typer.resolveOverloaded()(allAlts, proto, receiver.srcPos) + 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." + diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 433d018b9c71..ebb3d9efc43b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2112,15 +2112,71 @@ trait Applications extends Compatibility { } } + /** Overloadoing Priority Schemes, changing in `scala-3.7`. + * + * Old Scheme, until 3.7: + * - First, determines the _candidates_ (subset of the alternatives) by: + * + looking at the first paramater clause + * - Second, determine the best candidate by: + * + doing narrowMostSpecific on the first argument list; + * + if still ambigous and there is another parameter clause, then + * restarts narrowMostSpecific by mapping the problem onto the next parameter clause, + * but still considering all candidates. + * + * New Scheme, from 3.7 + * - First, determines the candidates by: + * + looking at first paramater clause; + * + if still no determined and there is another parameter clause, then + * looks at the next argument lists to narrow the candidates (not restarting from alts). + * If that finds no alternative is applicable, fallback to the previous iteration + * (see comment in narrowByNextParamClause for details). + * - Second, determine the best candidate by, restoring all parameter clauses, and: + * + doing narrowMostSpecific on the first argument list; + * + if still ambigous and there is another parameter clause, then + * continue narrowMostSpecific by mapping the problem onto the next parameter clause, + * but only considering the alternatives found until now. + */ + enum ResolveScheme: + case Old + case New + def isNewPriority: Boolean = this == New + + /** Resolve overloaded alternative `alts`, given expected type `pt`. Determines the current + * priority scheme and emits warnings for changes in resolution in migration version. + */ + def resolveOverloaded(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] = + record("resolveOverloaded") + lazy val oldRes = resolveOverloaded(resolveOverloaded1(ResolveScheme.Old))(alts, pt) + lazy val newRes = resolveOverloaded(resolveOverloaded1(ResolveScheme.New))(alts, pt) + + val sv = Feature.sourceVersion + val isNewPriorityVersion = sv.isAtLeast(SourceVersion.`3.7`) + val isWarnPriorityChangeVersion = sv == SourceVersion.`3.7-migration` + + def doWarn(oldChoice: String, newChoice: String): Unit = report.warning( + em"""Overloading resolution for ${err.expectedTypeStr(pt)} between alternatives + | ${alts map (_.info)}%\n % + |has changed. + |Previous choice : $oldChoice + |New choice from Scala 3.7: $newChoice""", srcPos) + + if isWarnPriorityChangeVersion then (oldRes, newRes) match + case (oldAlt :: Nil, newAlt :: Nil) if oldAlt != newAlt => doWarn(oldAlt.info.show, newAlt.info.show) + case (oldAlt :: Nil, Nil) => doWarn(oldAlt.info.show, "none") + case (Nil, newAlt :: Nil) => doWarn("none", newAlt.info.show) + case _ => // neither scheme has determined an alternative + + if isNewPriorityVersion then newRes else oldRes + end resolveOverloaded + /** Resolve overloaded alternative `alts`, given expected type `pt`. * Two trials: First, without implicits or SAM conversions enabled. Then, * if the first finds no eligible candidates, with implicits and SAM conversions enabled. * Each trial applies the `resolve` parameter. */ def resolveOverloaded - (resolve: (List[TermRef], Type) => Context ?=> List[TermRef] = resolveOverloaded1) - (alts: List[TermRef], pt: Type, srcPos: SrcPos = NoSourcePosition)(using Context): List[TermRef] = - record("resolveOverloaded") + (resolve: (List[TermRef], Type) => Context ?=> List[TermRef]) + (alts: List[TermRef], pt: Type)(using Context): List[TermRef] = /** Is `alt` a method or polytype whose result type after the first value parameter * section conforms to the expected type `resultType`? If `resultType` @@ -2157,7 +2213,7 @@ trait Applications extends Compatibility { case Nil => chosen case alt2 :: Nil => alt2 case alts2 => - resolveOverloaded(resolve)(alts2, pt, srcPos) match { + resolveOverloaded(resolve)(alts2, pt) match { case alt2 :: Nil => alt2 case _ => chosen } @@ -2224,7 +2280,7 @@ trait Applications extends Compatibility { * It might be called twice from the public `resolveOverloaded` method, once with * implicits and SAM conversions enabled, and once without. */ - private def resolveOverloaded1(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = + private def resolveOverloaded1(scheme: ResolveScheme)(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = trace(i"resolve over $alts%, %, pt = $pt", typr, show = true): record(s"resolveOverloaded1", alts.length) @@ -2414,7 +2470,7 @@ trait Applications extends Compatibility { val alts3 = narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType) resultType.deepenProto match - case resultType: FunOrPolyProto => + case resultType: FunOrPolyProto if scheme.isNewPriority => narrowByNextParamClause(resolveCandidates)(alts3, pt.typedArgs(), resultType) .fallbackTo(alts3) // see comment in narrowByNextParamClause case _ => @@ -2472,7 +2528,8 @@ trait Applications extends Compatibility { val deepPt = pt.deepenProto deepPt match case pt @ FunProto(_, resType: FunOrPolyProto) => - narrowByNextParamClause(resolveOverloaded1)(found, pt.typedArgs(), resType) + val alts1 = if scheme.isNewPriority then found else candidates + narrowByNextParamClause(resolveOverloaded1(scheme))(alts1, pt.typedArgs(), resType) case _ => // prefer alternatives that need no eta expansion val noCurried = alts.filterConserve(!resultIsMethod(_)) @@ -2480,7 +2537,7 @@ trait Applications extends Compatibility { if noCurriedCount == 1 then noCurried else if noCurriedCount > 1 && noCurriedCount < alts.length then - resolveOverloaded1(noCurried, pt) + resolveOverloaded1(scheme)(noCurried, pt) else // prefer alternatves that match without default parameters val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams) @@ -2488,19 +2545,14 @@ trait Applications extends Compatibility { if noDefaultsCount == 1 then noDefaults else if noDefaultsCount > 1 && noDefaultsCount < alts.length then - resolveOverloaded1(noDefaults, pt) + resolveOverloaded1(scheme)(noDefaults, pt) else if deepPt ne pt then // try again with a deeper known expected type - resolveOverloaded1(alts, deepPt) + resolveOverloaded1(scheme)(alts, deepPt) else candidates end resolveOverloaded2 - // First, we find the candidates by considering all parameter clauses. - // Second, we determine the most specific again by considering all parameter clauses; - // but restarting from the 1st argument list. - // In both cases, considering subsequent argument lists only narrows the set of alternatives - // (i.e. we do retry from the complete list of alternative mapped onto there next param clause). val candidates = resolveCandidates(alts, pt) record("resolveOverloaded.narrowedApplicable", candidates.length) resolveOverloaded2(candidates, pt) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index af4aa0d92419..eb26f58358b2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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, tree.srcPos) match + resolveOverloaded(alts, pt, tree.srcPos) match case alt :: Nil => readaptSimplified(tree.withType(alt)) case Nil => diff --git a/tests/neg/multiparamlist-overload-3.6.check b/tests/neg/multiparamlist-overload-3.6.check index 24854e7cb132..9ad8f1a9a78d 100755 --- a/tests/neg/multiparamlist-overload-3.6.check +++ b/tests/neg/multiparamlist-overload-3.6.check @@ -1,27 +1,7 @@ --- [E007] Type Mismatch Error: tests/neg/multiparamlist-overload-3.6.scala:33:21 --------------------------------------- -33 | val r = f(new B)(new A) // error since resolves to R2 in 3.7 (and 3.8) as expected +-- [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` --- Warning: tests/neg/multiparamlist-overload-3.6.scala:20:10 ---------------------------------------------------------- -20 | val r = f(new B)(new C) // resolves to: R1 in 3.7, R3 in 3.8 - | ^ - | Overloading resolution for arguments (B)(C) between alternatives - | (x: B)(y: B): R3 - | (x: B)(y: A): R2 - | (x: A)(y: C): R1 - | will change. - | Current choice : (x: A)(y: C): R1 - | New choice from Scala 3.7: (x: B)(y: B): R3 --- Warning: tests/neg/multiparamlist-overload-3.6.scala:40:12 ---------------------------------------------------------- -40 | val r = f(new B)(new A) // resolves to: R1 in 3.7, R2 in 3.8 as in Part1 - | ^ - | Overloading resolution for arguments (B)(A) between alternatives - | (x: B)(y: C): R3 - | (x: B)(y: B): R2 - | (x: A)(y: A): R1 - | will change. - | Current choice : (x: A)(y: A): R1 - | New choice from Scala 3.7: none diff --git a/tests/neg/multiparamlist-overload-3.6.scala b/tests/neg/multiparamlist-overload-3.6.scala index e61d932c18c8..b94a2ae55a21 100755 --- a/tests/neg/multiparamlist-overload-3.6.scala +++ b/tests/neg/multiparamlist-overload-3.6.scala @@ -1,4 +1,4 @@ -import scala.language.`3.7` +import scala.language.`3.6` class A class B extends A @@ -8,36 +8,37 @@ class R1 class R2 class R3 -// The alternatives are ordered from most genereal to most specific in each test, +// 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) // resolves to: R1 in 3.7, R3 in 3.8 + 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 it is only resolved to in Part2 by adding (an unapplicable) R3 + // 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) // error since resolves to R2 in 3.7 (and 3.8) as expected + 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 - def f(x: B)(y: C) = new R3 - val r = f(new B)(new A) // resolves to: R1 in 3.7, R2 in 3.8 as in Part1 - val _: R1 = r + val r = f(new B)(new A) // error: resolves to: R2 in 3.6, R1 in 3.7 + val _: R2 = r end Test2 diff --git a/tests/neg/multiparamlist-overload-3.7.check b/tests/neg/multiparamlist-overload-3.7.check deleted file mode 100755 index f9508f736889..000000000000 --- a/tests/neg/multiparamlist-overload-3.7.check +++ /dev/null @@ -1,25 +0,0 @@ --- [E007] Type Mismatch Error: tests/neg/multiparamlist-overload-3.7.scala:33:21 --------------------------------------- -33 | val r = f(new B)(new A) // error, since resolves to R2 in both 3.7 and 3.8, as expected - | ^^^^^ - | Found: A - | Required: B - | - | longer explanation available when compiling with `-explain` --- [E134] Type Error: tests/neg/multiparamlist-overload-3.7.scala:40:12 ------------------------------------------------ -40 | val r = f(new B)(new A) // error since resolves to R2 in 3.8 as in Part1 (was R1 in 3.7) - | ^ - | None of the overloaded alternatives of method f in object Part2 with types - | (x: B)(y: C): R3 - | (x: B)(y: B): R2 - | (x: A)(y: A): R1 - | match arguments (B)(A) --- Warning: tests/neg/multiparamlist-overload-3.7.scala:20:10 ---------------------------------------------------------- -20 | val r = f(new B)(new C) // resolves to: R1 in 3.7, R3 in 3.8 - | ^ - | 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 diff --git a/tests/neg/multiparamlist-overload-3.7.scala b/tests/neg/multiparamlist-overload-3.7.scala deleted file mode 100755 index d59b67682cb8..000000000000 --- a/tests/neg/multiparamlist-overload-3.7.scala +++ /dev/null @@ -1,42 +0,0 @@ -import scala.language.`3.8-migration` - -class A -class B extends A -class C extends B - -class R1 -class R2 -class R3 - -// The alternatives are ordered from most genereal to most specific in each test, -// with respect to a lexicographic ordering by parameter list. - - -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) // resolves to: R1 in 3.7, R3 in 3.8 - val _: R3 = r -end Test1 - - -object Test2: - // R1 is the only applicable alternative in both parts - // but it is never resolved to since R2 has a more specific 1st parameter list - - object Part1: - def f(x: A)(y: A) = new R1 - def f(x: B)(y: B) = new R2 - - val r = f(new B)(new A) // error, since resolves to R2 in both 3.7 and 3.8, as expected - - object Part2: - 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) // error since resolves to R2 in 3.8 as in Part1 (was R1 in 3.7) - -end Test2 diff --git a/tests/warn/scalatest-overload-3.7.scala b/tests/pos/scalatest-overload-3.7.scala similarity index 78% rename from tests/warn/scalatest-overload-3.7.scala rename to tests/pos/scalatest-overload-3.7.scala index ad0e38b2b692..9ee6188829e5 100644 --- a/tests/warn/scalatest-overload-3.7.scala +++ b/tests/pos/scalatest-overload-3.7.scala @@ -19,4 +19,5 @@ class Matchers: class Test extends Matchers: def test(): Unit = - "hello world" should endWith ("world") // warn: overloading resolution change + "hello world" should endWith ("world") // no overloading resolution change + // related to tests/warn/multiparamlist-overload-3.7.scala diff --git a/tests/warn/multiparamlist-overload-3.7.check b/tests/warn/multiparamlist-overload-3.7.check new file mode 100755 index 000000000000..2e0f5a53e233 --- /dev/null +++ b/tests/warn/multiparamlist-overload-3.7.check @@ -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 diff --git a/tests/warn/multiparamlist-overload-3.7.scala b/tests/warn/multiparamlist-overload-3.7.scala new file mode 100755 index 000000000000..8a1e8cafeb1b --- /dev/null +++ b/tests/warn/multiparamlist-overload-3.7.scala @@ -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 diff --git a/tests/warn/scalatest-overload-3.7.check b/tests/warn/scalatest-overload-3.7.check deleted file mode 100644 index 91874f913f42..000000000000 --- a/tests/warn/scalatest-overload-3.7.check +++ /dev/null @@ -1,13 +0,0 @@ --- Warning: tests/warn/scalatest-overload-3.7.scala:22:18 -------------------------------------------------------------- -22 | "hello world" should endWith ("world") // warn: overloading resolution change - | ^^^^^^ - | Overloading resolution for arguments (("hello world" : String))(EndWithWord) between alternatives - | [T](leftSideValue: T)(word: EndWithWord)(using x$3: T <:< String): Unit - | [T](leftSideValue: T)(word: StartWithWord)(using x$3: T <:< String): Unit - | (leftSideString: String)(body: TestBody2): Unit - | (leftSideString: String)(body: TestBody1): Unit - | will change. - | Current choice : [T](leftSideValue: T)(word: EndWithWord)(using x$3: T <:< String): Unit - | New choice from Scala 3.7: none - | - | where: T is a type variable with constraint >: ("hello world" : String)