Skip to content

Commit 427da51

Browse files
committed
Factor out suggestions logic into its own trait
1 parent aabcd81 commit 427da51

File tree

3 files changed

+260
-238
lines changed

3 files changed

+260
-238
lines changed

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

Lines changed: 1 addition & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Flags._
1414
import TypeErasure.{erasure, hasStableErasure}
1515
import Mode.ImplicitsEnabled
1616
import NameOps._
17-
import NameKinds.{LazyImplicitName, FlatName}
17+
import NameKinds.LazyImplicitName
1818
import Symbols._
1919
import Denotations._
2020
import Types._
@@ -37,8 +37,6 @@ import config.Printers.{implicits, implicitsDetailed}
3737
import collection.mutable
3838
import reporting.trace
3939
import annotation.tailrec
40-
import scala.util.control.NonFatal
41-
import java.util.{Timer, TimerTask}
4240

4341
import scala.annotation.internal.sharable
4442
import scala.annotation.threadUnsafe
@@ -69,12 +67,6 @@ object Implicits {
6967
final val Extension = 4
7068
}
7169

72-
/** Timeout to test a single implicit value as a suggestion, in ms */
73-
val testOneImplicitTimeOut = 500
74-
75-
/** Global timeout to stop looking for further implicit suggestions, in ms */
76-
val suggestImplicitTimeOut = 10000
77-
7870
/** If `expected` is a selection prototype, does `tp` have an extension
7971
* method with the selecting name? False otherwise.
8072
*/
@@ -695,235 +687,6 @@ trait Implicits { self: Typer =>
695687
}
696688
}
697689

