Skip to content

Commit d439b58

Browse files
EugeneFlesselleWojciechMazur
authored andcommitted
Fix healAmbiguous to compareAlternatives with disambiguate = true
On the final result, compared with all the ambiguous candidates we are trying to recover from. We should still use `disambiguate = false` when filtering the `pending` candidates for the purpose of warnings, as in the other cases. Before the changes, it was possible for an ambiguous SearchFailure to be healed by a candidate which was considered better (possibly only) under a prioritization scheme different from the current one. As an optimization, we can avoid redoing compareAlternatives in versions which could have only used the new prioritization scheme to begin with. Also restores behaviour avoiding false positive warnings. Specifically, in cases where we could report a change in prioritization, despite having not yet done `tryImplicit` on the alternative, i.e. it was only compared as part of an early filtering See #21045 for related changes
1 parent 33d7da8 commit d439b58

File tree

1 file changed

+27
-22
lines changed

1 file changed

+27
-22
lines changed

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

+27-22
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,10 @@ trait Implicits:
13101310
// message if one of the critical candidates is part of the result of the implicit search.
13111311
val priorityChangeWarnings = mutable.ListBuffer[(/*critical:*/ List[TermRef], Message)]()
13121312

1313+
val sv = Feature.sourceVersion
1314+
val isLastOldVersion = sv.stable == SourceVersion.`3.6`
1315+
val isWarnPriorityChangeVersion = isLastOldVersion || sv == SourceVersion.`3.7-migration`
1316+
13131317
/** Compare `alt1` with `alt2` to determine which one should be chosen.
13141318
*
13151319
* @return a number > 0 if `alt1` is preferred over `alt2`
@@ -1333,10 +1337,7 @@ trait Implicits:
13331337
else if alt1.level != alt2.level then alt1.level - alt2.level
13341338
else
13351339
val cmp = comp(using searchContext())
1336-
val sv = Feature.sourceVersion
1337-
val isLastOldVersion = sv.stable == SourceVersion.`3.6`
1338-
val isMigratingVersion = sv == SourceVersion.`3.7-migration`
1339-
if isLastOldVersion || isMigratingVersion then
1340+
if isWarnPriorityChangeVersion then
13401341
val prev = comp(using searchContext().addMode(Mode.OldImplicitResolution))
13411342
if disambiguate && cmp != prev then
13421343
implicits.println(i"PRIORITY CHANGE ${alt1.ref}, ${alt2.ref}")
@@ -1419,15 +1420,7 @@ trait Implicits:
14191420
if diff < 0 then alt2
14201421
else if diff > 0 then alt1
14211422
else SearchFailure(new AmbiguousImplicits(alt1, alt2, pt, argument), span)
1422-
case fail: SearchFailure =>
1423-
fail.reason match
1424-
case ambi: AmbiguousImplicits =>
1425-
if compareAlternatives(ambi.alt1, alt2, disambiguate = true) < 0
1426-
&& compareAlternatives(ambi.alt2, alt2, disambiguate = true) < 0
1427-
then alt2
1428-
else alt1
1429-
case _ =>
1430-
alt2
1423+
case _: SearchFailure => alt2
14311424

14321425
/** Try to find a best matching implicit term among all the candidates in `pending`.
14331426
* @param pending The list of candidates that remain to be tested
@@ -1451,12 +1444,27 @@ trait Implicits:
14511444
pending match {
14521445
case cand :: remaining =>
14531446
/** To recover from an ambiguous implicit failure, we need to find a pending
1454-
* candidate that is strictly better than the failed candidate(s).
1447+
* candidate that is strictly better than the failed `ambiguous` candidate(s).
14551448
* If no such candidate is found, we propagate the ambiguity.
14561449
*/
1457-
def healAmbiguous(fail: SearchFailure, betterThanFailed: Candidate => Boolean) =
1458-
val newPending = remaining.filter(betterThanFailed)
1459-
rank(newPending, fail, Nil).recoverWith(_ => fail)
1450+
def healAmbiguous(fail: SearchFailure, ambiguous: List[RefAndLevel]) =
1451+
def betterThanAmbiguous(newCand: RefAndLevel, disambiguate: Boolean): Boolean =
1452+
ambiguous.forall(compareAlternatives(newCand, _, disambiguate) > 0)
1453+
1454+
inline def betterByCurrentScheme(newCand: RefAndLevel): Boolean =
1455+
if isWarnPriorityChangeVersion then
1456+
// newCand may have only been kept in pending because it was better in the other priotization scheme.
1457+
// If that candidate produces a SearchSuccess, disambiguate will return it as the found SearchResult.
1458+
// We must now recheck it was really better than the ambigous candidates we are recovering from,
1459+
// under the rules of the current scheme, which are applied when disambiguate = true.
1460+
betterThanAmbiguous(newCand, disambiguate = true)
1461+
else true
1462+
1463+
val newPending = remaining.filter(betterThanAmbiguous(_, disambiguate = false))
1464+
rank(newPending, fail, Nil) match
1465+
case found: SearchSuccess if betterByCurrentScheme(found) => found
1466+
case _ => fail
1467+
end healAmbiguous
14601468

14611469
negateIfNot(tryImplicit(cand, contextual)) match {
14621470
case fail: SearchFailure =>
@@ -1471,8 +1479,7 @@ trait Implicits:
14711479
else
14721480
// The ambiguity happened in a nested search: to recover we
14731481
// need a candidate better than `cand`
1474-
healAmbiguous(fail, newCand =>
1475-
compareAlternatives(newCand, cand) > 0)
1482+
healAmbiguous(fail, cand :: Nil)
14761483
else
14771484
// keep only warnings that don't involve the failed candidate reference
14781485
priorityChangeWarnings.filterInPlace: (critical, _) =>
@@ -1491,9 +1498,7 @@ trait Implicits:
14911498
// The ambiguity happened in the current search: to recover we
14921499
// need a candidate better than the two ambiguous alternatives.
14931500
val ambi = fail.reason.asInstanceOf[AmbiguousImplicits]
1494-
healAmbiguous(fail, newCand =>
1495-
compareAlternatives(newCand, ambi.alt1) > 0 &&
1496-
compareAlternatives(newCand, ambi.alt2) > 0)
1501+
healAmbiguous(fail, ambi.alt1 :: ambi.alt2 :: Nil)
14971502
}
14981503
}
14991504
case nil =>

0 commit comments

Comments
 (0)