Skip to content

Commit 376f628

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 376f628

23 files changed

+219
-289
lines changed

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

+44-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,8 @@ 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 =
102+
path match
93103
// Ignore `package foo@@` and `package foo.bar@@`
94104
case ((_: tpd.Select) | (_: tpd.Ident)):: (_ : tpd.PackageDef) :: _ => Mode.None
95105
case GenericImportSelector(sel) =>
@@ -102,11 +112,14 @@ object Completion:
102112
case untpd.Literal(Constants.Constant(_: String)) :: _ => Mode.Term | Mode.Scope // literal completions
103113
case (ref: untpd.RefTree) :: _ =>
104114
val maybeSelectMembers = if ref.isInstanceOf[untpd.Select] then Mode.Member else Mode.Scope
105-
106-
if (ref.name.isTermName) Mode.Term | maybeSelectMembers
115+
if (forSymbolSearch) then Mode.Term | Mode.Type | maybeSelectMembers
116+
else if (ref.name.isTermName) Mode.Term | maybeSelectMembers
107117
else if (ref.name.isTypeName) Mode.Type | maybeSelectMembers
108118
else Mode.None
109119

120+
case (_: tpd.TypeTree | _: tpd.MemberDef) :: _ if forSymbolSearch => Mode.Type | Mode.Term
121+
case (_: tpd.CaseDef) :: _ if forSymbolSearch => Mode.Type | Mode.Term
122+
case Nil => Mode.Type | Mode.Term
110123
case _ => Mode.None
111124

112125
/** When dealing with <errors> in varios palces we check to see if they are
@@ -174,12 +187,12 @@ object Completion:
174187
case _ => None
175188

176189
private object StringContextApplication:
177-
def unapply(path: List[tpd.Tree]): Option[tpd.Apply] =
190+
def unapply(path: List[tpd.Tree]): Option[tpd.Apply] =
178191
path match
179192
case tpd.Select(qual @ tpd.Apply(tpd.Select(tpd.Select(_, StdNames.nme.StringContext), _), _), _) :: _ =>
180193
Some(qual)
181194
case _ => None
182-
195+
183196

184197
/** Inspect `path` to determine the offset where the completion result should be inserted. */
185198
def completionOffset(untpdPath: List[untpd.Tree]): Int =
@@ -230,14 +243,14 @@ object Completion:
230243
val result = adjustedPath match
231244
// Ignore synthetic select from `This` because in code it was `Ident`
232245
// See example in dotty.tools.languageserver.CompletionTest.syntheticThis
233-
case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
246+
case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions.names
234247
case StringContextApplication(qual) =>
235-
completer.scopeCompletions ++ completer.selectionCompletions(qual)
236-
case tpd.Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind =>
248+
completer.scopeCompletions.names ++ completer.selectionCompletions(qual)
249+
case tpd.Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind =>
237250
completer.selectionCompletions(qual)
238251
case tpd.Select(qual, _) :: _ => Map.empty
239252
case (tree: tpd.ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr)
240-
case _ => completer.scopeCompletions
253+
case _ => completer.scopeCompletions.names
241254