698-
/** A list of TermRefs referring to the roots where suggestions for
699-
* imports of givens or extension methods that might fix a type error
700-
* are searched.
701-
*
702-
* These roots are the smallest set of objects and packages that includes
703-
*
704-
* - any object that is a defined in an enclosing scope,
705-
* - any object that is a member of an enclosing class,
706-
* - any enclosing package (including the root package),
707-
* - any object that is a member of a searched object or package,
708-
* - any object or package from which something is imported in an enclosing scope,
709-
* - any package that is nested in a searched package, provided
710-
* the package was accessed in some way previously.
711-
*
712-
* Excluded from the root set are:
713-
*
714-
* - Objects that contain `$`s in their name. These have to
715-
* be omitted since they might be inner Java class files which
716-
* cannot be read by the ClassfileParser without crashing.
717-
* - Any members of static parts of Java classes.
718-
* - Any members of the empty package. These should be
719-
* skipped since the empty package often contains unrelated junk files
720-
* that should not be used for suggestions.
721-
* - Any members of the java or java.lang packages. These are
722-
* skipped as an optimization, since they won't contain implicits anyway.
723-
*/
724-
private def suggestionRoots(given Context) =
725-
val seen = mutable.Set[TermRef]()
726-
727-
def lookInside(root: Symbol)(given Context): Boolean =
728-
if root.is(Package) then root.isTerm && root.isCompleted
729-
else !root.name.is(FlatName)
730-
&& !root.name.lastPart.contains('$')
731-
&& root.is(ModuleVal, butNot = JavaDefined)
732-
733-
def nestedRoots(site: Type)(given Context): List[Symbol] =
734-
val seenNames = mutable.Set[Name]()
735-
site.baseClasses.flatMap { bc =>
736-
bc.info.decls.filter { dcl =>
737-
lookInside(dcl)
738-
&& !seenNames.contains(dcl.name)
739-
&& { seenNames += dcl.name; true }
740-
}
741-
}
742-
743-
def rootsStrictlyIn(ref: Type)(given Context): List[TermRef] =
744-
val site = ref.widen
745-
val refSym = site.typeSymbol
746-
val nested =
747-
if refSym.is(Package) then
748-
if refSym == defn.EmptyPackageClass // Don't search the empty package
749-
|| refSym == defn.JavaPackageClass // As an optimization, don't search java...
750-
|| refSym == defn.JavaLangPackageClass // ... or java.lang.
751-
then Nil
752-
else refSym.info.decls.filter(lookInside)
753-
else
754-
if !refSym.is(Touched) then refSym.ensureCompleted() // JavaDefined is reliably known only after completion
755-
if refSym.is(JavaDefined) then Nil
756-
else nestedRoots(site)
757-
nested
758-
.map(mbr => TermRef(ref, mbr.asTerm))
759-
.flatMap(rootsIn)
760-
.toList
761-
762-
def rootsIn(ref: TermRef)(given Context): List[TermRef] =
763-
if seen.contains(ref) then Nil
764-
else
765-
implicits.println(i"search for suggestions in ${ref.symbol.fullName}")
766-
seen += ref
767-
ref :: rootsStrictlyIn(ref)
768-
769-
def rootsOnPath(tp: Type)(given Context): List[TermRef] = tp match
770-
case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix)
771-
case _ => Nil
772-
773-
def recur(given ctx: Context): List[TermRef] =
774-
if ctx.owner.exists then
775-
val defined =
776-
if ctx.owner.isClass then
777-
if ctx.owner eq ctx.outer.owner then Nil
778-
else rootsStrictlyIn(ctx.owner.thisType)
779-
else
780-
if ctx.scope eq ctx.outer.scope then Nil
781-
else ctx.scope
782-
.filter(lookInside(_))
783-
.flatMap(sym => rootsIn(sym.termRef))
784-
val imported =
785-
if ctx.importInfo eq ctx.outer.importInfo then Nil
786-
else ctx.importInfo.sym.info match
787-
case ImportType(expr) => rootsOnPath(expr.tpe)
788-
case _ => Nil
789-
defined ++ imported ++ recur(given ctx.outer)
790-
else Nil
791-
792-
recur
793-
end suggestionRoots
794-
795-
/** Given an expected type `pt`, return two lists of TermRefs:
796-
*
797-
* 1. The _fully matching_ given instances that can be completed
798-
* to a full synthesized given term that matches the expected type `pt`.
799-
*
800-
* 2. The _head matching_ given instances, that conform to the
801-
* expected type `pt`, ignoring any dependent implicit arguments.
802-
*
803-
* If there are no fully matching given instances under (1), and `pt` is
804-
* a view prototype of a selection of the form `T ?=>? { name: ... }`,
805-
* return instead a list of all possible references to extension methods named
806-
* `name` that are applicable to `T`.
807-
*/
808-
private def importSuggestions(pt: Type)(given ctx: Context): (List[TermRef], List[TermRef]) =
809-
val timer = new Timer()
810-
val deadLine = System.currentTimeMillis() + suggestImplicitTimeOut
811-
812-
/** Test whether the head of a given instance matches the expected type `pt`,
813-
* ignoring any dependent implicit arguments.
814-
*/
815-
def shallowTest(ref: TermRef): Boolean =
816-
System.currentTimeMillis < deadLine
817-
&& (ref <:< pt)(given ctx.fresh.setExploreTyperState())
818-
819-
/** Test whether a full given term can be synthesized that matches
820-
* the expected type `pt`.
821-
*/
822-
def deepTest(ref: TermRef): Boolean =
823-
System.currentTimeMillis < deadLine
824-
&& {
825-
val task = new TimerTask with
826-
def run() =
827-
println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}")
828-
ctx.run.isCancelled = true
829-
val span = ctx.owner.sourcePos.span
830-
val (expectedType, argument, kind) = pt match
831-
case ViewProto(argType, resType) =>
832-
(resType,
833-
untpd.Ident(ref.name).withSpan(span).withType(argType),
834-
if hasExtMethod(ref, resType) then Candidate.Extension
835-
else Candidate.Conversion)
836-
case _ =>
837-
(pt, EmptyTree, Candidate.Value)
838-
val candidate = Candidate(ref, kind, 0)
839-
try
840-
timer.schedule(task, testOneImplicitTimeOut)
841-
typedImplicit(candidate, expectedType, argument, span)(
842-
given ctx.fresh.setExploreTyperState()).isSuccess
843-
finally
844-
task.cancel()
845-
ctx.run.isCancelled = false
846-
}
847-
end deepTest
848-
849-
/** Optionally, an extension method reference `site.name` that is
850-
* applicable to `argType`.
851-
*/
852-
def extensionMethod(site: TermRef, name: TermName, argType: Type): Option[TermRef] =
853-
site.member(name)
854-
.alternatives
855-
.map(mbr => TermRef(site, mbr.symbol))
856-
.filter(ref =>
857-
ref.symbol.is(Extension)
858-
&& isApplicableMethodRef(ref, argType :: Nil, WildcardType))
859-
.headOption
860-
861-
try
862-
val roots = suggestionRoots
863-
.filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol))
864-
// don't suggest things that are imported by default
865-
866-
def extensionImports = pt match
867-
case ViewProto(argType, SelectionProto(name: TermName, _, _, _)) =>
868-
roots.flatMap(extensionMethod(_, name, argType))
869-
case _ =>
870-
Nil
871-
872-
roots
873-
.flatMap(_.implicitMembers.filter(shallowTest))
874-
// filter whether the head of the implicit can match
875-
.partition(deepTest)
876-
// partition into full matches and head matches
877-
match
878-
case (Nil, partials) => (extensionImports, partials)
879-
case givenImports => givenImports
880-
catch
881-
case ex: Throwable =>
882-
if ctx.settings.Ydebug.value then
883-
println("caught exception when searching for suggestions")
884-
ex.printStackTrace()
885-
(Nil, Nil)
886-
finally timer.cancel()
887-
end importSuggestions
888-
889-
/** An addendum to an error message where the error might be fixed
890-
* by some implicit value of type `pt` that is however not found.
891-
* The addendum suggests given imports that might fix the problem.
892-
* If there's nothing to suggest, an empty string is returned.
893-
*/
894-
override def importSuggestionAddendum(pt: Type)(given ctx: Context): String =
895-
val (fullMatches, headMatches) =
896-
importSuggestions(pt)(given ctx.fresh.setExploreTyperState())
897-
implicits.println(i"suggestions for $pt in ${ctx.owner} = ($fullMatches%, %, $headMatches%, %)")
898-
val (suggestedRefs, help) =
899-
if fullMatches.nonEmpty then (fullMatches, "fix")
900-
else (headMatches, "make progress towards fixing")
901-
def importString(ref: TermRef): String =
902-
s" import ${ctx.printer.toTextRef(ref).show}"
903-
val suggestions = suggestedRefs
904-
.zip(suggestedRefs.map(importString))
905-
.filter((ref, str) => str.contains('.'))
906-
.sortWith { (x, y) =>
907-
// sort by specificity first, alphabetically second
908-
val ((ref1, str1), (ref2, str2)) = (x, y)
909-
val diff = compare(ref1, ref2)
910-
diff > 0 || diff == 0 && str1 < str2
911-
}
912-
.map((ref, str) => str)
913-
.distinct // TermRefs might be different but generate the same strings
914-
if suggestions.isEmpty then ""
915-
else
916-
val fix =
917-
if suggestions.tail.isEmpty then "The following import"
918-
else "One of the following imports"
919-
i"""
920-
|
921-
|$fix might $help the problem:
922-
|
923-
|$suggestions%\n%
924-
"""
925-
end importSuggestionAddendum
926-
927690
/** Handlers to synthesize implicits for special types */
928691
type SpecialHandler = (Type, Span) => Context => Tree
929692
type SpecialHandlers = List[(ClassSymbol, SpecialHandler)]

0 commit comments

Comments
 (0)