Skip to content

Avoid infinite recursion when looking for suggestions #22361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading