Skip to content

Commit 94521eb

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 d4d8cb4 commit 94521eb

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
@@ -1908,7 +1908,7 @@ object Trees {
19081908
case MethodTpe(_, _, x: MethodType) => !x.isImplicitMethod
19091909
case _ => true
19101910
}}
1911-
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
1911+
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto, receiver.srcPos)
19121912
assert(alternatives.size == 1,
19131913
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
19141914
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
@@ -2116,7 +2116,7 @@ trait Applications extends Compatibility {
21162116
* Two trials: First, without implicits or SAM conversions enabled. Then,
21172117
* if the first finds no eligible candidates, with implicits and SAM conversions enabled.
21182118
*/
2119-
def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] =
2119+
def resolveOverloaded(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] =
21202120
record("resolveOverloaded")
21212121

21222122
/** Is `alt` a method or polytype whose result type after the first value parameter
@@ -2154,7 +2154,7 @@ trait Applications extends Compatibility {
21542154
case Nil => chosen
21552155
case alt2 :: Nil => alt2
21562156
case alts2 =>
2157-
resolveOverloaded(alts2, pt) match {
2157+
resolveOverloaded(alts2, pt, srcPos) match {
21582158
case alt2 :: Nil => alt2
21592159
case _ => chosen
21602160
}
@@ -2169,12 +2169,12 @@ trait Applications extends Compatibility {
21692169
val alts0 = alts.filterConserve(_.widen.stripPoly.isImplicitMethod)
21702170
if alts0 ne alts then return resolve(alts0)
21712171
else if alts.exists(_.widen.stripPoly.isContextualMethod) then
2172-
return resolveMapped(alts, alt => stripImplicit(alt.widen), pt)
2172+
return resolveMapped(alts, alt => stripImplicit(alt.widen), pt, srcPos)
21732173
case _ =>
21742174

2175-
var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt))
2175+
var found = withoutMode(Mode.ImplicitsEnabled)(resolveOverloaded1(alts, pt, srcPos))
21762176
if found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled) then
2177-
found = resolveOverloaded1(alts, pt)
2177+
found = resolveOverloaded1(alts, pt, srcPos)
21782178
found match
21792179
case alt :: Nil => adaptByResult(alt, alts) :: Nil
21802180
case _ => found
@@ -2221,10 +2221,44 @@ trait Applications extends Compatibility {
22212221
* It might be called twice from the public `resolveOverloaded` method, once with
22222222
* implicits and SAM conversions enabled, and once without.
22232223
*/
2224-
private def resolveOverloaded1(alts: List[TermRef], pt: Type)(using Context): List[TermRef] =
2224+
private def resolveOverloaded1(alts: List[TermRef], pt: Type, srcPos: SrcPos)(using Context): List[TermRef] =
22252225
trace(i"resolve over $alts%, %, pt = $pt", typr, show = true) {
22262226
record(s"resolveOverloaded1", alts.length)
22272227

2228+
val sv = Feature.sourceVersion
2229+
val isOldPriorityVersion: Boolean = sv.isAtMost(SourceVersion.`3.6`)
2230+
val isWarnPriorityChangeVersion = sv == SourceVersion.`3.6` || sv == SourceVersion.`3.7-migration`
2231+
2232+
inline def warnOnPriorityChange(oldCands: List[TermRef], newCands: List[TermRef])(f: List[TermRef] => List[TermRef]): List[TermRef] =
2233+
2234+
def doWarn(oldChoice: String, newChoice: String): Unit =
2235+
val (change, whichChoice) =
2236+
if isOldPriorityVersion
2237+
then ("will change", "Current choice ")
2238+
else ("has changed", "Previous choice")
2239+
2240+
val msg = // uses oldCands as the list of alternatives since they should be a superset of newCands
2241+
em"""Overloading resolution for ${err.expectedTypeStr(pt)} between alternatives
2242+
| ${oldCands map (_.info)}%\n %
2243+
|$change.
2244+
|$whichChoice : $oldChoice
2245+
|New choice from Scala 3.7: $newChoice"""
2246+
2247+
report.warning(msg, srcPos)
2248+
end doWarn
2249+
2250+
lazy val oldRes = f(oldCands)
2251+
val newRes = f(newCands)
2252+
2253+
if isWarnPriorityChangeVersion then (oldRes, newRes) match
2254+
case (oldAlt :: Nil, newAlt :: Nil) if oldAlt != newAlt => doWarn(oldAlt.info.show, newAlt.info.show)
2255+
case (oldAlt :: Nil, Nil) => doWarn(oldAlt.info.show, "none")
2256+
case (Nil, newAlt :: Nil) => doWarn("none", newAlt.info.show)
2257+
case _ => // neither scheme has determined an alternative
2258+
2259+
if isOldPriorityVersion then oldRes else newRes
2260+
end warnOnPriorityChange
2261+
22282262
def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty
22292263

22302264
/** The shape of given tree as a type; cannot handle named arguments. */
@@ -2372,7 +2406,7 @@ trait Applications extends Compatibility {
23722406
TypeOps.boundsViolations(targs1, tp.paramInfos, _.substParams(tp, _), NoType).isEmpty
23732407
val alts2 = alts1.filter(withinBounds)
23742408
if isDetermined(alts2) then alts2
2375-
else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1)
2409+
else resolveMapped(alts1, _.widen.appliedTo(targs1.tpes), pt1, srcPos)
23762410

23772411
case pt =>
23782412
val compat = alts.filterConserve(normalizedCompatible(_, pt, keepConstraint = false))
@@ -2430,37 +2464,37 @@ trait Applications extends Compatibility {
24302464
candidates
24312465
else
24322466
val found = narrowMostSpecific(candidates)
2433-
if found.length <= 1 then found
2467+
if isDetermined(found) then found
24342468
else
24352469
val deepPt = pt.deepenProto
24362470
deepPt match
24372471
case pt @ FunProto(_, PolyProto(targs, resType)) =>
24382472
// try to narrow further with snd argument list and following type params
2439-
resolveMapped(found,
2440-
skipParamClause(pt.typedArgs().tpes, targs.tpes), resType)
2473+
warnOnPriorityChange(candidates, found):
2474+
resolveMapped(_, skipParamClause(pt.typedArgs().tpes, targs.tpes), resType, srcPos)
24412475
case pt @ FunProto(_, resType: FunOrPolyProto) =>
24422476
// try to narrow further with snd argument list
2443-
resolveMapped(found,
2444-
skipParamClause(pt.typedArgs().tpes, Nil), resType)
2477+
warnOnPriorityChange(candidates, found):
2478+
resolveMapped(_, skipParamClause(pt.typedArgs().tpes, Nil), resType, srcPos)
24452479
case _ =>
24462480
// prefer alternatives that need no eta expansion
24472481
val noCurried = alts.filterConserve(!resultIsMethod(_))
24482482
val noCurriedCount = noCurried.length
24492483
if noCurriedCount == 1 then
24502484
noCurried
24512485
else if noCurriedCount > 1 && noCurriedCount < alts.length then
2452-
resolveOverloaded1(noCurried, pt)
2486+
resolveOverloaded1(noCurried, pt, srcPos)
24532487
else
24542488
// prefer alternatves that match without default parameters
24552489
val noDefaults = alts.filterConserve(!_.symbol.hasDefaultParams)
24562490
val noDefaultsCount = noDefaults.length
24572491
if noDefaultsCount == 1 then
24582492
noDefaults
24592493
else if noDefaultsCount > 1 && noDefaultsCount < alts.length then
2460-
resolveOverloaded1(noDefaults, pt)
2494+
resolveOverloaded1(noDefaults, pt, srcPos)
24612495
else if deepPt ne pt then
24622496
// try again with a deeper known expected type
2463-
resolveOverloaded1(alts, deepPt)
2497+
resolveOverloaded1(alts, deepPt, srcPos)
24642498
else
24652499
candidates
24662500
}
@@ -2494,7 +2528,7 @@ trait Applications extends Compatibility {
24942528
* type is mapped with `f`, alternatives with non-existing types or symbols are dropped, and the
24952529
* expected type is `pt`. Map the results back to the original alternatives.
24962530
*/
2497-
def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type)(using Context): List[TermRef] =
2531+
def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type, srcPos: SrcPos)(using Context): List[TermRef] =
24982532
val reverseMapping = alts.flatMap { alt =>
24992533
val t = f(alt)
25002534
if t.exists && alt.symbol.exists then
@@ -2517,7 +2551,7 @@ trait Applications extends Compatibility {
25172551
}
25182552
val mapped = reverseMapping.map(_._1)
25192553
overload.println(i"resolve mapped: ${mapped.map(_.widen)}%, % with $pt")
2520-
resolveOverloaded(mapped, pt)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver))
2554+
resolveOverloaded(mapped, pt, srcPos)(using ctx.retractMode(Mode.SynthesizeExtMethodReceiver))
25212555
.map(reverseMapping.toMap)
25222556

25232557
/** 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
@@ -4108,7 +4108,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
41084108
def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt)
41094109
val alts = altDenots.map(altRef)
41104110

4111-
resolveOverloaded(alts, pt) match
4111+
resolveOverloaded(alts, pt, tree.srcPos) match
41124112
case alt :: Nil =>
41134113
readaptSimplified(tree.withType(alt))
41144114
case Nil =>

0 commit comments

Comments
 (0)