From 3ceb5520c073fac9d2a5b5c8732e7a9d6050b182 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 12 Oct 2018 13:21:06 +0200 Subject: [PATCH 01/21] Address review --- .../src/dotty/tools/dotc/interactive/Interactive.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 8c0e69afc5a6..48bc3177a29f 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -70,10 +70,12 @@ object Interactive { funSym.owner.info.member(name).symbol } else { val classTree = funSym.topLevelClass.asClass.rootTree - tpd.defPath(funSym, classTree).lastOption.flatMap { - case DefDef(_, _, paramss, _, _) => - paramss.flatten.find(_.name == name).map(_.symbol) - }.getOrElse(fn.symbol) + val paramSymbol = + for { + DefDef(_, _, paramss, _, _) <- tpd.defPath(funSym, classTree).lastOption + param <- paramss.flatten.find(_.name == name) + } yield param.symbol + paramSymbol.getOrElse(fn.symbol) } // For constructor calls, return the `` that was selected From f49ab2251b53d13a1c42ba46a47a7932d6eb92ce Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 12 Oct 2018 14:59:33 +0200 Subject: [PATCH 02/21] Improve IDE support for imports --- .../tools/dotc/interactive/Interactive.scala | 141 +++++++++++++----- .../dotc/interactive/InteractiveDriver.scala | 14 +- .../tools/dotc/interactive/SourceTree.scala | 44 ++++-- .../languageserver/DottyLanguageServer.scala | 103 +++++++------ .../tools/languageserver/DefinitionTest.scala | 60 ++++++++ .../tools/languageserver/HighlightTest.scala | 88 +++++++++++ .../tools/languageserver/ReferencesTest.scala | 110 ++++++++++++++ .../languageserver/util/actions/Action.scala | 8 + .../util/actions/CodeDefinition.scala | 4 +- .../util/actions/CodeDocumentHighlight.scala | 4 +- 10 files changed, 470 insertions(+), 106 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 48bc3177a29f..f40217187b87 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -8,7 +8,7 @@ import scala.collection._ import ast.{NavigateAST, Trees, tpd, untpd} import core._, core.Decorators.{sourcePos => _, _} import Contexts._, Flags._, Names._, NameOps._, Symbols._, Trees._, Types._ -import util.Positions._, util.SourcePosition +import util.Positions._, util.SourceFile, util.SourcePosition import core.Denotations.SingleDenotation import NameKinds.SimpleNameKind import config.Printers.interactiv @@ -28,6 +28,7 @@ object Interactive { val references: Int = 4 // include references val definitions: Int = 8 // include definitions val linkedClass: Int = 16 // include `symbol.linkedClass` + val imports: Int = 32 // include imports in the results } /** Does this tree define a symbol ? */ @@ -59,15 +60,15 @@ object Interactive { * * @see sourceSymbol */ - def enclosingSourceSymbol(path: List[Tree])(implicit ctx: Context): Symbol = { - val sym = path match { + def enclosingSourceSymbols(path: List[Tree], pos: SourcePosition)(implicit ctx: Context): List[Symbol] = { + val syms = path match { // For a named arg, find the target `DefDef` and jump to the param case NamedArg(name, _) :: Apply(fn, _) :: _ => val funSym = fn.symbol if (funSym.name == StdNames.nme.copy && funSym.is(Synthetic) && funSym.owner.is(CaseClass)) { - funSym.owner.info.member(name).symbol + funSym.owner.info.member(name).symbol :: Nil } else { val classTree = funSym.topLevelClass.asClass.rootTree val paramSymbol = @@ -75,38 +76,29 @@ object Interactive { DefDef(_, _, paramss, _, _) <- tpd.defPath(funSym, classTree).lastOption param <- paramss.flatten.find(_.name == name) } yield param.symbol - paramSymbol.getOrElse(fn.symbol) + paramSymbol.getOrElse(fn.symbol) :: Nil } // For constructor calls, return the `` that was selected case _ :: (_: New) :: (select: Select) :: _ => - select.symbol + select.symbol :: Nil + + case (_: Thicket) :: (imp: Import) :: _ => + importedSymbols(imp, _.pos.contains(pos.pos)) + + case (imp: Import) :: _ => + importedSymbols(imp, _.pos.contains(pos.pos)) case _ => - enclosingTree(path).symbol + enclosingTree(path).symbol :: Nil } - Interactive.sourceSymbol(sym) - } - /** - * The source symbol that is the closest to the path to `pos` in `trees`. - * - * Computes the path from the tree with position `pos` in `trees`, and extract it source - * symbol. - * - * @param trees The trees in which to look for a path to `pos`. - * @param pos That target position of the path. - * @return The source symbol that is the closest to the computed path. - * - * @see sourceSymbol - */ - def enclosingSourceSymbol(trees: List[SourceTree], pos: SourcePosition)(implicit ctx: Context): Symbol = { - enclosingSourceSymbol(pathTo(trees, pos)) + syms.map(Interactive.sourceSymbol).filter(_.exists) } /** A symbol related to `sym` that is defined in source code. * - * @see enclosingSourceSymbol + * @see enclosingSourceSymbols */ @tailrec def sourceSymbol(sym: Symbol)(implicit ctx: Context): Symbol = if (!sym.exists) @@ -304,32 +296,60 @@ object Interactive { * source code. */ def namedTrees(trees: List[SourceTree], include: Include.Set, sym: Symbol) - (implicit ctx: Context): List[SourceTree] = + (implicit ctx: Context): List[SourceNamedTree] = if (!sym.exists) Nil else - namedTrees(trees, (include & Include.references) != 0, matchSymbol(_, sym, include)) + namedTrees(trees, include, matchSymbol(_, sym, include)) /** Find named trees with a non-empty position whose name contains `nameSubstring` in `trees`. */ def namedTrees(trees: List[SourceTree], nameSubstring: String) - (implicit ctx: Context): List[SourceTree] = { + (implicit ctx: Context): List[SourceNamedTree] = { val predicate: NameTree => Boolean = _.name.toString.contains(nameSubstring) - namedTrees(trees, includeReferences = false, predicate) + namedTrees(trees, 0, predicate) } /** Find named trees with a non-empty position satisfying `treePredicate` in `trees`. * * @param includeReferences If true, include references and not just definitions */ - def namedTrees(trees: List[SourceTree], includeReferences: Boolean, treePredicate: NameTree => Boolean) - (implicit ctx: Context): List[SourceTree] = safely { - val buf = new mutable.ListBuffer[SourceTree] + def namedTrees(trees: List[SourceTree], include: Include.Set, treePredicate: NameTree => Boolean) + (implicit ctx: Context): List[SourceNamedTree] = safely { + val includeReferences = (include & Include.references) != 0 + val includeImports = (include & Include.imports) != 0 + val buf = new mutable.ListBuffer[SourceNamedTree] - trees foreach { case SourceTree(topTree, source) => + def traverser(source: SourceFile) = { new untpd.TreeTraverser { + private def handleImport(imported: List[Symbol], + uexpr: untpd.Tree, + id: untpd.Ident, + rename: Option[untpd.Ident]): Unit = { + val expr = uexpr.asInstanceOf[tpd.Tree] + imported match { + case Nil => + traverse(expr) + case syms => + syms.foreach { sym => + val tree = tpd.Select(expr, sym.name).withPos(id.pos) + val renameTree = rename.map { r => + val name = if (sym.name.isTypeName) r.name.toTypeName else r.name + RenameTree(name, tpd.Select(expr, sym.name)).withPos(r.pos) + } + renameTree.foreach(traverse) + traverse(tree) + } + } + } override def traverse(tree: untpd.Tree)(implicit ctx: Context) = { tree match { + case imp @ Import(uexpr, (id: untpd.Ident) :: Nil) if includeImports => + val imported = importedSymbols(imp.asInstanceOf[tpd.Import]) + handleImport(imported, uexpr, id, None) + case imp @ Import(uexpr, Thicket((id: untpd.Ident) :: (rename: untpd.Ident) :: Nil) :: Nil) if includeImports => + val imported = importedSymbols(imp.asInstanceOf[tpd.Import]) + handleImport(imported, uexpr, id, Some(rename)) case utree: untpd.NameTree if tree.hasType => val tree = utree.asInstanceOf[tpd.NameTree] if (tree.symbol.exists @@ -338,7 +358,7 @@ object Interactive { && !tree.pos.isZeroExtent && (includeReferences || isDefinition(tree)) && treePredicate(tree)) - buf += SourceTree(tree, source) + buf += SourceNamedTree(tree, source) traverseChildren(tree) case tree: untpd.Inlined => traverse(tree.call) @@ -346,9 +366,11 @@ object Interactive { traverseChildren(tree) } } - }.traverse(topTree) + } } + trees.foreach(t => traverser(t.source).traverse(t.tree)) + buf.toList } @@ -361,9 +383,8 @@ object Interactive { */ def findTreesMatching(trees: List[SourceTree], includes: Include.Set, - symbol: Symbol)(implicit ctx: Context): List[SourceTree] = { + symbol: Symbol)(implicit ctx: Context): List[SourceNamedTree] = { val linkedSym = symbol.linkedClass - val includeReferences = (includes & Include.references) != 0 val includeDeclaration = (includes & Include.definitions) != 0 val includeLinkedClass = (includes & Include.linkedClass) != 0 val predicate: NameTree => Boolean = tree => @@ -377,7 +398,7 @@ object Interactive { ) ) ) - namedTrees(trees, includeReferences, predicate) + namedTrees(trees, includes, predicate) } /** The reverse path to the node that closest encloses position `pos`, @@ -465,10 +486,8 @@ object Interactive { * @param driver The driver responsible for `path`. * @return The definitions for the symbol at the end of `path`. */ - def findDefinitions(path: List[Tree], driver: InteractiveDriver)(implicit ctx: Context): List[SourceTree] = { - val sym = enclosingSourceSymbol(path) - if (sym == NoSymbol) Nil - else { + def findDefinitions(path: List[Tree], pos: SourcePosition, driver: InteractiveDriver)(implicit ctx: Context): List[SourceNamedTree] = { + enclosingSourceSymbols(path, pos).flatMap { sym => val enclTree = enclosingTree(path) val (trees, include) = @@ -543,4 +562,44 @@ object Interactive { } } + /** + * All the symbols that are imported by import statement `imp`, if it matches + * the predicate `selectorPredicate`. + * + * @param imp The import statement to analyze + * @param selectorPredicate A test to find the selector to use. + * @return The symbols imported. + */ + private def importedSymbols(imp: tpd.Import, + selectorPredicate: untpd.Tree => Boolean = util.common.alwaysTrue) + (implicit ctx: Context): List[Symbol] = { + def lookup0(name: Name): Symbol = imp.expr.tpe.member(name).symbol + def lookup(name: Name): List[Symbol] = { + lookup0(name.toTermName) :: + lookup0(name.toTypeName) :: + lookup0(name.moduleClassName) :: + lookup0(name.sourceModuleName) :: Nil + } + + val symbols = imp.selectors.find(selectorPredicate) match { + case Some(id: untpd.Ident) => + lookup(id.name) + case Some(Thicket((id: untpd.Ident) :: (_: untpd.Ident) :: Nil)) => + lookup(id.name) + case _ => Nil + } + + symbols.map(sourceSymbol).filter(_.exists).distinct + } + + /** + * Used to represent a renaming import `{foo => bar}`. + * We need this because the name of the tree must be the new name, but the + * denotation must be that of the importee. + */ + private case class RenameTree(name: Name, underlying: Tree) extends NameTree { + override def denot(implicit ctx: Context) = underlying.denot + myTpe = NoType + } + } diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala index 0e4c38fddf30..ee6e0dba0d55 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala @@ -162,7 +162,7 @@ class InteractiveDriver(val settings: List[String]) extends Driver { val unit = ctx.run.units.head val t = unit.tpdTree cleanup(t) - myOpenedTrees(uri) = topLevelClassTrees(t, source) + myOpenedTrees(uri) = topLevelTrees(t, source) myCompilationUnits(uri) = unit reporter.removeBufferedMessages @@ -187,17 +187,17 @@ class InteractiveDriver(val settings: List[String]) extends Driver { * @see SourceTree.fromSymbol */ private def treesFromClassName(className: TypeName, id: String)(implicit ctx: Context): List[SourceTree] = { - def tree(className: TypeName, id: String): Option[SourceTree] = { + def trees(className: TypeName, id: String): List[SourceTree] = { val clsd = ctx.base.staticRef(className) clsd match { case clsd: ClassDenotation => clsd.ensureCompleted() SourceTree.fromSymbol(clsd.symbol.asClass, id) case _ => - None + Nil } } - List(tree(className, id), tree(className.moduleClassName, id)).flatten + trees(className, id) ::: trees(className.moduleClassName, id) } // FIXME: classfiles in directories may change at any point, so we retraverse @@ -246,14 +246,16 @@ class InteractiveDriver(val settings: List[String]) extends Driver { } } - private def topLevelClassTrees(topTree: Tree, source: SourceFile): List[SourceTree] = { + private def topLevelTrees(topTree: Tree, source: SourceFile): List[SourceTree] = { val trees = new mutable.ListBuffer[SourceTree] def addTrees(tree: Tree): Unit = tree match { case PackageDef(_, stats) => stats.foreach(addTrees) + case imp: Import => + trees += SourceImportTree(imp, source) case tree: TypeDef => - trees += SourceTree(tree, source) + trees += SourceNamedTree(tree, source) case _ => } addTrees(topTree) diff --git a/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala b/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala index 6d197d38077a..29c92d1aa5a0 100644 --- a/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala +++ b/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala @@ -9,10 +9,24 @@ import core._, core.Decorators.{sourcePos => _} import Contexts._, NameOps._, Symbols._, StdNames._ import util._, util.Positions._ -/** A typechecked named `tree` coming from `source` */ -case class SourceTree(tree: tpd.NameTree, source: SourceFile) { +/** A `tree` coming from `source` */ +sealed trait SourceTree { + + /** The underlying tree. */ + def tree: tpd.Tree + + /** The source from which `tree` comes. */ + def source: SourceFile + /** The position of `tree` */ - def pos(implicit ctx: Context): SourcePosition = source.atPos(tree.pos) + final def pos(implicit ctx: Context): SourcePosition = source.atPos(tree.pos) +} + +/** An import coming from `source` */ +case class SourceImportTree(tree: tpd.Import, source: SourceFile) extends SourceTree + +/** A typechecked `tree` coming from `source` */ +case class SourceNamedTree(tree: tpd.NameTree, source: SourceFile) extends SourceTree { /** The position of the name in `tree` */ def namePos(implicit ctx: Context): SourcePosition = { @@ -43,21 +57,33 @@ case class SourceTree(tree: tpd.NameTree, source: SourceFile) { } object SourceTree { - def fromSymbol(sym: ClassSymbol, id: String = "")(implicit ctx: Context): Option[SourceTree] = { + def fromSymbol(sym: ClassSymbol, id: String = "")(implicit ctx: Context): List[SourceTree] = { if (sym == defn.SourceFileAnnot || // FIXME: No SourceFile annotation on SourceFile itself sym.sourceFile == null) // FIXME: We cannot deal with external projects yet - None + Nil else { import ast.Trees._ - def sourceTreeOfClass(tree: tpd.Tree): Option[SourceTree] = tree match { + def sourceTreeOfClass(tree: tpd.Tree): Option[SourceNamedTree] = tree match { case PackageDef(_, stats) => stats.flatMap(sourceTreeOfClass).headOption case tree: tpd.TypeDef if tree.symbol == sym => val sourceFile = new SourceFile(sym.sourceFile, Codec.UTF8) - Some(SourceTree(tree, sourceFile)) - case _ => None + Some(SourceNamedTree(tree, sourceFile)) + case _ => + None + } + + def sourceImports(tree: tpd.Tree, sourceFile: SourceFile): List[SourceImportTree] = tree match { + case PackageDef(_, stats) => stats.flatMap(sourceImports(_, sourceFile)) + case imp: tpd.Import => SourceImportTree(imp, sourceFile) :: Nil + case _ => Nil + } + + val tree = sym.rootTreeContaining(id) + sourceTreeOfClass(tree) match { + case Some(namedTree) => namedTree :: sourceImports(tree, namedTree.source) + case None => Nil } - sourceTreeOfClass(sym.rootTreeContaining(id)) } } } diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 0712e0f3029c..f2e39a0cecd2 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -301,7 +301,7 @@ class DottyLanguageServer extends LanguageServer val pos = sourcePosition(driver, uri, params.getPosition) val path = Interactive.pathTo(driver.openedTrees(uri), pos) - val definitions = Interactive.findDefinitions(path, driver).toList + val definitions = Interactive.findDefinitions(path, pos, driver).toList definitions.flatMap(d => location(d.namePos, positionMapperFor(d.source))).asJava } @@ -311,34 +311,37 @@ class DottyLanguageServer extends LanguageServer val includes = { val includeDeclaration = params.getContext.isIncludeDeclaration - Include.references | Include.overriding | (if (includeDeclaration) Include.definitions else 0) + Include.references | Include.overriding | Include.imports | (if (includeDeclaration) Include.definitions else 0) } + val uriTrees = driver.openedTrees(uri) val pos = sourcePosition(driver, uri, params.getPosition) - val (definitions, originalSymbol, originalSymbolName) = { + val (definitions, originalSymbols) = { implicit val ctx: Context = driver.currentCtx val path = Interactive.pathTo(driver.openedTrees(uri), pos) - val originalSymbol = Interactive.enclosingSourceSymbol(path) - val originalSymbolName = originalSymbol.name.sourceModuleName.toString - val definitions = Interactive.findDefinitions(path, driver) + val definitions = Interactive.findDefinitions(path, pos, driver) + val originalSymbols = Interactive.enclosingSourceSymbols(path, pos) - (definitions, originalSymbol, originalSymbolName) + (definitions, originalSymbols) } val references = { // Collect the information necessary to look into each project separately: representation of // `originalSymbol` in this project, the context and correct Driver. - val perProjectInfo = inProjectsSeeing(driver, definitions, originalSymbol) - - perProjectInfo.flatMap { (remoteDriver, ctx, definition) => - val trees = remoteDriver.sourceTreesContaining(originalSymbolName)(ctx) - val matches = Interactive.findTreesMatching(trees, includes, definition)(ctx) - matches.map(tree => location(tree.namePos(ctx), positionMapperFor(tree.source))) + val perProjectInfo = inProjectsSeeing(driver, definitions, originalSymbols) + + perProjectInfo.flatMap { (remoteDriver, ctx, definitions) => + definitions.flatMap { definition => + val name = definition.name(ctx).sourceModuleName.toString + val trees = remoteDriver.sourceTreesContaining(name)(ctx) + val matches = Interactive.findTreesMatching(trees, includes, definition)(ctx) + matches.map(tree => location(tree.namePos(ctx), positionMapperFor(tree.source))) + } } }.toList - references.flatten.asJava + references.flatten.distinct.asJava } override def rename(params: RenameParams) = computeAsync { cancelToken => @@ -346,25 +349,27 @@ class DottyLanguageServer extends LanguageServer val driver = driverFor(uri) implicit val ctx = driver.currentCtx + val uriTrees = driver.openedTrees(uri) val pos = sourcePosition(driver, uri, params.getPosition) - val sym = Interactive.enclosingSourceSymbol(driver.openedTrees(uri), pos) + val path = Interactive.pathTo(uriTrees, pos) + val syms = Interactive.enclosingSourceSymbols(path, pos) + val newName = params.getNewName + val includes = + Include.references | Include.definitions | Include.linkedClass | Include.overriding - if (sym == NoSymbol) new WorkspaceEdit() - else { + val refs = syms.flatMap { sym => val trees = driver.allTreesContaining(sym.name.sourceModuleName.toString) - val newName = params.getNewName - val includes = - Include.references | Include.definitions | Include.linkedClass | Include.overriding - val refs = Interactive.findTreesMatching(trees, includes, sym) + Interactive.findTreesMatching(trees, includes, sym) + } - val changes = refs.groupBy(ref => toUriOption(ref.source)) + val changes = + refs.groupBy(ref => toUriOption(ref.source)) .flatMap((uriOpt, ref) => uriOpt.map(uri => (uri.toString, ref))) .mapValues(refs => refs.flatMap(ref => range(ref.namePos, positionMapperFor(ref.source)).map(nameRange => new TextEdit(nameRange, newName))).asJava) - new WorkspaceEdit(changes.asJava) - } + new WorkspaceEdit(changes.asJava) } override def documentHighlight(params: TextDocumentPositionParams) = computeAsync { cancelToken => @@ -374,16 +379,17 @@ class DottyLanguageServer extends LanguageServer val pos = sourcePosition(driver, uri, params.getPosition) val uriTrees = driver.openedTrees(uri) - val sym = Interactive.enclosingSourceSymbol(uriTrees, pos) + val path = Interactive.pathTo(uriTrees, pos) + val syms = Interactive.enclosingSourceSymbols(path, pos) + val includes = Include.definitions | Include.references | Include.imports - if (sym == NoSymbol) Nil.asJava - else { - val refs = Interactive.namedTrees(uriTrees, Include.references | Include.overriding, sym) + syms.flatMap { sym => + val refs = Interactive.findTreesMatching(uriTrees, includes, sym) (for { - ref <- refs if !ref.tree.symbol.isPrimaryConstructor + ref <- refs nameRange <- range(ref.namePos, positionMapperFor(ref.source)) - } yield new DocumentHighlight(nameRange, DocumentHighlightKind.Read)).asJava - } + } yield new DocumentHighlight(nameRange, DocumentHighlightKind.Read)) + }.distinct.asJava } override def hover(params: TextDocumentPositionParams) = computeAsync { cancelToken => @@ -393,12 +399,14 @@ class DottyLanguageServer extends LanguageServer val pos = sourcePosition(driver, uri, params.getPosition) val trees = driver.openedTrees(uri) + val path = Interactive.pathTo(trees, pos) val tp = Interactive.enclosingType(trees, pos) val tpw = tp.widenTermRefExpr if (tp.isError || tpw == NoType) null // null here indicates that no response should be sent else { - val symbol = Interactive.enclosingSourceSymbol(trees, pos) + val symbol = Interactive.enclosingSourceSymbols(path, pos).headOption.orNull + if (symbol == null) return null val docComment = ParsedComment.docOf(symbol) val content = hoverContent(Some(tpw.show), docComment) new Hover(content, null) @@ -412,7 +420,7 @@ class DottyLanguageServer extends LanguageServer val uriTrees = driver.openedTrees(uri) - val defs = Interactive.namedTrees(uriTrees, includeReferences = false, _ => true) + val defs = Interactive.namedTrees(uriTrees, 0, _ => true) (for { d <- defs if !isWorksheetWrapper(d) info <- symbolInfo(d.tree.symbol, d.namePos, positionMapperFor(d.source)) @@ -437,21 +445,24 @@ class DottyLanguageServer extends LanguageServer val pos = sourcePosition(driver, uri, params.getPosition) - val (definitions, originalSymbol) = { + val (definitions, originalSymbols) = { implicit val ctx: Context = driver.currentCtx val path = Interactive.pathTo(driver.openedTrees(uri), pos) - val originalSymbol = Interactive.enclosingSourceSymbol(path) - val definitions = Interactive.findDefinitions(path, driver) - (definitions, originalSymbol) + val originalSymbols = Interactive.enclosingSourceSymbols(path, pos) + val definitions = Interactive.findDefinitions(path, pos, driver) + (definitions, originalSymbols) } val implementations = { - val perProjectInfo = inProjectsSeeing(driver, definitions, originalSymbol) + val perProjectInfo = inProjectsSeeing(driver, definitions, originalSymbols) - perProjectInfo.flatMap { (remoteDriver, ctx, definition) => + perProjectInfo.flatMap { (remoteDriver, ctx, definitions) => val trees = remoteDriver.sourceTrees(ctx) - val predicate = Interactive.implementationFilter(definition)(ctx) - val matches = Interactive.namedTrees(trees, includeReferences = false, predicate)(ctx) + val predicate: NameTree => Boolean = { + val predicates = definitions.map(Interactive.implementationFilter(_)(ctx)) + tree => predicates.exists(_(tree)) + } + val matches = Interactive.namedTrees(trees, 0, predicate)(ctx) matches.map(tree => location(tree.namePos(ctx), positionMapperFor(tree.source))) } }.toList @@ -508,23 +519,23 @@ class DottyLanguageServer extends LanguageServer } /** - * Finds projects that can see any of `definitions`, translate `symbol` in their universe. + * Finds projects that can see any of `definitions`, translate `symbols` in their universe. * * @param baseDriver The driver responsible for the trees in `definitions` and `symbol`. * @param definitions The definitions to consider when looking for projects. - * @param symbol A symbol to translate in the universes of the remote projects. + * @param symbol Symbols to translate in the universes of the remote projects. * @return A list consisting of the remote drivers, their context, and the translation of `symbol` * into their universe. */ private def inProjectsSeeing(baseDriver: InteractiveDriver, definitions: List[SourceTree], - symbol: Symbol): List[(InteractiveDriver, Context, Symbol)] = { + symbols: List[Symbol]): List[(InteractiveDriver, Context, List[Symbol])] = { val projects = projectsSeeing(definitions)(baseDriver.currentCtx) projects.toList.map { config => val remoteDriver = drivers(config) val ctx = remoteDriver.currentCtx - val definition = Interactive.localize(symbol, baseDriver, remoteDriver) - (remoteDriver, ctx, definition) + val definitions = symbols.map(Interactive.localize(_, baseDriver, remoteDriver)) + (remoteDriver, ctx, definitions) } } diff --git a/language-server/test/dotty/tools/languageserver/DefinitionTest.scala b/language-server/test/dotty/tools/languageserver/DefinitionTest.scala index b215a7a277c3..de08fdf88259 100644 --- a/language-server/test/dotty/tools/languageserver/DefinitionTest.scala +++ b/language-server/test/dotty/tools/languageserver/DefinitionTest.scala @@ -249,4 +249,64 @@ class DefinitionTest { .definition(m7 to m8, List(m3 to m4)) } + @Test def goToDefinitionImport: Unit = { + withSources( + code"""package a + class ${m1}Foo${m2}""", + code"""package b + import a.${m3}Foo${m4} + class C extends ${m5}Foo${m6}""" + ).definition(m1 to m2, List(m1 to m2)) + .definition(m3 to m4, List(m1 to m2)) + .definition(m5 to m6, List(m1 to m2)) + } + + @Test def goToDefinitionRenamedImport: Unit = { + withSources( + code"""package a + class ${m1}Foo${m2}""", + code"""package b + import a.{${m3}Foo${m4} => ${m5}Bar${m6}} + class C extends ${m7}Bar${m8}""" + ).definition(m1 to m2, List(m1 to m2)) + .definition(m3 to m4, List(m1 to m2)) + .definition(m5 to m6, List(m1 to m2)) + .definition(m7 to m8, List(m1 to m2)) + } + + @Test def goToDefinitionImportAlternatives: Unit = { + withSources( + code"""package a + class ${m1}Foo${m2} + object ${m3}Foo${m4}""", + code"""package b + import a.${m5}Foo${m6} + class C extends ${m7}Foo${m8} { + val bar = ${m9}Foo${m10} + }""" + ).definition(m1 to m2, List(m1 to m2)) + .definition(m3 to m4, List(m3 to m4)) + .definition(m5 to m6, List(m1 to m2, m3 to m4)) + .definition(m7 to m8, List(m1 to m2)) + .definition(m9 to m10, List(m3 to m4)) + } + + @Test def goToDefinitionImportAlternativesWithRename: Unit = { + withSources( + code"""package a + class ${m1}Foo${m2} + object ${m3}Foo${m4}""", + code"""package b + import a.{${m5}Foo${m6} => ${m7}Bar${m8}} + class C extends ${m9}Bar${m10} { + val buzz = ${m11}Bar${m12} + }""" + ).definition(m1 to m2, List(m1 to m2)) + .definition(m3 to m4, List(m3 to m4)) + .definition(m5 to m6, List(m1 to m2, m3 to m4)) + .definition(m7 to m8, List(m1 to m2, m3 to m4)) + .definition(m9 to m10, List(m1 to m2)) + .definition(m11 to m12, List(m3 to m4)) + } + } diff --git a/language-server/test/dotty/tools/languageserver/HighlightTest.scala b/language-server/test/dotty/tools/languageserver/HighlightTest.scala index 3bf0957be1b8..7ce0fbc5109d 100644 --- a/language-server/test/dotty/tools/languageserver/HighlightTest.scala +++ b/language-server/test/dotty/tools/languageserver/HighlightTest.scala @@ -25,4 +25,92 @@ class HighlightTest { .highlight(m3 to m4, (m1 to m2, DocumentHighlightKind.Read), (m3 to m4, DocumentHighlightKind.Read)) } + @Test def importHighlight0: Unit = { + code"""object ${m1}Foo${m2} { def ${m5}bar${m6}: Int = 0 } + trait Bar { import ${m3}Foo${m4}._; def buzz = ${m7}bar${m8} } + trait Baz { def ${m9}bar${m10}: Int = 1 }""".withSource + + .highlight(m1 to m2, (m1 to m2, DocumentHighlightKind.Read), (m3 to m4, DocumentHighlightKind.Read)) + .highlight(m3 to m4, (m1 to m2, DocumentHighlightKind.Read), (m3 to m4, DocumentHighlightKind.Read)) + .highlight(m5 to m6, (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m7 to m8, (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m9 to m10, (m9 to m10, DocumentHighlightKind.Read)) + } + + @Test def importHighlight1: Unit = { + code"""import ${m1}Foo${m2}._ + object ${m3}Foo${m4} { def ${m5}bar${m6}: Int = 0 } + trait Bar { def buzz = ${m7}bar${m8} }""".withSource + + .highlight(m1 to m2, (m1 to m2, DocumentHighlightKind.Read), (m3 to m4, DocumentHighlightKind.Read)) + .highlight(m3 to m4, (m1 to m2, DocumentHighlightKind.Read), (m3 to m4, DocumentHighlightKind.Read)) + .highlight(m5 to m6, (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m7 to m8, (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + } + + @Test def importHighlight2: Unit = { + code"""object ${m1}Foo${m2} { object ${m3}Bar${m4} { object ${m5}Baz${m6} } } + trait Buzz { import ${m7}Foo${m8}.${m9}Bar${m10}.${m11}Baz${m12} }""".withSource + + .highlight(m1 to m2, (m1 to m2, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m3 to m4, (m3 to m4, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read)) + .highlight(m5 to m6, (m5 to m6, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read)) + .highlight(m7 to m8, (m1 to m2, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m9 to m10, (m3 to m4, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read)) + .highlight(m11 to m12, (m5 to m6, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read)) + } + + @Test def importHighlight3: Unit = { + code"""import ${m1}Foo${m2}.${m3}Bar${m4} + object ${m5}Foo${m6} { object ${m7}Bar${m8} }""".withSource + + .highlight(m1 to m2, (m1 to m2, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read)) + .highlight(m3 to m4, (m3 to m4, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m5 to m6, (m1 to m2, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read)) + .highlight(m7 to m8, (m3 to m4, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + } + + @Test def importHighlightClassAndCompanion: Unit = { + code"""object Foo { object ${m1}Bar${m2}; class ${m3}Bar${m4} } + trait Buzz { import Foo.${m5}Bar${m6} }""".withSource + .highlight(m1 to m2, (m1 to m2, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read)) + .highlight(m3 to m4, (m3 to m4, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read)) + .highlight(m5 to m6, (m3 to m4, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m1 to m2, DocumentHighlightKind.Read)) + } + + @Test def importHighlightWithRename: Unit = { + code"""object ${m1}Foo${m2} { object ${m3}Bar${m4} { object ${m5}Baz${m6} } } + trait Buzz { import ${m7}Foo${m8}.${m9}Bar${m10}.{${m11}Baz${m12} => ${m13}Quux${m14}}""".withSource + + .highlight(m1 to m2, (m1 to m2, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m3 to m4, (m3 to m4, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read)) + .highlight(m5 to m6, (m5 to m6, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read), (m13 to m14, DocumentHighlightKind.Read)) + .highlight(m7 to m8, (m1 to m2, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m9 to m10, (m3 to m4, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read)) + .highlight(m11 to m12, (m5 to m6, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read), (m13 to m14, DocumentHighlightKind.Read)) + .highlight(m13 to m14, (m5 to m6, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read), (m13 to m14, DocumentHighlightKind.Read)) + } + + @Test def importHighlightClassAndCompanionWithRename: Unit = { + code"""object ${m1}Foo${m2} { object ${m3}Bar${m4}; class ${m5}Bar${m6} } + trait Buzz { import ${m7}Foo${m8}.{${m9}Bar${m10} => ${m11}Baz${m12}} }""".withSource + + .highlight(m1 to m2, (m1 to m2, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m3 to m4, (m3 to m4, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read)) + .highlight(m5 to m6, (m5 to m6, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read)) + .highlight(m7 to m8, (m1 to m2, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m9 to m10, (m3 to m4, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read)) + .highlight(m11 to m12, (m3 to m4, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read)) + } + + @Test def importHighlightMembers: Unit = { + code"""object Foo { def ${m1}bar${m2} = 2; type ${m3}bar${m4} = fizz; class fizz } + trait Quux { import Foo.{${m5}bar${m6} => ${m7}buzz${m8}} }""".withSource + + .highlight(m1 to m2, (m1 to m2, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m3 to m4, (m3 to m4, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m5 to m6, (m1 to m2, DocumentHighlightKind.Read), (m3 to m4, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m7 to m8, (m1 to m2, DocumentHighlightKind.Read), (m3 to m4, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + } + } diff --git a/language-server/test/dotty/tools/languageserver/ReferencesTest.scala b/language-server/test/dotty/tools/languageserver/ReferencesTest.scala index 9b2425582157..930293970a76 100644 --- a/language-server/test/dotty/tools/languageserver/ReferencesTest.scala +++ b/language-server/test/dotty/tools/languageserver/ReferencesTest.scala @@ -217,4 +217,114 @@ class ReferencesTest { .references(m1 to m2, List(m3 to m4), withDecl = false) } + @Test def importReference1: Unit = { + code"""import ${m1}Foo${m2}._ + object ${m3}Foo${m4} { def ${m5}bar${m6}: Int = 0 } + trait Bar { def buzz = ${m7}bar${m8} }""".withSource + + .references(m1 to m2, List(m1 to m2, m3 to m4), withDecl = true) + .references(m1 to m2, List(m1 to m2), withDecl = false) + .references(m3 to m4, List(m1 to m2, m3 to m4), withDecl = true) + .references(m3 to m4, List(m1 to m2), withDecl = false) + .references(m5 to m6, List(m5 to m6, m7 to m8), withDecl = true) + .references(m5 to m6, List(m7 to m8), withDecl = false) + .references(m7 to m8, List(m5 to m6, m7 to m8), withDecl = true) + .references(m7 to m8, List(m7 to m8), withDecl = false) + } + + @Test def importReference2: Unit = { + code"""object ${m1}Foo${m2} { object ${m3}Bar${m4} { object ${m5}Baz${m6} } } + trait Buzz { import ${m7}Foo${m8}.${m9}Bar${m10}.${m11}Baz${m12} }""".withSource + + .references(m1 to m2, List(m1 to m2, m7 to m8), withDecl = true) + .references(m1 to m2, List(m7 to m8), withDecl = false) + .references(m3 to m4, List(m3 to m4, m9 to m10), withDecl = true) + .references(m3 to m4, List(m9 to m10), withDecl = false) + .references(m5 to m6, List(m5 to m6, m11 to m12), withDecl = true) + .references(m5 to m6, List(m11 to m12), withDecl = false) + .references(m7 to m8, List(m1 to m2, m7 to m8), withDecl = true) + .references(m7 to m8, List(m7 to m8), withDecl = false) + .references(m9 to m10, List(m3 to m4, m9 to m10), withDecl = true) + .references(m9 to m10, List(m9 to m10), withDecl = false) + .references(m11 to m12, List(m5 to m6, m11 to m12), withDecl = true) + .references(m11 to m12, List(m11 to m12), withDecl = false) + } + + @Test def importReference3: Unit = { + code"""import ${m1}Foo${m2}.${m3}Bar${m4} + object ${m5}Foo${m6} { object ${m7}Bar${m8} }""".withSource + + .references(m1 to m2, List(m1 to m2, m5 to m6), withDecl = true) + .references(m1 to m2, List(m1 to m2), withDecl = false) + .references(m3 to m4, List(m3 to m4, m7 to m8), withDecl = true) + .references(m3 to m4, List(m3 to m4), withDecl = false) + .references(m5 to m6, List(m1 to m2, m5 to m6), withDecl = true) + .references(m5 to m6, List(m1 to m2), withDecl = false) + .references(m7 to m8, List(m3 to m4, m7 to m8), withDecl = true) + .references(m7 to m8, List(m3 to m4), withDecl = false) + } + + @Test def importReferenceClassAndCompanion: Unit = { + code"""object Foo { object ${m1}Bar${m2}; class ${m3}Bar${m4} } + trait Buzz { import Foo.${m5}Bar${m6} }""".withSource + .references(m1 to m2, List(m1 to m2, m5 to m6), withDecl = true) + .references(m1 to m2, List(m5 to m6), withDecl = false) + .references(m3 to m4, List(m3 to m4, m5 to m6), withDecl = true) + .references(m3 to m4, List(m5 to m6), withDecl = false) + .references(m5 to m6, List(m1 to m2, m3 to m4, m5 to m6), withDecl = true) + .references(m5 to m6, List(m5 to m6), withDecl = false) + } + + @Test def importReferenceWithRename: Unit = { + code"""object ${m1}Foo${m2} { object ${m3}Bar${m4} { object ${m5}Baz${m6} } } + trait Buzz { import ${m7}Foo${m8}.${m9}Bar${m10}.{${m11}Baz${m12} => ${m13}Quux${m14}}""".withSource + + .references(m1 to m2, List(m1 to m2, m7 to m8), withDecl = true) + .references(m1 to m2, List(m7 to m8), withDecl = false) + .references(m3 to m4, List(m3 to m4, m9 to m10), withDecl = true) + .references(m3 to m4, List(m9 to m10), withDecl = false) + .references(m5 to m6, List(m5 to m6, m11 to m12, m13 to m14), withDecl = true) + .references(m5 to m6, List(m11 to m12, m13 to m14), withDecl = false) + .references(m7 to m8, List(m1 to m2, m7 to m8), withDecl = true) + .references(m7 to m8, List(m7 to m8), withDecl = false) + .references(m9 to m10, List(m3 to m4, m9 to m10), withDecl = true) + .references(m9 to m10, List(m9 to m10), withDecl = false) + .references(m11 to m12, List(m5 to m6, m11 to m12, m13 to m14), withDecl = true) + .references(m11 to m12, List(m11 to m12, m13 to m14), withDecl = false) + .references(m13 to m14, List(m5 to m6, m11 to m12, m13 to m14), withDecl = true) + .references(m13 to m14, List(m11 to m12, m13 to m14), withDecl = false) + } + + @Test def importReferenceClassAndCompanionWithRename: Unit = { + code"""object ${m1}Foo${m2} { object ${m3}Bar${m4}; class ${m5}Bar${m6} } + trait Buzz { import ${m7}Foo${m8}.{${m9}Bar${m10} => ${m11}Baz${m12}} }""".withSource + + .references(m1 to m2, List(m1 to m2, m7 to m8), withDecl = true) + .references(m1 to m2, List(m7 to m8), withDecl = false) + .references(m3 to m4, List(m3 to m4, m9 to m10, m11 to m12), withDecl = true) + .references(m3 to m4, List(m9 to m10, m11 to m12), withDecl = false) + .references(m5 to m6, List(m5 to m6, m9 to m10, m11 to m12), withDecl = true) + .references(m5 to m6, List(m9 to m10, m11 to m12), withDecl = false) + .references(m7 to m8, List(m1 to m2, m7 to m8), withDecl = true) + .references(m7 to m8, List(m7 to m8), withDecl = false) + .references(m9 to m10, List(m3 to m4, m5 to m6, m9 to m10, m11 to m12), withDecl = true) + .references(m9 to m10, List(m9 to m10, m11 to m12), withDecl = false) + .references(m11 to m12, List(m3 to m4, m5 to m6, m9 to m10, m11 to m12), withDecl = true) + .references(m11 to m12, List(m9 to m10, m11 to m12), withDecl = false) + } + + @Test def importReferenceMembers: Unit = { + code"""object Foo { def ${m1}bar${m2} = 2; type ${m3}bar${m4} = fizz; class fizz } + trait Quux { import Foo.{${m5}bar${m6} => ${m7}buzz${m8}} }""".withSource + + .references(m1 to m2, List(m1 to m2, m5 to m6, m7 to m8), withDecl = true) + .references(m1 to m2, List(m5 to m6, m7 to m8), withDecl = false) + .references(m3 to m4, List(m3 to m4, m5 to m6, m7 to m8), withDecl = true) + .references(m3 to m4, List(m5 to m6, m7 to m8), withDecl = false) + .references(m5 to m6, List(m1 to m2, m3 to m4, m5 to m6, m7 to m8), withDecl = true) + .references(m5 to m6, List(m5 to m6, m7 to m8), withDecl = false) + .references(m7 to m8, List(m1 to m2, m3 to m4, m5 to m6, m7 to m8), withDecl = true) + .references(m7 to m8, List(m5 to m6, m7 to m8), withDecl = false) + } + } diff --git a/language-server/test/dotty/tools/languageserver/util/actions/Action.scala b/language-server/test/dotty/tools/languageserver/util/actions/Action.scala index f4654c763ca7..746d9c4c01d4 100644 --- a/language-server/test/dotty/tools/languageserver/util/actions/Action.scala +++ b/language-server/test/dotty/tools/languageserver/util/actions/Action.scala @@ -25,4 +25,12 @@ trait Action { /** The client that executes this action. */ def client: Exec[TestClient] = implicitly[TestClient] + /** An ordering for `Location` that compares string representations. */ + implicit def locationOrdering: Ordering[org.eclipse.lsp4j.Location] = + Ordering.by(_.toString) + + /** An ordering for `Range` that compares string representations. */ + implicit def rangeOrdering: Ordering[org.eclipse.lsp4j.Range] = + Ordering.by(_.toString) + } diff --git a/language-server/test/dotty/tools/languageserver/util/actions/CodeDefinition.scala b/language-server/test/dotty/tools/languageserver/util/actions/CodeDefinition.scala index 0e1e41774ac2..0ece0d9a5447 100644 --- a/language-server/test/dotty/tools/languageserver/util/actions/CodeDefinition.scala +++ b/language-server/test/dotty/tools/languageserver/util/actions/CodeDefinition.scala @@ -17,8 +17,8 @@ import org.junit.Assert.assertEquals class CodeDefinition(override val range: CodeRange, expected: Seq[CodeRange]) extends ActionOnRange { override def onMarker(marker: CodeMarker): Exec[Unit] = { - val results = server.definition(marker.toTextDocumentPositionParams).get().asScala.toSeq - val expectedLocations = expected.map(_.toLocation) + val results = server.definition(marker.toTextDocumentPositionParams).get().asScala.toSeq.sorted + val expectedLocations = expected.map(_.toLocation).sorted assertEquals(expectedLocations, results) } diff --git a/language-server/test/dotty/tools/languageserver/util/actions/CodeDocumentHighlight.scala b/language-server/test/dotty/tools/languageserver/util/actions/CodeDocumentHighlight.scala index eefbac6dd40f..58cb288716cc 100644 --- a/language-server/test/dotty/tools/languageserver/util/actions/CodeDocumentHighlight.scala +++ b/language-server/test/dotty/tools/languageserver/util/actions/CodeDocumentHighlight.scala @@ -20,9 +20,9 @@ class CodeDocumentHighlight(override val range: CodeRange, expected: Seq[(CodeRange, DocumentHighlightKind)]) extends ActionOnRange { override def onMarker(marker: CodeMarker): Exec[Unit] = { - val expectedPairs = expected.map { case (codeRange, kind) => (codeRange.toRange, kind) } + val expectedPairs = expected.map { case (codeRange, kind) => (codeRange.toRange, kind) }.sorted val results = server.documentHighlight(marker.toTextDocumentPositionParams).get() - val resultPairs = results.asScala.map { result => (result.getRange, result.getKind) } + val resultPairs = results.asScala.map { result => (result.getRange, result.getKind) }.sorted assertEquals(expectedPairs, resultPairs) } From edf62302f299c458017969ee78edde7340516c56 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 12 Oct 2018 16:36:18 +0200 Subject: [PATCH 03/21] Support multiple imports per line --- .../tools/dotc/interactive/Interactive.scala | 59 +++++++++++-------- .../tools/languageserver/DefinitionTest.scala | 13 ++++ .../tools/languageserver/HighlightTest.scala | 13 ++++ .../tools/languageserver/ReferencesTest.scala | 19 ++++++ 4 files changed, 78 insertions(+), 26 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index f40217187b87..a9d7a0669316 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -322,20 +322,23 @@ object Interactive { def traverser(source: SourceFile) = { new untpd.TreeTraverser { - private def handleImport(imported: List[Symbol], - uexpr: untpd.Tree, - id: untpd.Ident, - rename: Option[untpd.Ident]): Unit = { - val expr = uexpr.asInstanceOf[tpd.Tree] + private def handleImport(imp: tpd.Import): Unit = { + val imported = + imp.selectors.flatMap { + case id: untpd.Ident => + importedSymbols(imp.expr, id.name).map((_, id, None)) + case Thicket((id: untpd.Ident) :: (newName: untpd.Ident) :: Nil) => + importedSymbols(imp.expr, id.name).map((_, id, Some(newName))) + } imported match { case Nil => - traverse(expr) + traverse(imp.expr) case syms => - syms.foreach { sym => - val tree = tpd.Select(expr, sym.name).withPos(id.pos) + syms.foreach { case (sym, name, rename) => + val tree = tpd.Select(imp.expr, sym.name).withPos(name.pos) val renameTree = rename.map { r => val name = if (sym.name.isTypeName) r.name.toTypeName else r.name - RenameTree(name, tpd.Select(expr, sym.name)).withPos(r.pos) + RenameTree(name, tpd.Select(imp.expr, sym.name)).withPos(r.pos) } renameTree.foreach(traverse) traverse(tree) @@ -344,12 +347,8 @@ object Interactive { } override def traverse(tree: untpd.Tree)(implicit ctx: Context) = { tree match { - case imp @ Import(uexpr, (id: untpd.Ident) :: Nil) if includeImports => - val imported = importedSymbols(imp.asInstanceOf[tpd.Import]) - handleImport(imported, uexpr, id, None) - case imp @ Import(uexpr, Thicket((id: untpd.Ident) :: (rename: untpd.Ident) :: Nil) :: Nil) if includeImports => - val imported = importedSymbols(imp.asInstanceOf[tpd.Import]) - handleImport(imported, uexpr, id, Some(rename)) + case imp: untpd.Import if includeImports => + handleImport(imp.asInstanceOf[tpd.Import]) case utree: untpd.NameTree if tree.hasType => val tree = utree.asInstanceOf[tpd.NameTree] if (tree.symbol.exists @@ -573,25 +572,33 @@ object Interactive { private def importedSymbols(imp: tpd.Import, selectorPredicate: untpd.Tree => Boolean = util.common.alwaysTrue) (implicit ctx: Context): List[Symbol] = { - def lookup0(name: Name): Symbol = imp.expr.tpe.member(name).symbol - def lookup(name: Name): List[Symbol] = { - lookup0(name.toTermName) :: - lookup0(name.toTypeName) :: - lookup0(name.moduleClassName) :: - lookup0(name.sourceModuleName) :: Nil - } - val symbols = imp.selectors.find(selectorPredicate) match { case Some(id: untpd.Ident) => - lookup(id.name) + importedSymbols(imp.expr, id.name) case Some(Thicket((id: untpd.Ident) :: (_: untpd.Ident) :: Nil)) => - lookup(id.name) - case _ => Nil + importedSymbols(imp.expr, id.name) + case _ => + Nil } symbols.map(sourceSymbol).filter(_.exists).distinct } + /** + * The symbols that are imported with `expr.name` + * + * @param expr The base of the import statement + * @param name The name that is being imported. + * @return All the symbols that would be imported with `expr.name`. + */ + private def importedSymbols(expr: tpd.Tree, name: Name)(implicit ctx: Context): List[Symbol] = { + def lookup(name: Name): Symbol = expr.tpe.member(name).symbol + lookup(name.toTermName) :: + lookup(name.toTypeName) :: + lookup(name.moduleClassName) :: + lookup(name.sourceModuleName) :: Nil + } + /** * Used to represent a renaming import `{foo => bar}`. * We need this because the name of the tree must be the new name, but the diff --git a/language-server/test/dotty/tools/languageserver/DefinitionTest.scala b/language-server/test/dotty/tools/languageserver/DefinitionTest.scala index de08fdf88259..fa71b14384f7 100644 --- a/language-server/test/dotty/tools/languageserver/DefinitionTest.scala +++ b/language-server/test/dotty/tools/languageserver/DefinitionTest.scala @@ -309,4 +309,17 @@ class DefinitionTest { .definition(m11 to m12, List(m3 to m4)) } + @Test def multipleImportsPerLineWithRename: Unit = { + withSources( + code"""object A { class ${m1}B${m2}; class ${m3}C${m4} }""", + code"""import A.{${m5}B${m6} => ${m7}B2${m8}, ${m9}C${m10} => ${m11}C2${m12}} + class E""" + ).definition(m1 to m2, List(m1 to m2)) + .definition(m3 to m4, List(m3 to m4)) + .definition(m5 to m6, List(m1 to m2)) + .definition(m7 to m8, List(m1 to m2)) + .definition(m9 to m10, List(m3 to m4)) + .definition(m11 to m12, List(m3 to m4)) + } + } diff --git a/language-server/test/dotty/tools/languageserver/HighlightTest.scala b/language-server/test/dotty/tools/languageserver/HighlightTest.scala index 7ce0fbc5109d..8c8b0a445eda 100644 --- a/language-server/test/dotty/tools/languageserver/HighlightTest.scala +++ b/language-server/test/dotty/tools/languageserver/HighlightTest.scala @@ -113,4 +113,17 @@ class HighlightTest { .highlight(m7 to m8, (m1 to m2, DocumentHighlightKind.Read), (m3 to m4, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) } + @Test def multipleImportsPerLineWithRename: Unit = { + withSources( + code"""object A { class ${m1}B${m2}; class ${m3}C${m4} } + import A.{${m5}B${m6} => ${m7}B2${m8}, ${m9}C${m10} => ${m11}C2${m12}} + class E""" + ).highlight(m1 to m2, (m1 to m2, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m3 to m4, (m3 to m4, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read)) + .highlight(m5 to m6, (m1 to m2, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m7 to m8, (m1 to m2, DocumentHighlightKind.Read), (m5 to m6, DocumentHighlightKind.Read), (m7 to m8, DocumentHighlightKind.Read)) + .highlight(m9 to m10, (m3 to m4, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read)) + .highlight(m11 to m12, (m3 to m4, DocumentHighlightKind.Read), (m9 to m10, DocumentHighlightKind.Read), (m11 to m12, DocumentHighlightKind.Read)) + } + } diff --git a/language-server/test/dotty/tools/languageserver/ReferencesTest.scala b/language-server/test/dotty/tools/languageserver/ReferencesTest.scala index 930293970a76..bfe811b2751e 100644 --- a/language-server/test/dotty/tools/languageserver/ReferencesTest.scala +++ b/language-server/test/dotty/tools/languageserver/ReferencesTest.scala @@ -327,4 +327,23 @@ class ReferencesTest { .references(m7 to m8, List(m5 to m6, m7 to m8), withDecl = false) } + @Test def multipleImportsPerLineWithRename: Unit = { + withSources( + code"""object A { class ${m1}B${m2}; class ${m3}C${m4} }""", + code"""import A.{${m5}B${m6} => ${m7}B2${m8}, ${m9}C${m10} => ${m11}C2${m12}} + class E""" + ).references(m1 to m2, List(m1 to m2, m5 to m6, m7 to m8), withDecl = true) + .references(m1 to m2, List(m5 to m6, m7 to m8), withDecl = false) + .references(m3 to m4, List(m3 to m4, m9 to m10, m11 to m12), withDecl = true) + .references(m3 to m4, List(m9 to m10, m11 to m12), withDecl = false) + .references(m5 to m6, List(m1 to m2, m5 to m6, m7 to m8), withDecl = true) + .references(m5 to m6, List(m5 to m6, m7 to m8), withDecl = false) + .references(m7 to m8, List(m1 to m2, m5 to m6, m7 to m8), withDecl = true) + .references(m7 to m8, List(m5 to m6, m7 to m8), withDecl = false) + .references(m9 to m10, List(m3 to m4, m9 to m10, m11 to m12), withDecl = true) + .references(m9 to m10, List(m9 to m10, m11 to m12), withDecl = false) + .references(m11 to m12, List(m3 to m4, m9 to m10, m11 to m12), withDecl = true) + .references(m11 to m12, List(m9 to m10, m11 to m12), withDecl = false) + } + } From 07def6c3b0c07f42f1237a527be7c24fb5460175 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 12 Oct 2018 22:25:38 +0200 Subject: [PATCH 04/21] IDE: Support imports when renaming --- .../tools/dotc/interactive/Interactive.scala | 20 +++++++++++- .../languageserver/DottyLanguageServer.scala | 9 +++--- .../tools/languageserver/RenameTest.scala | 32 +++++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index a9d7a0669316..a4ecbf72502d 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -29,6 +29,7 @@ object Interactive { val definitions: Int = 8 // include definitions val linkedClass: Int = 16 // include `symbol.linkedClass` val imports: Int = 32 // include imports in the results + val renamingImports: Int = 64 // Include renamed symbols and renaming part of imports in the results } /** Does this tree define a symbol ? */ @@ -318,6 +319,7 @@ object Interactive { (implicit ctx: Context): List[SourceNamedTree] = safely { val includeReferences = (include & Include.references) != 0 val includeImports = (include & Include.imports) != 0 + val includeRenamingImports = (include & Include.renamingImports) != 0 val buf = new mutable.ListBuffer[SourceNamedTree] def traverser(source: SourceFile) = { @@ -328,7 +330,8 @@ object Interactive { case id: untpd.Ident => importedSymbols(imp.expr, id.name).map((_, id, None)) case Thicket((id: untpd.Ident) :: (newName: untpd.Ident) :: Nil) => - importedSymbols(imp.expr, id.name).map((_, id, Some(newName))) + val renaming = if (includeRenamingImports) Some(newName) else None + importedSymbols(imp.expr, id.name).map((_, id, renaming)) } imported match { case Nil => @@ -386,9 +389,11 @@ object Interactive { val linkedSym = symbol.linkedClass val includeDeclaration = (includes & Include.definitions) != 0 val includeLinkedClass = (includes & Include.linkedClass) != 0 + val includeRenamingImports = (includes & Include.renamingImports) != 0 val predicate: NameTree => Boolean = tree => ( !tree.symbol.isPrimaryConstructor && (includeDeclaration || !Interactive.isDefinition(tree)) + && (includeRenamingImports || !isRenamed(tree)) && ( Interactive.matchSymbol(tree, symbol, includes) || ( includeDeclaration && includeLinkedClass @@ -609,4 +614,17 @@ object Interactive { myTpe = NoType } + /** + * Is this tree using a renaming introduced by an import statement? + * + * @param tree The tree to inspect + * @return True, if this tree's name is different than its symbol's name, indicating that + * it uses a renaming introduced by an import statement. + */ + private def isRenamed(tree: NameTree)(implicit ctx: Context): Boolean = { + val symbol = tree.symbol + symbol.exists && + tree.name.stripModuleClassSuffix != symbol.name.stripModuleClassSuffix + } + } diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index f2e39a0cecd2..4dc9de95dbe5 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -311,7 +311,8 @@ class DottyLanguageServer extends LanguageServer val includes = { val includeDeclaration = params.getContext.isIncludeDeclaration - Include.references | Include.overriding | Include.imports | (if (includeDeclaration) Include.definitions else 0) + Include.references | Include.overriding | Include.imports | Include.renamingImports | + (if (includeDeclaration) Include.definitions else 0) } val uriTrees = driver.openedTrees(uri) @@ -355,7 +356,7 @@ class DottyLanguageServer extends LanguageServer val syms = Interactive.enclosingSourceSymbols(path, pos) val newName = params.getNewName val includes = - Include.references | Include.definitions | Include.linkedClass | Include.overriding + Include.references | Include.definitions | Include.linkedClass | Include.overriding | Include.imports val refs = syms.flatMap { sym => val trees = driver.allTreesContaining(sym.name.sourceModuleName.toString) @@ -367,7 +368,7 @@ class DottyLanguageServer extends LanguageServer .flatMap((uriOpt, ref) => uriOpt.map(uri => (uri.toString, ref))) .mapValues(refs => refs.flatMap(ref => - range(ref.namePos, positionMapperFor(ref.source)).map(nameRange => new TextEdit(nameRange, newName))).asJava) + range(ref.namePos, positionMapperFor(ref.source)).map(nameRange => new TextEdit(nameRange, newName))).distinct.asJava) new WorkspaceEdit(changes.asJava) } @@ -381,7 +382,7 @@ class DottyLanguageServer extends LanguageServer val uriTrees = driver.openedTrees(uri) val path = Interactive.pathTo(uriTrees, pos) val syms = Interactive.enclosingSourceSymbols(path, pos) - val includes = Include.definitions | Include.references | Include.imports + val includes = Include.definitions | Include.references | Include.imports | Include.renamingImports syms.flatMap { sym => val refs = Interactive.findTreesMatching(uriTrees, includes, sym) diff --git a/language-server/test/dotty/tools/languageserver/RenameTest.scala b/language-server/test/dotty/tools/languageserver/RenameTest.scala index 199778b7352a..fa6c12804536 100644 --- a/language-server/test/dotty/tools/languageserver/RenameTest.scala +++ b/language-server/test/dotty/tools/languageserver/RenameTest.scala @@ -70,6 +70,38 @@ class RenameTest { testRenameFrom(m1) testRenameFrom(m2) + testRenameFrom(m3) + testRenameFrom(m4) + } + + @Test def renameImport: Unit = { + def testRenameFrom(m: CodeMarker) = + withSources( + code"""object A { class ${m1}C${m2} }""", + code"""import A.${m3}C${m4} + object B""" + ).rename(m, "NewName", Set(m1 to m2, m3 to m4)) + + testRenameFrom(m1) + testRenameFrom(m2) + testRenameFrom(m3) + testRenameFrom(m4) + } + + @Test def renameRenamedImport: Unit = { + def testRenameFrom(m: CodeMarker) = + withSources( + code"""object A { class ${m1}C${m2} }""", + code"""import A.{${m3}C${m4} => D} + object B { new ${m5}D${m6} }""" + ).rename(m, "NewName", Set(m1 to m2, m3 to m4)) + + testRenameFrom(m1) + testRenameFrom(m2) + testRenameFrom(m3) + testRenameFrom(m4) + testRenameFrom(m5) + testRenameFrom(m6) } } From 034c20d5e62524881c7b15c3a312c5749bffca23 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 19 Oct 2018 13:11:38 +0200 Subject: [PATCH 05/21] Style comments --- .../tools/dotc/interactive/Interactive.scala | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index a4ecbf72502d..d6fd640ed1fe 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -54,10 +54,13 @@ object Interactive { path.dropWhile(!_.symbol.exists).headOption.getOrElse(tpd.EmptyTree) /** - * The source symbol that is the closest to `path`. + * The source symbols that are the closest to `path`. * - * @param path The path to the tree whose symbol to extract. - * @return The source symbol that is the closest to `path`. + * If this path ends in an import, then this returns all the symbols that are imported by this + * import statement. + * + * @param path The path to the tree whose symbols to extract. + * @return The source symbols that are the closest to `path`. * * @see sourceSymbol */ @@ -69,7 +72,7 @@ object Interactive { if (funSym.name == StdNames.nme.copy && funSym.is(Synthetic) && funSym.owner.is(CaseClass)) { - funSym.owner.info.member(name).symbol :: Nil + List(funSym.owner.info.member(name).symbol) } else { val classTree = funSym.topLevelClass.asClass.rootTree val paramSymbol = @@ -77,12 +80,12 @@ object Interactive { DefDef(_, _, paramss, _, _) <- tpd.defPath(funSym, classTree).lastOption param <- paramss.flatten.find(_.name == name) } yield param.symbol - paramSymbol.getOrElse(fn.symbol) :: Nil + List(paramSymbol.getOrElse(fn.symbol)) } // For constructor calls, return the `` that was selected case _ :: (_: New) :: (select: Select) :: _ => - select.symbol :: Nil + List(select.symbol) case (_: Thicket) :: (imp: Import) :: _ => importedSymbols(imp, _.pos.contains(pos.pos)) @@ -91,7 +94,7 @@ object Interactive { importedSymbols(imp, _.pos.contains(pos.pos)) case _ => - enclosingTree(path).symbol :: Nil + List(enclosingTree(path).symbol) } syms.map(Interactive.sourceSymbol).filter(_.exists) @@ -598,10 +601,10 @@ object Interactive { */ private def importedSymbols(expr: tpd.Tree, name: Name)(implicit ctx: Context): List[Symbol] = { def lookup(name: Name): Symbol = expr.tpe.member(name).symbol - lookup(name.toTermName) :: - lookup(name.toTypeName) :: - lookup(name.moduleClassName) :: - lookup(name.sourceModuleName) :: Nil + List(lookup(name.toTermName), + lookup(name.toTypeName), + lookup(name.moduleClassName), + lookup(name.sourceModuleName)) } /** From fbac8a06db4416ae7d97b7a01b72f40b9e837b5e Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 19 Oct 2018 13:20:13 +0200 Subject: [PATCH 06/21] Get rid of `RenameTree` --- .../tools/dotc/interactive/Interactive.scala | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index d6fd640ed1fe..f062ed6c7dc9 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -343,8 +343,11 @@ object Interactive { syms.foreach { case (sym, name, rename) => val tree = tpd.Select(imp.expr, sym.name).withPos(name.pos) val renameTree = rename.map { r => + // Get the type of the symbol that is actually selected, and construct a select + // node with the new name and the type of the real symbol. val name = if (sym.name.isTypeName) r.name.toTypeName else r.name - RenameTree(name, tpd.Select(imp.expr, sym.name)).withPos(r.pos) + val actual = tpd.Select(imp.expr, sym.name) + tpd.Select(imp.expr, name).withPos(r.pos).withType(actual.tpe) } renameTree.foreach(traverse) traverse(tree) @@ -607,16 +610,6 @@ object Interactive { lookup(name.sourceModuleName)) } - /** - * Used to represent a renaming import `{foo => bar}`. - * We need this because the name of the tree must be the new name, but the - * denotation must be that of the importee. - */ - private case class RenameTree(name: Name, underlying: Tree) extends NameTree { - override def denot(implicit ctx: Context) = underlying.denot - myTpe = NoType - } - /** * Is this tree using a renaming introduced by an import statement? * From 0cd46a1ba4c7729e6ec23c1b573583481bbf003c Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 19 Oct 2018 19:06:35 +0200 Subject: [PATCH 07/21] IDE: Support renaming renaming imports --- .../tools/dotc/interactive/Interactive.scala | 128 +++++++++++++++++- .../languageserver/DottyLanguageServer.scala | 38 +++++- .../tools/languageserver/RenameTest.scala | 106 +++++++++++++-- .../tools/languageserver/util/Code.scala | 2 + 4 files changed, 255 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index f062ed6c7dc9..d97fba65709d 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -617,10 +617,132 @@ object Interactive { * @return True, if this tree's name is different than its symbol's name, indicating that * it uses a renaming introduced by an import statement. */ - private def isRenamed(tree: NameTree)(implicit ctx: Context): Boolean = { + def isRenamed(tree: NameTree)(implicit ctx: Context): Boolean = { val symbol = tree.symbol - symbol.exists && - tree.name.stripModuleClassSuffix != symbol.name.stripModuleClassSuffix + symbol.exists && !sameName(tree.name, symbol.name) + } + + /** Are the two names the same? */ + def sameName(n0: Name, n1: Name): Boolean = { + n0.stripModuleClassSuffix.toString == n1.stripModuleClassSuffix.toString + } + + /** + * Is this tree immediately enclosing an import that renames a symbol to `toName`? + * + * @param toName The target name to check + * @param tree The tree to check + * @return True if this tree immediately encloses an import that renames a symbol to `toName`, + * false otherwise. + */ + def immediatelyEnclosesRenaming(toName: Name, inTree: Tree)(implicit ctx: Context): Boolean = { + def isImportRenaming(tree: Tree): Boolean = { + tree match { + case Import(_, selectors) => + selectors.exists { + case Thicket(_ :: Ident(rename) :: Nil) => + rename.stripModuleClassSuffix.toString == toName.stripModuleClassSuffix.toString + case _ => + false + } + case _ => + false + } + } + + inTree match { + case PackageDef(_, stats) => + stats.exists(isImportRenaming) + case template: Template => + template.body.exists(isImportRenaming) + case Block(stats, _) => + stats.exists(isImportRenaming) + case _ => + false + } + } + + /** + * In `enclosing`, find all the references to any of `syms` that have been renamed to `toName`. + * + * If `enclosing` is empty, it means the renaming import was top-level and the whole source file + * should be considered. Otherwise, we can restrict the search to this tree because renaming + * imports are local. + * + * @param toName The name that is set by the renaming. + * @param enclosing The tree that encloses the renaming import, if it exists. + * @param syms The symbols to which we want to find renamed references. + * @param allTrees All the trees in this source file, in case we can't find `enclosing`. + * @param source The sourcefile that where to look for references. + * @return All the references to the symbol under the cursor that are using `toName`. + */ + def findTreesMatchingRenaming(toName: Name, + enclosing: Option[Tree], + syms: List[Symbol], + allTrees: List[Tree], + source: SourceFile + )(implicit ctx: Context): List[SourceNamedTree] = { + + /** + * Remove the blocks that immediately enclose a renaming to `toName` in `inTree`. + * + * @param toName The target name of renamings. + * @param inTree The tree in which to remove the blocks that have such a renaming. + * @return A tree that has no children containing a renaming to `toName`. + */ + def removeBlockWithRenaming(toName: Name, inTree: Tree): Tree = { + new TreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case pkg: PackageDef if immediatelyEnclosesRenaming(toName, pkg) => + EmptyTree + case template: Template if immediatelyEnclosesRenaming(toName, template) => + cpy.Template(template)(constr = DefDef(template.constr.symbol.asTerm), self = EmptyValDef, body = Nil) + case block @ Block(stats, expr) if immediatelyEnclosesRenaming(toName, block) => + EmptyTree + case other => + super.transform(other) + } + }.transform(inTree) + } + + val trees = { + val trees = enclosing match { + case Some(pkg: PackageDef) => + pkg.stats + case Some(template: Template) => + template.body + case Some(block: Block) => + block.expr :: block.stats + case _ => + // No enclosing tree; we'll search in the whole file. + allTrees + } + + // These trees may contain a new renaming of the same symbol to the same name, so we may + // have to cut some branches + val trimmedTrees = trees.map(removeBlockWithRenaming(toName, _)) + + // Some of these trees may not be `NameTrees`. Those that are not are wrapped in a + // synthetic val def, so that everything can go inside `SourceNamedTree`s. + trimmedTrees.map { + case tree: NameTree => + SourceNamedTree(tree, source) + case tree => + val valDef = tpd.SyntheticValDef(NameKinds.UniqueName.fresh(), tree) + SourceNamedTree(valDef, source) + } + } + + val includes = + Include.references | Include.imports | Include.renamingImports + + syms.flatMap { sym => + Interactive.namedTrees(trees, + includes, + tree => + Interactive.sameName(tree.name, toName) && + (Interactive.matchSymbol(tree, sym, includes) || Interactive.matchSymbol(tree, sym.linkedClass, includes))) + } } } diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 4dc9de95dbe5..90766343c751 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -355,13 +355,39 @@ class DottyLanguageServer extends LanguageServer val path = Interactive.pathTo(uriTrees, pos) val syms = Interactive.enclosingSourceSymbols(path, pos) val newName = params.getNewName - val includes = - Include.references | Include.definitions | Include.linkedClass | Include.overriding | Include.imports - val refs = syms.flatMap { sym => - val trees = driver.allTreesContaining(sym.name.sourceModuleName.toString) - Interactive.findTreesMatching(trees, includes, sym) - } + val refs = + path match { + // Selected a renaming in an import node + case Thicket(_ :: (rename: Ident) :: Nil) :: (_: Import) :: rest if rename.pos.contains(pos.pos) => + val allTrees = uriTrees.map(_.tree) + val source = uriTrees.head.source + Interactive.findTreesMatchingRenaming(rename.name, rest.headOption, syms, allTrees, source) + + // Selected a reference that has been renamed + case (nameTree: NameTree) :: rest if Interactive.isRenamed(nameTree) => + val enclosing = rest.find { + // If we selected one of the parents of this Template for doing the renaming, then this + // Template cannot immediately enclose the rename we're interesting in (the renaming + // happening inside its body cannot be used on the parents). + case template: Template if template.parents.exists(_.pos.contains(pos.pos)) => + false + case tree => + Interactive.immediatelyEnclosesRenaming(nameTree.name, tree) + } + val allTrees = uriTrees.map(_.tree) + val source = uriTrees.head.source + Interactive.findTreesMatchingRenaming(nameTree.name, enclosing, syms, allTrees, source) + + case _ => + val includes = + Include.references | Include.definitions | Include.linkedClass | Include.overriding | Include.imports + + syms.flatMap { sym => + val trees = driver.allTreesContaining(sym.name.sourceModuleName.toString) + Interactive.findTreesMatching(trees, includes, sym) + } + } val changes = refs.groupBy(ref => toUriOption(ref.source)) diff --git a/language-server/test/dotty/tools/languageserver/RenameTest.scala b/language-server/test/dotty/tools/languageserver/RenameTest.scala index fa6c12804536..32ea4fe8d2f0 100644 --- a/language-server/test/dotty/tools/languageserver/RenameTest.scala +++ b/language-server/test/dotty/tools/languageserver/RenameTest.scala @@ -3,6 +3,7 @@ package dotty.tools.languageserver import org.junit.Test import dotty.tools.languageserver.util.Code._ +import dotty.tools.languageserver.util.CodeRange import dotty.tools.languageserver.util.embedded.CodeMarker class RenameTest { @@ -89,19 +90,104 @@ class RenameTest { } @Test def renameRenamedImport: Unit = { - def testRenameFrom(m: CodeMarker) = + def sources = withSources( code"""object A { class ${m1}C${m2} }""", - code"""import A.{${m3}C${m4} => D} - object B { new ${m5}D${m6} }""" - ).rename(m, "NewName", Set(m1 to m2, m3 to m4)) + code"""import A.{${m3}C${m4} => ${m5}D${m6}} + object B { new ${m7}D${m8} }""" + ) + def testRename(m: CodeMarker, expectations: Set[CodeRange]) = + sources.rename(m, "NewName", expectations) + + testRename(m1, Set(m1 to m2, m3 to m4)) + testRename(m2, Set(m1 to m2, m3 to m4)) + testRename(m3, Set(m1 to m2, m3 to m4)) + testRename(m4, Set(m1 to m2, m3 to m4)) + testRename(m5, Set(m5 to m6, m7 to m8)) + testRename(m6, Set(m5 to m6, m7 to m8)) + testRename(m7, Set(m5 to m6, m7 to m8)) + testRename(m8, Set(m5 to m6, m7 to m8)) + } - testRenameFrom(m1) - testRenameFrom(m2) - testRenameFrom(m3) - testRenameFrom(m4) - testRenameFrom(m5) - testRenameFrom(m6) + @Test def renameRenamingImport: Unit = { + def sources = + withSources( + code"""object A { class ${m1}C${m2}; object ${m3}C${m4} }""", + code"""object O1 { + import A.{${m5}C${m6} => ${m7}Renamed${m8}} + class C2 extends ${m9}Renamed${m10} { val x = ${m11}Renamed${m12} } + } + object O2 { + import A.{${m13}C${m14} => ${m15}Renamed${m16}} + class C3 extends ${m17}Renamed${m18} { val x = ${m19}Renamed${m20} } + }""" + ) + def testRename(m: CodeMarker, expectations: Set[CodeRange]) = + sources.rename(m, "NewName", expectations) + + testRename(m1, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) + testRename(m2, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) + testRename(m3, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) + testRename(m4, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) + testRename(m5, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) + testRename(m6, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) + + testRename(m7, Set(m7 to m8, m9 to m10, m11 to m12)) + testRename(m8, Set(m7 to m8, m9 to m10, m11 to m12)) + testRename(m9, Set(m7 to m8, m9 to m10, m11 to m12)) + testRename(m10, Set(m7 to m8, m9 to m10, m11 to m12)) + testRename(m11, Set(m7 to m8, m9 to m10, m11 to m12)) + testRename(m12, Set(m7 to m8, m9 to m10, m11 to m12)) + + testRename(m13, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) + testRename(m14, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) + + testRename(m15, Set(m15 to m16, m17 to m18, m19 to m20)) + testRename(m16, Set(m15 to m16, m17 to m18, m19 to m20)) + testRename(m17, Set(m15 to m16, m17 to m18, m19 to m20)) + testRename(m18, Set(m15 to m16, m17 to m18, m19 to m20)) + testRename(m19, Set(m15 to m16, m17 to m18, m19 to m20)) + testRename(m20, Set(m15 to m16, m17 to m18, m19 to m20)) + + } + + @Test def renameRenamingImportNested: Unit = { + def sources = + withSources( + code"""object A { class C }""", + code"""import A.{C => ${m1}Renamed${m2}} + object O { + import A.{C => ${m3}Renamed${m4}} + class C2 extends ${m5}Renamed${m6} { + import A.{C => ${m7}Renamed${m8}} + } + 123 match { + case x if new ${m9}Renamed${m10} == null => ??? + case foo if { + import A.{C => ${m11}Renamed${m12}} + new ${m13}Renamed${m14} != null + } => ??? + } + new A.C + }""" + ) + def testRename(m: CodeMarker, expectations: Set[CodeRange]) = + sources.rename(m, "NewName", expectations) + + testRename(m1, Set(m1 to m2)) + testRename(m2, Set(m1 to m2)) + testRename(m3, Set(m3 to m4, m5 to m6, m9 to m10)) + testRename(m4, Set(m3 to m4, m5 to m6, m9 to m10)) + testRename(m5, Set(m3 to m4, m5 to m6, m9 to m10)) + testRename(m6, Set(m3 to m4, m5 to m6, m9 to m10)) + testRename(m7, Set(m7 to m8)) + testRename(m8, Set(m7 to m8)) + testRename(m9, Set(m3 to m4, m5 to m6, m9 to m10)) + testRename(m10, Set(m3 to m4, m5 to m6, m9 to m10)) + testRename(m11, Set(m11 to m12, m13 to m14)) + testRename(m12, Set(m11 to m12, m13 to m14)) + testRename(m13, Set(m11 to m12, m13 to m14)) + testRename(m14, Set(m11 to m12, m13 to m14)) } } diff --git a/language-server/test/dotty/tools/languageserver/util/Code.scala b/language-server/test/dotty/tools/languageserver/util/Code.scala index 81edf2c322d9..c5a742d21fa5 100644 --- a/language-server/test/dotty/tools/languageserver/util/Code.scala +++ b/language-server/test/dotty/tools/languageserver/util/Code.scala @@ -30,6 +30,8 @@ object Code { val m16 = new CodeMarker("m16") val m17 = new CodeMarker("m17") val m18 = new CodeMarker("m18") + val m19 = new CodeMarker("m19") + val m20 = new CodeMarker("m20") implicit class CodeHelper(val sc: StringContext) extends AnyVal { From 59945afc7747e09d08eab02dcc2c923e0b8e507d Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 29 Oct 2018 08:30:53 +0100 Subject: [PATCH 08/21] IDE: Support rename on renamed self types --- .../tools/dotc/interactive/Interactive.scala | 2 +- .../languageserver/DottyLanguageServer.scala | 8 ++++---- .../dotty/tools/languageserver/RenameTest.scala | 16 +++++++++------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index d97fba65709d..907d1349db4f 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -696,7 +696,7 @@ object Interactive { case pkg: PackageDef if immediatelyEnclosesRenaming(toName, pkg) => EmptyTree case template: Template if immediatelyEnclosesRenaming(toName, template) => - cpy.Template(template)(constr = DefDef(template.constr.symbol.asTerm), self = EmptyValDef, body = Nil) + cpy.Template(template)(constr = DefDef(template.constr.symbol.asTerm), self = template.self, body = Nil) case block @ Block(stats, expr) if immediatelyEnclosesRenaming(toName, block) => EmptyTree case other => diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 90766343c751..93b93f88003f 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -367,10 +367,10 @@ class DottyLanguageServer extends LanguageServer // Selected a reference that has been renamed case (nameTree: NameTree) :: rest if Interactive.isRenamed(nameTree) => val enclosing = rest.find { - // If we selected one of the parents of this Template for doing the renaming, then this - // Template cannot immediately enclose the rename we're interesting in (the renaming - // happening inside its body cannot be used on the parents). - case template: Template if template.parents.exists(_.pos.contains(pos.pos)) => + // If we selected one of the parents or the selftype of this Template for doing the + // renaming, then this Template cannot immediately enclose the rename we're interesting + // in (the renaming happening inside its body cannot be used on the parents or selftype). + case template: Template if template.parents.exists(_.pos.contains(pos.pos)) || template.self.pos.contains(pos.pos) => false case tree => Interactive.immediatelyEnclosesRenaming(nameTree.name, tree) diff --git a/language-server/test/dotty/tools/languageserver/RenameTest.scala b/language-server/test/dotty/tools/languageserver/RenameTest.scala index 32ea4fe8d2f0..8ab22168d4de 100644 --- a/language-server/test/dotty/tools/languageserver/RenameTest.scala +++ b/language-server/test/dotty/tools/languageserver/RenameTest.scala @@ -158,7 +158,7 @@ class RenameTest { code"""import A.{C => ${m1}Renamed${m2}} object O { import A.{C => ${m3}Renamed${m4}} - class C2 extends ${m5}Renamed${m6} { + class C2 extends ${m5}Renamed${m6} { self: ${m15}Renamed${m16} => import A.{C => ${m7}Renamed${m8}} } 123 match { @@ -176,18 +176,20 @@ class RenameTest { testRename(m1, Set(m1 to m2)) testRename(m2, Set(m1 to m2)) - testRename(m3, Set(m3 to m4, m5 to m6, m9 to m10)) - testRename(m4, Set(m3 to m4, m5 to m6, m9 to m10)) - testRename(m5, Set(m3 to m4, m5 to m6, m9 to m10)) - testRename(m6, Set(m3 to m4, m5 to m6, m9 to m10)) + testRename(m3, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) + testRename(m4, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) + testRename(m5, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) + testRename(m6, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) testRename(m7, Set(m7 to m8)) testRename(m8, Set(m7 to m8)) - testRename(m9, Set(m3 to m4, m5 to m6, m9 to m10)) - testRename(m10, Set(m3 to m4, m5 to m6, m9 to m10)) + testRename(m9, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) + testRename(m10, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) testRename(m11, Set(m11 to m12, m13 to m14)) testRename(m12, Set(m11 to m12, m13 to m14)) testRename(m13, Set(m11 to m12, m13 to m14)) testRename(m14, Set(m11 to m12, m13 to m14)) + testRename(m15, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) + testRename(m16, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) } } From b2d8c9a3042fc720fed385d8d24851495071cd6d Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 31 Oct 2018 13:54:09 +0100 Subject: [PATCH 09/21] Address review comments --- .../tools/dotc/interactive/Interactive.scala | 18 +++++++-------- .../tools/languageserver/RenameTest.scala | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 907d1349db4f..ad1c21477ac0 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -624,7 +624,7 @@ object Interactive { /** Are the two names the same? */ def sameName(n0: Name, n1: Name): Boolean = { - n0.stripModuleClassSuffix.toString == n1.stripModuleClassSuffix.toString + n0.stripModuleClassSuffix.toTermName eq n1.stripModuleClassSuffix.toTermName } /** @@ -640,13 +640,11 @@ object Interactive { tree match { case Import(_, selectors) => selectors.exists { - case Thicket(_ :: Ident(rename) :: Nil) => - rename.stripModuleClassSuffix.toString == toName.stripModuleClassSuffix.toString - case _ => - false + case Thicket(_ :: Ident(rename) :: Nil) => sameName(rename, toName) + case _ => false } - case _ => - false + case _ => + false } } @@ -696,7 +694,7 @@ object Interactive { case pkg: PackageDef if immediatelyEnclosesRenaming(toName, pkg) => EmptyTree case template: Template if immediatelyEnclosesRenaming(toName, template) => - cpy.Template(template)(constr = DefDef(template.constr.symbol.asTerm), self = template.self, body = Nil) + cpy.Template(template)(constr = DefDef(template.constr.symbol.asTerm), body = Nil) case block @ Block(stats, expr) if immediatelyEnclosesRenaming(toName, block) => EmptyTree case other => @@ -706,7 +704,7 @@ object Interactive { } val trees = { - val trees = enclosing match { + val enclosedTrees = enclosing match { case Some(pkg: PackageDef) => pkg.stats case Some(template: Template) => @@ -720,7 +718,7 @@ object Interactive { // These trees may contain a new renaming of the same symbol to the same name, so we may // have to cut some branches - val trimmedTrees = trees.map(removeBlockWithRenaming(toName, _)) + val trimmedTrees = enclosedTrees.map(removeBlockWithRenaming(toName, _)) // Some of these trees may not be `NameTrees`. Those that are not are wrapped in a // synthetic val def, so that everything can go inside `SourceNamedTree`s. diff --git a/language-server/test/dotty/tools/languageserver/RenameTest.scala b/language-server/test/dotty/tools/languageserver/RenameTest.scala index 8ab22168d4de..f5996701b86b 100644 --- a/language-server/test/dotty/tools/languageserver/RenameTest.scala +++ b/language-server/test/dotty/tools/languageserver/RenameTest.scala @@ -192,4 +192,27 @@ class RenameTest { testRename(m16, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) } + @Test def renameImportWithRenaming: Unit = { + def testRename(m: CodeMarker) = + withSources( + code"""object A { class ${m1}C${m2} }""", + code"""import A.${m3}C${m4} + object O { + class B extends ${m5}C${m6} { + import A.{${m7}C${m8} => Renamed} + def foo = new Renamed + } + }""" + ).rename(m, "NewName", Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8)) + + testRename(m1) + testRename(m2) + testRename(m3) + testRename(m4) + testRename(m5) + testRename(m6) + testRename(m7) + testRename(m8) + } + } From b7ce60a47444382b368b20679316c6a1dafa73f5 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 31 Oct 2018 14:03:28 +0100 Subject: [PATCH 10/21] Be less precise about renaming and imports This commit makes the renaming less precise for the (probably rare) case of an imports being imported multiple times with the same renaming. When doing a renaming of a symbol that is import-renamed, we would exclude subscope where the same symbol was imported again with the same name. This commit changes the logic so that we no longer exclude those subscope, and keep renaming. See RenameTest#renameRenamingImportNested for an example where the langauge server will now behave differently. --- .../tools/dotc/interactive/Interactive.scala | 28 +------------------ .../tools/languageserver/RenameTest.scala | 20 ++++++------- 2 files changed, 11 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index ad1c21477ac0..1dc1ce815a4d 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -681,28 +681,6 @@ object Interactive { source: SourceFile )(implicit ctx: Context): List[SourceNamedTree] = { - /** - * Remove the blocks that immediately enclose a renaming to `toName` in `inTree`. - * - * @param toName The target name of renamings. - * @param inTree The tree in which to remove the blocks that have such a renaming. - * @return A tree that has no children containing a renaming to `toName`. - */ - def removeBlockWithRenaming(toName: Name, inTree: Tree): Tree = { - new TreeMap { - override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case pkg: PackageDef if immediatelyEnclosesRenaming(toName, pkg) => - EmptyTree - case template: Template if immediatelyEnclosesRenaming(toName, template) => - cpy.Template(template)(constr = DefDef(template.constr.symbol.asTerm), body = Nil) - case block @ Block(stats, expr) if immediatelyEnclosesRenaming(toName, block) => - EmptyTree - case other => - super.transform(other) - } - }.transform(inTree) - } - val trees = { val enclosedTrees = enclosing match { case Some(pkg: PackageDef) => @@ -716,13 +694,9 @@ object Interactive { allTrees } - // These trees may contain a new renaming of the same symbol to the same name, so we may - // have to cut some branches - val trimmedTrees = enclosedTrees.map(removeBlockWithRenaming(toName, _)) - // Some of these trees may not be `NameTrees`. Those that are not are wrapped in a // synthetic val def, so that everything can go inside `SourceNamedTree`s. - trimmedTrees.map { + enclosedTrees.map { case tree: NameTree => SourceNamedTree(tree, source) case tree => diff --git a/language-server/test/dotty/tools/languageserver/RenameTest.scala b/language-server/test/dotty/tools/languageserver/RenameTest.scala index f5996701b86b..cc10ea21830e 100644 --- a/language-server/test/dotty/tools/languageserver/RenameTest.scala +++ b/language-server/test/dotty/tools/languageserver/RenameTest.scala @@ -174,22 +174,22 @@ class RenameTest { def testRename(m: CodeMarker, expectations: Set[CodeRange]) = sources.rename(m, "NewName", expectations) - testRename(m1, Set(m1 to m2)) - testRename(m2, Set(m1 to m2)) - testRename(m3, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) - testRename(m4, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) - testRename(m5, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) - testRename(m6, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) + testRename(m1, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m2, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m3, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m4, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m5, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m6, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) testRename(m7, Set(m7 to m8)) testRename(m8, Set(m7 to m8)) - testRename(m9, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) - testRename(m10, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) + testRename(m9, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m10, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) testRename(m11, Set(m11 to m12, m13 to m14)) testRename(m12, Set(m11 to m12, m13 to m14)) testRename(m13, Set(m11 to m12, m13 to m14)) testRename(m14, Set(m11 to m12, m13 to m14)) - testRename(m15, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) - testRename(m16, Set(m3 to m4, m5 to m6, m9 to m10, m15 to m16)) + testRename(m15, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m16, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) } @Test def renameImportWithRenaming: Unit = { From ab94c995c209918d6da88a57d732c0da7a1f45ff Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 31 Oct 2018 16:59:17 +0100 Subject: [PATCH 11/21] Be even less precise about renaming with imports Renaming a symbol that has been import-renamed will now rename all occurrences of that symbol with the new name in the whole file, regardless of scope. --- .../tools/dotc/interactive/Interactive.scala | 28 +--------- .../languageserver/DottyLanguageServer.scala | 17 +----- .../tools/languageserver/RenameTest.scala | 52 +++++++++---------- 3 files changed, 29 insertions(+), 68 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 1dc1ce815a4d..c4a3b5fd0ca6 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -675,36 +675,10 @@ object Interactive { * @return All the references to the symbol under the cursor that are using `toName`. */ def findTreesMatchingRenaming(toName: Name, - enclosing: Option[Tree], syms: List[Symbol], - allTrees: List[Tree], - source: SourceFile + trees: List[SourceTree] )(implicit ctx: Context): List[SourceNamedTree] = { - val trees = { - val enclosedTrees = enclosing match { - case Some(pkg: PackageDef) => - pkg.stats - case Some(template: Template) => - template.body - case Some(block: Block) => - block.expr :: block.stats - case _ => - // No enclosing tree; we'll search in the whole file. - allTrees - } - - // Some of these trees may not be `NameTrees`. Those that are not are wrapped in a - // synthetic val def, so that everything can go inside `SourceNamedTree`s. - enclosedTrees.map { - case tree: NameTree => - SourceNamedTree(tree, source) - case tree => - val valDef = tpd.SyntheticValDef(NameKinds.UniqueName.fresh(), tree) - SourceNamedTree(valDef, source) - } - } - val includes = Include.references | Include.imports | Include.renamingImports diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 93b93f88003f..f25b7094cdfe 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -360,24 +360,11 @@ class DottyLanguageServer extends LanguageServer path match { // Selected a renaming in an import node case Thicket(_ :: (rename: Ident) :: Nil) :: (_: Import) :: rest if rename.pos.contains(pos.pos) => - val allTrees = uriTrees.map(_.tree) - val source = uriTrees.head.source - Interactive.findTreesMatchingRenaming(rename.name, rest.headOption, syms, allTrees, source) + Interactive.findTreesMatchingRenaming(rename.name, syms, uriTrees) // Selected a reference that has been renamed case (nameTree: NameTree) :: rest if Interactive.isRenamed(nameTree) => - val enclosing = rest.find { - // If we selected one of the parents or the selftype of this Template for doing the - // renaming, then this Template cannot immediately enclose the rename we're interesting - // in (the renaming happening inside its body cannot be used on the parents or selftype). - case template: Template if template.parents.exists(_.pos.contains(pos.pos)) || template.self.pos.contains(pos.pos) => - false - case tree => - Interactive.immediatelyEnclosesRenaming(nameTree.name, tree) - } - val allTrees = uriTrees.map(_.tree) - val source = uriTrees.head.source - Interactive.findTreesMatchingRenaming(nameTree.name, enclosing, syms, allTrees, source) + Interactive.findTreesMatchingRenaming(nameTree.name, syms, uriTrees) case _ => val includes = diff --git a/language-server/test/dotty/tools/languageserver/RenameTest.scala b/language-server/test/dotty/tools/languageserver/RenameTest.scala index cc10ea21830e..e0c46748b0dd 100644 --- a/language-server/test/dotty/tools/languageserver/RenameTest.scala +++ b/language-server/test/dotty/tools/languageserver/RenameTest.scala @@ -132,22 +132,22 @@ class RenameTest { testRename(m5, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) testRename(m6, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) - testRename(m7, Set(m7 to m8, m9 to m10, m11 to m12)) - testRename(m8, Set(m7 to m8, m9 to m10, m11 to m12)) - testRename(m9, Set(m7 to m8, m9 to m10, m11 to m12)) - testRename(m10, Set(m7 to m8, m9 to m10, m11 to m12)) - testRename(m11, Set(m7 to m8, m9 to m10, m11 to m12)) - testRename(m12, Set(m7 to m8, m9 to m10, m11 to m12)) + testRename(m7, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) + testRename(m8, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) + testRename(m9, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) + testRename(m10, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) + testRename(m11, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) + testRename(m12, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) testRename(m13, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) testRename(m14, Set(m1 to m2, m3 to m4, m5 to m6, m13 to m14)) - testRename(m15, Set(m15 to m16, m17 to m18, m19 to m20)) - testRename(m16, Set(m15 to m16, m17 to m18, m19 to m20)) - testRename(m17, Set(m15 to m16, m17 to m18, m19 to m20)) - testRename(m18, Set(m15 to m16, m17 to m18, m19 to m20)) - testRename(m19, Set(m15 to m16, m17 to m18, m19 to m20)) - testRename(m20, Set(m15 to m16, m17 to m18, m19 to m20)) + testRename(m15, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) + testRename(m16, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) + testRename(m17, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) + testRename(m18, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) + testRename(m19, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) + testRename(m20, Set(m7 to m8, m9 to m10, m11 to m12, m15 to m16, m17 to m18, m19 to m20)) } @@ -176,20 +176,20 @@ class RenameTest { testRename(m1, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) testRename(m2, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) - testRename(m3, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) - testRename(m4, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) - testRename(m5, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) - testRename(m6, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) - testRename(m7, Set(m7 to m8)) - testRename(m8, Set(m7 to m8)) - testRename(m9, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) - testRename(m10, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) - testRename(m11, Set(m11 to m12, m13 to m14)) - testRename(m12, Set(m11 to m12, m13 to m14)) - testRename(m13, Set(m11 to m12, m13 to m14)) - testRename(m14, Set(m11 to m12, m13 to m14)) - testRename(m15, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) - testRename(m16, Set(m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m3, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m4, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m5, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m6, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m7, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m8, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m9, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m10, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m11, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m12, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m13, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m14, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m15, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) + testRename(m16, Set(m1 to m2, m3 to m4, m5 to m6, m7 to m8, m9 to m10, m11 to m12, m13 to m14, m15 to m16)) } @Test def renameImportWithRenaming: Unit = { From 653756b9b5cdcfc9137b080fd019a00f9434e0cd Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 9 Nov 2018 10:54:27 +0100 Subject: [PATCH 12/21] Move helpers for handling imports to tpd.scala --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 60 ++++++++++++ .../src/dotty/tools/dotc/core/Symbols.scala | 20 ++++ .../tools/dotc/interactive/Interactive.scala | 95 ++----------------- .../languageserver/DottyLanguageServer.scala | 13 ++- 4 files changed, 94 insertions(+), 94 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index e30180edd145..75d23e499fc9 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1147,5 +1147,65 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { case _ => EmptyTree } + + /** + * The symbols that are imported with `expr.name` + * + * @param expr The base of the import statement + * @param name The name that is being imported. + * @return All the symbols that would be imported with `expr.name`. + */ + def importedSymbols(expr: Tree, name: Name)(implicit ctx: Context): List[Symbol] = { + def lookup(name: Name): Symbol = expr.tpe.member(name).symbol + List(lookup(name.toTermName), + lookup(name.toTypeName), + lookup(name.moduleClassName), + lookup(name.sourceModuleName)) + } + + /** + * All the symbols that are imported by the first selector of `imp` that matches + * `selectorPredicate`. + * + * @param imp The import statement to analyze + * @param selectorPredicate A test to find the selector to use. + * @return The symbols imported. + */ + def importedSymbols(imp: Import, + selectorPredicate: untpd.Tree => Boolean = util.common.alwaysTrue) + (implicit ctx: Context): List[Symbol] = { + val symbols = imp.selectors.find(selectorPredicate) match { + case Some(id: untpd.Ident) => + importedSymbols(imp.expr, id.name) + case Some(Thicket((id: untpd.Ident) :: (_: untpd.Ident) :: Nil)) => + importedSymbols(imp.expr, id.name) + case _ => + Nil + } + + symbols.map(_.sourceSymbol).filter(_.exists).distinct + } + + def importSelections(imp: Import)(implicit ctx: Context): List[Select] = { + val imported = + imp.selectors.flatMap { + case id: untpd.Ident => + importedSymbols(imp.expr, id.name).map((_, id, None)) + case Thicket((id: untpd.Ident) :: (newName: untpd.Ident) :: Nil) => + val renaming = Some(newName) + importedSymbols(imp.expr, id.name).map((_, id, renaming)) + } + imported.flatMap { case (symbol, name, rename) => + val tree = Select(imp.expr, symbol.name).withPos(name.pos) + val renameTree = rename.map { r => + // Get the type of the symbol that is actually selected, and construct a select + // node with the new name and the type of the real symbol. + val name = if (symbol.name.isTypeName) r.name.toTypeName else r.name + val actual = Select(imp.expr, symbol.name) + Select(imp.expr, name).withPos(r.pos).withType(actual.tpe) + } + tree :: renameTree.toList + } + } } diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index dda067d926a9..d6b6ed1efd1c 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -624,6 +624,26 @@ object Symbols { } } + /** A symbol related to `sym` that is defined in source code. + * + * @see enclosingSourceSymbols + */ + @annotation.tailrec final def sourceSymbol(implicit ctx: Context): Symbol = + if (!denot.exists) + this + else if (denot.is(ModuleVal)) + this.moduleClass.sourceSymbol // The module val always has a zero-extent position + else if (denot.is(Synthetic)) { + val linked = denot.linkedClass + if (linked.exists && !linked.is(Synthetic)) + linked + else + denot.owner.sourceSymbol + } + else if (denot.isPrimaryConstructor) + denot.owner.sourceSymbol + else this + /** The position of this symbol, or NoPosition if the symbol was not loaded * from source or from TASTY. This is always a zero-extent position. * diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index c4a3b5fd0ca6..40454cfa03ac 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -97,29 +97,9 @@ object Interactive { List(enclosingTree(path).symbol) } - syms.map(Interactive.sourceSymbol).filter(_.exists) + syms.map(_.sourceSymbol).filter(_.exists) } - /** A symbol related to `sym` that is defined in source code. - * - * @see enclosingSourceSymbols - */ - @tailrec def sourceSymbol(sym: Symbol)(implicit ctx: Context): Symbol = - if (!sym.exists) - sym - else if (sym.is(ModuleVal)) - sourceSymbol(sym.moduleClass) // The module val always has a zero-extent position - else if (sym.is(Synthetic)) { - val linked = sym.linkedClass - if (linked.exists && !linked.is(Synthetic)) - linked - else - sourceSymbol(sym.owner) - } - else if (sym.isPrimaryConstructor) - sourceSymbol(sym.owner) - else sym - /** Check if `tree` matches `sym`. * This is the case if the symbol defined by `tree` equals `sym`, * or the source symbol of tree equals sym, @@ -132,7 +112,7 @@ object Interactive { sym1.owner.derivesFrom(sym2.owner) && sym1.overriddenSymbol(sym2.owner.asClass) == sym2 ( sym == tree.symbol - || sym.exists && sym == sourceSymbol(tree.symbol) + || sym.exists && sym == tree.symbol.sourceSymbol || include != 0 && sym.name == tree.symbol.name && sym.maybeOwner != tree.symbol.maybeOwner && ( (include & Include.overridden) != 0 && overrides(sym, tree.symbol) || (include & Include.overriding) != 0 && overrides(tree.symbol, sym) @@ -327,37 +307,12 @@ object Interactive { def traverser(source: SourceFile) = { new untpd.TreeTraverser { - private def handleImport(imp: tpd.Import): Unit = { - val imported = - imp.selectors.flatMap { - case id: untpd.Ident => - importedSymbols(imp.expr, id.name).map((_, id, None)) - case Thicket((id: untpd.Ident) :: (newName: untpd.Ident) :: Nil) => - val renaming = if (includeRenamingImports) Some(newName) else None - importedSymbols(imp.expr, id.name).map((_, id, renaming)) - } - imported match { - case Nil => - traverse(imp.expr) - case syms => - syms.foreach { case (sym, name, rename) => - val tree = tpd.Select(imp.expr, sym.name).withPos(name.pos) - val renameTree = rename.map { r => - // Get the type of the symbol that is actually selected, and construct a select - // node with the new name and the type of the real symbol. - val name = if (sym.name.isTypeName) r.name.toTypeName else r.name - val actual = tpd.Select(imp.expr, sym.name) - tpd.Select(imp.expr, name).withPos(r.pos).withType(actual.tpe) - } - renameTree.foreach(traverse) - traverse(tree) - } - } - } override def traverse(tree: untpd.Tree)(implicit ctx: Context) = { tree match { - case imp: untpd.Import if includeImports => - handleImport(imp.asInstanceOf[tpd.Import]) + case imp: untpd.Import if includeImports && tree.hasType => + val tree = imp.asInstanceOf[tpd.Import] + val selections = tpd.importSelections(tree) + selections.foreach(traverse) case utree: untpd.NameTree if tree.hasType => val tree = utree.asInstanceOf[tpd.NameTree] if (tree.symbol.exists @@ -572,44 +527,6 @@ object Interactive { } } - /** - * All the symbols that are imported by import statement `imp`, if it matches - * the predicate `selectorPredicate`. - * - * @param imp The import statement to analyze - * @param selectorPredicate A test to find the selector to use. - * @return The symbols imported. - */ - private def importedSymbols(imp: tpd.Import, - selectorPredicate: untpd.Tree => Boolean = util.common.alwaysTrue) - (implicit ctx: Context): List[Symbol] = { - val symbols = imp.selectors.find(selectorPredicate) match { - case Some(id: untpd.Ident) => - importedSymbols(imp.expr, id.name) - case Some(Thicket((id: untpd.Ident) :: (_: untpd.Ident) :: Nil)) => - importedSymbols(imp.expr, id.name) - case _ => - Nil - } - - symbols.map(sourceSymbol).filter(_.exists).distinct - } - - /** - * The symbols that are imported with `expr.name` - * - * @param expr The base of the import statement - * @param name The name that is being imported. - * @return All the symbols that would be imported with `expr.name`. - */ - private def importedSymbols(expr: tpd.Tree, name: Name)(implicit ctx: Context): List[Symbol] = { - def lookup(name: Name): Symbol = expr.tpe.member(name).symbol - List(lookup(name.toTermName), - lookup(name.toTypeName), - lookup(name.moduleClassName), - lookup(name.sourceModuleName)) - } - /** * Is this tree using a renaming introduced by an import statement? * diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index f25b7094cdfe..2b990b17105e 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -419,11 +419,14 @@ class DottyLanguageServer extends LanguageServer if (tp.isError || tpw == NoType) null // null here indicates that no response should be sent else { - val symbol = Interactive.enclosingSourceSymbols(path, pos).headOption.orNull - if (symbol == null) return null - val docComment = ParsedComment.docOf(symbol) - val content = hoverContent(Some(tpw.show), docComment) - new Hover(content, null) + Interactive.enclosingSourceSymbols(path, pos) match { + case Nil => + null + case symbol :: _ => + val docComment = ParsedComment.docOf(symbol) + val content = hoverContent(Some(tpw.show), docComment) + new Hover(content, null) + } } } From facc581983a3d7c61445bd88b262878af29167d0 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 9 Nov 2018 11:29:31 +0100 Subject: [PATCH 13/21] Remove `Include.renamingImport` --- .../tools/dotc/interactive/Interactive.scala | 54 ++++--------------- .../languageserver/DottyLanguageServer.scala | 17 +++--- 2 files changed, 22 insertions(+), 49 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 40454cfa03ac..bcaa00e1a0a2 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -29,7 +29,6 @@ object Interactive { val definitions: Int = 8 // include definitions val linkedClass: Int = 16 // include `symbol.linkedClass` val imports: Int = 32 // include imports in the results - val renamingImports: Int = 64 // Include renamed symbols and renaming part of imports in the results } /** Does this tree define a symbol ? */ @@ -302,7 +301,6 @@ object Interactive { (implicit ctx: Context): List[SourceNamedTree] = safely { val includeReferences = (include & Include.references) != 0 val includeImports = (include & Include.imports) != 0 - val includeRenamingImports = (include & Include.renamingImports) != 0 val buf = new mutable.ListBuffer[SourceNamedTree] def traverser(source: SourceFile) = { @@ -340,30 +338,31 @@ object Interactive { /** * Find trees that match `symbol` in `trees`. * - * @param trees The trees to inspect. - * @param includes Whether to include references, definitions, etc. - * @param symbol The symbol for which we want to find references. + * @param trees The trees to inspect. + * @param includes Whether to include references, definitions, etc. + * @param symbol The symbol for which we want to find references. + * @param predicate An additional predicate that the trees must match. */ def findTreesMatching(trees: List[SourceTree], includes: Include.Set, - symbol: Symbol)(implicit ctx: Context): List[SourceNamedTree] = { + symbol: Symbol, + predicate: NameTree => Boolean = util.common.alwaysTrue + )(implicit ctx: Context): List[SourceNamedTree] = { val linkedSym = symbol.linkedClass val includeDeclaration = (includes & Include.definitions) != 0 val includeLinkedClass = (includes & Include.linkedClass) != 0 - val includeRenamingImports = (includes & Include.renamingImports) != 0 - val predicate: NameTree => Boolean = tree => + val fullPredicate: NameTree => Boolean = tree => ( !tree.symbol.isPrimaryConstructor && (includeDeclaration || !Interactive.isDefinition(tree)) - && (includeRenamingImports || !isRenamed(tree)) && ( Interactive.matchSymbol(tree, symbol, includes) - || ( includeDeclaration - && includeLinkedClass + || ( includeLinkedClass && linkedSym.exists && Interactive.matchSymbol(tree, linkedSym, includes) ) ) + && predicate(tree) ) - namedTrees(trees, includes, predicate) + namedTrees(trees, includes, fullPredicate) } /** The reverse path to the node that closest encloses position `pos`, @@ -577,35 +576,4 @@ object Interactive { } } - /** - * In `enclosing`, find all the references to any of `syms` that have been renamed to `toName`. - * - * If `enclosing` is empty, it means the renaming import was top-level and the whole source file - * should be considered. Otherwise, we can restrict the search to this tree because renaming - * imports are local. - * - * @param toName The name that is set by the renaming. - * @param enclosing The tree that encloses the renaming import, if it exists. - * @param syms The symbols to which we want to find renamed references. - * @param allTrees All the trees in this source file, in case we can't find `enclosing`. - * @param source The sourcefile that where to look for references. - * @return All the references to the symbol under the cursor that are using `toName`. - */ - def findTreesMatchingRenaming(toName: Name, - syms: List[Symbol], - trees: List[SourceTree] - )(implicit ctx: Context): List[SourceNamedTree] = { - - val includes = - Include.references | Include.imports | Include.renamingImports - - syms.flatMap { sym => - Interactive.namedTrees(trees, - includes, - tree => - Interactive.sameName(tree.name, toName) && - (Interactive.matchSymbol(tree, sym, includes) || Interactive.matchSymbol(tree, sym.linkedClass, includes))) - } - } - } diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 2b990b17105e..11ad37709225 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -311,8 +311,7 @@ class DottyLanguageServer extends LanguageServer val includes = { val includeDeclaration = params.getContext.isIncludeDeclaration - Include.references | Include.overriding | Include.imports | Include.renamingImports | - (if (includeDeclaration) Include.definitions else 0) + Include.references | Include.overriding | Include.imports | (if (includeDeclaration) Include.definitions else 0) } val uriTrees = driver.openedTrees(uri) @@ -360,11 +359,17 @@ class DottyLanguageServer extends LanguageServer path match { // Selected a renaming in an import node case Thicket(_ :: (rename: Ident) :: Nil) :: (_: Import) :: rest if rename.pos.contains(pos.pos) => - Interactive.findTreesMatchingRenaming(rename.name, syms, uriTrees) + val includes = Include.references | Include.linkedClass | Include.imports + syms.flatMap { sym => + Interactive.findTreesMatching(uriTrees, includes, sym, t => Interactive.sameName(t.name, rename.name)) + } // Selected a reference that has been renamed case (nameTree: NameTree) :: rest if Interactive.isRenamed(nameTree) => - Interactive.findTreesMatchingRenaming(nameTree.name, syms, uriTrees) + val includes = Include.references | Include.linkedClass | Include.imports + syms.flatMap { sym => + Interactive.findTreesMatching(uriTrees, includes, sym, t => Interactive.sameName(t.name, nameTree.name)) + } case _ => val includes = @@ -372,7 +377,7 @@ class DottyLanguageServer extends LanguageServer syms.flatMap { sym => val trees = driver.allTreesContaining(sym.name.sourceModuleName.toString) - Interactive.findTreesMatching(trees, includes, sym) + Interactive.findTreesMatching(trees, includes, sym, t => Interactive.sameName(t.name, sym.name)) } } @@ -395,7 +400,7 @@ class DottyLanguageServer extends LanguageServer val uriTrees = driver.openedTrees(uri) val path = Interactive.pathTo(uriTrees, pos) val syms = Interactive.enclosingSourceSymbols(path, pos) - val includes = Include.definitions | Include.references | Include.imports | Include.renamingImports + val includes = Include.definitions | Include.references | Include.imports syms.flatMap { sym => val refs = Interactive.findTreesMatching(uriTrees, includes, sym) From d09f900345fa3d3823094c442df4987448e5fb3d Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 9 Nov 2018 12:38:27 +0100 Subject: [PATCH 14/21] Remove `SourceImportTree` We keep only `SourceTree`, that we had before, but it now accepts any `tpd.Tree`. After we bootstrap, this should become `tpd.Import | tpd.NameTree`. --- .../tools/dotc/interactive/Interactive.scala | 14 ++-- .../dotc/interactive/InteractiveDriver.scala | 4 +- .../tools/dotc/interactive/SourceTree.scala | 78 +++++++++---------- 3 files changed, 45 insertions(+), 51 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index bcaa00e1a0a2..9345f1641d04 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -279,7 +279,7 @@ object Interactive { * source code. */ def namedTrees(trees: List[SourceTree], include: Include.Set, sym: Symbol) - (implicit ctx: Context): List[SourceNamedTree] = + (implicit ctx: Context): List[SourceTree] = if (!sym.exists) Nil else @@ -288,7 +288,7 @@ object Interactive { /** Find named trees with a non-empty position whose name contains `nameSubstring` in `trees`. */ def namedTrees(trees: List[SourceTree], nameSubstring: String) - (implicit ctx: Context): List[SourceNamedTree] = { + (implicit ctx: Context): List[SourceTree] = { val predicate: NameTree => Boolean = _.name.toString.contains(nameSubstring) namedTrees(trees, 0, predicate) } @@ -298,10 +298,10 @@ object Interactive { * @param includeReferences If true, include references and not just definitions */ def namedTrees(trees: List[SourceTree], include: Include.Set, treePredicate: NameTree => Boolean) - (implicit ctx: Context): List[SourceNamedTree] = safely { + (implicit ctx: Context): List[SourceTree] = safely { val includeReferences = (include & Include.references) != 0 val includeImports = (include & Include.imports) != 0 - val buf = new mutable.ListBuffer[SourceNamedTree] + val buf = new mutable.ListBuffer[SourceTree] def traverser(source: SourceFile) = { new untpd.TreeTraverser { @@ -319,7 +319,7 @@ object Interactive { && !tree.pos.isZeroExtent && (includeReferences || isDefinition(tree)) && treePredicate(tree)) - buf += SourceNamedTree(tree, source) + buf += SourceTree(tree, source) traverseChildren(tree) case tree: untpd.Inlined => traverse(tree.call) @@ -347,7 +347,7 @@ object Interactive { includes: Include.Set, symbol: Symbol, predicate: NameTree => Boolean = util.common.alwaysTrue - )(implicit ctx: Context): List[SourceNamedTree] = { + )(implicit ctx: Context): List[SourceTree] = { val linkedSym = symbol.linkedClass val includeDeclaration = (includes & Include.definitions) != 0 val includeLinkedClass = (includes & Include.linkedClass) != 0 @@ -450,7 +450,7 @@ object Interactive { * @param driver The driver responsible for `path`. * @return The definitions for the symbol at the end of `path`. */ - def findDefinitions(path: List[Tree], pos: SourcePosition, driver: InteractiveDriver)(implicit ctx: Context): List[SourceNamedTree] = { + def findDefinitions(path: List[Tree], pos: SourcePosition, driver: InteractiveDriver)(implicit ctx: Context): List[SourceTree] = { enclosingSourceSymbols(path, pos).flatMap { sym => val enclTree = enclosingTree(path) diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala index ee6e0dba0d55..3bb3a53ef632 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala @@ -253,9 +253,9 @@ class InteractiveDriver(val settings: List[String]) extends Driver { case PackageDef(_, stats) => stats.foreach(addTrees) case imp: Import => - trees += SourceImportTree(imp, source) + trees += SourceTree(imp, source) case tree: TypeDef => - trees += SourceNamedTree(tree, source) + trees += SourceTree(tree, source) case _ => } addTrees(topTree) diff --git a/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala b/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala index 29c92d1aa5a0..2ceda42acf5f 100644 --- a/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala +++ b/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala @@ -9,50 +9,44 @@ import core._, core.Decorators.{sourcePos => _} import Contexts._, NameOps._, Symbols._, StdNames._ import util._, util.Positions._ -/** A `tree` coming from `source` */ -sealed trait SourceTree { - - /** The underlying tree. */ - def tree: tpd.Tree - - /** The source from which `tree` comes. */ - def source: SourceFile +/** + * A `tree` coming from `source` + * + * `tree` can be either an `Import` or a `NameTree`. + */ +case class SourceTree(tree: tpd.Tree /** really: tpd.Import | tpd.NameTree */, source: SourceFile) { /** The position of `tree` */ final def pos(implicit ctx: Context): SourcePosition = source.atPos(tree.pos) -} - -/** An import coming from `source` */ -case class SourceImportTree(tree: tpd.Import, source: SourceFile) extends SourceTree - -/** A typechecked `tree` coming from `source` */ -case class SourceNamedTree(tree: tpd.NameTree, source: SourceFile) extends SourceTree { /** The position of the name in `tree` */ - def namePos(implicit ctx: Context): SourcePosition = { - // FIXME: Merge with NameTree#namePos ? - val treePos = tree.pos - if (treePos.isZeroExtent || tree.name.toTermName == nme.ERROR) - NoSourcePosition - else { - // Constructors are named `` in the trees, but `this` in the source. - val nameLength = tree.name match { - case nme.CONSTRUCTOR => nme.this_.toString.length - case other => other.stripModuleClassSuffix.show.toString.length - } - val position = { - // FIXME: This is incorrect in some cases, like with backquoted identifiers, - // see https://github.com/lampepfl/dotty/pull/1634#issuecomment-257079436 - val (start, end) = - if (!treePos.isSynthetic) - (treePos.point, treePos.point + nameLength) - else - // If we don't have a point, we need to find it - (treePos.end - nameLength, treePos.end) - Position(start, end, start) + def namePos(implicit ctx: Context): SourcePosition = tree match { + case tree: tpd.NameTree => + // FIXME: Merge with NameTree#namePos ? + val treePos = tree.pos + if (treePos.isZeroExtent || tree.name.toTermName == nme.ERROR) + NoSourcePosition + else { + // Constructors are named `` in the trees, but `this` in the source. + val nameLength = tree.name match { + case nme.CONSTRUCTOR => nme.this_.toString.length + case other => other.stripModuleClassSuffix.show.toString.length + } + val position = { + // FIXME: This is incorrect in some cases, like with backquoted identifiers, + // see https://github.com/lampepfl/dotty/pull/1634#issuecomment-257079436 + val (start, end) = + if (!treePos.isSynthetic) + (treePos.point, treePos.point + nameLength) + else + // If we don't have a point, we need to find it + (treePos.end - nameLength, treePos.end) + Position(start, end, start) + } + source.atPos(position) } - source.atPos(position) - } + case _ => + NoSourcePosition } } @@ -63,19 +57,19 @@ object SourceTree { Nil else { import ast.Trees._ - def sourceTreeOfClass(tree: tpd.Tree): Option[SourceNamedTree] = tree match { + def sourceTreeOfClass(tree: tpd.Tree): Option[SourceTree] = tree match { case PackageDef(_, stats) => stats.flatMap(sourceTreeOfClass).headOption case tree: tpd.TypeDef if tree.symbol == sym => val sourceFile = new SourceFile(sym.sourceFile, Codec.UTF8) - Some(SourceNamedTree(tree, sourceFile)) + Some(SourceTree(tree, sourceFile)) case _ => None } - def sourceImports(tree: tpd.Tree, sourceFile: SourceFile): List[SourceImportTree] = tree match { + def sourceImports(tree: tpd.Tree, sourceFile: SourceFile): List[SourceTree] = tree match { case PackageDef(_, stats) => stats.flatMap(sourceImports(_, sourceFile)) - case imp: tpd.Import => SourceImportTree(imp, sourceFile) :: Nil + case imp: tpd.Import => SourceTree(imp, sourceFile) :: Nil case _ => Nil } From 55de5b2bc1fb5381b7f4a6fd5e3b8fc45681bb1a Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 9 Nov 2018 15:18:00 +0100 Subject: [PATCH 15/21] IDE: Generate fewer duplicates with imports --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 70 +++++++++++++------ .../tools/dotc/interactive/Interactive.scala | 1 + 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 75d23e499fc9..c3e9316d32f3 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1157,10 +1157,13 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { */ def importedSymbols(expr: Tree, name: Name)(implicit ctx: Context): List[Symbol] = { def lookup(name: Name): Symbol = expr.tpe.member(name).symbol - List(lookup(name.toTermName), - lookup(name.toTypeName), - lookup(name.moduleClassName), - lookup(name.sourceModuleName)) + val symbols = + List(lookup(name.toTermName), + lookup(name.toTypeName), + lookup(name.moduleClassName), + lookup(name.sourceModuleName)) + + symbols.map(_.sourceSymbol).filter(_.exists).distinct } /** @@ -1174,7 +1177,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def importedSymbols(imp: Import, selectorPredicate: untpd.Tree => Boolean = util.common.alwaysTrue) (implicit ctx: Context): List[Symbol] = { - val symbols = imp.selectors.find(selectorPredicate) match { + imp.selectors.find(selectorPredicate) match { case Some(id: untpd.Ident) => importedSymbols(imp.expr, id.name) case Some(Thicket((id: untpd.Ident) :: (_: untpd.Ident) :: Nil)) => @@ -1182,30 +1185,51 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { case _ => Nil } - - symbols.map(_.sourceSymbol).filter(_.exists).distinct } + /** + * The list of select trees that resolve to the same symbols as the ones that are imported + * by `imp`. + */ def importSelections(imp: Import)(implicit ctx: Context): List[Select] = { - val imported = - imp.selectors.flatMap { - case id: untpd.Ident => - importedSymbols(imp.expr, id.name).map((_, id, None)) - case Thicket((id: untpd.Ident) :: (newName: untpd.Ident) :: Nil) => - val renaming = Some(newName) - importedSymbols(imp.expr, id.name).map((_, id, renaming)) + def imported(sym: Symbol, id: untpd.Ident, rename: Option[untpd.Ident]): List[Select] = { + val noPosExpr = focusPositions(imp.expr) + val selectTree = Select(noPosExpr, sym.name).withPos(id.pos) + rename match { + case None => + selectTree :: Nil + case Some(rename) => + // Get the type of the symbol that is actually selected, and construct a select + // node with the new name and the type of the real symbol. + val name = if (sym.name.isTypeName) rename.name.toTypeName else rename.name + val actual = Select(noPosExpr, sym.name) + val renameTree = Select(noPosExpr, name).withPos(rename.pos).withType(actual.tpe) + selectTree :: renameTree :: Nil } - imported.flatMap { case (symbol, name, rename) => - val tree = Select(imp.expr, symbol.name).withPos(name.pos) - val renameTree = rename.map { r => - // Get the type of the symbol that is actually selected, and construct a select - // node with the new name and the type of the real symbol. - val name = if (symbol.name.isTypeName) r.name.toTypeName else r.name - val actual = Select(imp.expr, symbol.name) - Select(imp.expr, name).withPos(r.pos).withType(actual.tpe) + } + + imp.selectors.flatMap { + case Ident(nme.WILDCARD) => + Nil + case id: untpd.Ident => + importedSymbols(imp.expr, id.name).flatMap { sym => + imported(sym, id, None) + } + case Thicket((id: untpd.Ident) :: (newName: untpd.Ident) :: Nil) => + importedSymbols(imp.expr, id.name).flatMap { sym => + imported(sym, id, Some(newName)) + } + } + } + + /** Replaces all positions in `tree` with zero-extent positions */ + private def focusPositions(tree: Tree)(implicit ctx: Context): Tree = { + val transformer = new tpd.TreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + super.transform(tree).withPos(tree.pos.focus) } - tree :: renameTree.toList } + transformer.transform(tree) } } diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 9345f1641d04..0c4578ea19cf 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -310,6 +310,7 @@ object Interactive { case imp: untpd.Import if includeImports && tree.hasType => val tree = imp.asInstanceOf[tpd.Import] val selections = tpd.importSelections(tree) + traverse(imp.expr) selections.foreach(traverse) case utree: untpd.NameTree if tree.hasType => val tree = utree.asInstanceOf[tpd.NameTree] From 59b489d5393933e24b81011f09532d85e188318c Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 9 Nov 2018 15:59:36 +0100 Subject: [PATCH 16/21] IDE: Improve `Include` flags --- .../tools/dotc/interactive/Interactive.scala | 66 +++++++++++++------ .../languageserver/DottyLanguageServer.scala | 7 +- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 0c4578ea19cf..3976776cba29 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -21,14 +21,42 @@ import StdNames.nme object Interactive { import ast.tpd._ - object Include { // should be an enum, really. - type Set = Int - val overridden: Int = 1 // include trees whose symbol is overridden by `sym` - val overriding: Int = 2 // include trees whose symbol overrides `sym` (but for performance only in same source file) - val references: Int = 4 // include references - val definitions: Int = 8 // include definitions - val linkedClass: Int = 16 // include `symbol.linkedClass` - val imports: Int = 32 // include imports in the results + object Include { + case class Set private (val bits: Int) extends AnyVal { + def | (that: Set): Set = Set(bits | that.bits) + + def isEmpty: Boolean = bits == 0 + def isOverridden: Boolean = (bits & overridden.bits) != 0 + def isOverriding: Boolean = (bits & overriding.bits) != 0 + def isReferences: Boolean = (bits & references.bits) != 0 + def isDefinitions: Boolean = (bits & definitions.bits) != 0 + def isLinkedClass: Boolean = (bits & linkedClass.bits) != 0 + def isImports: Boolean = (bits & imports.bits) != 0 + } + + /** The empty set */ + val empty: Set = Set(0) + + /** Include trees whose symbol is overridden by `sym` */ + val overridden: Set = Set(1 << 0) + + /** + * Include trees whose symbol overrides `sym` (but for performance only in same source + * file) + */ + val overriding: Set = Set(1 << 1) + + /** Include references */ + val references: Set = Set(1 << 2) + + /** Include definitions */ + val definitions: Set = Set(1 << 3) + + /** Include `sym.linkedClass */ + val linkedClass: Set = Set(1 << 4) + + /** Include imports in the results */ + val imports: Set = Set(1 << 5) } /** Does this tree define a symbol ? */ @@ -112,9 +140,9 @@ object Interactive { ( sym == tree.symbol || sym.exists && sym == tree.symbol.sourceSymbol - || include != 0 && sym.name == tree.symbol.name && sym.maybeOwner != tree.symbol.maybeOwner - && ( (include & Include.overridden) != 0 && overrides(sym, tree.symbol) - || (include & Include.overriding) != 0 && overrides(tree.symbol, sym) + || !include.isEmpty && sym.name == tree.symbol.name && sym.maybeOwner != tree.symbol.maybeOwner + && ( include.isOverridden && overrides(sym, tree.symbol) + || include.isOverriding && overrides(tree.symbol, sym) ) ) } @@ -290,7 +318,7 @@ object Interactive { def namedTrees(trees: List[SourceTree], nameSubstring: String) (implicit ctx: Context): List[SourceTree] = { val predicate: NameTree => Boolean = _.name.toString.contains(nameSubstring) - namedTrees(trees, 0, predicate) + namedTrees(trees, Include.empty, predicate) } /** Find named trees with a non-empty position satisfying `treePredicate` in `trees`. @@ -299,15 +327,13 @@ object Interactive { */ def namedTrees(trees: List[SourceTree], include: Include.Set, treePredicate: NameTree => Boolean) (implicit ctx: Context): List[SourceTree] = safely { - val includeReferences = (include & Include.references) != 0 - val includeImports = (include & Include.imports) != 0 val buf = new mutable.ListBuffer[SourceTree] def traverser(source: SourceFile) = { new untpd.TreeTraverser { override def traverse(tree: untpd.Tree)(implicit ctx: Context) = { tree match { - case imp: untpd.Import if includeImports && tree.hasType => + case imp: untpd.Import if include.isImports && tree.hasType => val tree = imp.asInstanceOf[tpd.Import] val selections = tpd.importSelections(tree) traverse(imp.expr) @@ -318,7 +344,7 @@ object Interactive { && !tree.symbol.is(Synthetic) && tree.pos.exists && !tree.pos.isZeroExtent - && (includeReferences || isDefinition(tree)) + && (include.isReferences || isDefinition(tree)) && treePredicate(tree)) buf += SourceTree(tree, source) traverseChildren(tree) @@ -350,13 +376,11 @@ object Interactive { predicate: NameTree => Boolean = util.common.alwaysTrue )(implicit ctx: Context): List[SourceTree] = { val linkedSym = symbol.linkedClass - val includeDeclaration = (includes & Include.definitions) != 0 - val includeLinkedClass = (includes & Include.linkedClass) != 0 val fullPredicate: NameTree => Boolean = tree => ( !tree.symbol.isPrimaryConstructor - && (includeDeclaration || !Interactive.isDefinition(tree)) + && (includes.isDefinitions || !Interactive.isDefinition(tree)) && ( Interactive.matchSymbol(tree, symbol, includes) - || ( includeLinkedClass + || ( includes.isLinkedClass && linkedSym.exists && Interactive.matchSymbol(tree, linkedSym, includes) ) @@ -469,7 +493,7 @@ object Interactive { } (trees, Include.definitions | Include.overriding) case _ => - (Nil, 0) + (Nil, Include.empty) } findTreesMatching(trees, include, sym) diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 11ad37709225..c98dbbe19fcf 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -311,7 +311,8 @@ class DottyLanguageServer extends LanguageServer val includes = { val includeDeclaration = params.getContext.isIncludeDeclaration - Include.references | Include.overriding | Include.imports | (if (includeDeclaration) Include.definitions else 0) + Include.references | Include.overriding | Include.imports | + (if (includeDeclaration) Include.definitions else Include.empty) } val uriTrees = driver.openedTrees(uri) @@ -442,7 +443,7 @@ class DottyLanguageServer extends LanguageServer val uriTrees = driver.openedTrees(uri) - val defs = Interactive.namedTrees(uriTrees, 0, _ => true) + val defs = Interactive.namedTrees(uriTrees, Include.empty, _ => true) (for { d <- defs if !isWorksheetWrapper(d) info <- symbolInfo(d.tree.symbol, d.namePos, positionMapperFor(d.source)) @@ -484,7 +485,7 @@ class DottyLanguageServer extends LanguageServer val predicates = definitions.map(Interactive.implementationFilter(_)(ctx)) tree => predicates.exists(_(tree)) } - val matches = Interactive.namedTrees(trees, 0, predicate)(ctx) + val matches = Interactive.namedTrees(trees, Include.empty, predicate)(ctx) matches.map(tree => location(tree.namePos(ctx), positionMapperFor(tree.source))) } }.toList From cf3c247fc637f1ab738c5f0a39338af43229a797 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 12 Nov 2018 14:21:04 +0100 Subject: [PATCH 17/21] Address review comments --- .../tools/dotc/interactive/Interactive.scala | 4 ++++ .../languageserver/DottyLanguageServer.scala | 21 +++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 3976776cba29..840fd000b4b4 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -24,6 +24,7 @@ object Interactive { object Include { case class Set private (val bits: Int) extends AnyVal { def | (that: Set): Set = Set(bits | that.bits) + def except(that: Set): Set = Set(bits & ~that.bits) def isEmpty: Boolean = bits == 0 def isOverridden: Boolean = (bits & overridden.bits) != 0 @@ -57,6 +58,9 @@ object Interactive { /** Include imports in the results */ val imports: Set = Set(1 << 5) + + /** All the flags */ + val all: Set = Set(~0) } /** Does this tree define a symbol ? */ diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index c98dbbe19fcf..5e44b8ab86b0 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -356,26 +356,25 @@ class DottyLanguageServer extends LanguageServer val syms = Interactive.enclosingSourceSymbols(path, pos) val newName = params.getNewName + def findRenamedReferences(trees: List[SourceTree], syms: List[Symbol], withName: Name): List[SourceTree] = { + val includes = Include.all + syms.flatMap { sym => + Interactive.findTreesMatching(trees, Include.all, sym, t => Interactive.sameName(t.name, withName)) + } + } + val refs = path match { // Selected a renaming in an import node case Thicket(_ :: (rename: Ident) :: Nil) :: (_: Import) :: rest if rename.pos.contains(pos.pos) => - val includes = Include.references | Include.linkedClass | Include.imports - syms.flatMap { sym => - Interactive.findTreesMatching(uriTrees, includes, sym, t => Interactive.sameName(t.name, rename.name)) - } + findRenamedReferences(uriTrees, syms, rename.name) // Selected a reference that has been renamed case (nameTree: NameTree) :: rest if Interactive.isRenamed(nameTree) => - val includes = Include.references | Include.linkedClass | Include.imports - syms.flatMap { sym => - Interactive.findTreesMatching(uriTrees, includes, sym, t => Interactive.sameName(t.name, nameTree.name)) - } + findRenamedReferences(uriTrees, syms, nameTree.name) case _ => - val includes = - Include.references | Include.definitions | Include.linkedClass | Include.overriding | Include.imports - + val includes = Include.all.except(Include.overridden) syms.flatMap { sym => val trees = driver.allTreesContaining(sym.name.sourceModuleName.toString) Interactive.findTreesMatching(trees, includes, sym, t => Interactive.sameName(t.name, sym.name)) From 99636937367aa8585cc5c9b08817d8dc6b966fdb Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 13 Nov 2018 11:13:27 +0100 Subject: [PATCH 18/21] Improve renaming with overridden symbols When renaming a symbol that overrides other symbols, the overridden symbols and all their overrides will be renamed too. --- .../languageserver/DottyLanguageServer.scala | 13 +++++++++---- .../tools/languageserver/RenameTest.scala | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 5e44b8ab86b0..36e02fc49ca2 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -353,7 +353,9 @@ class DottyLanguageServer extends LanguageServer val uriTrees = driver.openedTrees(uri) val pos = sourcePosition(driver, uri, params.getPosition) val path = Interactive.pathTo(uriTrees, pos) - val syms = Interactive.enclosingSourceSymbols(path, pos) + val syms = Interactive.enclosingSourceSymbols(path, pos).flatMap { sym => + sym :: sym.allOverriddenSymbols.toList + } val newName = params.getNewName def findRenamedReferences(trees: List[SourceTree], syms: List[Symbol], withName: Name): List[SourceTree] = { @@ -374,10 +376,13 @@ class DottyLanguageServer extends LanguageServer findRenamedReferences(uriTrees, syms, nameTree.name) case _ => - val includes = Include.all.except(Include.overridden) + val names = syms.map(_.name.sourceModuleName).toSet + val trees = names.flatMap(name => driver.allTreesContaining(name.toString)).toList syms.flatMap { sym => - val trees = driver.allTreesContaining(sym.name.sourceModuleName.toString) - Interactive.findTreesMatching(trees, includes, sym, t => Interactive.sameName(t.name, sym.name)) + Interactive.findTreesMatching(trees, + Include.all, + sym, + t => names.exists(Interactive.sameName(t.name, _))) } } diff --git a/language-server/test/dotty/tools/languageserver/RenameTest.scala b/language-server/test/dotty/tools/languageserver/RenameTest.scala index e0c46748b0dd..b09a00158812 100644 --- a/language-server/test/dotty/tools/languageserver/RenameTest.scala +++ b/language-server/test/dotty/tools/languageserver/RenameTest.scala @@ -215,4 +215,23 @@ class RenameTest { testRename(m8) } + @Test def renameOverridden: Unit = { + def testRename(m: CodeMarker) = + withSources( + code"""class A { def ${m1}foo${m2}: Int = 0 } + class B extends A { override def ${m3}foo${m4}: Int = 1 } + class C extends A { override def ${m5}foo${m6}: Int = 2 }""" + ).rename(m, "NewName", Set(m1 to m2, m3 to m4, m5 to m6)) + + testRename(m1) + testRename(m2) + testRename(m3) + testRename(m4) + testRename(m5) + testRename(m6) + + } + + + } From ec6882c8fb570b95c0219db3e160255860216cd4 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 13 Nov 2018 14:44:36 +0100 Subject: [PATCH 19/21] Ask user when renaming overriding symbols When renaming a symbol that is overriding another symbol, we now ask the user whether we should rename the base symbol or only this member. --- .../languageserver/DottyLanguageServer.scala | 54 ++++++++++++++++--- .../tools/languageserver/RenameTest.scala | 22 ++++---- .../languageserver/util/CodeTester.scala | 16 ++++-- .../util/actions/CodeRename.scala | 32 +++++++++-- .../util/server/TestClient.scala | 6 ++- 5 files changed, 103 insertions(+), 27 deletions(-) diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 36e02fc49ca2..ebd1c40a21a0 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -353,9 +353,7 @@ class DottyLanguageServer extends LanguageServer val uriTrees = driver.openedTrees(uri) val pos = sourcePosition(driver, uri, params.getPosition) val path = Interactive.pathTo(uriTrees, pos) - val syms = Interactive.enclosingSourceSymbols(path, pos).flatMap { sym => - sym :: sym.allOverriddenSymbols.toList - } + val syms = Interactive.enclosingSourceSymbols(path, pos) val newName = params.getNewName def findRenamedReferences(trees: List[SourceTree], syms: List[Symbol], withName: Name): List[SourceTree] = { @@ -376,13 +374,25 @@ class DottyLanguageServer extends LanguageServer findRenamedReferences(uriTrees, syms, nameTree.name) case _ => - val names = syms.map(_.name.sourceModuleName).toSet + val (include, allSymbols) = + if (syms.exists(_.allOverriddenSymbols.nonEmpty)) { + showMessageRequest(MessageType.Info, + RENAME_OVERRIDDEN_QUESTION, + List( + RENAME_OVERRIDDEN -> (() => (Include.all, syms.flatMap(s => s :: s.allOverriddenSymbols.toList))), + RENAME_NO_OVERRIDDEN -> (() => (Include.all.except(Include.overridden), syms))) + ).get.getOrElse((Include.empty, List.empty[Symbol])) + } else { + (Include.all, syms) + } + + val names = allSymbols.map(_.name.sourceModuleName).toSet val trees = names.flatMap(name => driver.allTreesContaining(name.toString)).toList - syms.flatMap { sym => + allSymbols.flatMap { sym => Interactive.findTreesMatching(trees, - Include.all, - sym, - t => names.exists(Interactive.sameName(t.name, _))) + include, + sym, + t => names.exists(Interactive.sameName(t.name, _))) } } @@ -566,12 +576,40 @@ class DottyLanguageServer extends LanguageServer } } + /** + * Send a `window/showMessageRequest` to the client, asking to choose between `choices`, and + * perform the associated operation. + * + * @param tpe The type of the request + * @param message The message accompanying the request + * @param choices The choices and their associated operation + * @return A future that will complete with the result of executing the action corresponding to + * the user's response. + */ + private def showMessageRequest[T](tpe: MessageType, + message: String, + choices: List[(String, () => T)]): CompletableFuture[Option[T]] = { + val options = choices.map((title, _) => new MessageActionItem(title)) + val request = new ShowMessageRequestParams(options.asJava) + request.setMessage(message) + request.setType(tpe) + + client.showMessageRequest(request).thenApply { (answer: MessageActionItem) => + choices.find(_._1 == answer.getTitle).map { + case (_, action) => action() + } + } + } } object DottyLanguageServer { /** Configuration file normally generated by sbt-dotty */ final val IDE_CONFIG_FILE = ".dotty-ide.json" + final val RENAME_OVERRIDDEN_QUESTION = "Do you want to rename the base member, or only this member?" + final val RENAME_OVERRIDDEN= "Rename the base member" + final val RENAME_NO_OVERRIDDEN = "Rename only this member" + /** Convert an lsp4j.Position to a SourcePosition */ def sourcePosition(driver: InteractiveDriver, uri: URI, pos: lsp4j.Position): SourcePosition = { val actualPosition = diff --git a/language-server/test/dotty/tools/languageserver/RenameTest.scala b/language-server/test/dotty/tools/languageserver/RenameTest.scala index b09a00158812..be1dc06fec2c 100644 --- a/language-server/test/dotty/tools/languageserver/RenameTest.scala +++ b/language-server/test/dotty/tools/languageserver/RenameTest.scala @@ -216,19 +216,23 @@ class RenameTest { } @Test def renameOverridden: Unit = { - def testRename(m: CodeMarker) = + def testRename(m: CodeMarker, expectations: Set[CodeRange], withOverridden: Option[Boolean]) = withSources( code"""class A { def ${m1}foo${m2}: Int = 0 } class B extends A { override def ${m3}foo${m4}: Int = 1 } class C extends A { override def ${m5}foo${m6}: Int = 2 }""" - ).rename(m, "NewName", Set(m1 to m2, m3 to m4, m5 to m6)) - - testRename(m1) - testRename(m2) - testRename(m3) - testRename(m4) - testRename(m5) - testRename(m6) + ).rename(m, "NewName", expectations, withOverridden) + + testRename(m1, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = None) + testRename(m2, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = None) + testRename(m3, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = Some(true)) + testRename(m4, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = Some(true)) + testRename(m5, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = Some(true)) + testRename(m6, Set(m1 to m2, m3 to m4, m5 to m6), withOverridden = Some(true)) + testRename(m3, Set(m3 to m4), withOverridden = Some(false)) + testRename(m4, Set(m3 to m4), withOverridden = Some(false)) + testRename(m5, Set(m5 to m6), withOverridden = Some(false)) + testRename(m6, Set(m5 to m6), withOverridden = Some(false)) } diff --git a/language-server/test/dotty/tools/languageserver/util/CodeTester.scala b/language-server/test/dotty/tools/languageserver/util/CodeTester.scala index 8614e13bf721..c808ff6b8d05 100644 --- a/language-server/test/dotty/tools/languageserver/util/CodeTester.scala +++ b/language-server/test/dotty/tools/languageserver/util/CodeTester.scala @@ -92,14 +92,20 @@ class CodeTester(projects: List[Project]) { * Performs a workspace-wide renaming of the symbol under `marker`, verifies that the positions to * update match `expected`. * - * @param marker The position from which to ask for renaming. - * @param newName The new name to give to the symbol. - * @param expected The expected positions to change. + * @param marker The position from which to ask for renaming. + * @param newName The new name to give to the symbol. + * @param expected The expected positions to change. + * @param withOverridden If `None`, do not expect the server to ask whether to include overridden + * symbol. Otherwise, wait for this question from the server and include + * overridden symbols if this is true. * * @see dotty.tools.languageserver.util.actions.CodeRename */ - def rename(marker: CodeMarker, newName: String, expected: Set[CodeRange]): this.type = - doAction(new CodeRename(marker, newName, expected)) // TODO apply changes to the sources and positions + def rename(marker: CodeMarker, + newName: String, + expected: Set[CodeRange], + withOverridden: Option[Boolean] = None): this.type = + doAction(new CodeRename(marker, newName, expected, withOverridden)) // TODO apply changes to the sources and positions /** * Queries for all the symbols referenced in the source file in `marker`, verifies that they match diff --git a/language-server/test/dotty/tools/languageserver/util/actions/CodeRename.scala b/language-server/test/dotty/tools/languageserver/util/actions/CodeRename.scala index 5e69d00b4bbb..331e8529e2fd 100644 --- a/language-server/test/dotty/tools/languageserver/util/actions/CodeRename.scala +++ b/language-server/test/dotty/tools/languageserver/util/actions/CodeRename.scala @@ -2,8 +2,13 @@ package dotty.tools.languageserver.util.actions import dotty.tools.languageserver.util.embedded.CodeMarker import dotty.tools.languageserver.util.{CodeRange, PositionContext} +import dotty.tools.languageserver.DottyLanguageServer.{RENAME_OVERRIDDEN, RENAME_NO_OVERRIDDEN} -import org.junit.Assert.{assertEquals, assertNull} +import org.junit.Assert.{assertEquals, assertNull, fail} + +import org.eclipse.lsp4j.{MessageActionItem, ShowMessageRequestParams} + +import java.util.concurrent.CompletableFuture import scala.collection.JavaConverters._ @@ -17,10 +22,31 @@ import scala.collection.JavaConverters._ */ class CodeRename(override val marker: CodeMarker, newName: String, - expected: Set[CodeRange]) extends ActionOnMarker { + expected: Set[CodeRange], + withOverridden: Option[Boolean]) extends ActionOnMarker { + + private final val TIMEOUT_MS = 10000 override def execute(): Exec[Unit] = { - val results = server.rename(marker.toRenameParams(newName)).get() + val query = server.rename(marker.toRenameParams(newName)) + + withOverridden.foreach { includeOverridden => + var question: (ShowMessageRequestParams, CompletableFuture[MessageActionItem]) = null + val startTime = System.currentTimeMillis() + do { + Thread.sleep(50) + question = client.requests.get.headOption.orNull + } while (question == null && System.currentTimeMillis() - startTime < TIMEOUT_MS) + + if (question == null) fail("The server didn't ask about overridden symbols.") + + val answerStr = if (includeOverridden) RENAME_OVERRIDDEN else RENAME_NO_OVERRIDDEN + val action = question._1.getActions.asScala.find(_.getTitle == answerStr).get + question._2.complete(action) + } + + val results = query.get() + val changes = results.getChanges.asScala.mapValues(_.asScala.toSet.map(ch => (ch.getNewText, ch.getRange))) val expectedChanges = expected.groupBy(_.file.uri).mapValues(_.map(range => (newName, range.toRange))) diff --git a/language-server/test/dotty/tools/languageserver/util/server/TestClient.scala b/language-server/test/dotty/tools/languageserver/util/server/TestClient.scala index 5d2effffa26e..bba69c5e2b9a 100644 --- a/language-server/test/dotty/tools/languageserver/util/server/TestClient.scala +++ b/language-server/test/dotty/tools/languageserver/util/server/TestClient.scala @@ -23,6 +23,7 @@ class TestClient extends WorksheetClient { val diagnostics = new Log[PublishDiagnosticsParams] val telemetry = new Log[Any] val worksheetOutput = new Log[WorksheetRunOutput] + val requests = new Log[(ShowMessageRequestParams, CompletableFuture[MessageActionItem])] override def logMessage(message: MessageParams) = { log += message @@ -37,8 +38,9 @@ class TestClient extends WorksheetClient { } override def showMessageRequest(requestParams: ShowMessageRequestParams) = { - log += requestParams - new CompletableFuture[MessageActionItem] + val reply = new CompletableFuture[MessageActionItem] + requests += ((requestParams, reply)) + reply } override def publishDiagnostics(diagnosticsParams: PublishDiagnosticsParams) = { From 8dc5f8f5564ac46f83d1942a28ddaf11c3b08f40 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 13 Nov 2018 16:50:42 +0100 Subject: [PATCH 20/21] Show documentation for all imported symbols When doing hover on an imported node, the window now shows the documentation for all the imported symbols. --- .../tools/languageserver/DottyLanguageServer.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index ebd1c40a21a0..b8ee33d04eb0 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -442,9 +442,9 @@ class DottyLanguageServer extends LanguageServer Interactive.enclosingSourceSymbols(path, pos) match { case Nil => null - case symbol :: _ => - val docComment = ParsedComment.docOf(symbol) - val content = hoverContent(Some(tpw.show), docComment) + case symbols => + val docComments = symbols.flatMap(ParsedComment.docOf) + val content = hoverContent(Some(tpw.show), docComments) new Hover(content, null) } } @@ -810,7 +810,7 @@ object DottyLanguageServer { } private def hoverContent(typeInfo: Option[String], - comment: Option[ParsedComment] + comments: List[ParsedComment] )(implicit ctx: Context): lsp4j.MarkupContent = { val buf = new StringBuilder typeInfo.foreach { info => @@ -819,8 +819,7 @@ object DottyLanguageServer { |``` |""".stripMargin) } - - comment.foreach { comment => + comments.foreach { comment => buf.append(comment.renderAsMarkdown) } From f88ad867967c80e8827bef031f6188d620af5edf Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 16 Nov 2018 08:01:13 +0100 Subject: [PATCH 21/21] Address last review comments --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 + .../tools/dotc/interactive/Interactive.scala | 37 +------------------ .../languageserver/DottyLanguageServer.scala | 2 +- 3 files changed, 5 insertions(+), 36 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index c3e9316d32f3..eb0ad1dc3384 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1193,6 +1193,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { */ def importSelections(imp: Import)(implicit ctx: Context): List[Select] = { def imported(sym: Symbol, id: untpd.Ident, rename: Option[untpd.Ident]): List[Select] = { + // Give a zero-extent position to the qualifier to prevent it from being included several + // times in results in the language server. val noPosExpr = focusPositions(imp.expr) val selectTree = Select(noPosExpr, sym.name).withPos(id.pos) rename match { diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 840fd000b4b4..b33d6c0407e1 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -556,11 +556,11 @@ object Interactive { } /** - * Is this tree using a renaming introduced by an import statement? + * Is this tree using a renaming introduced by an import statement or an alias for `this`? * * @param tree The tree to inspect * @return True, if this tree's name is different than its symbol's name, indicating that - * it uses a renaming introduced by an import statement. + * it uses a renaming introduced by an import statement or an alias for `this`. */ def isRenamed(tree: NameTree)(implicit ctx: Context): Boolean = { val symbol = tree.symbol @@ -572,37 +572,4 @@ object Interactive { n0.stripModuleClassSuffix.toTermName eq n1.stripModuleClassSuffix.toTermName } - /** - * Is this tree immediately enclosing an import that renames a symbol to `toName`? - * - * @param toName The target name to check - * @param tree The tree to check - * @return True if this tree immediately encloses an import that renames a symbol to `toName`, - * false otherwise. - */ - def immediatelyEnclosesRenaming(toName: Name, inTree: Tree)(implicit ctx: Context): Boolean = { - def isImportRenaming(tree: Tree): Boolean = { - tree match { - case Import(_, selectors) => - selectors.exists { - case Thicket(_ :: Ident(rename) :: Nil) => sameName(rename, toName) - case _ => false - } - case _ => - false - } - } - - inTree match { - case PackageDef(_, stats) => - stats.exists(isImportRenaming) - case template: Template => - template.body.exists(isImportRenaming) - case Block(stats, _) => - stats.exists(isImportRenaming) - case _ => - false - } - } - } diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index b8ee33d04eb0..6fe1b228cb6a 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -415,7 +415,7 @@ class DottyLanguageServer extends LanguageServer val uriTrees = driver.openedTrees(uri) val path = Interactive.pathTo(uriTrees, pos) val syms = Interactive.enclosingSourceSymbols(path, pos) - val includes = Include.definitions | Include.references | Include.imports + val includes = Include.all.except(Include.linkedClass) syms.flatMap { sym => val refs = Interactive.findTreesMatching(uriTrees, includes, sym)