Skip to content

Commit b057c81

Browse files
committed
improvement: Rework IndexedContext to reuse the previously calculated scopes (scala#22898)
It turns out the work being done in IndexedContext was already done in Completions, but better, since it doesn't try to read files as the separate logic does. There is still some improvement to be done to not calculate it twice, but in order to keep this PR as simple as possible I will skip that for now.
1 parent f5207d3 commit b057c81

23 files changed

+252
-282
lines changed

compiler/src/dotty/tools/dotc/interactive/Completion.scala

+40-16
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ case class Completion(label: String, description: String, symbols: List[Symbol])
4646

4747
object Completion:
4848

49+
def scopeContext(pos: SourcePosition)(using Context): CompletionResult =
50+
val tpdPath = Interactive.pathTo(ctx.compilationUnit.tpdTree, pos.span)
51+
val completionContext = Interactive.contextOfPath(tpdPath).withPhase(Phases.typerPhase)
52+
inContext(completionContext):
53+
val untpdPath = Interactive.resolveTypedOrUntypedPath(tpdPath, pos)
54+
val mode = completionMode(untpdPath, pos, forSymbolSearch = true)
55+
val rawPrefix = completionPrefix(untpdPath, pos)
56+
val completer = new Completer(mode, pos, untpdPath, _ => true)
57+
completer.scopeCompletions
58+
4959
/** Get possible completions from tree at `pos`
5060
*
5161
* @return offset and list of symbols for possible completions
@@ -58,7 +68,6 @@ object Completion:
5868
val mode = completionMode(untpdPath, pos)
5969
val rawPrefix = completionPrefix(untpdPath, pos)
6070
val completions = rawCompletions(pos, mode, rawPrefix, tpdPath, untpdPath)
61-
6271
postProcessCompletions(untpdPath, completions, rawPrefix)
6372

6473
/** Get possible completions from tree at `pos`
@@ -87,7 +96,7 @@ object Completion:
8796
*
8897
* Otherwise, provide no completion suggestion.
8998
*/
90-
def completionMode(path: List[untpd.Tree], pos: SourcePosition): Mode = path match
99+
def completionMode(path: List[untpd.Tree], pos: SourcePosition, forSymbolSearch: Boolean = false): Mode = path match
91100
// Ignore `package foo@@` and `package foo.bar@@`
92101
case ((_: tpd.Select) | (_: tpd.Ident)):: (_ : tpd.PackageDef) :: _ => Mode.None
93102
case GenericImportSelector(sel) =>
@@ -100,11 +109,14 @@ object Completion:
100109
case untpd.Literal(Constants.Constant(_: String)) :: _ => Mode.Term | Mode.Scope // literal completions
101110
case (ref: untpd.RefTree) :: _ =>
102111
val maybeSelectMembers = if ref.isInstanceOf[untpd.Select] then Mode.Member else Mode.Scope
103-
104-
if (ref.name.isTermName) Mode.Term | maybeSelectMembers
112+
if (forSymbolSearch) then Mode.Term | Mode.Type | maybeSelectMembers
113+
else if (ref.name.isTermName) Mode.Term | maybeSelectMembers
105114
else if (ref.name.isTypeName) Mode.Type | maybeSelectMembers
106115
else Mode.None
107116

117+
case (_: tpd.TypeTree | _: tpd.MemberDef) :: _ if forSymbolSearch => Mode.Type | Mode.Term
118+
case (_: tpd.CaseDef) :: _ if forSymbolSearch => Mode.Type | Mode.Term
119+
case Nil if forSymbolSearch => Mode.Type | Mode.Term
108120
case _ => Mode.None
109121

110122
/** When dealing with <errors> in varios palces we check to see if they are
@@ -227,14 +239,14 @@ object Completion:
227239
val result = adjustedPath match
228240
// Ignore synthetic select from `This` because in code it was `Ident`
229241
// See example in dotty.tools.languageserver.CompletionTest.syntheticThis
230-
case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
242+
case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions.names
231243
case StringContextApplication(qual) =>
232-
completer.scopeCompletions ++ completer.selectionCompletions(qual)
244+
completer.scopeCompletions.names ++ completer.selectionCompletions(qual)
233245
case tpd.Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind =>
234246
completer.selectionCompletions(qual)
235247
case tpd.Select(qual, _) :: _ => Map.empty
236248
case (tree: tpd.ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr)
237-
case _ => completer.scopeCompletions
249+
case _ => completer.scopeCompletions.names
238250

239251
interactiv.println(i"""completion info with pos = $pos,
240252
| term = ${completer.mode.is(Mode.Term)},
@@ -335,6 +347,7 @@ object Completion:
335347
(completionMode.is(Mode.Term) && (sym.isTerm || sym.is(ModuleClass))
336348
|| (completionMode.is(Mode.Type) && (sym.isType || sym.isStableMember)))
337349
)
350+
end isValidCompletionSymbol
338351

339352
given ScopeOrdering(using Context): Ordering[Seq[SingleDenotation]] with
340353
val order =
@@ -368,7 +381,7 @@ object Completion:
368381
* (even if the import follows it syntactically)
369382
* - a more deeply nested import shadowing a member or a local definition causes an ambiguity
370383
*/
371-
def scopeCompletions(using context: Context): CompletionMap =
384+
def scopeCompletions(using context: Context): CompletionResult =
372385

373386
/** Temporary data structure representing denotations with the same name introduced in a given scope
374387
* as a member of a type, by a local definition or by an import clause
@@ -379,14 +392,19 @@ object Completion:
379392
ScopedDenotations(denots.filter(includeFn), ctx)
380393

381394
val mappings = collection.mutable.Map.empty[Name, List[ScopedDenotations]].withDefaultValue(List.empty)
395+
val renames = collection.mutable.Map.empty[Symbol, Name]
382396
def addMapping(name: Name, denots: ScopedDenotations) =
383397
mappings(name) = mappings(name) :+ denots
384398

385399
ctx.outersIterator.foreach { case ctx @ given Context =>
386400
if ctx.isImportContext then
387-
importedCompletions.foreach { (name, denots) =>
401+
val imported = importedCompletions
402+
imported.names.foreach { (name, denots) =>
388403
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
389404
}
405+
imported.renames.foreach { (name, newName) =>
406+
renames(name) = newName
407+
}
390408
else if ctx.owner.isClass then
391409
accessibleMembers(ctx.owner.thisType)
392410
.groupByName.foreach { (name, denots) =>
@@ -430,7 +448,6 @@ object Completion:
430448
// most deeply nested member or local definition if not shadowed by an import
431449
case Some(local) if local.ctx.scope == first.ctx.scope =>
432450
resultMappings += name -> local.denots
433-
434451
case None if isSingleImport || isImportedInDifferentScope || isSameSymbolImportedDouble =>
435452
resultMappings += name -> first.denots
436453
case None if notConflictingWithDefaults =>
@@ -440,7 +457,7 @@ object Completion:
440457
}
441458
}
442459

443-
resultMappings
460+
CompletionResult(resultMappings, renames.toMap)
444461
end scopeCompletions
445462

446463
/** Widen only those types which are applied or are exactly nothing
@@ -474,15 +491,20 @@ object Completion:
474491
/** Completions introduced by imports directly in this context.
475492
* Completions from outer contexts are not included.
476493
*/
477-
private def importedCompletions(using Context): CompletionMap =
494+
private def importedCompletions(using Context): CompletionResult =
478495
val imp = ctx.importInfo
496+
val renames = collection.mutable.Map.empty[Symbol, Name]
479497

480498
if imp == null then
481-
Map.empty
499+
CompletionResult(Map.empty, Map.empty)
482500
else
483501
def fromImport(name: Name, nameInScope: Name): Seq[(Name, SingleDenotation)] =
484502
imp.site.member(name).alternatives
485-
.collect { case denot if include(denot, nameInScope) => nameInScope -> denot }
503+
.collect { case denot if include(denot, nameInScope) =>
504+
if name != nameInScope then
505+
renames(denot.symbol) = nameInScope
506+
nameInScope -> denot
507+
}
486508

487509
val givenImports = imp.importedImplicits
488510
.map { ref => (ref.implicitName: Name, ref.underlyingRef.denot.asSingleDenotation) }
@@ -508,7 +530,8 @@ object Completion:
508530
fromImport(original.toTypeName, nameInScope.toTypeName)
509531
}.toSeq.groupByName
510532

