Skip to content

Commit

Permalink
Avoid infinite recursion when looking for import suggestions
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Odersky <[email protected]>
Co-authored-by: Ondřej Lhoták <[email protected]>
Co-authored-by: Nguyen Pham <[email protected]>
  • Loading branch information
4 people committed Jan 14, 2025
1 parent af655c9 commit a2412d8
Show file tree
Hide file tree
Showing 15 changed files with 199 additions and 22 deletions.
47 changes: 25 additions & 22 deletions compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ trait ImportSuggestions:
&& !(root.name == nme.raw.BAR && ctx.settings.scalajs.value && root == JSDefinitions.jsdefn.PseudoUnionModule)
}

def nestedRoots(site: Type)(using Context): List[Symbol] =
def nestedRoots(site: Type, parentSymbols: Set[Symbol])(using Context): List[Symbol] =
val seenNames = mutable.Set[Name]()
site.baseClasses.flatMap { bc =>
bc.info.decls.filter { dcl =>
Expand All @@ -79,34 +79,37 @@ trait ImportSuggestions:
}
}

def rootsStrictlyIn(ref: Type)(using Context): List[TermRef] =
def rootsStrictlyIn(ref: Type, parentSymbols: Set[Symbol] = Set())(using Context): List[TermRef] =
val site = ref.widen
val refSym = site.typeSymbol
val nested =
if refSym.is(Package) then
if refSym == defn.EmptyPackageClass // Don't search the empty package
|| refSym == defn.JavaPackageClass // As an optimization, don't search java...
|| refSym == defn.JavaLangPackageClass // ... or java.lang.
then Nil
else refSym.info.decls.filter(lookInside)
else if refSym.infoOrCompleter.isInstanceOf[StubInfo] then
Nil // Don't chase roots that do not exist
else
if !refSym.is(Touched) then
refSym.ensureCompleted() // JavaDefined is reliably known only after completion
if refSym.is(JavaDefined) then Nil
else nestedRoots(site)
nested
.map(mbr => TermRef(ref, mbr.asTerm))
.flatMap(rootsIn)
.toList
if parentSymbols.contains(refSym) then Nil
else
val nested =
if refSym.is(Package) then
if refSym == defn.EmptyPackageClass // Don't search the empty package
|| refSym == defn.JavaPackageClass // As an optimization, don't search java...
|| refSym == defn.JavaLangPackageClass // ... or java.lang.
then Nil
else refSym.info.decls.filter(lookInside)
else if refSym.infoOrCompleter.isInstanceOf[StubInfo] then
Nil // Don't chase roots that do not exist
else
if !refSym.is(Touched) then
refSym.ensureCompleted() // JavaDefined is reliably known only after completion
if refSym.is(JavaDefined) then Nil
else nestedRoots(site, parentSymbols)
val newParentSymbols = parentSymbols + refSym
nested
.map(mbr => TermRef(ref, mbr.asTerm))
.flatMap(rootsIn(_, newParentSymbols))
.toList

def rootsIn(ref: TermRef)(using Context): List[TermRef] =
def rootsIn(ref: TermRef, parentSymbols: Set[Symbol] = Set())(using Context): List[TermRef] =
if seen.contains(ref) then Nil
else
implicitsDetailed.println(i"search for suggestions in ${ref.symbol.fullName}")
seen += ref
ref :: rootsStrictlyIn(ref)
ref :: rootsStrictlyIn(ref, parentSymbols)

def rootsOnPath(tp: Type)(using Context): List[TermRef] = tp match
case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix)
Expand Down
4 changes: 4 additions & 0 deletions tests/neg/22145.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- [E008] Not Found Error: tests/neg/22145.scala:5:7 -------------------------------------------------------------------
5 | base.foo() // error
| ^^^^^^^^
| value foo is not a member of foo.Collection
8 changes: 8 additions & 0 deletions tests/neg/22145.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package foo

trait Collection:
val base: Collection = ???
base.foo() // error

