Skip to content

Support multi-project rename #5462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 34 additions & 26 deletions compiler/src/dotty/tools/dotc/interactive/Interactive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ object Interactive {
/** 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)
*/
/** Include trees whose symbol overrides `sym` */
val overriding: Set = Set(1 << 1)

/** Include references */
Expand Down Expand Up @@ -328,34 +325,45 @@ object Interactive {
path.find(_.isInstanceOf[DefTree]).getOrElse(EmptyTree)

/**
* Find the definitions of the symbol at the end of `path`.
* Find the definitions of the symbol at the end of `path`. In the case of an import node,
* all imported symbols will be considered.
*
* @param path The path to the symbol for which we want the definitions.
* @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[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])
(driver.allTreesContaining(sym.name.sourceModuleName.toString),
Include.definitions | Include.overriding | Include.overridden)
else sym.topLevelClass match {
case cls: ClassSymbol =>
val trees = Option(cls.sourceFile).flatMap(InteractiveDriver.toUriOption) match {
case Some(uri) if driver.openedTrees.contains(uri) =>
driver.openedTrees(uri)
case _ => // Symbol comes from the classpath
SourceTree.fromSymbol(cls).toList
}
(trees, Include.definitions | Include.overriding)
case _ =>
(Nil, Include.empty)
}
def findDefinitions(path: List[Tree], pos: SourcePosition, driver: InteractiveDriver): List[SourceTree] = {
implicit val ctx = driver.currentCtx
val enclTree = enclosingTree(path)
val includeOverridden = enclTree.isInstanceOf[MemberDef]
val symbols = enclosingSourceSymbols(path, pos)
val includeExternal = symbols.exists(!_.isLocal)
findDefinitions(symbols, driver, includeOverridden, includeExternal)
}

/**
* Find the definitions of `symbols`.
*
* @param symbols The list of symbols for which to find a definition.
* @param driver The driver responsible for the given symbols.
* @param includeOverridden If true, also include the symbols overridden by any of `symbols`.
* @param includeExternal If true, also look for definitions on the classpath.
* @return The definitions for the symbols in `symbols`, and if `includeOverridden` is set, the
* definitions for the symbols that they override.
*/
def findDefinitions(symbols: List[Symbol],
driver: InteractiveDriver,
includeOverridden: Boolean,
includeExternal: Boolean): List[SourceTree] = {
implicit val ctx = driver.currentCtx
val include = Include.definitions | Include.overriding |
(if (includeOverridden) Include.overridden else Include.empty)
symbols.flatMap { sym =>
val name = sym.name.sourceModuleName.toString
val includeLocal = if (sym.exists && sym.isLocal) Include.local else Include.empty
val trees =
if (includeExternal) driver.allTreesContaining(name)
else driver.sourceTreesContaining(name)
findTreesMatching(trees, include | includeLocal, sym)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,9 @@ class DottyLanguageServer extends LanguageServer
/*isIncomplete = */ false, items.map(completionItem).asJava))
}

/** If cursor is on a reference, show its definition and all overriding definitions in
* the same source as the primary definition.
/** If cursor is on a reference, show its definition and all overriding definitions.
* If cursor is on a definition, show this definition together with all overridden
* and overriding definitions (in all sources).
* and overriding definitions.
*/
override def definition(params: TextDocumentPositionParams) = computeAsync { cancelToken =>
val uri = new URI(params.getTextDocument.getUri)
Expand Down Expand Up @@ -382,18 +381,24 @@ class DottyLanguageServer extends LanguageServer
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]))
).get.getOrElse((Include.empty, Nil))
} else {
(Include.all, syms)
}

