Permalink
Browse files

SI-8460 Fix regression in divergent implicit recovery

Implicit search detects likely cycles by looking at the stack of
open implicits and checking the same implicit appears twice, and
if the second occurrence is trying satisfy an implicit search for
a "dominant" type.

Originally, this condition immediately failed the entire implicit
search. However, since Scala 2.10, this mechanism has been refined to
continue searching after the first divergent implicit is detected.
If a second divergence is found, we fail immediately. If the followup
search fails, we report the first divergence. Otherwise, we
take the successful result.

This mechanism was originally built around exceptions. This proved
to be fragile, and was refactored in SI-7291 / accaa31 to instead
use the `Context.errors` to control the process.

But, since that change, the pattern of implicits in scalanlp/breeze
and Shapeless have been prone to reporting the divergent implicit
errors where they used to recover.

So long as we left the `DivergentImplictTypeError` that originates
from a nested implicit search in `context.errors`, we are unable to
successfully typecheck other candidates. This commit instead
stashes the first such error away in `DivergentImplicitRecovery`,
to clear the way for the alternative path to succeed.

We must retain any other divergent implicit errors, as witnessed by
test/files/neg/t2031.scala, which loops unless we retain divergent
implicit errors that we don't stash in `DivergentImplicitRecovery`.
  • Loading branch information...
1 parent 5e795fc commit 1c330e67c837ab56b208ebf827ac422b21818824 @retronym retronym committed with adriaanm Mar 31, 2014
@@ -839,11 +839,22 @@ trait Implicits {
if (divergentError.isEmpty) divergentError = Some(err)
}
+ def retainRemainingDivergentErrors() = {
+ val saved = divergentError.getOrElse(null)
+ context.reportBuffer.retainErrors {
+ case err: DivergentImplicitTypeError => err ne saved
+ }
+ }
+
def issueSavedDivergentError() {
divergentError foreach (err => context.issue(err))
}
def apply(search: SearchResult, i: ImplicitInfo, errors: Seq[AbsTypeError]): SearchResult = {
+ // A divergent error from a nested implicit search will be found in `errors`. Stash that
+ // aside to be re-issued if this implicit search fails.
+ errors.collectFirst { case err: DivergentImplicitTypeError => err } foreach saveDivergent
+
if (search.isDivergent && divergentError.isEmpty) {
// Divergence triggered by `i` at this level of the implicit serach. We haven't
// seen divergence so far, we won't issue this error just yet, and instead temporarily
@@ -879,16 +890,18 @@ trait Implicits {
case Nil => acc
case i :: is =>
val typedImplicitResult = typedImplicit(i, ptChecked = true, isLocalToCallsite)
+ // Pass the errors to `DivergentImplicitRecovery` so that it can note the first `DivergentImplicitTypeError`
+ // that is being propagated from a nested implicit search;
+ // this one will be re-issued if this level of the search fails.
val recoveredResult = DivergentImplicitRecovery(typedImplicitResult, i, context.errors)
recoveredResult match {
case sr if sr.isDivergent =>
Nil
case sr if sr.isFailure =>
// We don't want errors that occur during checking implicit info
- // to influence the check of further infos.
- context.reportBuffer.retainErrors {
- case err: DivergentImplicitTypeError => true
- }
+ // to influence the check of further infos, but we should retain divergent implicit errors
+ // (except for the one we already squirreled away)
+ DivergentImplicitRecovery.retainRemainingDivergentErrors()
rankImplicits(is, acc)
case newBest =>
best = newBest
@@ -3,6 +3,10 @@ divergent-implicit.scala:4: error: type mismatch;
required: String
val x1: String = 1
^
+divergent-implicit.scala:5: error: diverging implicit expansion for type Int => String
+starting with method $conforms in object Predef
+ val x2: String = cast[Int, String](1)
+ ^
divergent-implicit.scala:14: error: type mismatch;
found : Test2.Foo
required: Test2.Bar
@@ -13,4 +17,4 @@ divergent-implicit.scala:15: error: type mismatch;
required: Test2.Bar
val y: Bar = new Baz
^
-three errors found
+four errors found
File renamed without changes.

1 comment on commit 1c330e6

Owner

retronym commented on 1c330e6 Mar 31, 2014

It's a precarious tightrope on which we tread. Thanks for the patch! Will digest it and report back.

Please sign in to comment.