Skip to content

Commit e165251

Browse files
authored
Merge pull request #389 from scala/backport-lts-3.3-22898
Backport "improvement: Rework IndexedContext to reuse the previously calculated scopes" to 3.3 LTS
2 parents 7ebc7bf + 79e4a42 commit e165251

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)