Skip to content

Commit fc89087

Browse files
committed
Properly recover from nested ambiguous implicit search failures
From http://dotty.epfl.ch/docs/reference/changed-features/implicit-resolution.html: > The treatment of ambiguity errors has changed. If an ambiguity is > encountered in some recursive step of an implicit search, the ambiguity > is propagated to the caller. This is supposed to be handled by `healAmbiguous` but the implementation was incorrect: it compared pending candidates against the two ambiguous results from the search, but this doesn't make sense when the failure happened in a nested search: for example in `i9793`, the failure is: Applied.bazApplied[F](/* ambiguous: both value baz and value bar match type Foo[F] */summon[Foo[F]) We should be able to recover from this failure because `barApplied` is more specific than `bazApplied`, but before this commit we ended up comparing `barApplied` to `baz` and `bar` which isn't meaningful. Fixes #9793.
1 parent d873ae5 commit fc89087

File tree

2 files changed

+40
-15
lines changed

2 files changed

+40
-15
lines changed

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

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,18 +1169,6 @@ trait Implicits:
11691169
else SearchFailure(new AmbiguousImplicits(alt1, alt2, pt, argument))
11701170
case _: SearchFailure => alt2
11711171

1172-
/** Faced with an ambiguous implicits failure `fail`, try to find another
1173-
* alternative among `pending` that is strictly better than both ambiguous
1174-
* alternatives. If that fails, return `fail`
1175-
*/
1176-
def healAmbiguous(pending: List[Candidate], fail: SearchFailure) = {
1177-
val ambi = fail.reason.asInstanceOf[AmbiguousImplicits]
1178-
val newPending = pending.filter(cand =>
1179-
compareAlternatives(ambi.alt1, cand) < 0 &&
1180-
compareAlternatives(ambi.alt2, cand) < 0)
1181-
rank(newPending, fail, Nil).recoverWith(_ => fail)
1182-
}
1183-
11841172
/** Try to find a best matching implicit term among all the candidates in `pending`.
11851173
* @param pending The list of candidates that remain to be tested
11861174
* @param found The result obtained from previously tried candidates
@@ -1194,14 +1182,22 @@ trait Implicits:
11941182
* worse than the successful candidate.
11951183
* If a trial failed:
11961184
* - if the query term is a `Not[T]` treat it as a success,
1197-
* - otherwise, if the failure is an ambiguity, try to heal it (see @healAmbiguous)
1185+
* - otherwise, if the failure is an ambiguity, try to heal it (see `healAmbiguous`)
11981186
* and return an ambiguous error otherwise. However, under Scala2 mode this is
11991187
* treated as a simple failure, with a warning that semantics will change.
12001188
* - otherwise add the failure to `rfailures` and continue testing the other candidates.
12011189
*/
12021190
def rank(pending: List[Candidate], found: SearchResult, rfailures: List[SearchFailure]): SearchResult =
12031191
pending match {
12041192
case cand :: remaining =>
1193+
/** To recover from an ambiguous implicit failure, we need to find a pending
1194+
* candidate that is strictly better than the failed candidate(s).
1195+
* If no such candidate is found, we propagate the ambiguity.
1196+
*/
1197+
def healAmbiguous(fail: SearchFailure, betterThanFailed: Candidate => Boolean) =
1198+
val newPending = remaining.filter(betterThanFailed)
1199+
rank(newPending, fail, Nil).recoverWith(_ => fail)
1200+
12051201
negateIfNot(tryImplicit(cand, contextual)) match {
12061202
case fail: SearchFailure =>
12071203
if (fail.isAmbiguous)
@@ -1210,7 +1206,11 @@ trait Implicits:
12101206
if (result.isSuccess)
12111207
warnAmbiguousNegation(fail.reason.asInstanceOf[AmbiguousImplicits])
12121208
result
1213-
else healAmbiguous(remaining, fail)
1209+
else
1210+
// The ambiguity happened in a nested search: to recover we
1211+
// need a candidate better than `cand`
1212+
healAmbiguous(fail, newCand =>
1213+
compareAlternatives(newCand, cand) > 0)
12141214
else rank(remaining, found, fail :: rfailures)
12151215
case best: SearchSuccess =>
12161216
if (ctx.mode.is(Mode.ImplicitExploration) || isCoherent)
@@ -1223,7 +1223,12 @@ trait Implicits:
12231223
compareAlternatives(retained, cand) <= 0)
12241224
rank(newPending, retained, rfailures)
12251225
case fail: SearchFailure =>
1226-
healAmbiguous(remaining, fail)
1226+
// The ambiguity happened in the current search: to recover we
1227+
// need a candidate better than the two ambiguous alternatives.
1228+
val ambi = fail.reason.asInstanceOf[AmbiguousImplicits]
1229+
healAmbiguous(fail, newCand =>
1230+
compareAlternatives(newCand, ambi.alt1) > 0 &&
1231+
compareAlternatives(newCand, ambi.alt2) > 0)
12271232
}
12281233
}
12291234
case nil =>

tests/pos/i9793.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
trait Foo[F[_]]
2+
3+
trait Bar[F[_]] extends Foo[F]
4+
trait Baz[F[_]] extends Foo[F]
5+
6+
case class Applied[F[_], A](a: F[A])
7+
8+
9+
object Applied extends AppliedLowPrio {
10+
implicit def barApplied[F[_]: Baz]: Baz[({ type L[X] = Applied[F, X] })#L] = ???
11+
}
12+
13+
trait AppliedLowPrio {
14+
implicit def bazApplied[F[_]: Foo]: Foo[({ type L[X] = Applied[F, X] })#L] = ???
15+
}
16+
17+
18+
object Test {
19+
def test[F[_]](implicit bar: Bar[F], baz: Baz[F]) = implicitly[Foo[({ type L[X] = Applied[F, X] })#L]]
20+
}

0 commit comments

Comments
 (0)