Skip to content

Commit 9c67ee4

Browse files
committed
Revise search logic
- Also search in nested objects - Avoid calling `fields` since this can cause cyclic reference exceptions (e.g. in neg/i2006.scala).
1 parent 55b1a31 commit 9c67ee4

File tree

4 files changed

+129
-19
lines changed

4 files changed

+129
-19
lines changed

compiler/src/dotty/tools/dotc/core/TypeErrors.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ object CyclicReference {
153153
def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = {
154154
val ex = new CyclicReference(denot)
155155
if (!(ctx.mode is Mode.CheckCyclic)) {
156-
cyclicErrors.println(ex.getMessage)
156+
cyclicErrors.println(s"Cyclic reference involving $denot")
157157
for (elem <- ex.getStackTrace take 200)
158158
cyclicErrors.println(elem.toString)
159159
}

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

+55-18
Original file line numberDiff line numberDiff line change
@@ -490,30 +490,55 @@ object Implicits {
490490
* the package was accessed in some way previously.
491491
*/
492492
class suggestions(qualifies: TermRef => Boolean) with
493+
private type RootRef = TermRef | ThisType
494+
495+
private def symbolOf(ref: RootRef)(given Context) = ref match
496+
case ref: TermRef => ref.symbol
497+
case ref: ThisType => ref.cls
498+
493499
private val seen = mutable.Set[TermRef]()
494500

495501
private def lookInside(root: Symbol)(given ctx: Context): Boolean =
496-
if root.is(PackageVal) then root.isCompleted
502+
if root.is(Package) then root.isTerm && root.isCompleted
497503
else !root.name.is(FlatName)
498504
&& !root.name.lastPart.contains('$')
499505
&& root.is(ModuleVal, butNot = JavaDefined)
500506

507+
def nestedRoots(ref: RootRef)(given Context): List[Symbol] =
508+
val seenNames = mutable.Set[Name]()
509+
ref.widen.baseClasses.flatMap { bc =>
510+
bc.info.decls.filter { dcl =>
511+
lookInside(dcl)
512+
&& !seenNames.contains(dcl.name)
513+
&& { seenNames += dcl.name; true }
514+
}
515+
}
516+
517+
private def rootsStrictlyIn(ref: RootRef)(given ctx: Context): List[TermRef] =
518+
val refSym = symbolOf(ref)
519+
val nested =
520+
if refSym == defn.EmptyPackageClass // Don't search the empty package, either as enclosing package ...
521+
|| refSym == defn.EmptyPackageVal // ... or as a member of _root_.
522+
|| refSym == defn.JavaPackageVal // As an optimization, don't search java...
523+
|| refSym == defn.JavaLangPackageVal // ... or java.lang.
524+
then Nil
525+
else if refSym.is(Package) || refSym.isPackageObject then
526+
refSym.info.decls.filter(lookInside)
527+
else
528+
if !refSym.is(Touched) then refSym.ensureCompleted() // JavaDefined is reliably known only after completion
529+
if refSym.is(JavaDefined) then Nil
530+
else nestedRoots(ref)
531+
nested
532+
.map(mbr => TermRef(ref, mbr.asTerm))
533+
.flatMap(rootsIn)
534+
.toList
535+
501536
private def rootsIn(ref: TermRef)(given ctx: Context): List[TermRef] =
502537
if seen.contains(ref) then Nil
503538
else
504-
implicitsDetailed.println(i"search for suggestions in ${ref.symbol.fullName}")
539+
implicits.println(i"search for suggestions in ${ref.symbol.fullName}")
505540
seen += ref
506-
val nested =
507-
if ref.symbol.is(Package) then
508-
ref.info.decls.filter(lookInside)
509-
else
510-
ref.symbol.ensureCompleted() // JavaDefined in reliably known only after completion
511-
if ref.symbol.is(JavaDefined) then Nil
512-
else ref.fields.map(_.symbol).filter(lookInside)
513-
ref :: nested
514-
.map(mbr => TermRef(ref, mbr.asTerm))
515-
.flatMap(rootsIn)
516-
.toList
541+
ref :: rootsStrictlyIn(ref)
517542

518543
private def rootsOnPath(tp: Type)(given ctx: Context): List[TermRef] = tp match
519544
case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix)
@@ -522,8 +547,15 @@ object Implicits {
522547
private def roots(given ctx: Context): List[TermRef] =
523548
if ctx.owner.exists then
524549
val defined =
525-
if ctx.scope eq ctx.outer.scope then Nil
526-
else ctx.scope
550+
if ctx.owner.isClass then
551+
if ctx.owner eq ctx.outer.owner then Nil
552+
else ctx.owner.thisType match
553+
case ref: TermRef => rootsStrictlyIn(ref)
554+
case ref: ThisType => rootsStrictlyIn(ref)
555+
case _ => Nil
556+
else if ctx.scope eq ctx.outer.scope then Nil
557+
else
558+
ctx.scope
527559
.filter(lookInside(_))
528560
.flatMap(sym => rootsIn(sym.termRef))
529561
val imported =
@@ -543,7 +575,7 @@ object Implicits {
543575
&& {
544576
val task = new TimerTask {
545577
def run() =
546-
implicitsDetailed.println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}")
578+
implicits.println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}")
547579
ctx.run.isCancelled = true
548580
}
549581
timer.schedule(task, testOneImplicitTimeOut)
@@ -558,6 +590,12 @@ object Implicits {
558590
.filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol))
559591
// don't suggest things that are imported by default
560592
.flatMap(_.implicitMembers.filter(test))
593+
catch
594+
case ex: Throwable =>
595+
if ctx.settings.Ydebug.value then
596+
println("caught exceptioon when searching for suggestions")
597+
ex.printStackTrace()
598+
Nil
561599
finally timer.cancel()
562600
end search
563601
end suggestions
@@ -789,8 +827,7 @@ trait Implicits { self: Typer =>
789827
*/
790828
override def implicitSuggestionsFor(pt: Type)(given ctx: Context): String =
791829
val suggestedRefs =
792-
try Implicits.suggestions(_ <:< pt).search(given ctx.fresh.setExploreTyperState())
793-
catch case NonFatal(ex) => Nil
830+
Implicits.suggestions(_ <:< pt).search(given ctx.fresh.setExploreTyperState())
794831
def importString(ref: TermRef): String =
795832
s" import ${ctx.printer.toTextRef(ref).show}"
796833
val suggestions = suggestedRefs.map(importString)

tests/neg/missing-implicit1.check

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
-- Error: tests/neg/missing-implicit1.scala:16:4 -----------------------------------------------------------------------
2+
16 | ff // error
3+
| ^
4+
|no implicit argument of type testObjectInstance.Zip[Option] was found for parameter xs of method ff in object testObjectInstance
5+
|
6+
|The following import might fix the problem:
7+
|
8+
| import testObjectInstance.instances.zipOption
9+
|
10+
-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:18:16 ----------------------------------------------
11+
18 | List(1, 2, 3).traverse(x => Option(x)) // error
12+
| ^^^^^^^^^^^^^^^^^^^^^^
13+
| value traverse is not a member of List[Int] - did you mean List[Int].reverse?
14+
|
15+
| The following import might fix the problem:
16+
|
17+
| import testObjectInstance.instances.traverseList
18+
|
19+
-- Error: tests/neg/missing-implicit1.scala:35:4 -----------------------------------------------------------------------
20+
35 | ff // error
21+
| ^
22+
| no implicit argument of type Zip[Option] was found for parameter xs of method ff
23+
|
24+
| The following import might fix the problem:
25+
|
26+
| import instances.zipOption
27+
|
28+
-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:37:16 ----------------------------------------------
29+
37 | List(1, 2, 3).traverse(x => Option(x)) // error
30+
| ^^^^^^^^^^^^^^^^^^^^^^
31+
| value traverse is not a member of List[Int] - did you mean List[Int].reverse?
32+
|
33+
| The following import might fix the problem:
34+
|
35+
| import instances.traverseList
36+
|

tests/neg/missing-implicit1.scala

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
object testObjectInstance with
2+
trait Zip[F[_]]
3+
trait Traverse[F[_]] {
4+
def [A, B, G[_] : Zip](fa: F[A]) traverse(f: A => G[B]): G[F[B]]
5+
}
6+
7+
object instances {
8+
given zipOption: Zip[Option] = ???
9+
given traverseList: Traverse[List] = ???
10+
}
11+
12+
def ff(given xs: Zip[Option]) = ???
13+
14+
//import instances.zipOption
15+
16+
ff // error
17+
18+
List(1, 2, 3).traverse(x => Option(x)) // error
19+
20+
def testLocalInstance =
21+
trait Zip[F[_]]
22+
trait Traverse[F[_]] {
23+
def [A, B, G[_] : Zip](fa: F[A]) traverse(f: A => G[B]): G[F[B]]
24+
}
25+
26+
object instances {
27+
given zipOption: Zip[Option] = ???
28+
given traverseList: Traverse[List] = ???
29+
}
30+
31+
def ff(given xs: Zip[Option]) = ???
32+
33+
//import instances.zipOption
34+
35+
ff // error
36+
37+
List(1, 2, 3).traverse(x => Option(x)) // error

0 commit comments

Comments
 (0)