Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 46 additions & 32 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,9 @@ object Completion:

private val logger = Logger.getLogger(this.getClass.getName)

def scopeContext(pos: SourcePosition)(using Context): CompletionResult =
val tpdPath = Interactive.pathTo(ctx.compilationUnit.tpdTree, pos.span)
val completionContext = Interactive.contextOfPath(tpdPath).withPhase(Phases.typerPhase)
def scopeContext(pos: SourcePosition, tpdPath: List[tpd.Tree], completionContext: Context)(using Context): CompletionResult =
inContext(completionContext):
val untpdPath = Interactive.resolveTypedOrUntypedPath(tpdPath, pos)
val rawPrefix = completionPrefix(untpdPath, pos)
// Lazy mode is to avoid too many checks as it's mostly for printing types
val completer = new Completer(Mode.Lazy, pos, untpdPath, _ => true)
completer.scopeCompletions
Expand Down Expand Up @@ -87,10 +84,11 @@ object Completion:
rawPrefix: String,
tpdPath: List[tpd.Tree],
untpdPath: List[untpd.Tree],
customMatcher: Option[Name => Boolean] = None
customMatcher: Option[Name => Boolean] = None,
calculatedScopeContext: Option[CompletionResult] = None
)(using Context): CompletionMap =
val adjustedPath = typeCheckExtensionConstructPath(untpdPath, tpdPath, pos)
computeCompletions(pos, mode, rawPrefix, adjustedPath, untpdPath, customMatcher)
computeCompletions(pos, mode, rawPrefix, adjustedPath, untpdPath, customMatcher, calculatedScopeContext)

/**
* Inspect `path` to determine what kinds of symbols should be considered.
Expand Down Expand Up @@ -240,23 +238,38 @@ object Completion:
rawPrefix: String,
adjustedPath: List[tpd.Tree],
untpdPath: List[untpd.Tree],
matches: Option[Name => Boolean]
)(using Context): CompletionMap =
matches: Option[Name => Boolean],
calculatedScopeContext: Option[CompletionResult]
)(using ctx: Context): CompletionMap =
val hasBackTick = rawPrefix.headOption.contains('`')
val prefix = if hasBackTick then rawPrefix.drop(1) else rawPrefix
val matches0 = matches.getOrElse(_.startsWith(prefix))
val completer = new Completer(mode, pos, untpdPath, matches0)
lazy val completer = new Completer(mode, pos, untpdPath, matches0)
lazy val scopeContextNames = calculatedScopeContext match
case Some(scopeContext) =>
val isNew = isInNewContext(untpdPath)
scopeContext.names.flatMap {
case (name, CompletionDenotation(denots, site)) if matches0(name) =>
def isAccessible(denot: SingleDenotation): Boolean =
site.map(denot.symbol.isAccessibleFrom(_)).getOrElse(true)
val filtered = denots.filter(denot =>
isValidCompletionSymbol(denot.symbol, mode, isNew) && isAccessible(denot)
)
if filtered.nonEmpty then Some(name -> filtered) else None
case _ => None
}.toMap
case None => completer.scopeCompletions.names.map((name, denot) => name -> denot.denots)

val result = adjustedPath match
// Ignore synthetic select from `This` because in code it was `Ident`
// See example in dotty.tools.languageserver.CompletionTest.syntheticThis
case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions.names
case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => scopeContextNames
case StringContextApplication(qual) =>
completer.scopeCompletions.names ++ completer.selectionCompletions(qual)
scopeContextNames ++ completer.selectionCompletions(qual)
case tpd.Select(qual, _) :: _ => completer.selectionCompletions(qual)
case (tree: tpd.ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr)
case NamedTupleSelection(qual) => completer.selectionCompletions(qual)
case _ => completer.scopeCompletions.names
case _ => scopeContextNames

interactiv.println(i"""completion info with pos = $pos,
| term = ${completer.mode.is(Mode.Term)},
Expand Down Expand Up @@ -350,7 +363,7 @@ object Completion:
// running sourceSymbol on ExportedTerm will force a lot of computation from collectSubTrees
(sym.is(ExportedTerm) || sym.sourceSymbol.exists) &&
(!sym.is(Package) || sym.is(ModuleClass)) &&
!sym.isAllOf(Mutable | Accessor) &&
!(sym.is(Mutable) && sym.is(Accessor)) &&
!sym.isPackageObject &&
!sym.is(Artifact) &&
!(completionMode.is(Mode.Type) && sym.isAllOf(ConstructorProxyModule)) &&
Expand Down Expand Up @@ -401,10 +414,10 @@ object Completion:
/** Temporary data structure representing denotations with the same name introduced in a given scope
* as a member of a type, by a local definition or by an import clause
*/
case class ScopedDenotations private (denots: Seq[SingleDenotation], ctx: Context)
case class ScopedDenotations private (denot: CompletionDenotation, ctx: Context)
object ScopedDenotations:
def apply(denots: Seq[SingleDenotation], ctx: Context, includeFn: SingleDenotation => Boolean): ScopedDenotations =
ScopedDenotations(denots.filter(includeFn), ctx)
def apply(denot: CompletionDenotation, ctx: Context, includeFn: SingleDenotation => Boolean): ScopedDenotations =
ScopedDenotations(CompletionDenotation(denot.denots.filter(includeFn), denot.site), ctx)

