diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index b33d6c0407e1..78ee8aabfc9e 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -8,6 +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 transform.SymUtils.decorateSymbol import util.Positions._, util.SourceFile, util.SourcePosition import core.Denotations.SingleDenotation import NameKinds.SimpleNameKind @@ -33,6 +34,7 @@ object Interactive { def isDefinitions: Boolean = (bits & definitions.bits) != 0 def isLinkedClass: Boolean = (bits & linkedClass.bits) != 0 def isImports: Boolean = (bits & imports.bits) != 0 + def isLocal: Boolean = (bits & local.bits) != 0 } /** The empty set */ @@ -59,6 +61,9 @@ object Interactive { /** Include imports in the results */ val imports: Set = Set(1 << 5) + /** Include local symbols, inspect local trees */ + val local: Set = Set(1 << 6) + /** All the flags */ val all: Set = Set(~0) } @@ -317,24 +322,32 @@ object Interactive { else 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] = { - val predicate: NameTree => Boolean = _.name.toString.contains(nameSubstring) - namedTrees(trees, Include.empty, predicate) - } - /** Find named trees with a non-empty position satisfying `treePredicate` in `trees`. * - * @param includeReferences If true, include references and not just definitions + * @param trees The trees to inspect. + * @param include Whether to include references, definitions, etc. + * @param treePredicate An additional predicate that the trees must match. + * @return The trees with a non-empty position satisfying `treePredicate`. */ - def namedTrees(trees: List[SourceTree], include: Include.Set, treePredicate: NameTree => Boolean) - (implicit ctx: Context): List[SourceTree] = safely { + def namedTrees(trees: List[SourceTree], + include: Include.Set, + treePredicate: NameTree => Boolean = util.common.alwaysTrue + )(implicit ctx: Context): List[SourceTree] = safely { val buf = new mutable.ListBuffer[SourceTree] def traverser(source: SourceFile) = { new untpd.TreeTraverser { + private def handle(utree: untpd.NameTree): Unit = { + val tree = utree.asInstanceOf[tpd.NameTree] + if (tree.symbol.exists + && !tree.symbol.is(Synthetic) + && !tree.symbol.isPrimaryConstructor + && tree.pos.exists + && !tree.pos.isZeroExtent + && (include.isReferences || isDefinition(tree)) + && treePredicate(tree)) + buf += SourceTree(tree, source) + } override def traverse(tree: untpd.Tree)(implicit ctx: Context) = { tree match { case imp: untpd.Import if include.isImports && tree.hasType => @@ -342,15 +355,11 @@ object Interactive { val selections = tpd.importSelections(tree) traverse(imp.expr) selections.foreach(traverse) + case utree: untpd.ValOrDefDef if tree.hasType => + handle(utree) + if (include.isLocal) traverseChildren(tree) case utree: untpd.NameTree if tree.hasType => - val tree = utree.asInstanceOf[tpd.NameTree] - if (tree.symbol.exists - && !tree.symbol.is(Synthetic) - && tree.pos.exists - && !tree.pos.isZeroExtent - && (include.isReferences || isDefinition(tree)) - && treePredicate(tree)) - buf += SourceTree(tree, source) + handle(utree) traverseChildren(tree) case tree: untpd.Inlined => traverse(tree.call) @@ -381,8 +390,7 @@ object Interactive { )(implicit ctx: Context): List[SourceTree] = { val linkedSym = symbol.linkedClass val fullPredicate: NameTree => Boolean = tree => - ( !tree.symbol.isPrimaryConstructor - && (includes.isDefinitions || !Interactive.isDefinition(tree)) + ( (includes.isDefinitions || !Interactive.isDefinition(tree)) && ( Interactive.matchSymbol(tree, symbol, includes) || ( includes.isLinkedClass && linkedSym.exists @@ -482,6 +490,7 @@ object Interactive { def findDefinitions(path: List[Tree], pos: SourcePosition, driver: InteractiveDriver)(implicit ctx: Context): List[SourceTree] = { enclosingSourceSymbols(path, pos).flatMap { sym => val enclTree = enclosingTree(path) + val includeLocal = if (sym.exists && sym.isLocal) Include.local else Include.empty val (trees, include) = if (enclTree.isInstanceOf[MemberDef]) @@ -500,7 +509,7 @@ object Interactive { (Nil, Include.empty) } - findTreesMatching(trees, include, sym) + findTreesMatching(trees, include | includeLocal, sym) } } diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index d4d38f38a4d5..a8dd44db60fb 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -311,7 +311,7 @@ class DottyLanguageServer extends LanguageServer val includes = { val includeDeclaration = params.getContext.isIncludeDeclaration - Include.references | Include.overriding | Include.imports | + Include.references | Include.overriding | Include.imports | Include.local | (if (includeDeclaration) Include.definitions else Include.empty) } @@ -457,7 +457,7 @@ class DottyLanguageServer extends LanguageServer val uriTrees = driver.openedTrees(uri) - val defs = Interactive.namedTrees(uriTrees, Include.empty, _ => true) + val defs = Interactive.namedTrees(uriTrees, Include.empty) (for { d <- defs if !isWorksheetWrapper(d) info <- symbolInfo(d.tree.symbol, d.namePos, positionMapperFor(d.source)) @@ -470,8 +470,8 @@ class DottyLanguageServer extends LanguageServer drivers.values.toList.flatMap { driver => implicit val ctx = driver.currentCtx - val trees = driver.allTrees - val defs = Interactive.namedTrees(trees, nameSubstring = query) + val trees = driver.sourceTreesContaining(query) + val defs = Interactive.namedTrees(trees, Include.empty, _.name.toString.contains(query)) defs.flatMap(d => symbolInfo(d.tree.symbol, d.namePos, positionMapperFor(d.source))) }.asJava } @@ -499,7 +499,7 @@ class DottyLanguageServer extends LanguageServer val predicates = definitions.map(Interactive.implementationFilter(_)(ctx)) tree => predicates.exists(_(tree)) } - val matches = Interactive.namedTrees(trees, Include.empty, predicate)(ctx) + val matches = Interactive.namedTrees(trees, Include.local, predicate)(ctx) matches.map(tree => location(tree.namePos(ctx), positionMapperFor(tree.source))) } }.toList diff --git a/language-server/test/dotty/tools/languageserver/DocumentSymbolTest.scala b/language-server/test/dotty/tools/languageserver/DocumentSymbolTest.scala index a19137865b76..b27fbf0a6e40 100644 --- a/language-server/test/dotty/tools/languageserver/DocumentSymbolTest.scala +++ b/language-server/test/dotty/tools/languageserver/DocumentSymbolTest.scala @@ -38,4 +38,10 @@ class DocumentSymbolTest { .documentSymbol(m1, (m1 to m2).symInfo("Foo", SymbolKind.Module), (m3 to m4).symInfo("Foo", SymbolKind.Class)) } + + @Test def documentSymbolSynthetic: Unit = { + code"""case class ${m1}Foo${m2}(${m3}x${m4}: Int)""".withSource + .documentSymbol(m1, (m1 to m2).symInfo("Foo", SymbolKind.Class), + (m3 to m4).symInfo("x", SymbolKind.Field, "Foo")) + } } diff --git a/language-server/test/dotty/tools/languageserver/ReferencesTest.scala b/language-server/test/dotty/tools/languageserver/ReferencesTest.scala index bfe811b2751e..b99947bfe9bb 100644 --- a/language-server/test/dotty/tools/languageserver/ReferencesTest.scala +++ b/language-server/test/dotty/tools/languageserver/ReferencesTest.scala @@ -346,4 +346,14 @@ class ReferencesTest { .references(m11 to m12, List(m9 to m10, m11 to m12), withDecl = false) } + @Test def referenceInsideLocalMember: Unit = { + withSources( + code"""object A { + | val ${m1}foo${m2} = 0 + | def fizz = println(${m3}foo${m4}) + |}""" + ).references(m1 to m2, List(m1 to m2, m3 to m4), withDecl = true) + .references(m1 to m2, List(m3 to m4), withDecl = false) + } + } diff --git a/language-server/test/dotty/tools/languageserver/SymbolTest.scala b/language-server/test/dotty/tools/languageserver/SymbolTest.scala index e9e6318b8e38..c586c5c17aba 100644 --- a/language-server/test/dotty/tools/languageserver/SymbolTest.scala +++ b/language-server/test/dotty/tools/languageserver/SymbolTest.scala @@ -39,4 +39,26 @@ class SymbolTest { .symbol("Foo", (m1 to m2).symInfo("Foo", SymbolKind.Module), (m3 to m4).symInfo("Foo", SymbolKind.Class)) } + + @Test def multipleProjects0: Unit = { + val p0 = Project.withSources( + code"""class ${m1}Foo${m2}""" + ) + + val p1 = Project.dependingOn(p0).withSources( + code"""class ${m3}Bar${m4} extends Foo""" + ) + + withProjects(p0, p1) + .symbol("Foo", (m1 to m2).symInfo("Foo", SymbolKind.Class)) + } + + @Test def noLocalSymbols: Unit = { + code"""object O { + def foo = { + val hello = 0 + } + }""".withSource + .symbol("hello") + } }