Skip to content

Commit 00a7ea1

Browse files
authored
Merge pull request #248 from scala/backport-lts-3.3-22361
Backport "Avoid infinite recursion when looking for suggestions" to 3.3 LTS
2 parents 310bda6 + e1fe8f3 commit 00a7ea1

15 files changed

+199
-22
lines changed

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

+25-22
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ trait ImportSuggestions:
6868
&& !(root.name == nme.raw.BAR && ctx.settings.scalajs.value && root == JSDefinitions.jsdefn.PseudoUnionModule)
6969
}
7070

71-
def nestedRoots(site: Type)(using Context): List[Symbol] =
71+
def nestedRoots(site: Type, parentSymbols: Set[Symbol])(using Context): List[Symbol] =
7272
val seenNames = mutable.Set[Name]()
7373
site.baseClasses.flatMap { bc =>
7474
bc.info.decls.filter { dcl =>
@@ -78,34 +78,37 @@ trait ImportSuggestions:
7878
}
7979
}
8080

81-
def rootsStrictlyIn(ref: Type)(using Context): List[TermRef] =
81+
def rootsStrictlyIn(ref: Type, parentSymbols: Set[Symbol] = Set())(using Context): List[TermRef] =
8282
val site = ref.widen
8383
val refSym = site.typeSymbol
84-
val nested =
85-
if refSym.is(Package) then
86-
if refSym == defn.EmptyPackageClass // Don't search the empty package
87-
|| refSym == defn.JavaPackageClass // As an optimization, don't search java...
88-
|| refSym == defn.JavaLangPackageClass // ... or java.lang.
89-
then Nil
90-
else refSym.info.decls.filter(lookInside)
91-
else if refSym.infoOrCompleter.isInstanceOf[StubInfo] then
92-
Nil // Don't chase roots that do not exist
93-
else
94-
if !refSym.is(Touched) then
95-
refSym.ensureCompleted() // JavaDefined is reliably known only after completion
96-
if refSym.is(JavaDefined) then Nil
97-
else nestedRoots(site)
98-
nested
99-
.map(mbr => TermRef(ref, mbr.asTerm))
100-
.flatMap(rootsIn)
101-
.toList
84+
if parentSymbols.contains(refSym) then Nil
85+
else
86+
val nested =
87+
if refSym.is(Package) then
88+
if refSym == defn.EmptyPackageClass // Don't search the empty package
89+
|| refSym == defn.JavaPackageClass // As an optimization, don't search java...
90+
|| refSym == defn.JavaLangPackageClass // ... or java.lang.
91+
then Nil
92+
else refSym.info.decls.filter(lookInside)
93+
else if refSym.infoOrCompleter.isInstanceOf[StubInfo] then
94+
Nil // Don't chase roots that do not exist
95+
else
96+
if !refSym.is(Touched) then
97+
refSym.ensureCompleted() // JavaDefined is reliably known only after completion
98+
if refSym.is(JavaDefined) then Nil
99+
else nestedRoots(site, parentSymbols)
100+
val newParentSymbols = parentSymbols + refSym
101+
nested
102+
.map(mbr => TermRef(ref, mbr.asTerm))
103+
.flatMap(rootsIn(_, newParentSymbols))
104+
.toList
102105

103-
def rootsIn(ref: TermRef)(using Context): List[TermRef] =
106+
def rootsIn(ref: TermRef, parentSymbols: Set[Symbol] = Set())(using Context): List[TermRef] =
104107
if seen.contains(ref) then Nil
105108
else
106109
implicitsDetailed.println(i"search for suggestions in ${ref.symbol.fullName}")
107110
seen += ref
108-
ref :: rootsStrictlyIn(ref)
111+
ref :: rootsStrictlyIn(ref, parentSymbols)
109112

110113
def rootsOnPath(tp: Type)(using Context): List[TermRef] = tp match
111114
case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix)

tests/neg/22145.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- [E008] Not Found Error: tests/neg/22145.scala:5:7 -------------------------------------------------------------------
2+
5 | base.foo() // error
3+
| ^^^^^^^^
4+
| value foo is not a member of foo.Collection

tests/neg/22145.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package foo
2+
3+
trait Collection:
4+
val base: Collection = ???
5+
base.foo() // error
6+
7+
object O extends Collection:
8+
def foo(): Int = ???

