@@ -37,6 +37,7 @@ import config.Printers.{implicits, implicitsDetailed}
37
37
import collection .mutable
38
38
import reporting .trace
39
39
import annotation .tailrec
40
+ import scala .util .control .NonFatal
40
41
41
42
import scala .annotation .internal .sharable
42
43
import scala .annotation .threadUnsafe
@@ -462,6 +463,66 @@ object Implicits {
462
463
def explanation (implicit ctx : Context ): String =
463
464
em " ${err.refStr(ref)} produces a diverging implicit search when trying to $qualify"
464
465
}
466
+
467
+ /** A helper class to find imports of givens that might fix a type error.
468
+ *
469
+ * suggestions(p).search
470
+ *
471
+ * returns a list of TermRefs that refer to implicits or givens
472
+ * that satisfy predicate `p`.
473
+ *
474
+ * The search algorithm looks for givens in the smallest set of objects
475
+ * and packages that includes
476
+ *
477
+ * - any object that is a defined in an enclosing scope,
478
+ * - any object that is a member of an enclosing class,
479
+ * - any enclosing package (including the root package),
480
+ * - any object that is a member of a searched object or package,
481
+ * - any object or package from which something is imported in an enclosing scope,
482
+ * - any package that is nested in a searched package, provided
483
+ * the package was accessed in some way previously.
484
+ */
485
+ class suggestions (qualifies : TermRef => Boolean ) with
486
+ private val seen = mutable.Set [TermRef ]()
487
+
488
+ private def lookInside (root : Symbol )(given ctx : Context ): Boolean =
489
+ ! root.name.lastPart.contains('$' )
490
+ && root.is(ModuleVal , butNot = JavaDefined )
491
+ && (root.isCompleted || ! root.is(Package ))
492
+
493
+ private def rootsIn (ref : TermRef )(given ctx : Context ): List [TermRef ] =
494
+ if seen.contains(ref) then Nil
495
+ else
496
+ implicitsDetailed.println(i " search in ${ref.symbol.fullName}" )
497
+ seen += ref
498
+ ref :: ref.fields
499
+ .filter(fld => lookInside(fld.symbol))
500
+ .map(fld => TermRef (ref, fld.symbol.asTerm))
501
+ .flatMap(rootsIn)
502
+ .toList
503
+
504
+ private def rootsOnPath (tp : Type )(given ctx : Context ): List [TermRef ] = tp match
505
+ case ref : TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix)
506
+ case _ => Nil
507
+
508
+ private def roots (given ctx : Context ): List [TermRef ] =
509
+ if ctx.owner.exists then
510
+ val defined =
511
+ if ctx.scope eq ctx.outer.scope then Nil
512
+ else ctx.scope
513
+ .filter(lookInside(_))
514
+ .flatMap(sym => rootsIn(sym.termRef))
515
+ val imported =
516
+ if ctx.importInfo eq ctx.outer.importInfo then Nil
517
+ else ctx.importInfo.sym.info match
518
+ case ImportType (expr) => rootsOnPath(expr.tpe)
519
+ case _ => Nil
520
+ defined ++ imported ++ roots(given ctx .outer)
521
+ else Nil
522
+
523
+ def search (given ctx : Context ): List [TermRef ] =
524
+ roots.flatMap(_.implicitMembers.filter(qualifies))
525
+ end suggestions
465
526
}
466
527
467
528
import Implicits ._
@@ -683,6 +744,35 @@ trait Implicits { self: Typer =>
683
744
}
684
745
}
685
746
747
+ /** An addendum to an error message where the error might be fixed
748
+ * be some implicit value of type `pt` that is however not found.
749
+ * The addendum suggests suggests implicit imports that might fix the problem.
750
+ */
751
+ override def implicitSuggestionsFor (pt : Type )(given ctx : Context ): String =
752
+ val suggestedRefs =
753
+ try Implicits .suggestions(_ <:< pt).search(given ctx .fresh.setExploreTyperState())
754
+ catch case NonFatal (ex) => Nil
755
+ def refToRawString (ref : TermRef ) = ctx.printer.toTextRef(ref).show
756
+ def refToString (ref : TermRef ): String =
757
+ val raw = refToRawString(ref)
758
+ ref.prefix match
759
+ case prefix : TermRef if ! raw.contains(" ." ) => s " ${refToRawString(prefix)}. $raw"
760
+ case _ => raw
761
+ def suggestStr (ref : TermRef ) = i " import ${refToString(ref)}"
762
+ if suggestedRefs.isEmpty then " "
763
+ else
764
+ val suggestions = suggestedRefs.map(suggestStr).distinct
765
+ // TermRefs might be different but generate the same strings
766
+ val fix =
767
+ if suggestions.tail.isEmpty then " The following import"
768
+ else " One of the following imports"
769
+ i """
770
+ |
771
+ | $fix might fix the problem:
772
+ |
773
+ | $suggestions%\n%
774
+ """
775
+
686
776
/** Handlers to synthesize implicits for special types */
687
777
type SpecialHandler = (Type , Span ) => Context => Tree
688
778
type SpecialHandlers = List [(ClassSymbol , SpecialHandler )]
@@ -1215,32 +1305,37 @@ trait Implicits { self: Typer =>
1215
1305
pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
1216
1306
pt.widenExpr.argInfos))
1217
1307
1218
- def hiddenImplicitsAddendum : String = arg.tpe match {
1219
- case fail : SearchFailureType =>
1220
-
1221
- def hiddenImplicitNote (s : SearchSuccess ) =
1222
- em " \n\n Note: given instance ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`. "
1308
+ def hiddenImplicitsAddendum : String =
1309
+
1310
+ def hiddenImplicitNote (s : SearchSuccess ) =
1311
+ em " \n\n Note: given instance ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`. "
1312
+
1313
+ def FindHiddenImplicitsCtx (ctx : Context ): Context =
1314
+ if (ctx == NoContext ) ctx
1315
+ else ctx.freshOver(FindHiddenImplicitsCtx (ctx.outer)).addMode(Mode .FindHiddenImplicits )
1316
+
1317
+ val normalImports = arg.tpe match
1318
+ case fail : SearchFailureType =>
1319
+ if (fail.expectedType eq pt) || isFullyDefined(fail.expectedType, ForceDegree .none) then
1320
+ inferImplicit(fail.expectedType, fail.argument, arg.span)(
1321
+ FindHiddenImplicitsCtx (ctx)) match {
1322
+ case s : SearchSuccess => hiddenImplicitNote(s)
1323
+ case f : SearchFailure =>
1324
+ f.reason match {
1325
+ case ambi : AmbiguousImplicits => hiddenImplicitNote(ambi.alt1)
1326
+ case r => " "
1327
+ }
1328
+ }
1329
+ else
1330
+ // It's unsafe to search for parts of the expected type if they are not fully defined,
1331
+ // since these come with nested contexts that are lost at this point. See #7249 for an
1332
+ // example where searching for a nested type causes an infinite loop.
1333
+ " "
1223
1334
1224
- def FindHiddenImplicitsCtx ( ctx : Context ) : Context =
1225
- if (ctx == NoContext ) ctx
1226
- else ctx.freshOver( FindHiddenImplicitsCtx (ctx.outer)).addMode( Mode . FindHiddenImplicits )
1335
+ def suggestedImports = implicitSuggestionsFor(pt)
1336
+ if normalImports.isEmpty then suggestedImports else normalImports
1337
+ end hiddenImplicitsAddendum
1227
1338
1228
- if (fail.expectedType eq pt) || isFullyDefined(fail.expectedType, ForceDegree .none) then
1229
- inferImplicit(fail.expectedType, fail.argument, arg.span)(
1230
- FindHiddenImplicitsCtx (ctx)) match {
1231
- case s : SearchSuccess => hiddenImplicitNote(s)
1232
- case f : SearchFailure =>
1233
- f.reason match {
1234
- case ambi : AmbiguousImplicits => hiddenImplicitNote(ambi.alt1)
1235
- case r => " "
1236
- }
1237
- }
1238
- else
1239
- // It's unsafe to search for parts of the expected type if they are not fully defined,
1240
- // since these come with nested contexts that are lost at this point. See #7249 for an
1241
- // example where searching for a nested type causes an infinite loop.
1242
- " "
1243
- }
1244
1339
msg(userDefined.getOrElse(
1245
1340
em " no implicit argument of type $pt was found ${location(" for" )}" ))() ++
1246
1341
hiddenImplicitsAddendum
0 commit comments