242255
interactiv.println(i"""completion info with pos = $pos,
243256
| term = ${completer.mode.is(Mode.Term)},
@@ -338,6 +351,7 @@ object Completion:
338351
(completionMode.is(Mode.Term) && (sym.isTerm || sym.is(ModuleClass))
339352
|| (completionMode.is(Mode.Type) && (sym.isType || sym.isStableMember)))
340353
)
354+
end isValidCompletionSymbol
341355

342356
given ScopeOrdering(using Context): Ordering[Seq[SingleDenotation]] with
343357
val order =
@@ -371,7 +385,7 @@ object Completion:
371385
* (even if the import follows it syntactically)
372386
* - a more deeply nested import shadowing a member or a local definition causes an ambiguity
373387
*/
374-
def scopeCompletions(using context: Context): CompletionMap =
388+
def scopeCompletions(using context: Context): CompletionResult =
375389

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

384398
val mappings = collection.mutable.Map.empty[Name, List[ScopedDenotations]].withDefaultValue(List.empty)
399+
val renames = collection.mutable.Map.empty[Symbol, Name]
385400
def addMapping(name: Name, denots: ScopedDenotations) =
386401
mappings(name) = mappings(name) :+ denots
387402

388403
ctx.outersIterator.foreach { case ctx @ given Context =>
389404
if ctx.isImportContext then
390-
importedCompletions.foreach { (name, denots) =>
405+
val imported = importedCompletions
406+
imported.names.foreach { (name, denots) =>
391407
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
392408
}
409+
imported.renames.foreach { (name, newName) =>
410+
renames(name) = newName
411+
}
393412
else if ctx.owner.isClass then
394413
accessibleMembers(ctx.owner.thisType)
395414
.groupByName.foreach { (name, denots) =>
@@ -433,7 +452,6 @@ object Completion:
433452
// most deeply nested member or local definition if not shadowed by an import
434453
case Some(local) if local.ctx.scope == first.ctx.scope =>
435454
resultMappings += name -> local.denots
436-
437455
case None if isSingleImport || isImportedInDifferentScope || isSameSymbolImportedDouble =>
438456
resultMappings += name -> first.denots
439457
case None if notConflictingWithDefaults =>
@@ -443,7 +461,7 @@ object Completion:
443461
}
444462
}
445463

446-
resultMappings
464+
CompletionResult(resultMappings, renames.toMap)
447465
end scopeCompletions
448466

449467
/** Widen only those types which are applied or are exactly nothing
@@ -485,15 +503,20 @@ object Completion:
485503
/** Completions introduced by imports directly in this context.
486504
* Completions from outer contexts are not included.
487505
*/
488-
private def importedCompletions(using Context): CompletionMap =
506+
private def importedCompletions(using Context): CompletionResult =
489507
val imp = ctx.importInfo
508+
val renames = collection.mutable.Map.empty[Symbol, Name]
490509

491510
if imp == null then
492-
Map.empty
511+
CompletionResult(Map.empty, Map.empty)
493512
else
494513
def fromImport(name: Name, nameInScope: Name): Seq[(Name, SingleDenotation)] =
495514
imp.site.member(name).alternatives
496-
.collect { case denot if include(denot, nameInScope) => nameInScope -> denot }
515+
.collect { case denot if include(denot, nameInScope) =>
516+
if name != nameInScope then
517+
renames(denot.symbol) = nameInScope
518+
nameInScope -> denot
519+
}
497520

498521
val givenImports = imp.importedImplicits
499522
.map { ref => (ref.implicitName: Name, ref.underlyingRef.denot.asSingleDenotation) }
@@ -519,7 +542,8 @@ object Completion:
519542
fromImport(original.toTypeName, nameInScope.toTypeName)
520543
}.toSeq.groupByName
521544

522-
givenImports ++ wildcardMembers ++ explicitMembers
545+
val results = givenImports ++ wildcardMembers ++ explicitMembers
546+
CompletionResult(results, renames.toMap)
523547
end importedCompletions
524548

525549
/** Completions from implicit conversions including old style extensions using implicit classes */
@@ -597,7 +621,7 @@ object Completion:
597621

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

@@ -699,6 +723,7 @@ object Completion:
699723

700724
private type CompletionMap = Map[Name, Seq[SingleDenotation]]
701725

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

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

+25-18
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
@@ -49,11 +49,11 @@ object AutoImports:
4949

5050
symbols match
5151
case head :: tail =>
52-
tail.foldLeft(direct(head.nameBackticked))((acc, next) =>
52+
tail.foldLeft(direct(head.name.decoded))((acc, next) =>
5353
Select(acc, next.nameBackticked)
5454
)
5555
case Nil =>
56-
SymbolIdent.direct("<no symbol>")
56+
SymbolIdent.direct("")
5757

5858
end SymbolIdent
5959

@@ -70,12 +70,12 @@ 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

7777
def simple(sym: Symbol)(using Context): SymbolImport =
78-
SymbolImport(sym, SymbolIdent.direct(sym.nameBackticked), None)
78+
SymbolImport(sym, SymbolIdent.direct(sym.name.decoded), None)
7979

8080
/**
8181
* Returns AutoImportsGenerator
@@ -98,7 +98,7 @@ object AutoImports:
9898
import indexedContext.ctx
9999

100100
val importPos = autoImportPosition(pos, text, tree, comments)
101-
val renameConfig: Map[Symbol, String] = AutoImport.renameConfigMap(config)
101+
val renameConfig: Map[Symbol, String] = AutoImport.renameConfigMap(config)
102102

103103
val renames =
104104
(sym: Symbol) =>
@@ -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.name.decoded),
197+
Some(ImportSel.Direct(symbol)),
198+
)
196199
end val
197200

198201
SymbolImport(
@@ -205,7 +208,7 @@ object AutoImports:
205208
renames(owner) match
206209
case Some(rename) =>
207210
val importSel =
208-
if rename != owner.showName then
211+
if rename != owner.name.decoded then
209212
Some(ImportSel.Rename(owner, rename)).filter(_ =>
210213
!indexedContext.hasRename(owner, rename)
211214
)
@@ -223,14 +226,18 @@ 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.name.decoded)){
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
232239
case IndexedContext.Result.InScope =>
233-
val direct = renames(symbol).getOrElse(symbol.nameBackticked)
240+
val direct = renames(symbol).getOrElse(symbol.name.decoded)
234241
SymbolImport(symbol, SymbolIdent.direct(direct), None)
235242
end match
236243
end inferSymbolImport
@@ -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/CompletionItemResolver.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ object CompletionItemResolver extends ItemResolver:
4242
item
4343
end match
4444

45-
case _ => item
45+
case _ =>
46+
item
4647
end match
4748
end resolve
4849

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)