Skip to content

Commit

Permalink
Refine implicit priority change warnings
Browse files Browse the repository at this point in the history
Fixes #21036
Fixes #20572
  • Loading branch information
odersky committed Jul 5, 2024
1 parent cd8c5ed commit b519258
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 30 deletions.
32 changes: 22 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1302,9 +1302,8 @@ trait Implicits:

// A map that associates a priority change warning (between -source 3.4 and 3.6)
// with the candidate refs mentioned in the warning. We report the associated
// message if both candidates qualify in tryImplicit and at least one of the candidates
// is part of the result of the implicit search.
val priorityChangeWarnings = mutable.ListBuffer[(TermRef, TermRef, Message)]()
// message if one of the critical candidates is part of the result of the implicit search.
val priorityChangeWarnings = mutable.ListBuffer[(/*critical:*/ List[TermRef], Message)]()

/** Compare `alt1` with `alt2` to determine which one should be chosen.
*
Expand All @@ -1319,11 +1318,16 @@ trait Implicits:
* return new result with preferGeneral = true
* 3.6 and higher: compare with preferGeneral = true
*
* @param only2ndCritical If true only the second alternative is critical in case
* of a priority change.
*/
def compareAlternatives(alt1: RefAndLevel, alt2: RefAndLevel): Int =
def compareAlternatives(alt1: RefAndLevel, alt2: RefAndLevel, only2ndCritical: Boolean = false): Int =
def comp(using Context) = explore(compare(alt1.ref, alt2.ref, preferGeneral = true))
def warn(msg: Message) =
priorityChangeWarnings += ((alt1.ref, alt2.ref, msg))
val critical =
if only2ndCritical then alt2.ref :: Nil
else alt1.ref :: alt2.ref :: Nil
priorityChangeWarnings += ((critical, msg))
if alt1.ref eq alt2.ref then 0
else if alt1.level != alt2.level then alt1.level - alt2.level
else
Expand Down Expand Up @@ -1443,8 +1447,8 @@ trait Implicits:
compareAlternatives(newCand, cand) > 0)
else
// keep only warnings that don't involve the failed candidate reference
priorityChangeWarnings.filterInPlace: (ref1, ref2, _) =>
ref1 != cand.ref && ref2 != cand.ref
priorityChangeWarnings.filterInPlace: (critical, _) =>
!critical.contains(cand.ref)
rank(remaining, found, fail :: rfailures)
case best: SearchSuccess =>
if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent)
Expand All @@ -1454,7 +1458,15 @@ trait Implicits:
val newPending =
if (retained eq found) || remaining.isEmpty then remaining
else remaining.filterConserve(cand =>
compareAlternatives(retained, cand) <= 0)
compareAlternatives(retained, cand, only2ndCritical = true) <= 0)
// Here we drop some pending alternatives but retain in each case
// `retained`. Therefore, it's a priorty change only if the
// second alternative appears in the final search result. Otherwise
// we have the following scenario:
// - 1st alternative, bit not snd appears in final result
// - Hence, snd was eliminated either here, or otherwise by a direct
// comparison later.
// - Hence, no change in resolution.
rank(newPending, retained, rfailures)
case fail: SearchFailure =>
// The ambiguity happened in the current search: to recover we
Expand Down Expand Up @@ -1601,8 +1613,8 @@ trait Implicits:
throw ex

val result = rank(sort(eligible), NoMatchingImplicitsFailure, Nil)
for (ref1, ref2, msg) <- priorityChangeWarnings do
if result.found.exists(ref => ref == ref1 || ref == ref2) then
for (critical, msg) <- priorityChangeWarnings do
if result.found.exists(critical.contains(_)) then
report.warning(msg, srcPos)
result
end searchImplicit
Expand Down
4 changes: 4 additions & 0 deletions tests/neg/given-triangle.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- [E172] Type Error: tests/neg/given-triangle.scala:14:18 -------------------------------------------------------------
14 |@main def Test = f // error
| ^
|Ambiguous given instances: both given instance given_B and given instance given_C match type A of parameter a of method f
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//> using options -source 3.6-migration

class A
class B extends A
class C extends A
Expand All @@ -13,4 +11,4 @@ def f(using a: A, b: B, c: C) =
println(b.getClass)
println(c.getClass)

@main def Test = f // warn
@main def Test = f // error
File renamed without changes.
File renamed without changes.
7 changes: 7 additions & 0 deletions tests/pos/i20572.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//> using options -Werror
trait Writes[T]
trait Format[T] extends Writes[T]
given [T: List]: Writes[T] = null
given [T]: Format[T] = null

val _ = summon[Writes[Int]]
16 changes: 16 additions & 0 deletions tests/pos/i21036.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//> using options -source 3.5 -Werror
trait SameRuntime[A, B]
trait BSONWriter[T]
trait BSONHandler[T] extends BSONWriter[T]

opaque type Id = String
object Id:
given SameRuntime[Id, String] = ???

given BSONHandler[String] = ???
given [T: BSONHandler]: BSONHandler[List[T]] = ???

given opaqueWriter[T, A](using rs: SameRuntime[T, A], writer: BSONWriter[A]): BSONWriter[T] = ???

val x = summon[BSONHandler[List[Id]]] // this doesn't emit warning
val y = summon[BSONWriter[List[Id]]] // this did emit warning
2 changes: 1 addition & 1 deletion tests/run/given-triangle.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import language.future
import language.`3.6`

class A
class B extends A
Expand Down
10 changes: 0 additions & 10 deletions tests/warn/bson.check

This file was deleted.

6 changes: 0 additions & 6 deletions tests/warn/given-triangle.check

This file was deleted.

0 comments on commit b519258

Please sign in to comment.