Skip to content

Commit 9de2f64

Browse files
committed
Restrict import suggestions or mention accessibility
1 parent 3f2365d commit 9de2f64

File tree

3 files changed

+55
-8
lines changed

3 files changed

+55
-8
lines changed

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import config.Printers.{implicits, implicitsDetailed}
1212
import ast.{untpd, tpd}
1313
import Implicits.{hasExtMethod, Candidate}
1414
import java.util.{Timer, TimerTask}
15-
import collection.mutable
15+
import collection.mutable, mutable.ListBuffer
1616
import scala.util.control.NonFatal
1717
import cc.isCaptureChecking
1818

@@ -57,7 +57,7 @@ trait ImportSuggestions:
5757
* skipped as an optimization, since they won't contain implicits anyway.
5858
*/
5959
private def suggestionRoots(using Context) =
60-
val seen = mutable.Set[TermRef]()
60+
val seen = mutable.Set.empty[TermRef]
6161

6262
def lookInside(root: Symbol)(using Context): Boolean =
6363
explore {
@@ -70,7 +70,7 @@ trait ImportSuggestions:
7070
}
7171

7272
def nestedRoots(site: Type)(using Context): List[Symbol] =
73-
val seenNames = mutable.Set[Name]()
73+
val seenNames = mutable.Set.empty[Name]
7474
site.baseClasses.flatMap { bc =>
7575
bc.info.decls.filter { dcl =>
7676
lookInside(dcl)
@@ -275,7 +275,7 @@ trait ImportSuggestions:
275275
* have the same String part. Elements are sorted by their String parts.
276276
*/
277277
extension (refs: List[(TermRef, String)]) def distinctRefs(using Context): List[TermRef] =
278-
val buf = new mutable.ListBuffer[TermRef]
278+
val buf = ListBuffer.empty[TermRef]
279279
var last = ""
280280
for (ref, str) <- refs do
281281
if last != str then
@@ -290,7 +290,7 @@ trait ImportSuggestions:
290290
extension (refs: List[TermRef]) def best(n: Int)(using Context): List[TermRef] =
291291
val top = new Array[TermRef](n)
292292
var filled = 0
293-
val rest = new mutable.ListBuffer[TermRef]
293+
val rest = ListBuffer.empty[TermRef]
294294
val noImplicitsCtx = ctx.retractMode(Mode.ImplicitsEnabled)
295295
for ref <- refs do
296296
var i = 0
@@ -335,15 +335,33 @@ trait ImportSuggestions:
335335
else
336336
ctx.printer.toTextRef(ref).show
337337
s" import $imported"
338-
val suggestions = suggestedRefs
338+
def indubitably(ref: TermRef): Boolean =
339+
ref.symbol.isAccessibleFrom(ctx.owner.info)
340+
val (suggested, dubious) = suggestedRefs
339341
.zip(suggestedRefs.map(importString))
340342
.filter((ref, str) => str.contains('.')) // must be a real import with `.`
341343
.sortBy(_._2) // sort first alphabetically for stability
342344
.distinctRefs // TermRefs might be different but generate the same strings
343345
.best(MaxSuggestions) // take MaxSuggestions best references according to specificity
344-
.map(importString)
345-
if suggestions.isEmpty then ""
346+
.partition(indubitably)
347+
if suggested.isEmpty then
348+
if dubious.isEmpty then ""
349+
else
350+
def isImportable(ref: TermRef): Boolean =
351+
ctx.outersIterator.exists(outer => outer.isImportContext && !outer.importInfo.nn.isRootImport
352+
&& outer.importInfo.nn.site =:= ref.prefix
353+
&& outer.importInfo.nn.selectors.exists(sel => sel.isWildcard || sel.name == ref.name))
354+
def dubiousAdvice(ref: TermRef): String =
355+
s"${importString(ref)}${if isImportable(ref) then " // existing imported member is not accessible" else ""}"
356+
i"""
357+
|
358+
|Consider making one of the following imports accessible to $help:
359+
|
360+
|${dubious.map(dubiousAdvice)}%\n%
361+
|
362+
|"""
346363
else
364+
val suggestions = suggested.map(importString)
347365
val fix =
348366
if suggestions.tail.isEmpty then "The following import"
349367
else "One of the following imports"

tests/neg/i22429.check

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- [E008] Not Found Error: tests/neg/i22429.scala:9:16 -----------------------------------------------------------------
2+
9 | val a = "abc".testExt // error
3+
| ^^^^^^^^^^^^^
4+
| value testExt is not a member of String, but could be made available as an extension method.
5+
|
6+
| Consider making one of the following imports accessible to fix:
7+
|
8+
| import Extensions.testExt // existing imported member is not accessible
9+
|
10+
-- [E008] Not Found Error: tests/neg/i22429.scala:10:13 ----------------------------------------------------------------
11+
10 | val b = 42.xs // error
12+
| ^^^^^
13+
| value xs is not a member of Int, but could be made available as an extension method.
14+
|
15+
| Consider making one of the following imports accessible to fix:
16+
|
17+
| import Extensions.xs
18+
|

tests/neg/i22429.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
import Extensions.testExt
3+
4+
object Extensions:
5+
extension (s: String) private def testExt = s.reverse
6+
extension (n: Int) private def xs = "x" * n
7+
8+
@main def test() = println:
9+
val a = "abc".testExt // error
10+
val b = 42.xs // error
11+
a + b

0 commit comments

Comments
 (0)