From 12c317e46d1ddda8875b7eed385c2aca1072fd24 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Fri, 25 Apr 2025 17:18:52 +0200 Subject: [PATCH 1/2] improvement: Rework IndexedContext to reuse the previously calculated scopes (#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. --- .../tools/dotc/interactive/Completion.scala | 56 ++-- .../src/main/dotty/tools/pc/AutoImports.scala | 31 ++- .../dotty/tools/pc/AutoImportsProvider.scala | 6 +- .../tools/pc/ExtractMethodProvider.scala | 2 +- .../main/dotty/tools/pc/HoverProvider.scala | 11 +- .../main/dotty/tools/pc/IndexedContext.scala | 240 +++++------------- .../dotty/tools/pc/InferExpectedType.scala | 2 +- .../dotty/tools/pc/InferredTypeProvider.scala | 2 +- .../dotty/tools/pc/PcDefinitionProvider.scala | 2 +- .../dotty/tools/pc/PcInlayHintsProvider.scala | 4 +- .../tools/pc/PcInlineValueProvider.scala | 16 +- .../tools/pc/SignatureHelpProvider.scala | 2 +- .../pc/completions/CompletionProvider.scala | 3 +- .../tools/pc/completions/Completions.scala | 1 + .../pc/completions/MatchCaseCompletions.scala | 9 +- .../pc/completions/NamedArgCompletions.scala | 47 ++-- .../pc/completions/OverrideCompletions.scala | 2 +- .../pc/printer/ShortenedTypePrinter.scala | 45 ++-- .../pc/utils/InteractiveEnrichments.scala | 5 +- .../tests/completion/CompletionArgSuite.scala | 2 +- .../completion/CompletionCaseSuite.scala | 6 +- .../completion/CompletionContextSuite.scala | 38 +++ .../pc/tests/inlayHints/InlayHintsSuite.scala | 2 +- 23 files changed, 252 insertions(+), 282 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index a37e972ef715..81afc68e2342 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -46,6 +46,16 @@ case class Completion(label: String, description: String, symbols: List[Symbol]) object Completion: + def scopeContext(pos: SourcePosition)(using Context): CompletionResult = + val tpdPath = Interactive.pathTo(ctx.compilationUnit.tpdTree, pos.span) + val completionContext = Interactive.contextOfPath(tpdPath).withPhase(Phases.typerPhase) + inContext(completionContext): + val untpdPath = Interactive.resolveTypedOrUntypedPath(tpdPath, pos) + val mode = completionMode(untpdPath, pos, forSymbolSearch = true) + val rawPrefix = completionPrefix(untpdPath, pos) + val completer = new Completer(mode, pos, untpdPath, _ => true) + completer.scopeCompletions + /** Get possible completions from tree at `pos` * * @return offset and list of symbols for possible completions @@ -58,7 +68,6 @@ object Completion: val mode = completionMode(untpdPath, pos) val rawPrefix = completionPrefix(untpdPath, pos) val completions = rawCompletions(pos, mode, rawPrefix, tpdPath, untpdPath) - postProcessCompletions(untpdPath, completions, rawPrefix) /** Get possible completions from tree at `pos` @@ -87,7 +96,7 @@ object Completion: * * Otherwise, provide no completion suggestion. */ - def completionMode(path: List[untpd.Tree], pos: SourcePosition): Mode = path match + def completionMode(path: List[untpd.Tree], pos: SourcePosition, forSymbolSearch: Boolean = false): Mode = path match // Ignore `package foo@@` and `package foo.bar@@` case ((_: tpd.Select) | (_: tpd.Ident)):: (_ : tpd.PackageDef) :: _ => Mode.None case GenericImportSelector(sel) => @@ -100,11 +109,14 @@ object Completion: case untpd.Literal(Constants.Constant(_: String)) :: _ => Mode.Term | Mode.Scope // literal completions case (ref: untpd.RefTree) :: _ => val maybeSelectMembers = if ref.isInstanceOf[untpd.Select] then Mode.Member else Mode.Scope - - if (ref.name.isTermName) Mode.Term | maybeSelectMembers + if (forSymbolSearch) then Mode.Term | Mode.Type | maybeSelectMembers + else if (ref.name.isTermName) Mode.Term | maybeSelectMembers else if (ref.name.isTypeName) Mode.Type | maybeSelectMembers else Mode.None + case (_: tpd.TypeTree | _: tpd.MemberDef) :: _ if forSymbolSearch => Mode.Type | Mode.Term + case (_: tpd.CaseDef) :: _ if forSymbolSearch => Mode.Type | Mode.Term + case Nil if forSymbolSearch => Mode.Type | Mode.Term case _ => Mode.None /** When dealing with in varios palces we check to see if they are @@ -227,14 +239,14 @@ object Completion: 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 + case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions.names case StringContextApplication(qual) => - completer.scopeCompletions ++ completer.selectionCompletions(qual) + completer.scopeCompletions.names ++ completer.selectionCompletions(qual) case tpd.Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind => completer.selectionCompletions(qual) case tpd.Select(qual, _) :: _ => Map.empty case (tree: tpd.ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr) - case _ => completer.scopeCompletions + case _ => completer.scopeCompletions.names interactiv.println(i"""completion info with pos = $pos, | term = ${completer.mode.is(Mode.Term)}, @@ -335,6 +347,7 @@ object Completion: (completionMode.is(Mode.Term) && (sym.isTerm || sym.is(ModuleClass)) || (completionMode.is(Mode.Type) && (sym.isType || sym.isStableMember))) ) + end isValidCompletionSymbol given ScopeOrdering(using Context): Ordering[Seq[SingleDenotation]] with val order = @@ -368,7 +381,7 @@ object Completion: * (even if the import follows it syntactically) * - a more deeply nested import shadowing a member or a local definition causes an ambiguity */ - def scopeCompletions(using context: Context): CompletionMap = + def scopeCompletions(using context: Context): CompletionResult = /** 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 @@ -379,14 +392,19 @@ object Completion: ScopedDenotations(denots.filter(includeFn), ctx) val mappings = collection.mutable.Map.empty[Name, List[ScopedDenotations]].withDefaultValue(List.empty) + val renames = collection.mutable.Map.empty[Symbol, Name] def addMapping(name: Name, denots: ScopedDenotations) = mappings(name) = mappings(name) :+ denots ctx.outersIterator.foreach { case ctx @ given Context => if ctx.isImportContext then - importedCompletions.foreach { (name, denots) => + val imported = importedCompletions + imported.names.foreach { (name, denots) => addMapping(name, ScopedDenotations(denots, 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) => @@ -430,7 +448,6 @@ object Completion: // 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 - case None if isSingleImport || isImportedInDifferentScope || isSameSymbolImportedDouble => resultMappings += name -> first.denots case None if notConflictingWithDefaults => @@ -440,7 +457,7 @@ object Completion: } } - resultMappings + CompletionResult(resultMappings, renames.toMap) end scopeCompletions /** Widen only those types which are applied or are exactly nothing @@ -474,15 +491,20 @@ object Completion: /** Completions introduced by imports directly in this context. * Completions from outer contexts are not included. */ - private def importedCompletions(using Context): CompletionMap = + private def importedCompletions(using Context): CompletionResult = val imp = ctx.importInfo + val renames = collection.mutable.Map.empty[Symbol, Name] if imp == null then - Map.empty + CompletionResult(Map.empty, Map.empty) else def fromImport(name: Name, nameInScope: Name): Seq[(Name, SingleDenotation)] = imp.site.member(name).alternatives - .collect { case denot if include(denot, nameInScope) => nameInScope -> denot } + .collect { case denot if include(denot, nameInScope) => + if name != nameInScope then + renames(denot.symbol) = nameInScope + nameInScope -> denot + } val givenImports = imp.importedImplicits .map { ref => (ref.implicitName: Name, ref.underlyingRef.denot.asSingleDenotation) } @@ -508,7 +530,8 @@ object Completion: fromImport(original.toTypeName, nameInScope.toTypeName) }.toSeq.groupByName - givenImports ++ wildcardMembers ++ explicitMembers + val results = givenImports ++ wildcardMembers ++ explicitMembers + CompletionResult(results, renames.toMap) end importedCompletions /** Completions from implicit conversions including old style extensions using implicit classes */ @@ -562,7 +585,7 @@ 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 termCompleter = new Completer(Mode.Term, pos, untpdPath, matches) - val extMethodsInScope = termCompleter.scopeCompletions.toList.flatMap: + val extMethodsInScope = termCompleter.scopeCompletions.names.toList.flatMap: case (name, denots) => denots.collect: case d: SymDenotation if d.isTerm && d.termRef.symbol.is(Extension) => (d.termRef, name.asTermName) @@ -664,6 +687,7 @@ object Completion: private type CompletionMap = Map[Name, Seq[SingleDenotation]] + case class CompletionResult(names: Map[Name, Seq[SingleDenotation]], renames: Map[Symbol, Name]) /** * The completion mode: defines what kinds of symbols should be included in the completion * results. diff --git a/presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala b/presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala index 1b44dce8c642..7b30c745e3ed 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala @@ -40,7 +40,7 @@ object AutoImports: case class Select(qual: SymbolIdent, name: String) extends SymbolIdent: def value: String = s"${qual.value}.$name" - def direct(name: String): SymbolIdent = Direct(name) + def direct(name: String)(using Context): SymbolIdent = Direct(name) def fullIdent(symbol: Symbol)(using Context): SymbolIdent = val symbols = symbol.ownersIterator.toList @@ -70,7 +70,7 @@ object AutoImports: importSel: Option[ImportSel] ): - def name: String = ident.value + def name(using Context): String = ident.value object SymbolImport: @@ -189,10 +189,13 @@ object AutoImports: ownerImport.importSel, ) else - ( - SymbolIdent.direct(symbol.nameBackticked), - Some(ImportSel.Direct(symbol)), - ) + renames(symbol) match + case Some(rename) => (SymbolIdent.direct(rename), None) + case None => + ( + SymbolIdent.direct(symbol.nameBackticked), + Some(ImportSel.Direct(symbol)), + ) end val SymbolImport( @@ -223,9 +226,13 @@ object AutoImports: importSel ) case None => + val reverse = symbol.ownersIterator.toList.reverse + val fullName = reverse.drop(1).foldLeft(SymbolIdent.direct(reverse.head.nameBackticked)){ + case (acc, sym) => SymbolIdent.Select(acc, sym.nameBackticked(false)) + } SymbolImport( symbol, - SymbolIdent.direct(symbol.fullNameBackticked), + SymbolIdent.Direct(symbol.fullNameBackticked), None ) end match @@ -252,7 +259,6 @@ object AutoImports: val topPadding = if importPosition.padTop then "\n" else "" - val formatted = imports .map { case ImportSel.Direct(sym) => importName(sym) @@ -267,15 +273,16 @@ object AutoImports: end renderImports private def importName(sym: Symbol): String = - if indexedContext.importContext.toplevelClashes(sym) then + if indexedContext.toplevelClashes(sym, inImportScope = true) then s"_root_.${sym.fullNameBackticked(false)}" else sym.ownersIterator.zipWithIndex.foldLeft((List.empty[String], false)) { case ((acc, isDone), (sym, idx)) => if(isDone || sym.isEmptyPackage || sym.isRoot) (acc, true) else indexedContext.rename(sym) match - case Some(renamed) => (renamed :: acc, true) - case None if !sym.isPackageObject => (sym.nameBackticked(false) :: acc, false) - case None => (acc, false) + // we can't import first part + case Some(renamed) if idx != 0 => (renamed :: acc, true) + case _ if !sym.isPackageObject => (sym.nameBackticked(false) :: acc, false) + case _ => (acc, false) }._1.mkString(".") end AutoImportsGenerator diff --git a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala index d30ab813f9f2..0a6178eae106 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala @@ -44,8 +44,8 @@ final class AutoImportsProvider( val path = Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using newctx) - val indexedContext = IndexedContext( - Interactive.contextOfPath(path)(using newctx) + val indexedContext = IndexedContext(pos)( + using Interactive.contextOfPath(path)(using newctx) ) import indexedContext.ctx @@ -96,7 +96,7 @@ final class AutoImportsProvider( text, tree, unit.comments, - indexedContext.importContext, + indexedContext, config ) (sym: Symbol) => generator.forSymbol(sym) diff --git a/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala index c72a0602f1ce..00cde67873d5 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala @@ -51,7 +51,7 @@ final class ExtractMethodProvider( given locatedCtx: Context = val newctx = driver.currentCtx.fresh.setCompilationUnit(unit) Interactive.contextOfPath(path)(using newctx) - val indexedCtx = IndexedContext(locatedCtx) + val indexedCtx = IndexedContext(pos)(using locatedCtx) val printer = ShortenedTypePrinter(search, IncludeDefaultParam.Never)(using indexedCtx) def prettyPrint(tpe: Type) = diff --git a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala index f8249aebf68c..11c2011029ba 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala @@ -47,7 +47,7 @@ object HoverProvider: val path = unit .map(unit => Interactive.pathTo(unit.tpdTree, pos.span)) .getOrElse(Interactive.pathTo(driver.openedTrees(uri), pos)) - val indexedContext = IndexedContext(ctx) + val indexedContext = IndexedContext(pos)(using ctx) def typeFromPath(path: List[Tree]) = if path.isEmpty then NoType else path.head.typeOpt @@ -94,7 +94,7 @@ object HoverProvider: val printerCtx = Interactive.contextOfPath(path) val printer = ShortenedTypePrinter(search, IncludeDefaultParam.Include)( - using IndexedContext(printerCtx) + using IndexedContext(pos)(using printerCtx) ) MetalsInteractive.enclosingSymbolsWithExpressionType( enclosing, @@ -131,7 +131,12 @@ object HoverProvider: .flatMap(symTpe => search.symbolDocumentation(symTpe._1, contentType)) .map(_.docstring()) .mkString("\n") - printer.expressionType(exprTpw) match + + val expresionTypeOpt = + if symbol.name == StdNames.nme.??? then + InferExpectedType(search, driver, params).infer() + else printer.expressionType(exprTpw) + expresionTypeOpt match case Some(expressionType) => val forceExpressionType = !pos.span.isZeroExtent || ( diff --git a/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala b/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala index 7c2c34cf5ebb..2db37f801349 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala @@ -4,64 +4,45 @@ import scala.annotation.tailrec import scala.util.control.NonFatal 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.moduleClassName +import dotty.tools.dotc.core.NameOps.* import dotty.tools.dotc.core.Names.* 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.Interactive 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 scopeSymbols: List[Symbol] - def names: IndexedContext.Names def rename(sym: Symbol): Option[String] - def outer: IndexedContext - - def findSymbol(name: String): Option[List[Symbol]] - - final def findSymbol(name: Name): Option[List[Symbol]] = - findSymbol(name.decoded) + def findSymbol(name: Name): Option[List[Symbol]] + def findSymbolInLocalScope(name: String): Option[List[Symbol]] final def lookupSym(sym: Symbol): Result = - findSymbol(sym.decodedName) match - case Some(symbols) if symbols.exists(_ == sym) => - Result.InScope - case Some(symbols) - if symbols.exists(s => isNotConflictingWithDefault(s, sym) || isTypeAliasOf(s, sym) || isTermAliasOf(s, sym)) => - Result.InScope - // when all the conflicting symbols came from an old version of the file + def all(symbol: Symbol): Set[Symbol] = Set(symbol, symbol.companionModule, symbol.companionClass, symbol.companion).filter(_ != NoSymbol) + val isRelated = all(sym) ++ all(sym.dealiasType) + findSymbol(sym.name) match + case Some(symbols) if symbols.exists(isRelated) => Result.InScope + case Some(symbols) if symbols.exists(isTermAliasOf(_, sym)) => Result.InScope + case Some(symbols) if symbols.map(_.dealiasType).exists(isRelated) => Result.InScope case Some(symbols) if symbols.nonEmpty && symbols.forall(_.isStale) => Result.Missing case Some(symbols) if symbols.exists(rename(_).isEmpty) => Result.Conflict + case Some(symbols) => Result.InScope case _ => Result.Missing end lookupSym - /** - * Scala by default imports following packages: - * https://scala-lang.org/files/archive/spec/3.4/02-identifiers-names-and-scopes.html - * import java.lang.* - * { - * import scala.* - * { - * import Predef.* - * { /* source */ } - * } - * } - * - * This check is necessary for proper scope resolution, because when we compare symbols from - * index including the underlying type like scala.collection.immutable.List it actually - * is in current scope in form of type forwarder imported from Predef. - */ - private def isNotConflictingWithDefault(sym: Symbol, queriedSym: Symbol): Boolean = - sym.info.widenDealias =:= queriedSym.info.widenDealias && (Interactive.isImportedByDefault(sym)) - final def hasRename(sym: Symbol, as: String): Boolean = rename(sym) match - case Some(v) => v == as + case Some(v) => + v == as case None => false // detects import scope aliases like @@ -74,73 +55,74 @@ sealed trait IndexedContext: case _ => false ) - private def isTypeAliasOf(alias: Symbol, queriedSym: Symbol): Boolean = - alias.isAliasType && alias.info.deepDealias.typeSymbol == queriedSym - - final def isEmpty: Boolean = this match - case IndexedContext.Empty => true - case _ => false - - final def importContext: IndexedContext = - this match - case IndexedContext.Empty => this - case _ if ctx.owner.is(Package) => this - case _ => outer.importContext - @tailrec - final def toplevelClashes(sym: Symbol): Boolean = + final def toplevelClashes(sym: Symbol, inImportScope: Boolean): Boolean = if sym == NoSymbol || sym.owner == NoSymbol || sym.owner.isRoot then - lookupSym(sym) match - case IndexedContext.Result.Conflict => true + val possibleConflictingSymbols = findSymbolInLocalScope(sym.name.show) + // if it's import scope we only care about toplevel conflicts, not any clashes inside objects etc. + val symbolClashes = if inImportScope then + // It's toplevel if it's parent is a package + possibleConflictingSymbols.filter(_.exists(_.owner.is(Package))) + else + possibleConflictingSymbols + symbolClashes match + case Some(symbols) if !symbols.contains(sym) => true case _ => false - else toplevelClashes(sym.owner) + else toplevelClashes(sym.owner, inImportScope) end IndexedContext object IndexedContext: - def apply(ctx: Context): IndexedContext = + def apply(pos: SourcePosition)(using Context): IndexedContext = ctx match case NoContext => Empty - case _ => LazyWrapper(using ctx) + case _ => LazyWrapper(pos)(using ctx) case object Empty extends IndexedContext: given ctx: Context = NoContext - def findSymbol(name: String): Option[List[Symbol]] = None + def findSymbol(name: Name): Option[List[Symbol]] = None + def findSymbolInLocalScope(name: String): Option[List[Symbol]] = None def scopeSymbols: List[Symbol] = List.empty - val names: Names = Names(Map.empty, Map.empty) def rename(sym: Symbol): Option[String] = None - def outer: IndexedContext = this - - class LazyWrapper(using val ctx: Context) extends IndexedContext: - val outer: IndexedContext = IndexedContext(ctx.outer) - val names: Names = extractNames(ctx) - def findSymbol(name: String): Option[List[Symbol]] = - names.symbols - .get(name) - .map(_.toList) - .orElse(outer.findSymbol(name)) + class LazyWrapper(pos: SourcePosition)(using val ctx: Context) extends IndexedContext: + + val completionContext = Completion.scopeContext(pos) + val names: Map[String, Seq[SingleDenotation]] = completionContext.names.toList.groupBy(_._1.show).map{ + case (name, denotations) => + val denots = denotations.flatMap(_._2) + val nonRoot = denots.filter(!_.symbol.owner.isRoot) + val (importedByDefault, conflictingValue) = denots.partition(denot => Interactive.isImportedByDefault(denot.symbol)) + if importedByDefault.nonEmpty && conflictingValue.nonEmpty then + name.trim -> conflictingValue + else + name.trim -> nonRoot + } + val renames = completionContext.renames + + def defaultScopes(name: Name): Option[List[Symbol]] = + List(defn.ScalaPredefModuleClass, defn.ScalaPackageClass, defn.JavaLangPackageClass) + .map(_.membersNamed(name)) + .collect { case denot if denot.exists => denot.first.symbol } + .toList match + case Nil => None + case list => Some(list) + + override def findSymbolInLocalScope(name: String): Option[List[Symbol]] = + names.get(name).map(_.map(_.symbol).toList).filter(_.nonEmpty) + def findSymbol(name: Name): Option[List[Symbol]] = + names + .get(name.show) + .map(_.map(_.symbol).toList) + .orElse(defaultScopes(name)) def scopeSymbols: List[Symbol] = - val acc = Set.newBuilder[Symbol] - (this :: outers).foreach { ref => - acc ++= ref.names.symbols.values.flatten - } - acc.result.toList + names.values.flatten.map(_.symbol).toList def rename(sym: Symbol): Option[String] = - names.renames - .get(sym) - .orElse(outer.rename(sym)) - - private def outers: List[IndexedContext] = - val builder = List.newBuilder[IndexedContext] - var curr = outer - while !curr.isEmpty do - builder += curr - curr = curr.outer - builder.result + renames.get(sym).orElse(renames.get(sym.companion)).map(_.decoded) + end LazyWrapper enum Result: @@ -149,97 +131,5 @@ object IndexedContext: case InScope | Conflict => true case Missing => false - case class Names( - symbols: Map[String, List[Symbol]], - renames: Map[Symbol, String] - ) - - private def extractNames(ctx: Context): Names = - def isAccessibleFromSafe(sym: Symbol, site: Type): Boolean = - try sym.isAccessibleFrom(site, superAccess = false) - catch - case NonFatal(e) => - false - - def accessibleSymbols(site: Type, tpe: Type)(using - Context - ): List[Symbol] = - tpe.decls.toList.filter(sym => isAccessibleFromSafe(sym, site)) - - def accesibleMembers(site: Type)(using Context): List[Symbol] = - site.allMembers - .filter(denot => - try isAccessibleFromSafe(denot.symbol, site) - catch - case NonFatal(e) => - false - ) - .map(_.symbol) - .toList - - def allAccessibleSymbols( - tpe: Type, - filter: Symbol => Boolean = _ => true - )(using Context): List[Symbol] = - val initial = accessibleSymbols(tpe, tpe).filter(filter) - val fromPackageObjects = - initial - .filter(_.isPackageObject) - .flatMap(sym => accessibleSymbols(tpe, sym.thisType)) - initial ++ fromPackageObjects - - def fromImport(site: Type, name: Name)(using Context): List[Symbol] = - List( - site.member(name.toTypeName), - site.member(name.toTermName), - site.member(name.moduleClassName), - ) - .flatMap(_.alternatives) - .map(_.symbol) - - def fromImportInfo( - imp: ImportInfo - )(using Context): List[(Symbol, Option[TermName])] = - val excludedNames = imp.excluded.map(_.decoded) - - if imp.isWildcardImport then - allAccessibleSymbols( - imp.site, - sym => !excludedNames.contains(sym.name.decoded) - ).map((_, None)) - else - imp.forwardMapping.toList.flatMap { (name, rename) => - val isRename = name != rename - if !isRename && !excludedNames.contains(name.decoded) then - fromImport(imp.site, name).map((_, None)) - else if isRename then - fromImport(imp.site, name).map((_, Some(rename))) - else Nil - } - end if - end fromImportInfo - - given Context = ctx - val (symbols, renames) = - if ctx.isImportContext then - val (syms, renames) = - fromImportInfo(ctx.importInfo.nn) - .map((sym, rename) => (sym, rename.map(r => sym -> r.decoded))) - .unzip - (syms, renames.flatten.toMap) - else if ctx.owner.isClass then - val site = ctx.owner.thisType - (accesibleMembers(site), Map.empty) - else if ctx.scope != EmptyScope then (ctx.scope.toList, Map.empty) - else (List.empty, Map.empty) - - val initial = Map.empty[String, List[Symbol]] - val values = - symbols.foldLeft(initial) { (acc, sym) => - val name = sym.decodedName - val syms = acc.getOrElse(name, List.empty) - acc.updated(name, sym :: syms) - } - Names(values, renames) - end extractNames + end IndexedContext diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala index d121c8059286..67f7f43aa030 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala @@ -51,7 +51,7 @@ class InferExpectedType( ) val locatedCtx = Interactive.contextOfPath(tpdPath)(using newctx) - val indexedCtx = IndexedContext(locatedCtx) + val indexedCtx = IndexedContext(pos)(using locatedCtx) val printer = ShortenedTypePrinter(search, IncludeDefaultParam.ResolveLater)(using indexedCtx) InterCompletionType.inferType(path)(using newctx).map{ diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala index a0d726d5f382..2006774ae19b 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala @@ -75,7 +75,7 @@ final class InferredTypeProvider( Interactive.pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx) given locatedCtx: Context = driver.localContext(params) - val indexedCtx = IndexedContext(locatedCtx) + val indexedCtx = IndexedContext(pos)(using locatedCtx) val autoImportsGen = AutoImports.generator( pos, sourceText, diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala index a2e9b05e0610..69ec509043f8 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala @@ -51,7 +51,7 @@ class PcDefinitionProvider( Interactive.pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx) given ctx: Context = driver.localContext(params) - val indexedContext = IndexedContext(ctx) + val indexedContext = IndexedContext(pos)(using ctx) val result = if findTypeDef then findTypeDefinitions(path, pos, indexedContext, uri) else findDefinitions(path, pos, indexedContext, uri) diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index 5d8739fb3343..0f69e16831a0 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -125,7 +125,7 @@ class PcInlayHintsProvider( val tpdPath = Interactive.pathTo(unit.tpdTree, pos.span) - val indexedCtx = IndexedContext(Interactive.contextOfPath(tpdPath)) + val indexedCtx = IndexedContext(pos)(using Interactive.contextOfPath(tpdPath)) val printer = ShortenedTypePrinter( symbolSearch )(using indexedCtx) @@ -149,7 +149,7 @@ class PcInlayHintsProvider( InlayHints.makeLabelParts(parts, tpeStr) end toLabelParts - private val definitions = IndexedContext(ctx).ctx.definitions + private val definitions = IndexedContext(pos)(using ctx).ctx.definitions private def syntheticTupleApply(tree: Tree): Boolean = tree match case sel: Select => diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProvider.scala index 7d04ea8771a4..8d0b1b569f5a 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProvider.scala @@ -110,8 +110,8 @@ final class PcInlineValueProvider( } .toRight(Errors.didNotFindDefinition) path = Interactive.pathTo(unit.tpdTree, definition.tree.rhs.span)(using newctx) - indexedContext = IndexedContext(Interactive.contextOfPath(path)(using newctx)) - symbols = symbolsUsedInDefn(definition.tree.rhs).filter(indexedContext.lookupSym(_) == Result.InScope) + indexedContext = IndexedContext(definition.tree.namePos)(using Interactive.contextOfPath(path)(using newctx)) + symbols = symbolsUsedInDefn(definition.tree.rhs, indexedContext) references <- getReferencesToInline(definition, allOccurences, symbols) yield val (deleteDefinition, refsEdits) = references @@ -184,15 +184,19 @@ final class PcInlineValueProvider( val adjustedEnd = extend(pos.end - 1, ')', 1) + 1 text.slice(adjustedStart, adjustedEnd).mkString - private def symbolsUsedInDefn(rhs: Tree): Set[Symbol] = + private def symbolsUsedInDefn(rhs: Tree, indexedContext: IndexedContext): Set[Symbol] = def collectNames( symbols: Set[Symbol], tree: Tree ): Set[Symbol] = tree match - case id: (Ident | Select) + case id: Ident if !id.symbol.is(Synthetic) && !id.symbol.is(Implicit) => symbols + tree.symbol + case sel: Select => + indexedContext.lookupSym(sel.symbol) match + case IndexedContext.Result.InScope => symbols + sel.symbol + case _ => symbols case _ => symbols val traverser = new DeepFolder[Set[Symbol]](collectNames) @@ -247,8 +251,8 @@ final class PcInlineValueProvider( def buildRef(occurrence: Occurence): Either[String, Reference] = val path = Interactive.pathTo(unit.tpdTree, occurrence.pos.span)(using newctx) - val indexedContext = IndexedContext( - Interactive.contextOfPath(path)(using newctx) + val indexedContext = IndexedContext(pos)( + using Interactive.contextOfPath(path)(using newctx) ) import indexedContext.ctx val conflictingSymbols = symbols diff --git a/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala index bd16d2ce2aa9..5f925ea80ee7 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala @@ -37,7 +37,7 @@ object SignatureHelpProvider: val path = Interactive.pathTo(unit.tpdTree, pos.span)(using driver.currentCtx) val localizedContext = Interactive.contextOfPath(path)(using driver.currentCtx) - val indexedContext = IndexedContext(driver.currentCtx) + val indexedContext = IndexedContext(pos)(using driver.currentCtx) given Context = localizedContext.fresh .setCompilationUnit(unit) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala index 2a63d6a92a81..ef9f77eb58fc 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala @@ -107,7 +107,7 @@ class CompletionProvider( val locatedCtx = Interactive.contextOfPath(tpdPath)(using newctx) - val indexedCtx = IndexedContext(locatedCtx) + val indexedCtx = IndexedContext(pos)(using locatedCtx) val completionPos = CompletionPos.infer(pos, params, adjustedPath, wasCursorApplied)(using locatedCtx) @@ -222,7 +222,6 @@ class CompletionProvider( if config.isDetailIncludedInLabel then completion.labelWithDescription(printer) else completion.label val ident = underlyingCompletion.insertText.getOrElse(underlyingCompletion.label) - lazy val isInStringInterpolation = path match // s"My name is $name" diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index 1114a07f91b5..050ab50dae49 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -571,6 +571,7 @@ class Completions( then indexedContext.lookupSym(sym) match case IndexedContext.Result.InScope => false + case IndexedContext.Result.Missing if indexedContext.rename(sym).isDefined => false case _ if completionMode.is(Mode.ImportOrExport) => visit( CompletionValue.Workspace( diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala index 5e809f5b0110..536163d6dacb 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala @@ -147,7 +147,7 @@ object CaseKeywordCompletion: definitions.NullClass, definitions.NothingClass, ) - val tpes = Set(selectorSym, selectorSym.companion) + val tpes = Set(selectorSym, selectorSym.companion).filter(_ != NoSymbol) def isSubclass(sym: Symbol) = tpes.exists(par => sym.isSubClass(par)) def visit(symImport: SymbolImport): Unit = @@ -174,8 +174,9 @@ object CaseKeywordCompletion: indexedContext.scopeSymbols .foreach(s => - val ts = s.info.deepDealias.typeSymbol - if isValid(ts) then visit(autoImportsGen.inferSymbolImport(ts)) + val ts = if s.is(Flags.Module) then s.info.typeSymbol else s.dealiasType + if isValid(ts) then + visit(autoImportsGen.inferSymbolImport(ts)) ) // Step 2: walk through known subclasses of sealed types. val sealedDescs = subclassesForType( @@ -185,6 +186,7 @@ object CaseKeywordCompletion: val symbolImport = autoImportsGen.inferSymbolImport(sym) visit(symbolImport) } + val res = result.result().flatMap { case si @ SymbolImport(sym, name, importSel) => completionGenerator.labelForCaseMember(sym, name.value).map { @@ -293,7 +295,6 @@ object CaseKeywordCompletion: val (labels, imports) = sortedSubclasses.map((si, label) => (label, si.importSel)).unzip - val (obracket, cbracket) = if noIndent then (" {", "}") else ("", "") val basicMatch = CompletionValue.MatchCompletion( "match", diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala index a21706b9e36e..7b88d8edfbc8 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala @@ -30,6 +30,9 @@ import dotty.tools.dotc.core.Types.WildcardType import dotty.tools.pc.IndexedContext import dotty.tools.pc.utils.InteractiveEnrichments.* import scala.annotation.tailrec +import dotty.tools.dotc.core.Denotations.Denotation +import dotty.tools.dotc.core.Denotations.MultiDenotation +import dotty.tools.dotc.core.Denotations.SingleDenotation object NamedArgCompletions: @@ -136,46 +139,42 @@ object NamedArgCompletions: // fallback for when multiple overloaded methods match the supplied args def fallbackFindMatchingMethods() = - def maybeNameAndIndexedContext( + def matchingMethodsSymbols( method: Tree - ): Option[(Name, IndexedContext)] = + ): List[Symbol] = method match - case Ident(name) => Some((name, indexedContext)) - case Select(This(_), name) => Some((name, indexedContext)) - case Select(from, name) => + case Ident(name) => indexedContext.findSymbol(name).getOrElse(Nil) + case Select(This(_), name) => indexedContext.findSymbol(name).getOrElse(Nil) + case sel @ Select(from, name) => val symbol = from.symbol val ownerSymbol = if symbol.is(Method) && symbol.owner.isClass then Some(symbol.owner) else Try(symbol.info.classSymbol).toOption - ownerSymbol.map(sym => - (name, IndexedContext(context.localContext(from, sym))) - ) - case Apply(fun, _) => maybeNameAndIndexedContext(fun) - case _ => None + ownerSymbol.map(sym => sym.info.member(name)).collect{ + case single: SingleDenotation => List(single.symbol) + case multi: MultiDenotation => multi.allSymbols + }.getOrElse(Nil) + case Apply(fun, _) => matchingMethodsSymbols(fun) + case _ => Nil val matchingMethods = for - (name, indexedContext) <- maybeNameAndIndexedContext(method) - potentialMatches <- indexedContext.findSymbol(name) - yield - potentialMatches.collect { - case m - if m.is(Flags.Method) && - m.vparamss.length >= argss.length && - Try(m.isAccessibleFrom(apply.symbol.info)).toOption + potentialMatch <- matchingMethodsSymbols(method) + if potentialMatch.is(Flags.Method) && + potentialMatch.vparamss.length >= argss.length && + Try(potentialMatch.isAccessibleFrom(apply.symbol.info)).toOption .getOrElse(false) && - m.vparamss + potentialMatch.vparamss .zip(argss) .reverse .zipWithIndex .forall { case (pair, index) => - FuzzyArgMatcher(m.tparams) + FuzzyArgMatcher(potentialMatch.tparams) .doMatch(allArgsProvided = index != 0, ident) .tupled(pair) - } => - m - } - matchingMethods.getOrElse(Nil) + } + yield potentialMatch + matchingMethods end fallbackFindMatchingMethods val matchingMethods: List[Symbols.Symbol] = diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala index f5c15ca6df0e..8123bc8fa216 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala @@ -191,7 +191,7 @@ object OverrideCompletions: template :: path case path => path - val indexedContext = IndexedContext( + val indexedContext = IndexedContext(pos)(using Interactive.contextOfPath(path)(using newctx) ) import indexedContext.ctx diff --git a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala index 4fb84b87695c..7ae332a7c21c 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala @@ -133,11 +133,16 @@ class ShortenedTypePrinter( prefixIterator.flatMap { owner => val prefixAfterRename = ownersAfterRename(owner) val ownerRename = indexedCtx.rename(owner) - ownerRename.foreach(rename => foundRenames += owner -> rename) + ownerRename.foreach(rename => foundRenames += owner -> rename) val currentRenamesSearchResult = - ownerRename.map(Found(owner, _, prefixAfterRename)) + ownerRename.map(rename => Found(owner, rename, prefixAfterRename)) lazy val configRenamesSearchResult = - renameConfigMap.get(owner).map(Missing(owner, _, prefixAfterRename)) + renameConfigMap.get(owner).flatMap{rename => + // if the rename is taken, we don't want to use it + indexedCtx.findSymbolInLocalScope(rename) match + case Some(symbols) => None + case None => Some(Missing(owner, rename, prefixAfterRename)) + } currentRenamesSearchResult orElse configRenamesSearchResult }.nextOption @@ -150,40 +155,36 @@ class ShortenedTypePrinter( private def optionalRootPrefix(sym: Symbol): Text = // If the symbol has toplevel clash we need to prepend `_root_.` to the symbol to disambiguate // it from the local symbol. It is only required when we are computing text for text edit. - if isTextEdit && indexedCtx.toplevelClashes(sym) then + if isTextEdit && indexedCtx.toplevelClashes(sym, inImportScope = false) then Str("_root_.") else Text() private def findRename(tp: NamedType): Option[Text] = val maybePrefixRename = findPrefixRename(tp.symbol.maybeOwner) + maybePrefixRename.map { + case res: Found => res.toPrefixText + case res: Missing => + val importSel = + if res.owner.name.decoded == res.rename then + ImportSel.Direct(res.owner) + else ImportSel.Rename(res.owner, res.rename) - if maybePrefixRename.exists(importRename => indexedCtx.findSymbol(importRename.rename).isDefined) then - Some(super.toTextPrefixOf(tp)) - else - maybePrefixRename.map { - case res: Found => res.toPrefixText - case res: Missing => - val importSel = - if res.owner.name.toString == res.rename then - ImportSel.Direct(res.owner) - else ImportSel.Rename(res.owner, res.rename) - - missingImports += importSel - res.toPrefixText - } + missingImports += importSel + res.toPrefixText + } override def toTextPrefixOf(tp: NamedType): Text = controlled { val maybeRenamedPrefix: Option[Text] = findRename(tp) - val trimmedPrefix: Text = + def trimmedPrefix: Text = if !tp.designator.isInstanceOf[Symbol] && tp.typeSymbol == NoSymbol then - maybeRenamedPrefix.getOrElse(super.toTextPrefixOf(tp)) + super.toTextPrefixOf(tp) else indexedCtx.lookupSym(tp.symbol) match + case _ if indexedCtx.rename(tp.symbol).isDefined => Text() // symbol is missing and is accessible statically, we can import it and add proper prefix case Result.Missing if isAccessibleStatically(tp.symbol) => - maybeRenamedPrefix.getOrElse: missingImports += ImportSel.Direct(tp.symbol) Text() // the symbol is in scope, we can omit the prefix @@ -193,7 +194,7 @@ class ShortenedTypePrinter( maybeRenamedPrefix.getOrElse(super.toTextPrefixOf(tp)) case _ => super.toTextPrefixOf(tp) - optionalRootPrefix(tp.symbol) ~ trimmedPrefix + optionalRootPrefix(tp.symbol) ~ maybeRenamedPrefix.getOrElse(trimmedPrefix) } override protected def selectionString(tp: NamedType): String = diff --git a/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala b/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala index d909b6ee7e04..fb5e56085e15 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala @@ -166,7 +166,7 @@ object InteractiveEnrichments extends CommonMtagsEnrichments: @tailrec def loop(acc: List[String], sym: Symbol): List[String] = if sym == NoSymbol || sym.isRoot || sym.isEmptyPackage then acc - else if sym.isPackageObject then loop(acc, sym.owner) + else if sym.isPackageObject || sym.isConstructor then loop(acc, sym.owner) else val v = this.nameBackticked(sym)(exclusions) loop(v :: acc, sym.owner) @@ -177,6 +177,9 @@ object InteractiveEnrichments extends CommonMtagsEnrichments: def companion: Symbol = if sym.is(Module) then sym.companionClass else sym.companionModule + def dealiasType: Symbol = + if sym.isType then sym.info.deepDealias.typeSymbol else sym + def nameBackticked: String = nameBackticked(Set.empty[String]) def nameBackticked(backtickSoftKeyword: Boolean = true): String = diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala index e5f2d31ad808..044b5456d31d 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala @@ -826,8 +826,8 @@ class CompletionArgSuite extends BaseCompletionSuite: |""".stripMargin, """|aaa = : Int |aaa = g : Int - |abb = : Option[Int] |abb = : Int + |abb = : Option[Int] |abb = g : Int |""".stripMargin, topLines = Some(5), diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionCaseSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionCaseSuite.scala index e72ee5221d91..edc6cb2b4fb2 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionCaseSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionCaseSuite.scala @@ -37,7 +37,7 @@ class CompletionCaseSuite extends BaseCompletionSuite: |class Cat extends Animal |class Dog extends Animal |object Elephant extends Animal - |class HasFeet[A, B](e: T, f: B) extends Animal + |class HasFeet[A, B](e: A, f: B) extends Animal |class HasMouth[T](e: T) extends Animal |case class HasWings[T](e: T) extends Animal |case object Seal extends Animal @@ -146,14 +146,12 @@ class CompletionCaseSuite extends BaseCompletionSuite: |""".stripMargin ) - // TODO: `Left` has conflicting name in Scope, we should fix it so the result is the same as for scala 2 - // Issue: https://github.com/scalameta/metals/issues/4368 @Test def `sealed-conflict` = check( """ |object A { | val e: Either[Int, String] = ??? - | type Left = String + | val Left = 123 | e match { | case@@ | } diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionContextSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionContextSuite.scala index 5314a61ab599..961022375f86 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionContextSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionContextSuite.scala @@ -25,3 +25,41 @@ class CompletionContextSuite extends BaseCompletionSuite: |""".stripMargin, filter = futureCompletionResult.contains ) + + @Test def multipleFoos = + checkEdit( + """| + |package a{ + | class Foo + |} + |package b{ + | class Foo + |} + |package c{ + | class Foo + |} + |package d{ + | import a.{Foo => Bar} + | import b.Foo + | + | val x = new Foo@@ // I want to import c.Foo + |}""".stripMargin, + """| + |package a{ + | class Foo + |} + |package b{ + | class Foo + |} + |package c{ + | class Foo + |} + |package d{ + | import a.{Foo => Bar} + | import b.Foo + | + | val x = new c.Foo // I want to import c.Foo + |}""".stripMargin, + assertSingleItem = false, + filter = (label: String) => label.contains("Foo - c") + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala index 8aa885149852..1fb9cb51ae80 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala @@ -655,7 +655,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { | val y/*: S<>[Char<>]*/ = f | ??? | } - | val x/*: AB<>[Int<>, String<>]*/ = test(Set/*[Int<>]*/(1), Set/*[Char<>]*/('a')) + | val x/*: AB<>[Int<>, String<>]*/ = test(Set/*[Int<>]*/(1), Set/*[Char<>]*/('a')) |} |""".stripMargin, ) From e36ce09ac0275b525ef93edd03434b6c93862438 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Mon, 28 Apr 2025 16:26:40 +0200 Subject: [PATCH 2/2] improvement: Rework IndexedContext to reuse the previously calculated scopes (#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. [Cherry-picked 9d90ff5f5f7525785799c6a5844a25930ab4cb92][modified]