@@ -695,15 +695,10 @@ trait Implicits { self: Typer =>
695695 }
696696 }
697697
698- /** A helper class to find imports of givens that might fix a type error.
698+ /** A list of TermRefs referring to the roots where suggestions for
699+ * imports of givens that might fix a type error are searched.
699700 *
700- * suggestions(p).search
701- *
702- * returns a list of TermRefs that refer to implicits or givens
703- * that satisfy predicate `p`.
704- *
705- * The search algorithm looks for givens in the smallest set of objects
706- * and packages that includes
701+ * These roots are the smallest set of objects and packages that includes
707702 *
708703 * - any object that is a defined in an enclosing scope,
709704 * - any object that is a member of an enclosing class,
@@ -712,11 +707,23 @@ trait Implicits { self: Typer =>
712707 * - any object or package from which something is imported in an enclosing scope,
713708 * - any package that is nested in a searched package, provided
714709 * the package was accessed in some way previously.
710+ *
711+ * Excluded from the root set are:
712+ *
713+ * - Objects that contain `$`s in their name. These have to
714+ * be omitted since they might be inner Java class files which
715+ * cannot be read by the ClassfileParser without crashing.
716+ * - Any members of static parts of Java classes.
717+ * - Any members of the empty package. These should be
718+ * skipped since the empty package often contains unrelated junk files
719+ * that should not be used for suggestions.
720+ * - Any members of the java or java.lang packages. These are
721+ * skipped as an optimization, since they won't contain implicits anyway.
715722 */
716- private class Suggestions ( pt : Type ) with
717- private val seen = mutable.Set [TermRef ]()
723+ private def suggestionRoots ( given Context ) =
724+ val seen = mutable.Set [TermRef ]()
718725
719- private def lookInside (root : Symbol )(given Context ): Boolean =
726+ def lookInside (root : Symbol )(given Context ): Boolean =
720727 if root.is(Package ) then root.isTerm && root.isCompleted
721728 else ! root.name.is(FlatName )
722729 && ! root.name.lastPart.contains('$' )
@@ -732,7 +739,7 @@ trait Implicits { self: Typer =>
732739 }
733740 }
734741
735- private def rootsStrictlyIn (ref : Type )(given Context ): List [TermRef ] =
742+ def rootsStrictlyIn (ref : Type )(given Context ): List [TermRef ] =
736743 val site = ref.widen
737744 val refSym = site.typeSymbol
738745 val nested =
@@ -751,18 +758,18 @@ trait Implicits { self: Typer =>
751758 .flatMap(rootsIn)
752759 .toList
753760
754- private def rootsIn (ref : TermRef )(given Context ): List [TermRef ] =
761+ def rootsIn (ref : TermRef )(given Context ): List [TermRef ] =
755762 if seen.contains(ref) then Nil
756763 else
757764 implicits.println(i " search for suggestions in ${ref.symbol.fullName}" )
758765 seen += ref
759766 ref :: rootsStrictlyIn(ref)
760767
761- private def rootsOnPath (tp : Type )(given Context ): List [TermRef ] = tp match
768+ def rootsOnPath (tp : Type )(given Context ): List [TermRef ] = tp match
762769 case ref : TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix)
763770 case _ => Nil
764771
765- private def roots (given ctx : Context ): List [TermRef ] =
772+ def recur (given ctx : Context ): List [TermRef ] =
766773 if ctx.owner.exists then
767774 val defined =
768775 if ctx.owner.isClass then
@@ -778,76 +785,86 @@ trait Implicits { self: Typer =>
778785 else ctx.importInfo.sym.info match
779786 case ImportType (expr) => rootsOnPath(expr.tpe)
780787 case _ => Nil
781- defined ++ imported ++ roots (given ctx .outer)
788+ defined ++ imported ++ recur (given ctx .outer)
782789 else Nil
783790
784- def search (given ctx : Context ): (List [TermRef ], List [TermRef ]) =
785- val timer = new Timer ()
786- val deadLine = System .currentTimeMillis() + suggestImplicitTimeOut
791+ recur
792+ end suggestionRoots
787793
788- /** Test whether the head of a given instance matches the expected type `pt`,
789- * ignoring any dependent implicit arguments.
790- */
791- def shallowTest (ref : TermRef )(given Context ): Boolean =
792- System .currentTimeMillis < deadLine
793- && (ref <:< pt)(given ctx .fresh.setExploreTyperState())
794+ /** Given an expected type `pt`, return two lists of TermRefs:
795+ *
796+ * 1. The _fully matching_ given instances that can be completed
797+ * to a full synthesized given term that matches the expected type `pt`.
798+ *
799+ * 2. The _head matching_ given instances, that conform to the
800+ * expected type `pt`, ignoring any dependent implicit arguments.
801+ */
802+ private def importSuggestions (pt : Type )(given ctx : Context ): (List [TermRef ], List [TermRef ]) =
803+ val timer = new Timer ()
804+ val deadLine = System .currentTimeMillis() + suggestImplicitTimeOut
794805
795- /** Test whether a full given term can be synthesized that matches
796- * the expected type `pt`.
797- */
798- def deepTest (ref : TermRef )(given Context ): Boolean =
799- System .currentTimeMillis < deadLine
800- && {
801- val task = new TimerTask with
802- def run () =
803- println(i " Cancelling test of $ref when making suggestions for error in ${ctx.source}" )
804- ctx.run.isCancelled = true
805- val span = ctx.owner.sourcePos.span
806- val (expectedType, argument, kind) = pt match
807- case ViewProto (argType, resType) =>
808- (resType,
809- untpd.Ident (ref.name).withSpan(span).withType(argType),
810- if hasExtMethod(ref, resType) then Candidate .Extension
811- else Candidate .Conversion )
812- case _ =>
813- (pt, EmptyTree , Candidate .Value )
814- val candidate = Candidate (ref, kind, 0 )
815- try
816- timer.schedule(task, testOneImplicitTimeOut)
817- typedImplicit(candidate, expectedType, argument, span)(
818- given ctx .fresh.setExploreTyperState()).isSuccess
819- finally
820- task.cancel()
821- ctx.run.isCancelled = false
822- }
823- end deepTest
824-
825- try
826- roots
827- .filterNot(root => defn.RootImportTypes .exists(_.symbol == root.symbol))
828- // don't suggest things that are imported by default
829- .flatMap(_.implicitMembers.filter(shallowTest))
830- // filter whether the head of the implicit can match
831- .partition(deepTest)
832- // partition into full matches and head matches
833- catch
834- case ex : Throwable =>
835- if ctx.settings.Ydebug .value then
836- println(" caught exception when searching for suggestions" )
837- ex.printStackTrace()
838- (Nil , Nil )
839- finally timer.cancel()
840- end search
841- end Suggestions
806+ /** Test whether the head of a given instance matches the expected type `pt`,
807+ * ignoring any dependent implicit arguments.
808+ */
809+ def shallowTest (ref : TermRef )(given Context ): Boolean =
810+ System .currentTimeMillis < deadLine
811+ && (ref <:< pt)(given ctx .fresh.setExploreTyperState())
812+
813+ /** Test whether a full given term can be synthesized that matches
814+ * the expected type `pt`.
815+ */
816+ def deepTest (ref : TermRef )(given Context ): Boolean =
817+ System .currentTimeMillis < deadLine
818+ && {
819+ val task = new TimerTask with
820+ def run () =
821+ println(i " Cancelling test of $ref when making suggestions for error in ${ctx.source}" )
822+ ctx.run.isCancelled = true
823+ val span = ctx.owner.sourcePos.span
824+ val (expectedType, argument, kind) = pt match
825+ case ViewProto (argType, resType) =>
826+ (resType,
827+ untpd.Ident (ref.name).withSpan(span).withType(argType),
828+ if hasExtMethod(ref, resType) then Candidate .Extension
829+ else Candidate .Conversion )
830+ case _ =>
831+ (pt, EmptyTree , Candidate .Value )
832+ val candidate = Candidate (ref, kind, 0 )
833+ try
834+ timer.schedule(task, testOneImplicitTimeOut)
835+ typedImplicit(candidate, expectedType, argument, span)(
836+ given ctx .fresh.setExploreTyperState()).isSuccess
837+ finally
838+ task.cancel()
839+ ctx.run.isCancelled = false
840+ }
841+ end deepTest
842+
843+ try
844+ suggestionRoots
845+ .filterNot(root => defn.RootImportTypes .exists(_.symbol == root.symbol))
846+ // don't suggest things that are imported by default
847+ .flatMap(_.implicitMembers.filter(shallowTest))
848+ // filter whether the head of the implicit can match
849+ .partition(deepTest)
850+ // partition into full matches and head matches
851+ catch
852+ case ex : Throwable =>
853+ if ctx.settings.Ydebug .value then
854+ println(" caught exception when searching for suggestions" )
855+ ex.printStackTrace()
856+ (Nil , Nil )
857+ finally timer.cancel()
858+ end importSuggestions
842859
843860 /** An addendum to an error message where the error might be fixed
844861 * by some implicit value of type `pt` that is however not found.
845862 * The addendum suggests given imports that might fix the problem.
846863 * If there's nothing to suggest, an empty string is returned.
847864 */
848- override def implicitSuggestionsFor (pt : Type )(given ctx : Context ): String =
865+ override def implicitSuggestionAddendum (pt : Type )(given ctx : Context ): String =
849866 val (fullMatches, headMatches) =
850- Suggestions (pt).search (given ctx .fresh.setExploreTyperState())
867+ importSuggestions (pt)(given ctx .fresh.setExploreTyperState())
851868 implicits.println(i " suggestions for $pt in ${ctx.owner} = ( $fullMatches%, %, $headMatches%, %) " )
852869 val (suggestedRefs, help) =
853870 if fullMatches.nonEmpty then (fullMatches, " fix" )
@@ -874,7 +891,7 @@ trait Implicits { self: Typer =>
874891 |
875892 | $suggestions%\n%
876893 """
877- end implicitSuggestionsFor
894+ end implicitSuggestionAddendum
878895
879896 /** Handlers to synthesize implicits for special types */
880897 type SpecialHandler = (Type , Span ) => Context => Tree
@@ -1435,7 +1452,7 @@ trait Implicits { self: Typer =>
14351452 // example where searching for a nested type causes an infinite loop.
14361453 " "
14371454
1438- def suggestedImports = implicitSuggestionsFor (pt)
1455+ def suggestedImports = implicitSuggestionAddendum (pt)
14391456 if normalImports.isEmpty then suggestedImports else normalImports
14401457 end hiddenImplicitsAddendum
14411458
0 commit comments