object O extends Collection:
def foo(): Int = ???
36 changes: 36 additions & 0 deletions tests/neg/22145b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- [E008] Not Found Error: tests/neg/22145b.scala:15:19 ----------------------------------------------------------------
15 | require(base.isWithin(p, start, end), "position is out of bounds") // error
| ^^^^^^^^^^^^^
| value isWithin is not a member of Collection.this.Self
-- [E008] Not Found Error: tests/neg/22145b.scala:28:59 ----------------------------------------------------------------
28 | def positionAfter(p: Position): Position = self.base.positionAfter(p) // error
| ^^^^^^^^^^^^^^^^^^^^^^^
|value positionAfter is not a member of Collection.this.Self.
|An extension method was tried, but could not be fully constructed:
|
| this.positionAfter(self.base)
|
| failed with:
|
| Found: (self.base : Collection.this.Self)
| Required: foo.Collection.given_is_Slice_Collection.Self²
|
| where: Self is a type in trait Collection
| Self² is a type in object given_is_Slice_Collection which is an alias of Collection.this.Slice
|
-- [E008] Not Found Error: tests/neg/22145b.scala:29:50 ----------------------------------------------------------------
29 | def apply(p: Position): Element = self.base.apply(p) // error
| ^^^^^^^^^^^^^^^
|value apply is not a member of Collection.this.Self.
|An extension method was tried, but could not be fully constructed:
|
| this.apply(self.base)
|
| failed with:
|
| Found: (self.base : Collection.this.Self)
| Required: foo.Collection.given_is_Slice_Collection.Self²
|
| where: Self is a type in trait Collection
| Self² is a type in object given_is_Slice_Collection which is an alias of Collection.this.Slice
|
40 changes: 40 additions & 0 deletions tests/neg/22145b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package foo

import language.experimental.modularity

trait Collection:
me =>

type Self
type Position
type Element

final class Slice(private[Collection] val base: Self, val start: Position, val end: Position):

final def apply(p: Position): Element =
require(base.isWithin(p, start, end), "position is out of bounds") // error
base.apply(p)

end Slice

given Slice is Collection:

type Position = me.Position
type Element = me.Element

extension (self: Self)
def start: Position = self.start
def end: Position = self.end
def positionAfter(p: Position): Position = self.base.positionAfter(p) // error
def apply(p: Position): Element = self.base.apply(p) // error

end given

extension (self: Self)

def start: Position
def end: Position
def positionAfter(p: Position): Position
def apply(p: Position): Element

end extension
4 changes: 4 additions & 0 deletions tests/neg/22145c.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- [E008] Not Found Error: tests/neg/22145c.scala:4:35 -----------------------------------------------------------------
4 | def bar(base: Collection) = base.foo // error
| ^^^^^^^^
| value foo is not a member of foo.Collection
8 changes: 8 additions & 0 deletions tests/neg/22145c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package foo

trait Collection:
def bar(base: Collection) = base.foo // error
object a extends Collection:
def foo: Int = 0
object b extends Collection:
def foo: Int = 1
9 changes: 9 additions & 0 deletions tests/neg/22145d.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- [E008] Not Found Error: tests/neg/22145d.scala:10:4 -----------------------------------------------------------------
10 | 2.f() // error
| ^^^
| value f is not a member of Int, but could be made available as an extension method.
|
| The following import might fix the problem:
|
| import foo.O2.f
|
10 changes: 10 additions & 0 deletions tests/neg/22145d.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package foo

class C[T]:
extension (x: T) def f(): Int = 1

object O1 extends C[String]
object O2 extends C[Int]

def main =
2.f() // error
9 changes: 9 additions & 0 deletions tests/neg/22145e.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- [E008] Not Found Error: tests/neg/22145e.scala:11:4 -----------------------------------------------------------------
11 | 2.f() // error
| ^^^
| value f is not a member of Int, but could be made available as an extension method.
|
| The following import might fix the problem:
|
| import foo.O2.Ext.f
|
11 changes: 11 additions & 0 deletions tests/neg/22145e.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package foo

class C[T]:
object Ext:
extension (x: T) def f(): Int = 1

object O1 extends C[String]
object O2 extends C[Int]

def main =
2.f() // error
10 changes: 10 additions & 0 deletions tests/neg/22145f.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- [E008] Not Found Error: tests/neg/22145f.scala:11:6 -----------------------------------------------------------------
11 | 2.f() // error
| ^^^
| value f is not a member of Int, but could be made available as an extension method.
|
| One of the following imports might fix the problem:
|
| import C.this.O1.O2.Ext.f
| import C.this.O2.Ext.f
|
11 changes: 11 additions & 0 deletions tests/neg/22145f.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package foo

class C[T]:
object Ext:
extension (x: T) def f(): Int = 1

object O1 extends C[String]
object O2 extends C[Int]

def g =
2.f() // error
4 changes: 4 additions & 0 deletions tests/neg/22145g.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- [E008] Not Found Error: tests/neg/22145g.scala:10:4 -----------------------------------------------------------------
10 | 2.f() // error
| ^^^
| value f is not a member of Int
10 changes: 10 additions & 0 deletions tests/neg/22145g.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package foo

class C[T]:
extension (x: T) def f(): Int = 1

object O:
val c0: C[String] = new C[String]
val c1: C[Int] = new C[Int]
// Currently no import suggestions here
2.f() // error

0 comments on commit a2412d8

Please sign in to comment.