From 9840abb4ec986a407efbbe04eda4d911cd1b229f Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Sun, 31 Mar 2024 12:50:04 +0200 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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