val mappings = collection.mutable.Map.empty[Name, List[ScopedDenotations]].withDefaultValue(List.empty)
val renames = collection.mutable.Map.empty[Symbol, Name]
Expand All @@ -414,26 +427,26 @@ object Completion:
ctx.outersIterator.foreach { case ctx @ given Context =>
if ctx.isImportContext then
val imported = importedCompletions
imported.names.foreach { (name, denots) =>
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
imported.names.foreach { (name, denot) =>
addMapping(name, ScopedDenotations(denot, ctx, include(_, name)))
}
imported.renames.foreach { (name, newName) =>
renames(name) = newName
}
else if ctx.owner.isClass then
accessibleMembers(ctx.owner.thisType)
.groupByName.foreach { (name, denots) =>
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
addMapping(name, ScopedDenotations(CompletionDenotation(denots, Some(ctx.owner.thisType)), ctx, include(_, name)))
}
else if ctx.scope ne EmptyScope then
ctx.scope.toList.filter(symbol => include(symbol, symbol.name))
.flatMap(_.alternatives)
.groupByName.foreach { (name, denots) =>
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
addMapping(name, ScopedDenotations(CompletionDenotation(denots, None), ctx, include(_, name)))
}
}

var resultMappings = Map.empty[Name, Seq[SingleDenotation]]
var resultMappings = Map.empty[Name, CompletionDenotation]

mappings.foreach { (name, denotss) =>
val first = denotss.head
Expand All @@ -445,7 +458,7 @@ object Completion:
def isImportedInDifferentScope = first.ctx.scope ne denotss(1).ctx.scope
// import a.C
// import a.C
def isSameSymbolImportedDouble = denotss.forall(_.denots == first.denots)
def isSameSymbolImportedDouble = denotss.forall(_.denot.denots == first.denot.denots)

// https://scala-lang.org/files/archive/spec/3.4/02-identifiers-names-and-scopes.html
// import java.lang.*
Expand All @@ -457,16 +470,16 @@ object Completion:
// }
// }
def notConflictingWithDefaults = // is imported symbol
denotss.filterNot(_.denots.exists(denot => Interactive.isImportedByDefault(denot.symbol))).size <= 1
denotss.filterNot(_.denot.denots.exists(denot => Interactive.isImportedByDefault(denot.symbol))).size <= 1

denotss.find(!_.ctx.isImportContext) match {
// most deeply nested member or local definition if not shadowed by an import
case Some(local) if local.ctx.scope == first.ctx.scope =>
resultMappings += name -> local.denots
resultMappings += name -> local.denot
case None if isSingleImport || isImportedInDifferentScope || isSameSymbolImportedDouble =>
resultMappings += name -> first.denots
resultMappings += name -> first.denot
case None if notConflictingWithDefaults =>
val ordered = denotss.map(_.denots).sorted
val ordered = denotss.map(_.denot).sortBy(_.denots)
resultMappings += name -> ordered.head
case _ =>
}
Expand Down Expand Up @@ -553,7 +566,7 @@ object Completion:
}.toSeq.groupByName

val results = givenImports ++ wildcardMembers ++ explicitMembers
CompletionResult(results, renames.toMap)
CompletionResult(results.map((name, denots) => name -> CompletionDenotation(denots, Some(imp.site))), renames.toMap)
end importedCompletions

/** Completions from implicit conversions including old style extensions using implicit classes */
Expand Down Expand Up @@ -638,8 +651,8 @@ object Completion:

// 1. The extension method is visible under a simple name, by being defined or inherited or imported in a scope enclosing the reference.
val extMethodsInScope = scopeCompletions.names.toList.flatMap:
case (name, denots) =>
denots.collect:
case (name, denot) =>
denot.denots.collect:
case d if d.isTerm && d.symbol.is(Extension) => (d.symbol.termRef, name.asTermName)

// 2. The extension method is a member of some given instance that is visible at the point of the reference.
Expand Down Expand Up @@ -732,7 +745,7 @@ object Completion:
/** Filter for names that should appear when looking for completions. */
private object completionsFilter extends NameFilter:
def apply(pre: Type, name: Name)(using Context): Boolean =
!name.isConstructorName && name.toTermName.info.kind == SimpleNameKind
!name.isConstructorName && name.toTermName.info.kind == SimpleNameKind && matches(name)
def isStable = true

extension (denotations: Seq[SingleDenotation])
Expand All @@ -743,8 +756,9 @@ object Completion:
def groupByName: CompletionMap = namedDenotations.groupMap((name, denot) => name)((name, denot) => denot)

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

case class CompletionResult(names: Map[Name, Seq[SingleDenotation]], renames: Map[Symbol, Name])
// A list of denotations together with site for checking accessibility
case class CompletionDenotation(denots: Seq[SingleDenotation], site: Option[Type])
case class CompletionResult(names: Map[Name, CompletionDenotation], renames: Map[Symbol, Name])
/**
* The completion mode: defines what kinds of symbols should be included in the completion
* results.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ final class AutoImportsProvider(
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
val path =
Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using newctx)

val indexedContext = IndexedContext(pos)(
using Interactive.contextOfPath(path)(using newctx)
)
val indexedContext = IndexedContext(pos, path, newctx)
import indexedContext.ctx

def correctInTreeContext(sym: Symbol) = path match
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,11 @@ final class ExtractMethodProvider(
val pos = driver.sourcePosition(range).startPos
val path =
Interactive.pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx)
given locatedCtx: Context =
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
Interactive.contextOfPath(path)(using newctx)
val indexedCtx = IndexedContext(pos)(using locatedCtx)
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
val indexedContext = IndexedContext(pos, path, newctx)
import indexedContext.ctx
val printer =
ShortenedTypePrinter(search, IncludeDefaultParam.Never)(using indexedCtx)
ShortenedTypePrinter(search, IncludeDefaultParam.Never)(using indexedContext)
def prettyPrint(tpe: Type) =
def prettyPrintReturnType(tpe: Type): String =
tpe match
Expand Down Expand Up @@ -135,7 +134,7 @@ final class ExtractMethodProvider(
val extractedPos = head.sourcePos.withEnd(expr.sourcePos.end)
val exprType = prettyPrint(expr.typeOpt.widen)
val name =
genName(indexedCtx.scopeSymbols.map(_.decodedName).toSet, "newMethod")
genName(indexedContext.scopeSymbols.map(_.decodedName).toSet, "newMethod")
val (allMethodParams, typeParams) =
localRefs(extracted, stat.sourcePos, extractedPos)
val (methodParams, implicitParams) = allMethodParams.partition(!_.isOneOf(Flags.GivenOrImplicit))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ object HoverProvider:
val path = unit
.map(unit => Interactive.pathTo(unit.tpdTree, pos.span))
.getOrElse(Interactive.pathTo(driver.openedTrees(uri), pos))
val indexedContext = IndexedContext(pos)(using ctx)
val indexedContext = IndexedContext(pos, path, ctx)
import indexedContext.ctx

def typeFromPath(path: List[Tree]) =
if path.isEmpty then NoType else path.head.typeOpt
Expand Down Expand Up @@ -170,6 +171,10 @@ object HoverProvider:
else
val skipCheckOnName =
!pos.isPoint // don't check isHoveringOnName for RangeHover

val printer = ShortenedTypePrinter(search, IncludeDefaultParam.Include)(
using indexedContext
)
MetalsInteractive.enclosingSymbolsWithExpressionType(
enclosing,
pos,
Expand Down
26 changes: 18 additions & 8 deletions presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
package dotty.tools.pc

import scala.annotation.tailrec
import scala.meta.pc.OffsetParams
import scala.util.control.NonFatal

import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Denotations.PreDenotation
import dotty.tools.dotc.core.Denotations.SingleDenotation
import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.NameOps.*
import dotty.tools.dotc.core.Names.*
import dotty.tools.dotc.core.Phases
import dotty.tools.dotc.core.Scopes.EmptyScope
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.interactive.Completion
import dotty.tools.dotc.interactive.Completion.CompletionResult
import dotty.tools.dotc.interactive.Interactive
import dotty.tools.dotc.interactive.InteractiveDriver
import dotty.tools.dotc.typer.ImportInfo
import dotty.tools.dotc.util.SourcePosition
import dotty.tools.pc.IndexedContext.Result
import dotty.tools.pc.utils.InteractiveEnrichments.*

sealed trait IndexedContext:
given ctx: Context
def scopeContext: CompletionResult
def scopeSymbols: List[Symbol]
def rename(sym: Symbol): Option[String]
def findSymbol(name: Name, fromPrefix: Option[Type] = None): Option[List[Symbol]]
Expand Down Expand Up @@ -74,24 +80,28 @@ end IndexedContext

object IndexedContext:

def apply(pos: SourcePosition)(using Context): IndexedContext =
ctx match
def apply(pos: SourcePosition, tpdPath: List[tpd.Tree], driverCtx: Context): IndexedContext =
driverCtx match
case NoContext => Empty
case _ => LazyWrapper(pos)(using ctx)
case _ =>
val typerCtx: Context = Interactive
.contextOfPath(tpdPath)(using driverCtx).withPhase(Phases.typerPhase(using driverCtx))
LazyWrapper(pos, tpdPath)(using typerCtx)

case object Empty extends IndexedContext:
given ctx: Context = NoContext
def scopeContext: CompletionResult = CompletionResult(Map.empty, Map.empty)
def findSymbol(name: Name, fromPrefix: Option[Type]): Option[List[Symbol]] = None
def findSymbolInLocalScope(name: String): Option[List[Symbol]] = None
def scopeSymbols: List[Symbol] = List.empty
def rename(sym: Symbol): Option[String] = None

class LazyWrapper(pos: SourcePosition)(using val ctx: Context) extends IndexedContext:
class LazyWrapper(pos: SourcePosition, tpdPath: List[tpd.Tree])(using val ctx: Context) extends IndexedContext:

val completionContext = Completion.scopeContext(pos)
val names: Map[String, Seq[SingleDenotation]] = completionContext.names.toList.groupBy(_._1.show).map {
val scopeContext: CompletionResult = Completion.scopeContext(pos, tpdPath, ctx)
val names: Map[String, Seq[SingleDenotation]] = scopeContext.names.toList.groupBy(_._1.show).map {
case (name, denotations) =>
val denots = denotations.flatMap(_._2)
val denots = denotations.flatMap(_._2.denots)
val nonRoot = denots.filter(!_.symbol.owner.isRoot)
val (importedByDefault, conflictingValue) =
denots.partition(denot => Interactive.isImportedByDefault(denot.symbol))
Expand All @@ -100,7 +110,7 @@ object IndexedContext:
else
name.trim -> nonRoot
}
val renames = completionContext.renames
val renames = scopeContext.renames

def defaultScopes(name: Name): Option[List[Symbol]] =
List(defn.ScalaPredefModuleClass, defn.ScalaPackageClass, defn.JavaLangPackageClass)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,13 @@ class InferExpectedType(
case Some(unit) =>
val path =
Interactive.pathTo(driver.openedTrees(uri), pos)(using ctx)
val newctx = ctx.fresh.setCompilationUnit(unit)
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
val tpdPath =
Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using newctx)
val locatedCtx =
Interactive.contextOfPath(tpdPath)(using newctx)
val indexedCtx = IndexedContext(pos)(using locatedCtx)
val indexedContext = IndexedContext(pos, tpdPath, newctx)
import indexedContext.ctx
val printer =
ShortenedTypePrinter(search, IncludeDefaultParam.ResolveLater)(using indexedCtx)
ShortenedTypePrinter(search, IncludeDefaultParam.ResolveLater)(using indexedContext)
InferCompletionType.inferType(path)(using newctx).map {
tpe => printer.tpe(tpe)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,24 @@ final class InferredMethodProvider(
val path =
Interactive.pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx)

given locatedCtx: Context = driver.localContext(params)
val indexedCtx = IndexedContext(pos)(using locatedCtx)
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
val indexedContext = IndexedContext(pos, path, newctx)
import indexedContext.ctx

val autoImportsGen = AutoImports.generator(
pos,
sourceText,
unit.tpdTree,
unit.comments,
indexedCtx,
indexedContext,
config
)

val printer = ShortenedTypePrinter(
symbolSearch,
includeDefaultParam = IncludeDefaultParam.ResolveLater,
isTextEdit = true
)(using indexedCtx)
)(using indexedContext)

def imports: List[TextEdit] =
printer.imports(autoImportsGen)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ final class InferredTypeProvider(
driver.run(uri, source)
val unit = driver.currentCtx.run.nn.units.head
val pos = driver.sourcePosition(params)
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
val path =
Interactive.pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx)

given locatedCtx: Context = driver.localContext(params)
val indexedCtx = IndexedContext(pos)(using locatedCtx)
Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using newctx)
val indexedCtx = IndexedContext(pos, path, newctx)
import indexedCtx.ctx
val autoImportsGen = AutoImports.generator(
pos,
sourceText,
Expand Down
Loading
Loading