511-
givenImports ++ wildcardMembers ++ explicitMembers
533+
val results = givenImports ++ wildcardMembers ++ explicitMembers
534+
CompletionResult(results, renames.toMap)
512535
end importedCompletions
513536

514537
/** Completions from implicit conversions including old style extensions using implicit classes */
@@ -562,7 +585,7 @@ object Completion:
562585

563586
// 1. The extension method is visible under a simple name, by being defined or inherited or imported in a scope enclosing the reference.
564587
val termCompleter = new Completer(Mode.Term, pos, untpdPath, matches)
565-
val extMethodsInScope = termCompleter.scopeCompletions.toList.flatMap:
588+
val extMethodsInScope = termCompleter.scopeCompletions.names.toList.flatMap:
566589
case (name, denots) => denots.collect:
567590
case d: SymDenotation if d.isTerm && d.termRef.symbol.is(Extension) => (d.termRef, name.asTermName)
568591

@@ -664,6 +687,7 @@ object Completion:
664687

665688
private type CompletionMap = Map[Name, Seq[SingleDenotation]]
666689

690+
case class CompletionResult(names: Map[Name, Seq[SingleDenotation]], renames: Map[Symbol, Name])
667691
/**
668692
* The completion mode: defines what kinds of symbols should be included in the completion
669693
* results.

presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala

+19-12
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ object AutoImports:
4040
case class Select(qual: SymbolIdent, name: String) extends SymbolIdent:
4141
def value: String = s"${qual.value}.$name"
4242

43-
def direct(name: String): SymbolIdent = Direct(name)
43+
def direct(name: String)(using Context): SymbolIdent = Direct(name)
4444

4545
def fullIdent(symbol: Symbol)(using Context): SymbolIdent =
4646
val symbols = symbol.ownersIterator.toList
@@ -70,7 +70,7 @@ object AutoImports:
7070
importSel: Option[ImportSel]
7171
):
7272

73-
def name: String = ident.value
73+
def name(using Context): String = ident.value
7474

7575
object SymbolImport:
7676

@@ -189,10 +189,13 @@ object AutoImports:
189189
ownerImport.importSel,
190190
)
191191
else
192-
(
193-
SymbolIdent.direct(symbol.nameBackticked),
194-
Some(ImportSel.Direct(symbol)),
195-
)
192+
renames(symbol) match
193+
case Some(rename) => (SymbolIdent.direct(rename), None)
194+
case None =>
195+
(
196+
SymbolIdent.direct(symbol.nameBackticked),
197+
Some(ImportSel.Direct(symbol)),
198+
)
196199
end val
197200

198201
SymbolImport(
@@ -223,9 +226,13 @@ object AutoImports:
223226
importSel
224227
)
225228
case None =>
229+
val reverse = symbol.ownersIterator.toList.reverse
230+
val fullName = reverse.drop(1).foldLeft(SymbolIdent.direct(reverse.head.nameBackticked)){
231+
case (acc, sym) => SymbolIdent.Select(acc, sym.nameBackticked(false))
232+
}
226233
SymbolImport(
227234
symbol,
228-
SymbolIdent.direct(symbol.fullNameBackticked),
235+
SymbolIdent.Direct(symbol.fullNameBackticked),
229236
None
230237
)
231238
end match
@@ -252,7 +259,6 @@ object AutoImports:
252259
val topPadding =
253260
if importPosition.padTop then "\n"
254261
else ""
255-
256262
val formatted = imports
257263
.map {
258264
case ImportSel.Direct(sym) => importName(sym)
@@ -267,15 +273,16 @@ object AutoImports:
267273
end renderImports
268274

269275
private def importName(sym: Symbol): String =
270-
if indexedContext.importContext.toplevelClashes(sym) then
276+
if indexedContext.toplevelClashes(sym, inImportScope = true) then
271277
s"_root_.${sym.fullNameBackticked(false)}"
272278
else
273279
sym.ownersIterator.zipWithIndex.foldLeft((List.empty[String], false)) { case ((acc, isDone), (sym, idx)) =>
274280
if(isDone || sym.isEmptyPackage || sym.isRoot) (acc, true)
275281
else indexedContext.rename(sym) match
276-
case Some(renamed) => (renamed :: acc, true)
277-
case None if !sym.isPackageObject => (sym.nameBackticked(false) :: acc, false)
278-
case None => (acc, false)
282+
// we can't import first part
283+
case Some(renamed) if idx != 0 => (renamed :: acc, true)
284+
case _ if !sym.isPackageObject => (sym.nameBackticked(false) :: acc, false)
285+
case _ => (acc, false)
279286
}._1.mkString(".")
280287
end AutoImportsGenerator
281288

presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ final class AutoImportsProvider(
4444
val path =
4545
Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using newctx)
4646

47-
val indexedContext = IndexedContext(
48-
Interactive.contextOfPath(path)(using newctx)
47+
val indexedContext = IndexedContext(pos)(
48+
using Interactive.contextOfPath(path)(using newctx)
4949
)
5050
import indexedContext.ctx
5151

@@ -96,7 +96,7 @@ final class AutoImportsProvider(
9696
text,
9797
tree,
9898
unit.comments,
99-
indexedContext.importContext,
99+
indexedContext,
100100
config
101101
)
102102
(sym: Symbol) => generator.forSymbol(sym)

presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ final class ExtractMethodProvider(
5151
given locatedCtx: Context =
5252
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
5353
Interactive.contextOfPath(path)(using newctx)
54-
val indexedCtx = IndexedContext(locatedCtx)
54+
val indexedCtx = IndexedContext(pos)(using locatedCtx)
5555
val printer =
5656
ShortenedTypePrinter(search, IncludeDefaultParam.Never)(using indexedCtx)
5757
def prettyPrint(tpe: Type) =

presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala

+8-3
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ object HoverProvider:
4747
val path = unit
4848
.map(unit => Interactive.pathTo(unit.tpdTree, pos.span))
4949
.getOrElse(Interactive.pathTo(driver.openedTrees(uri), pos))
50-
val indexedContext = IndexedContext(ctx)
50+
val indexedContext = IndexedContext(pos)(using ctx)
5151

5252
def typeFromPath(path: List[Tree]) =
5353
if path.isEmpty then NoType else path.head.typeOpt
@@ -94,7 +94,7 @@ object HoverProvider:
9494

9595
val printerCtx = Interactive.contextOfPath(path)
9696
val printer = ShortenedTypePrinter(search, IncludeDefaultParam.Include)(
97-
using IndexedContext(printerCtx)
97+
using IndexedContext(pos)(using printerCtx)
9898
)
9999
MetalsInteractive.enclosingSymbolsWithExpressionType(
100100
enclosing,
@@ -131,7 +131,12 @@ object HoverProvider:
131131
.flatMap(symTpe => search.symbolDocumentation(symTpe._1, contentType))
132132
.map(_.docstring())
133133
.mkString("\n")
134-
printer.expressionType(exprTpw) match
134+
135+
val expresionTypeOpt =
136+
if symbol.name == nme.??? then
137+
InferExpectedType(search, driver, params).infer()
138+
else printer.expressionType(exprTpw)
139+
expresionTypeOpt match
135140
case Some(expressionType) =>
136141
val forceExpressionType =
137142
!pos.span.isZeroExtent || (

0 commit comments

Comments
 (0)