Skip to content

Commit b2c2318

Browse files
committed
improvement: Rework IndexedContext to reuse the previously calculated scopes
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 2d034c2 commit b2c2318

22 files changed

+207
-280
lines changed

Diff for: compiler/src/dotty/tools/dotc/interactive/Completion.scala

+43-19
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ case class Completion(label: String, description: String, symbols: List[Symbol])
4848

4949
object Completion:
5050

51+
def scopeContext(pos: SourcePosition)(using Context): CompletionResult =
52+
val tpdPath = Interactive.pathTo(ctx.compilationUnit.tpdTree, pos.span)
53+
val completionContext = Interactive.contextOfPath(tpdPath).withPhase(Phases.typerPhase)
54+
inContext(completionContext):
55+
val untpdPath = Interactive.resolveTypedOrUntypedPath(tpdPath, pos)
56+
val mode = completionMode(untpdPath, pos, forSymbolSearch = true)
57+
val rawPrefix = completionPrefix(untpdPath, pos)
58+
val completer = new Completer(mode, pos, untpdPath, _ => true)
59+
completer.scopeCompletions
60+
5161
/** Get possible completions from tree at `pos`
5262
*
5363
* @return offset and list of symbols for possible completions
@@ -60,7 +70,6 @@ object Completion:
6070
val mode = completionMode(untpdPath, pos)
6171
val rawPrefix = completionPrefix(untpdPath, pos)
6272
val completions = rawCompletions(pos, mode, rawPrefix, tpdPath, untpdPath)
63-
6473
postProcessCompletions(untpdPath, completions, rawPrefix)
6574

6675
/** Get possible completions from tree at `pos`
@@ -89,7 +98,7 @@ object Completion:
8998
*
9099
* Otherwise, provide no completion suggestion.
91100
*/
92-
def completionMode(path: List[untpd.Tree], pos: SourcePosition): Mode = path match
101+
def completionMode(path: List[untpd.Tree], pos: SourcePosition, forSymbolSearch: Boolean = false): Mode = path match
93102
// Ignore `package foo@@` and `package foo.bar@@`
94103
case ((_: tpd.Select) | (_: tpd.Ident)):: (_ : tpd.PackageDef) :: _ => Mode.None
95104
case GenericImportSelector(sel) =>
@@ -102,11 +111,14 @@ object Completion:
102111
case untpd.Literal(Constants.Constant(_: String)) :: _ => Mode.Term | Mode.Scope // literal completions
103112
case (ref: untpd.RefTree) :: _ =>
104113
val maybeSelectMembers = if ref.isInstanceOf[untpd.Select] then Mode.Member else Mode.Scope
105-
106-
if (ref.name.isTermName) Mode.Term | maybeSelectMembers
114+
if (forSymbolSearch) then Mode.Term | Mode.Type | maybeSelectMembers
115+
else if (ref.name.isTermName) Mode.Term | maybeSelectMembers
107116
else if (ref.name.isTypeName) Mode.Type | maybeSelectMembers
108117
else Mode.None
109118

119+
case (_: tpd.TypeTree | _: tpd.MemberDef) :: _ if forSymbolSearch => Mode.Type | Mode.Term
120+
case (_: tpd.CaseDef) :: _ if forSymbolSearch => Mode.Type | Mode.Term
121+
case Nil if forSymbolSearch => Mode.Type | Mode.Term
110122
case _ => Mode.None
111123

112124
/** When dealing with <errors> in varios palces we check to see if they are
@@ -174,12 +186,12 @@ object Completion:
174186
case _ => None
175187

176188
private object StringContextApplication:
177-
def unapply(path: List[tpd.Tree]): Option[tpd.Apply] =
189+
def unapply(path: List[tpd.Tree]): Option[tpd.Apply] =
178190
path match
179191
case tpd.Select(qual @ tpd.Apply(tpd.Select(tpd.Select(_, StdNames.nme.StringContext), _), _), _) :: _ =>
180192
Some(qual)
181193
case _ => None
182-
194+
183195

184196
/** Inspect `path` to determine the offset where the completion result should be inserted. */
185197
def completionOffset(untpdPath: List[untpd.Tree]): Int =
@@ -230,14 +242,14 @@ object Completion:
230242
val result = adjustedPath match
231243
// Ignore synthetic select from `This` because in code it was `Ident`
232244
// See example in dotty.tools.languageserver.CompletionTest.syntheticThis
233-
case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
245+
case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions.names
234246
case StringContextApplication(qual) =>
235-
completer.scopeCompletions ++ completer.selectionCompletions(qual)
236-
case tpd.Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind =>
247+
completer.scopeCompletions.names ++ completer.selectionCompletions(qual)
248+
case tpd.Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind =>
237249
completer.selectionCompletions(qual)
238250
case tpd.Select(qual, _) :: _ => Map.empty
239251
case (tree: tpd.ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr)
240-
case _ => completer.scopeCompletions
252+
case _ => completer.scopeCompletions.names
241253

