diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 3ae533d58b2e..98fbede5f5ba 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -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 => @@ -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) diff --git a/tests/neg/22145.check b/tests/neg/22145.check new file mode 100644 index 000000000000..4592c42e9e7f --- /dev/null +++ b/tests/neg/22145.check @@ -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 diff --git a/tests/neg/22145.scala b/tests/neg/22145.scala new file mode 100644 index 000000000000..59d58b167ab4 --- /dev/null +++ b/tests/neg/22145.scala @@ -0,0 +1,8 @@ +package foo + +trait Collection: + val base: Collection = ??? + base.foo() // error + + object O extends Collection: + def foo(): Int = ??? diff --git a/tests/neg/22145b.check b/tests/neg/22145b.check new file mode 100644 index 000000000000..de605ce24276 --- /dev/null +++ b/tests/neg/22145b.check @@ -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 + | diff --git a/tests/neg/22145b.scala b/tests/neg/22145b.scala new file mode 100644 index 000000000000..5b8de5672fba --- /dev/null +++ b/tests/neg/22145b.scala @@ -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 diff --git a/tests/neg/22145c.check b/tests/neg/22145c.check new file mode 100644 index 000000000000..ddfb6b9daf1d --- /dev/null +++ b/tests/neg/22145c.check @@ -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 diff --git a/tests/neg/22145c.scala b/tests/neg/22145c.scala new file mode 100644 index 000000000000..7776e57e7906 --- /dev/null +++ b/tests/neg/22145c.scala @@ -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 diff --git a/tests/neg/22145d.check b/tests/neg/22145d.check new file mode 100644 index 000000000000..ac6469c10b82 --- /dev/null +++ b/tests/neg/22145d.check @@ -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 + | diff --git a/tests/neg/22145d.scala b/tests/neg/22145d.scala new file mode 100644 index 000000000000..bfb68e088322 --- /dev/null +++ b/tests/neg/22145d.scala @@ -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 diff --git a/tests/neg/22145e.check b/tests/neg/22145e.check new file mode 100644 index 000000000000..e1c34e59f239 --- /dev/null +++ b/tests/neg/22145e.check @@ -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 + | diff --git a/tests/neg/22145e.scala b/tests/neg/22145e.scala new file mode 100644 index 000000000000..579fd65c685e --- /dev/null +++ b/tests/neg/22145e.scala @@ -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 diff --git a/tests/neg/22145f.check b/tests/neg/22145f.check new file mode 100644 index 000000000000..b870eb21057a --- /dev/null +++ b/tests/neg/22145f.check @@ -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 + | diff --git a/tests/neg/22145f.scala b/tests/neg/22145f.scala new file mode 100644 index 000000000000..97f1336d1b1b --- /dev/null +++ b/tests/neg/22145f.scala @@ -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 diff --git a/tests/neg/22145g.check b/tests/neg/22145g.check new file mode 100644 index 000000000000..175949ac113f --- /dev/null +++ b/tests/neg/22145g.check @@ -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 diff --git a/tests/neg/22145g.scala b/tests/neg/22145g.scala new file mode 100644 index 000000000000..8b888516c044 --- /dev/null +++ b/tests/neg/22145g.scala @@ -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