Skip to content

Commit 2e0cff0

Browse files
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.
1 parent 04a59ae commit 2e0cff0

File tree

3 files changed

+53
-19
lines changed

3 files changed

+53
-19
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1905,7 +1905,7 @@ object Trees {
19051905
case MethodTpe(_, _, x: MethodType) => !x.isImplicitMethod
19061906
case _ => true
19071907
}}
1908-
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
1908+
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto, receiver.srcPos)
19091909
assert(alternatives.size == 1,
19101910
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
19111911
i"$method on ${receiver.tpe.widenDealiasKeepAnnots} with targs: $targs%, %; args: $args%, %; expectedType: $expectedType." +

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,7 +2062,7 @@ trait Applications extends Compatibility {
20622062
* Two trials: First, without implicits or SAM conversions enabled. Then,
20632063
* if the first finds no eligible candidates, with implicits and SAM conversions enabled.
20642064
*/
2065-
def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] =
2065+
def resolveOverloaded(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] =
20662066
record("resolveOverloaded")
20672067

20682068
/** Is `alt` a method or polytype whose result type after the first value parameter
@@ -2100,7 +2100,7 @@ trait Applications extends Compatibility {
21002100
case Nil => chosen
21012101
case alt2 :: Nil => alt2
21022102
case alts2 =>
2103-
resolveOverloaded(alts2, pt) match {
2103+
resolveOverloaded(alts2, pt, srcPos) match {
21042104
case alt2 :: Nil => alt2
21052105
case _ => chosen
21062106
}
@@ -2115,12 +2115,12 @@ trait Applications extends Compatibility {
21152115
val alts0 = alts.filterConserve(_.widen.stripPoly.isImplicitMethod)
21162116
if alts0 ne alts then return resolve(alts0)
21172117
else if alts.exists(_.widen.stripPoly.isContextualMethod) then
2118-
return resolveMapped(alts, alt => stripImplicit(alt.widen), pt)
2118+
return resolveMapped(alts, alt => stripImplicit(alt.widen), pt, srcPos)
21192119
case _ =>
21202120

2121-
var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt))
2121+
var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt, srcPos))
21222122
if found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled) then
2123-
found = resolveOverloaded1(alts, pt)
2123+
found = resolveOverloaded1(alts, pt, srcPos)
21242124
found match
21252125
case alt :: Nil => adaptByResult(alt, alts) :: Nil
21262126
case _ => found
@@ -2167,10 +2167,44 @@ trait Applications extends Compatibility {
21672167
* It might be called twice from the public `resolveOverloaded` method, once with
21682168
* implicits and SAM conversions enabled, and once without.
21692169
*/
2170-
private def resolveOverloaded1(alts: List[TermRef], pt: Type)(using Context): List[TermRef] =
2170+
private def resolveOverloaded1(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] =
21712171
trace(i"resolve over $alts%, %, pt = $pt", typr, show = true) {
21722172
record(s"resolveOverloaded1", alts.length)
21732173

2174+
val sv = Feature.sourceVersion
2175+
val isOldPriorityVersion: Boolean = sv.isAtMost(SourceVersion.`3.6`)
2176+
val isWarnPriorityChangeVersion = sv == SourceVersion.`3.6` || sv == SourceVersion.`3.7-migration`
2177+
2178+
inline def warnOnPriorityChange(oldCands: List[TermRef], newCands: List[TermRef])(f: List[TermRef] => List[TermRef]): List[TermRef] =
2179+
2180+
def doWarn(oldChoice: String, newChoice: String): Unit =
2181+
val (change, whichChoice) =
2182+
if isOldPriorityVersion
2183+
then ("will change", "Current choice ")
2184+
else ("has changed", "Previous choice")
2185+
2186+
val msg = // uses oldCands as the list of alternatives since they should be a superset of newCands
2187+
em"""Overloading resolution for ${err.expectedTypeStr(pt)} between alternatives
2188+
| ${oldCands map (_.info)}%\n %
2189+
|$change.
2190+
|$whichChoice : $oldChoice
2191+
|New choice from Scala 3.7: $newChoice"""
2192+
2193+
report.warning(msg, srcPos)
2194+
end doWarn
2195+
2196+
lazy val oldRes = f(oldCands)
2197+
val newRes = f(newCands)
2198+
2199+
if isWarnPriorityChangeVersion then (oldRes, newRes) match
2200+
case (oldAlt :: Nil, newAlt :: Nil) if oldAlt != newAlt => doWarn(oldAlt.info.show, newAlt.info.show)
2201+
case (oldAlt :: Nil, Nil) => doWarn(oldAlt.info.show, "none")
2202+
case (Nil, newAlt :: Nil) => doWarn("none", newAlt.info.show)
2203+
case _ => // neither scheme has determined an alternative
2204+
2205+
if isOldPriorityVersion then oldRes else newRes
2206+
end warnOnPriorityChange
2207+
21742208
def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty
21752209

21762210
/** The shape of given tree as a type; cannot handle named arguments. */
@@ -2299,7 +2333,7 @@ trait Applications extends Compatibility {
22992333
TypeOps.boundsViolations(targs1, tp.paramInfos, _.substParams(tp, _), NoType).isEmpty
23002334
val alts2 = alts1.filter(withinBounds)
23012335
if isDetermined(alts2) then alts2
2302-
else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1)
2336+
else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1, srcPos)
23032337

23042338
case pt =>
23052339
val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false))
@@ -2357,37 +2391,37 @@ trait Applications extends Compatibility {
23572391
candidates
23582392
else
23592393
val found = narrowMostSpecific(candidates)
2360-
if found.length <= 1 then found
2394+
if isDetermined(found) then found
23612395
else
23622396
val deepPt = pt.deepenProto
23632397
deepPt match
23642398
case pt @ FunProto(_, PolyProto(targs, resType)) =>
23652399
// try to narrow further with snd argument list and following type params
2366-
resolveMapped(found,
2367-
skipParamClause(pt.typedArgs().tpes, targs.tpes), resType)
2400+
warnOnPriorityChange(candidates, found):
2401+
resolveMapped(_, skipParamClause(pt.typedArgs().tpes, targs.tpes), resType, srcPos)
23682402
case pt @ FunProto(_, resType: FunOrPolyProto) =>
23692403
// try to narrow further with snd argument list
2370-
resolveMapped(found,
2371-
skipParamClause(pt.typedArgs().tpes, Nil), resType)
2404+
warnOnPriorityChange(candidates, found):
2405+
resolveMapped(_, skipParamClause(pt.typedArgs().tpes, Nil), resType, srcPos)
23722406
case _ =>
23732407
// prefer alternatives that need no eta expansion
23742408
val noCurried = alts.filterConserve(!resultIsMethod(_))
23752409
val noCurriedCount = noCurried.length
23762410
if noCurriedCount == 1 then
23772411
noCurried
23782412
else if noCurriedCount > 1 && noCurriedCount < alts.length then
2379-
resolveOverloaded1(noCurried, pt)
2413+
resolveOverloaded1(noCurried, pt, srcPos)
23802414
else
23812415
// prefer alternatves that match without default parameters
23822416
val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams)
23832417
val noDefaultsCount = noDefaults.length
23842418
if noDefaultsCount == 1 then
23852419
noDefaults
23862420
else if noDefaultsCount > 1 && noDefaultsCount < alts.length then
2387-
resolveOverloaded1(noDefaults, pt)
2421+
resolveOverloaded1(noDefaults, pt, srcPos)
23882422
else if deepPt ne pt then
23892423
// try again with a deeper known expected type
2390-
resolveOverloaded1(alts, deepPt)
2424+
resolveOverloaded1(alts, deepPt, srcPos)
23912425
else
23922426
candidates
23932427
}
@@ -2414,7 +2448,7 @@ trait Applications extends Compatibility {
24142448
* type is mapped with `f`, alternatives with non-existing types or symbols are dropped, and the
24152449
* expected type is `pt`. Map the results back to the original alternatives.
24162450
*/
2417-
def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type)(using Context): List[TermRef] =
2451+
def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type, srcPos: SrcPos)(using Context): List[TermRef] =
24182452
val reverseMapping = alts.flatMap { alt =>
24192453
val t = f(alt)
24202454
if t.exists && alt.symbol.exists then
@@ -2437,7 +2471,7 @@ trait Applications extends Compatibility {
24372471
}
24382472
val mapped = reverseMapping.map(_._1)
24392473
overload.println(i"resolve mapped: ${mapped.map(_.widen)}%, % with $pt")
2440-
resolveOverloaded(mapped, pt)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver))
2474+
resolveOverloaded(mapped, pt, srcPos)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver))
24412475
.map(reverseMapping.toMap)
24422476

24432477
/** Try to typecheck any arguments in `pt` that are function values missing a

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4022,7 +4022,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
40224022
def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt)
40234023
val alts = altDenots.map(altRef)
40244024

4025-
resolveOverloaded(alts, pt) match
4025+
resolveOverloaded(alts, pt, tree.srcPos) match
40264026
case alt :: Nil =>
40274027
readaptSimplified(tree.withType(alt))
40284028
case Nil =>

0 commit comments

Comments
 (0)