diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index cef5de613920..36f6fa18e94b 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -48,6 +48,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 @@ -60,7 +70,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` @@ -89,7 +98,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) => @@ -102,11 +111,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 @@ -174,12 +186,12 @@ object Completion: case _ => None private object StringContextApplication: - def unapply(path: List[tpd.Tree]): Option[tpd.Apply] = + def unapply(path: List[tpd.Tree]): Option[tpd.Apply] = path match case tpd.Select(qual @ tpd.Apply(tpd.Select(tpd.Select(_, StdNames.nme.StringContext), _), _), _) :: _ => Some(qual) case _ => None - + /** Inspect `path` to determine the offset where the completion result should be inserted. */ def completionOffset(untpdPath: List[untpd.Tree]): Int = @@ -230,14 +242,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) - case tpd.Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind => + 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)}, @@ -338,6 +350,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 = @@ -371,7 +384,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 @@ -382,14 +395,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) => @@ -433,7 +451,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 => @@ -443,7 +460,7 @@ object Completion: } } - resultMappings + CompletionResult(resultMappings, renames.toMap) end scopeCompletions /** Widen only those types which are applied or are exactly nothing @@ -485,15 +502,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) } @@ -519,7 +541,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 */ @@ -597,7 +620,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) @@ -699,6 +722,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 3b2f4d2aa9b0..746be65155d9 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala @@ -49,7 +49,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 @@ -96,7 +96,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, @@ -134,7 +134,7 @@ object HoverProvider: .map(_.docstring()) .mkString("\n") - val expresionTypeOpt = + val expresionTypeOpt = if symbol.name == StdNames.nme.??? then InferExpectedType(search, driver, params).infer() else printer.expressionType(exprTpw) diff --git a/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala b/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala index 7c2c34cf5ebb..e457edfe1798 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) if symbols.exists(rename(_).isEmpty) && rename(sym).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,71 @@ 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 + // we need to fix the import only if the toplevel package conflicts with the imported one + findSymbolInLocalScope(sym.name.show) match + case Some(symbols) if !symbols.contains(sym) && (symbols.exists(_.owner.is(Package)) || !inImportScope) => 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) + def hasImportedByDefault = denots.exists(denot => Interactive.isImportedByDefault(denot.symbol)) + def hasConflictingValue = denots.exists(denot => !Interactive.isImportedByDefault(denot.symbol)) + if hasImportedByDefault && hasConflictingValue then + name.trim -> nonRoot.filter(denot => !Interactive.isImportedByDefault(denot.symbol)) + else + name.trim -> nonRoot + } + val renames = completionContext.renames + + def defaultScopes(name: Name): Option[List[Symbol]] = + val fromPredef = defn.ScalaPredefModuleClass.membersNamed(name) + val fromScala = defn.ScalaPackageClass.membersNamed(name) + val fromJava = defn.JavaLangPackageClass.membersNamed(name) + val predefList = if fromPredef.exists then List(fromPredef.first.symbol) else Nil + val scalaList = if fromScala.exists then List(fromScala.first.symbol) else Nil + val javaList = if fromJava.exists then List(fromJava.first.symbol) else Nil + val combined = predefList ++ scalaList ++ javaList + if combined.nonEmpty then Some(combined) else None + + 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 +128,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 3d65f69621e1..075167f3f5c1 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 9fbaa8dcd16d..ca5a36cefad0 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 cf4929dfc91d..8718eaf58a88 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 eafac513c7e1..edd556c53a2f 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 05dbe1ef5a43..4b2e76807895 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -569,6 +569,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 2efcba48e82d..4fbf22e2294c 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..c72dd3008256 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: @@ -138,44 +141,40 @@ object NamedArgCompletions: def fallbackFindMatchingMethods() = def maybeNameAndIndexedContext( 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))) - ) + ownerSymbol.map(sym => sym.info.member(name)).collect{ + case single: SingleDenotation => List(single.symbol) + case multi: MultiDenotation => multi.allSymbols + }.getOrElse(Nil) case Apply(fun, _) => maybeNameAndIndexedContext(fun) - case _ => None + 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 <- maybeNameAndIndexedContext(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 b66fbe56fb9b..ff58a1b01960 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,28 +155,24 @@ 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 { @@ -184,7 +185,10 @@ class ShortenedTypePrinter( // 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) + indexedCtx.rename(tp.symbol) match + case None => + missingImports += ImportSel.Direct(tp.symbol) + case _ => Text() // the symbol is in scope, we can omit the prefix case Result.InScope => Text() 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 b65f23fae40f..02d384cccf0a 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala @@ -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/inlayHints/InlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala index 84002e54dcb7..bf917f05669b 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, )