@@ -37,6 +37,7 @@ import config.Printers.{implicits, implicitsDetailed}
3737import collection .mutable
3838import reporting .trace
3939import annotation .tailrec
40+ import scala .util .control .NonFatal
4041
4142import scala .annotation .internal .sharable
4243import scala .annotation .threadUnsafe
@@ -462,6 +463,66 @@ object Implicits {
462463 def explanation (implicit ctx : Context ): String =
463464 em " ${err.refStr(ref)} produces a diverging implicit search when trying to $qualify"
464465 }
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
465526}
466527
467528import Implicits ._
@@ -683,6 +744,35 @@ trait Implicits { self: Typer =>
683744 }
684745 }
685746
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+
686776 /** Handlers to synthesize implicits for special types */
687777 type SpecialHandler = (Type , Span ) => Context => Tree
688778 type SpecialHandlers = List [(ClassSymbol , SpecialHandler )]
@@ -1215,32 +1305,37 @@ trait Implicits { self: Typer =>
12151305 pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
12161306 pt.widenExpr.argInfos))
12171307
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+ " "
12231334
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
12271338
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- }
12441339 msg(userDefined.getOrElse(
12451340 em " no implicit argument of type $pt was found ${location(" for" )}" ))() ++
12461341 hiddenImplicitsAddendum
0 commit comments