val names = allSymbols.map(_.name.sourceModuleName).toSet
val trees = names.flatMap(name => driver.allTreesContaining(name.toString)).toList
allSymbols.flatMap { sym =>
Interactive.findTreesMatching(trees,
include,
sym,
t => names.exists(Interactive.sameName(t.name, _)))
val definitions = Interactive.findDefinitions(allSymbols, driver, include.isOverridden, includeExternal = true)
val perProjectInfo = inProjectsSeeing(driver, definitions, allSymbols)

perProjectInfo.flatMap { (remoteDriver, ctx, definitions) =>
definitions.flatMap { definition =>
val name = definition.name(ctx).sourceModuleName.toString
val trees = remoteDriver.sourceTreesContaining(name)(ctx)
Interactive.findTreesMatching(trees,
include,
definition,
t => names.exists(Interactive.sameName(t.name, _)))(ctx)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,26 @@ class DefinitionTest {
.definition(m11 to m12, List(m3 to m4))
}

@Test def definitionShowOverrides: Unit = {
withSources(
code"""class A { def ${m1}foo${m2}: Int = 0 }""",
code"""class B extends A { def ${m3}foo${m4}: Int = 1 }""",
code"""class C extends A { def ${m5}foo${m6}: Int = 2 }""",
code"""class D extends C { def ${m7}foo${m8}: Int = 3 }""",
code"""object O {
val a = new A().${m9}foo${m10}
val b = new B().${m11}foo${m12}
val c = new C().${m13}foo${m14}
val d = new D().${m15}foo${m16}
}"""
).definition(m1 to m2, List(m1 to m2, m3 to m4, m5 to m6, m7 to m8))
.definition(m3 to m4, List(m1 to m2, m3 to m4))
.definition(m5 to m6, List(m1 to m2, m5 to m6, m7 to m8))
.definition(m7 to m8, List(m1 to m2, m5 to m6, m7 to m8))
.definition(m9 to m10, List(m1 to m2, m3 to m4, m5 to m6, m7 to m8))
.definition(m11 to m12, List(m3 to m4))
.definition(m13 to m14, List(m5 to m6, m7 to m8))
.definition(m15 to m16, List(m7 to m8))
}

}
81 changes: 81 additions & 0 deletions language-server/test/dotty/tools/languageserver/RenameTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,85 @@ class RenameTest {
testRename(m2)
}

@Test def renameValMultiProject: Unit = {
def testRename(m: CodeMarker, expectations: Set[CodeRange]) = {
val p0 = Project.withSources(
code"""object A { val ${m1}foo${m2} = 0 }"""
)

val p1 = Project.dependingOn(p0).withSources(
code"""object B { val ${m3}bar${m4} = A.${m5}foo${m6} }"""
)

val p2 = Project.dependingOn(p1).withSources(
code"""object C { val ${m7}baz${m8} = A.${m9}foo${m10} + B.${m11}bar${m12} }"""
)

withProjects(p0, p1, p2).rename(m, "NewName", expectations)
}

testRename(m1, Set(m1 to m2, m5 to m6, m9 to m10))
testRename(m5, Set(m1 to m2, m5 to m6, m9 to m10))
testRename(m9, Set(m1 to m2, m5 to m6, m9 to m10))

testRename(m3, Set(m3 to m4, m11 to m12))
testRename(m11, Set(m3 to m4, m11 to m12))

testRename(m7, Set(m7 to m8))
}

@Test def renameClassMultiProject: Unit = {
val m21 = new CodeMarker("m21")
val m22 = new CodeMarker("m22")
val m23 = new CodeMarker("m23")
val m24 = new CodeMarker("m24")
val m25 = new CodeMarker("m25")
val m26 = new CodeMarker("m26")
val m27 = new CodeMarker("m27")
val m28 = new CodeMarker("m28")
def testRename(m: CodeMarker, expectations: Set[CodeRange]) = {
val p0 = Project.withSources(
code"""package a
object ${m1}A${m2} { class ${m3}B${m4} }"""
)

val p1 = Project.dependingOn(p0).withSources(
code"""package b
import a.${m5}A${m6}.{${m7}B${m8} => ${m9}AB${m10}}
object ${m11}B${m12} { class ${m13}C${m14} extends ${m15}AB${m16} }"""
)

val p2 = Project.dependingOn(p1).withSources(
code"""package c
import b.${m17}B${m18}.{${m19}C${m20} => ${m21}BC${m22}}
object ${m23}C${m24} { class ${m25}D${m26} extends ${m27}BC${m28} }"""
)

withProjects(p0, p1, p2).rename(m, "NewName", expectations)
}

testRename(m1, Set(m1 to m2, m5 to m6))
testRename(m5, Set(m1 to m2, m5 to m6))

testRename(m3, Set(m3 to m4, m7 to m8))
testRename(m7, Set(m3 to m4, m7 to m8))

testRename(m9, Set(m9 to m10, m15 to m16))
testRename(m15, Set(m9 to m10, m15 to m16))

testRename(m11, Set(m11 to m12, m17 to m18))
testRename(m17, Set(m11 to m12, m17 to m18))

testRename(m13, Set(m13 to m14, m19 to m20))
testRename(m19, Set(m13 to m14, m19 to m20))

testRename(m21, Set(m21 to m22, m27 to m28))
testRename(m27, Set(m21 to m22, m27 to m28))

testRename(m23, Set(m23 to m24))

testRename(m25, Set(m25 to m26))

}

}