Skip to content

Commit 0e36424

Browse files
authored
Refine implicit priority change warnings (#21045)
Fixes #21036 Fixes #20572
2 parents 2d0e373 + 1474e69 commit 0e36424

14 files changed

+82
-37
lines changed

Diff for: compiler/src/dotty/tools/dotc/typer/Implicits.scala

+26-17
Original file line numberDiff line numberDiff line change
@@ -1302,9 +1302,11 @@ trait Implicits:
13021302

13031303
// A map that associates a priority change warning (between -source 3.4 and 3.6)
13041304
// with the candidate refs mentioned in the warning. We report the associated
1305-
// message if both candidates qualify in tryImplicit and at least one of the candidates
1306-
// is part of the result of the implicit search.
1307-
val priorityChangeWarnings = mutable.ListBuffer[(TermRef, TermRef, Message)]()
1305+
// message if one of the critical candidates is part of the result of the implicit search.
1306+
val priorityChangeWarnings = mutable.ListBuffer[(/*critical:*/ List[TermRef], Message)]()
1307+
1308+
def isWarnPriorityChangeVersion(sv: SourceVersion): Boolean =
1309+
sv.stable == SourceVersion.`3.5` || sv == SourceVersion.`3.6-migration`
13081310

13091311
/** Compare `alt1` with `alt2` to determine which one should be chosen.
13101312
*
@@ -1319,19 +1321,24 @@ trait Implicits:
13191321
* return new result with preferGeneral = true
13201322
* 3.6 and higher: compare with preferGeneral = true
13211323
*
1324+
* @param disambiguate The call is used to disambiguate two successes, not for ranking.
1325+
* When ranking, we are always filtering out either > 0 or <= 0 results.
1326+
* In each case a priority change from 0 to -1 or vice versa makes no difference.
13221327
*/
1323-
def compareAlternatives(alt1: RefAndLevel, alt2: RefAndLevel): Int =
1328+
def compareAlternatives(alt1: RefAndLevel, alt2: RefAndLevel, disambiguate: Boolean = false): Int =
13241329
def comp(using Context) = explore(compare(alt1.ref, alt2.ref, preferGeneral = true))
1325-
def warn(msg: Message) =
1326-
priorityChangeWarnings += ((alt1.ref, alt2.ref, msg))
13271330
if alt1.ref eq alt2.ref then 0
13281331
else if alt1.level != alt2.level then alt1.level - alt2.level
13291332
else
13301333
var cmp = comp(using searchContext())
13311334
val sv = Feature.sourceVersion
1332-
if sv.stable == SourceVersion.`3.5` || sv == SourceVersion.`3.6-migration` then
1335+
if isWarnPriorityChangeVersion(sv) then
13331336
val prev = comp(using searchContext().addMode(Mode.OldImplicitResolution))
1334-
if cmp != prev then
1337+
if disambiguate && cmp != prev then
1338+
def warn(msg: Message) =
1339+
val critical = alt1.ref :: alt2.ref :: Nil
1340+
priorityChangeWarnings += ((critical, msg))
1341+
implicits.println(i"PRIORITY CHANGE ${alt1.ref}, ${alt2.ref}, $disambiguate")
13351342
def choice(c: Int) = c match
13361343
case -1 => "the second alternative"
13371344
case 1 => "the first alternative"
@@ -1348,7 +1355,9 @@ trait Implicits:
13481355
|Previous choice : ${choice(prev)}
13491356
|New choice from Scala 3.6: ${choice(cmp)}""")
13501357
cmp
1351-
else cmp
1358+
else cmp max prev
1359+
// When ranking, we keep the better of cmp and prev, which ends up retaining a candidate
1360+
// if it is retained in either version.
13521361
else cmp
13531362
end compareAlternatives
13541363

@@ -1358,8 +1367,9 @@ trait Implicits:
13581367
*/
13591368
def disambiguate(alt1: SearchResult, alt2: SearchSuccess) = alt1 match
13601369
case alt1: SearchSuccess =>
1361-
var diff = compareAlternatives(alt1, alt2)
1362-
assert(diff <= 0) // diff > 0 candidates should already have been eliminated in `rank`
1370+
var diff = compareAlternatives(alt1, alt2, disambiguate = true)
1371+
assert(diff <= 0 || isWarnPriorityChangeVersion(Feature.sourceVersion))
1372+
// diff > 0 candidates should already have been eliminated in `rank`
13631373
if diff == 0 && alt1.ref =:= alt2.ref then
13641374
diff = 1 // See i12951 for a test where this happens
13651375
else if diff == 0 && alt2.isExtension then
@@ -1443,8 +1453,8 @@ trait Implicits:
14431453
compareAlternatives(newCand, cand) > 0)
14441454
else
14451455
// keep only warnings that don't involve the failed candidate reference
1446-
priorityChangeWarnings.filterInPlace: (ref1, ref2, _) =>
1447-
ref1 != cand.ref && ref2 != cand.ref
1456+
priorityChangeWarnings.filterInPlace: (critical, _) =>
1457+
!critical.contains(cand.ref)
14481458
rank(remaining, found, fail :: rfailures)
14491459
case best: SearchSuccess =>
14501460
if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent)
@@ -1453,8 +1463,7 @@ trait Implicits:
14531463
case retained: SearchSuccess =>
14541464
val newPending =
14551465
if (retained eq found) || remaining.isEmpty then remaining
1456-
else remaining.filterConserve(cand =>
1457-
compareAlternatives(retained, cand) <= 0)
1466+
else remaining.filterConserve(newCand => compareAlternatives(newCand, retained) >= 0)
14581467
rank(newPending, retained, rfailures)
14591468
case fail: SearchFailure =>
14601469
// The ambiguity happened in the current search: to recover we
@@ -1601,8 +1610,8 @@ trait Implicits:
16011610
throw ex
16021611

16031612
val result = rank(sort(eligible), NoMatchingImplicitsFailure, Nil)
1604-
for (ref1, ref2, msg) <- priorityChangeWarnings do
1605-
if result.found.exists(ref => ref == ref1 || ref == ref2) then
1613+
for (critical, msg) <- priorityChangeWarnings do
1614+
if result.found.exists(critical.contains(_)) then
16061615
report.warning(msg, srcPos)
16071616
result
16081617
end searchImplicit

Diff for: tests/neg/given-triangle.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- [E172] Type Error: tests/neg/given-triangle.scala:15:18 -------------------------------------------------------------
2+
15 |@main def Test = f // error
3+
| ^
4+
|Ambiguous given instances: both given instance given_B and given instance given_C match type A of parameter a of method f

Diff for: tests/warn/given-triangle.scala renamed to tests/neg/given-triangle.scala

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
//> using options -source 3.6-migration
2-
1+
//> using options -source 3.5
32
class A
43
class B extends A
54
class C extends A
@@ -13,4 +12,4 @@ def f(using a: A, b: B, c: C) =
1312
println(b.getClass)
1413
println(c.getClass)
1514

16-
@main def Test = f // warn
15+
@main def Test = f // error
File renamed without changes.
File renamed without changes.

Diff for: tests/pos/i20572.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//> using options -Werror
2+
trait Writes[T]
3+
trait Format[T] extends Writes[T]
4+
given [T: List]: Writes[T] = null
5+
given [T]: Format[T] = null
6+
7+
val _ = summon[Writes[Int]]

Diff for: tests/pos/i21036.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//> using options -source 3.5 -Werror
2+
trait SameRuntime[A, B]
3+
trait BSONWriter[T]
4+
trait BSONHandler[T] extends BSONWriter[T]
5+
6+
opaque type Id = String
7+
object Id:
8+
given SameRuntime[Id, String] = ???
9+
10+
given BSONHandler[String] = ???
11+
given [T: BSONHandler]: BSONHandler[List[T]] = ???
12+
13+
given opaqueWriter[T, A](using rs: SameRuntime[T, A], writer: BSONWriter[A]): BSONWriter[T] = ???
14+
15+
val x = summon[BSONHandler[List[Id]]] // this doesn't emit warning
16+
val y = summon[BSONWriter[List[Id]]] // this did emit warning

Diff for: tests/run/given-triangle.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import language.future
1+
import language.`3.6`
22

33
class A
44
class B extends A

Diff for: tests/warn/bson.check

-10
This file was deleted.

Diff for: tests/warn/given-triangle.check

-6
This file was deleted.

Diff for: tests/warn/i21036a.check

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- Warning: tests/warn/i21036a.scala:7:17 ------------------------------------------------------------------------------
2+
7 |val y = summon[A] // warn
3+
| ^
4+
| Given search preference for A between alternatives (b : B) and (a : A) will change
5+
| Current choice : the first alternative
6+
| New choice from Scala 3.6: the second alternative

Diff for: tests/warn/i21036a.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//> using options -source 3.5
2+
trait A
3+
trait B extends A
4+
given b: B = ???
5+
given a: A = ???
6+
7+
val y = summon[A] // warn

Diff for: tests/warn/i21036b.check

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- Warning: tests/warn/i21036b.scala:7:17 ------------------------------------------------------------------------------
2+
7 |val y = summon[A] // warn
3+
| ^
4+
| Change in given search preference for A between alternatives (b : B) and (a : A)
5+
| Previous choice : the first alternative
6+
| New choice from Scala 3.6: the second alternative

Diff for: tests/warn/i21036b.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//> using options -source 3.6-migration
2+
trait A
3+
trait B extends A
4+
given b: B = ???
5+
given a: A = ???
6+
7+
val y = summon[A] // warn

0 commit comments

Comments
 (0)