242254
interactiv.println(i"""completion info with pos = $pos,
243255
| term = ${completer.mode.is(Mode.Term)},
@@ -338,6 +350,7 @@ object Completion:
338350
(completionMode.is(Mode.Term) && (sym.isTerm || sym.is(ModuleClass))
339351
|| (completionMode.is(Mode.Type) && (sym.isType || sym.isStableMember)))
340352
)
353+
end isValidCompletionSymbol
341354

342355
given ScopeOrdering(using Context): Ordering[Seq[SingleDenotation]] with
343356
val order =
@@ -371,7 +384,7 @@ object Completion:
371384
* (even if the import follows it syntactically)
372385
* - a more deeply nested import shadowing a member or a local definition causes an ambiguity
373386
*/
374-
def scopeCompletions(using context: Context): CompletionMap =
387+
def scopeCompletions(using context: Context): CompletionResult =
375388

376389
/** Temporary data structure representing denotations with the same name introduced in a given scope
377390
* as a member of a type, by a local definition or by an import clause
@@ -382,14 +395,19 @@ object Completion:
382395
ScopedDenotations(denots.filter(includeFn), ctx)
383396

384397
val mappings = collection.mutable.Map.empty[Name, List[ScopedDenotations]].withDefaultValue(List.empty)
398+
val renames = collection.mutable.Map.empty[Symbol, Name]
385399
def addMapping(name: Name, denots: ScopedDenotations) =
386400
mappings(name) = mappings(name) :+ denots
387401

388402
ctx.outersIterator.foreach { case ctx @ given Context =>
389403
if ctx.isImportContext then
390-
importedCompletions.foreach { (name, denots) =>
404+
val imported = importedCompletions
405+
imported.names.foreach { (name, denots) =>
391406
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
392407
}
408+
imported.renames.foreach { (name, newName) =>
409+
renames(name) = newName
410+
}
393411
else if ctx.owner.isClass then
394412
accessibleMembers(ctx.owner.thisType)
395413
.groupByName.foreach { (name, denots) =>
@@ -433,7 +451,6 @@ object Completion:
433451
// most deeply nested member or local definition if not shadowed by an import
434452
case Some(local) if local.ctx.scope == first.ctx.scope =>
435453
resultMappings += name -> local.denots
436-
437454
case None if isSingleImport || isImportedInDifferentScope || isSameSymbolImportedDouble =>
438455
resultMappings += name -> first.denots
439456
case None if notConflictingWithDefaults =>
@@ -443,7 +460,7 @@ object Completion:
443460
}
444461
}
445462

446-
resultMappings
463+
CompletionResult(resultMappings, renames.toMap)
447464
end scopeCompletions
448465

449466
/** Widen only those types which are applied or are exactly nothing
@@ -485,15 +502,20 @@ object Completion:
485502
/** Completions introduced by imports directly in this context.
486503
* Completions from outer contexts are not included.
487504
*/
488-
private def importedCompletions(using Context): CompletionMap =
505+
private def importedCompletions(using Context): CompletionResult =
489506
val imp = ctx.importInfo
507+
val renames = collection.mutable.Map.empty[Symbol, Name]
490508

491509
if imp == null then
492-
Map.empty
510+
CompletionResult(Map.empty, Map.empty)
493511
else
494512
def fromImport(name: Name, nameInScope: Name): Seq[(Name, SingleDenotation)] =
495513
imp.site.member(name).alternatives
496-
.collect { case denot if include(denot, nameInScope) => nameInScope -> denot }
514+
.collect { case denot if include(denot, nameInScope) =>
515+
if name != nameInScope then
516+
renames(denot.symbol) = nameInScope
517+
nameInScope -> denot
518+
}
497519

498520
val givenImports = imp.importedImplicits
499521
.map { ref => (ref.implicitName: Name, ref.underlyingRef.denot.asSingleDenotation) }
@@ -519,7 +541,8 @@ object Completion:
519541
fromImport(original.toTypeName, nameInScope.toTypeName)
520542
}.toSeq.groupByName
521543

522-
givenImports ++ wildcardMembers ++ explicitMembers
544+
val results = givenImports ++ wildcardMembers ++ explicitMembers
545+
CompletionResult(results, renames.toMap)
523546
end importedCompletions
524547

525548
/** Completions from implicit conversions including old style extensions using implicit classes */
@@ -597,7 +620,7 @@ object Completion:
597620

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

@@ -699,6 +722,7 @@ object Completion:
699722

700723
private type CompletionMap = Map[Name, Seq[SingleDenotation]]
701724

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

Diff for: 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

Diff for: 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)

Diff for: 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) =

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

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

5454
def typeFromPath(path: List[Tree]) =
5555
if path.isEmpty then NoType else path.head.typeOpt
@@ -96,7 +96,7 @@ object HoverProvider:
9696

9797
val printerCtx = Interactive.contextOfPath(path)
9898
val printer = ShortenedTypePrinter(search, IncludeDefaultParam.Include)(
99-
using IndexedContext(printerCtx)
99+
using IndexedContext(pos)(using printerCtx)
100100
)
101101
MetalsInteractive.enclosingSymbolsWithExpressionType(
102102
enclosing,
@@ -134,7 +134,7 @@ object HoverProvider:
134134
.map(_.docstring())
135135
.mkString("\n")
136136

137-
val expresionTypeOpt =
137+
val expresionTypeOpt =
138138
if symbol.name == StdNames.nme.??? then
139139
InferExpectedType(search, driver, params).infer()
140140
else printer.expressionType(exprTpw)

0 commit comments

Comments
 (0)