tests/neg/22145b.check

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
-- [E008] Not Found Error: tests/neg/22145b.scala:15:19 ----------------------------------------------------------------
2+
15 | require(base.isWithin(p, start, end), "position is out of bounds") // error
3+
| ^^^^^^^^^^^^^
4+
| value isWithin is not a member of Collection.this.Self
5+
-- [E008] Not Found Error: tests/neg/22145b.scala:28:59 ----------------------------------------------------------------
6+
28 | def positionAfter(p: Position): Position = self.base.positionAfter(p) // error
7+
| ^^^^^^^^^^^^^^^^^^^^^^^
8+
|value positionAfter is not a member of Collection.this.Self.
9+
|An extension method was tried, but could not be fully constructed:
10+
|
11+
| this.positionAfter(self.base)
12+
|
13+
| failed with:
14+
|
15+
| Found: (self.base : Collection.this.Self)
16+
| Required: foo.Collection.given_is_Slice_Collection.Self²
17+
|
18+
| where: Self is a type in trait Collection
19+
| Self² is a type in object given_is_Slice_Collection which is an alias of Collection.this.Slice
20+
|
21+
-- [E008] Not Found Error: tests/neg/22145b.scala:29:50 ----------------------------------------------------------------
22+
29 | def apply(p: Position): Element = self.base.apply(p) // error
23+
| ^^^^^^^^^^^^^^^
24+
|value apply is not a member of Collection.this.Self.
25+
|An extension method was tried, but could not be fully constructed:
26+
|
27+
| this.apply(self.base)
28+
|
29+
| failed with:
30+
|
31+
| Found: (self.base : Collection.this.Self)
32+
| Required: foo.Collection.given_is_Slice_Collection.Self²
33+
|
34+
| where: Self is a type in trait Collection
35+
| Self² is a type in object given_is_Slice_Collection which is an alias of Collection.this.Slice
36+
|

tests/neg/22145b.scala

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package foo
2+
3+
import language.experimental.modularity
4+
5+
trait Collection:
6+
me =>
7+
8+
type Self
9+
type Position
10+
type Element
11+
12+
final class Slice(private[Collection] val base: Self, val start: Position, val end: Position):
13+
14+
final def apply(p: Position): Element =
15+
require(base.isWithin(p, start, end), "position is out of bounds") // error
16+
base.apply(p)
17+
18+
end Slice
19+
20+
given Slice is Collection:
21+
22+
type Position = me.Position
23+
type Element = me.Element
24+
25+
extension (self: Self)
26+
def start: Position = self.start
27+
def end: Position = self.end
28+
def positionAfter(p: Position): Position = self.base.positionAfter(p) // error
29+
def apply(p: Position): Element = self.base.apply(p) // error
30+
31+
end given
32+
33+
extension (self: Self)
34+
35+
def start: Position
36+
def end: Position
37+
def positionAfter(p: Position): Position
38+
def apply(p: Position): Element
39+
40+
end extension

tests/neg/22145c.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- [E008] Not Found Error: tests/neg/22145c.scala:4:35 -----------------------------------------------------------------
2+
4 | def bar(base: Collection) = base.foo // error
3+
| ^^^^^^^^
4+
| value foo is not a member of foo.Collection

tests/neg/22145c.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package foo
2+
3+
trait Collection:
4+
def bar(base: Collection) = base.foo // error
5+
object a extends Collection:
6+
def foo: Int = 0
7+
object b extends Collection:
8+
def foo: Int = 1

tests/neg/22145d.check

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- [E008] Not Found Error: tests/neg/22145d.scala:10:4 -----------------------------------------------------------------
2+
10 | 2.f() // error
3+
| ^^^
4+
| value f is not a member of Int, but could be made available as an extension method.
5+
|
6+
| The following import might fix the problem:
7+
|
8+
| import foo.O2.f
9+
|

tests/neg/22145d.scala

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package foo
2+
3+
class C[T]:
4+
extension (x: T) def f(): Int = 1
5+
6+
object O1 extends C[String]
7+
object O2 extends C[Int]
8+
9+
def main =
10+
2.f() // error

tests/neg/22145e.check

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- [E008] Not Found Error: tests/neg/22145e.scala:11:4 -----------------------------------------------------------------
2+
11 | 2.f() // error
3+
| ^^^
4+
| value f is not a member of Int, but could be made available as an extension method.
5+
|
6+
| The following import might fix the problem:
7+
|
8+
| import foo.O2.Ext.f
9+
|

tests/neg/22145e.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package foo
2+
3+
class C[T]:
4+
object Ext:
5+
extension (x: T) def f(): Int = 1
6+
7+
object O1 extends C[String]
8+
object O2 extends C[Int]
9+
10+
def main =
11+
2.f() // error

tests/neg/22145f.check

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- [E008] Not Found Error: tests/neg/22145f.scala:11:6 -----------------------------------------------------------------
2+
11 | 2.f() // error
3+
| ^^^
4+
| value f is not a member of Int, but could be made available as an extension method.
5+
|
6+
| One of the following imports might fix the problem:
7+
|
8+
| import C.this.O1.O2.Ext.f
9+
| import C.this.O2.Ext.f
10+
|

tests/neg/22145f.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package foo
2+
3+
class C[T]:
4+
object Ext:
5+
extension (x: T) def f(): Int = 1
6+
7+
object O1 extends C[String]
8+
object O2 extends C[Int]
9+
10+
def g =
11+
2.f() // error

tests/neg/22145g.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- [E008] Not Found Error: tests/neg/22145g.scala:10:4 -----------------------------------------------------------------
2+
10 | 2.f() // error
3+
| ^^^
4+
| value f is not a member of Int

tests/neg/22145g.scala

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package foo
2+
3+
class C[T]:
4+
extension (x: T) def f(): Int = 1
5+
6+
object O:
7+
val c0: C[String] = new C[String]
8+
val c1: C[Int] = new C[Int]
9+
// Currently no import suggestions here
10+
2.f() // error

0 commit comments

Comments
 (0)