Skip to content

Commit 6eddf68

Browse files
authored
Merge pull request #5462 from dotty-staging/topic/ide-multi-project-rename
Support multi-project rename
2 parents 2b84d7c + 72053ca commit 6eddf68

File tree

4 files changed

+152
-36
lines changed

4 files changed

+152
-36
lines changed

compiler/src/dotty/tools/dotc/interactive/Interactive.scala

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@ object Interactive {
4343
/** Include trees whose symbol is overridden by `sym` */
4444
val overridden: Set = Set(1 << 0)
4545

46-
/**
47-
* Include trees whose symbol overrides `sym` (but for performance only in same source
48-
* file)
49-
*/
46+
/** Include trees whose symbol overrides `sym` */
5047
val overriding: Set = Set(1 << 1)
5148

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

330327
/**
331-
* Find the definitions of the symbol at the end of `path`.
328+
* Find the definitions of the symbol at the end of `path`. In the case of an import node,
329+
* all imported symbols will be considered.
332330
*
333331
* @param path The path to the symbol for which we want the definitions.
334332
* @param driver The driver responsible for `path`.
335333
* @return The definitions for the symbol at the end of `path`.
336334
*/
337-
def findDefinitions(path: List[Tree], pos: SourcePosition, driver: InteractiveDriver)(implicit ctx: Context): List[SourceTree] = {
338-
enclosingSourceSymbols(path, pos).flatMap { sym =>
339-
val enclTree = enclosingTree(path)
340-
val includeLocal = if (sym.exists && sym.isLocal) Include.local else Include.empty
341-
342-
val (trees, include) =
343-
if (enclTree.isInstanceOf[MemberDef])
344-
(driver.allTreesContaining(sym.name.sourceModuleName.toString),
345-
Include.definitions | Include.overriding | Include.overridden)
346-
else sym.topLevelClass match {
347-
case cls: ClassSymbol =>
348-
val trees = Option(cls.sourceFile).flatMap(InteractiveDriver.toUriOption) match {
349-
case Some(uri) if driver.openedTrees.contains(uri) =>
350-
driver.openedTrees(uri)
351-
case _ => // Symbol comes from the classpath
352-
SourceTree.fromSymbol(cls).toList
353-
}
354-
(trees, Include.definitions | Include.overriding)
355-
case _ =>
356-
(Nil, Include.empty)
357-
}
335+
def findDefinitions(path: List[Tree], pos: SourcePosition, driver: InteractiveDriver): List[SourceTree] = {
336+
implicit val ctx = driver.currentCtx
337+
val enclTree = enclosingTree(path)
338+
val includeOverridden = enclTree.isInstanceOf[MemberDef]
339+
val symbols = enclosingSourceSymbols(path, pos)
340+
val includeExternal = symbols.exists(!_.isLocal)
341+
findDefinitions(symbols, driver, includeOverridden, includeExternal)
342+
}
358343

344+
/**
345+
* Find the definitions of `symbols`.
346+
*
347+
* @param symbols The list of symbols for which to find a definition.
348+
* @param driver The driver responsible for the given symbols.
349+
* @param includeOverridden If true, also include the symbols overridden by any of `symbols`.
350+
* @param includeExternal If true, also look for definitions on the classpath.
351+
* @return The definitions for the symbols in `symbols`, and if `includeOverridden` is set, the
352+
* definitions for the symbols that they override.
353+
*/
354+
def findDefinitions(symbols: List[Symbol],
355+
driver: InteractiveDriver,
356+
includeOverridden: Boolean,
357+
includeExternal: Boolean): List[SourceTree] = {
358+
implicit val ctx = driver.currentCtx
359+
val include = Include.definitions | Include.overriding |
360+
(if (includeOverridden) Include.overridden else Include.empty)
361+
symbols.flatMap { sym =>
362+
val name = sym.name.sourceModuleName.toString
363+
val includeLocal = if (sym.exists && sym.isLocal) Include.local else Include.empty
364+
val trees =
365+
if (includeExternal) driver.allTreesContaining(name)
366+
else driver.sourceTreesContaining(name)
359367
findTreesMatching(trees, include | includeLocal, sym)
360368
}
361369
}

language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,9 @@ class DottyLanguageServer extends LanguageServer
289289
/*isIncomplete = */ false, items.map(completionItem).asJava))
290290
}
291291

292-
/** If cursor is on a reference, show its definition and all overriding definitions in
293-
* the same source as the primary definition.
292+
/** If cursor is on a reference, show its definition and all overriding definitions.
294293
* If cursor is on a definition, show this definition together with all overridden
295-
* and overriding definitions (in all sources).
294+
* and overriding definitions.
296295
*/
297296
override def definition(params: TextDocumentPositionParams) = computeAsync { cancelToken =>
298297
val uri = new URI(params.getTextDocument.getUri)
@@ -382,18 +381,24 @@ class DottyLanguageServer extends LanguageServer
382381
List(
383382
RENAME_OVERRIDDEN -> (() => (Include.all, syms.flatMap(s => s :: s.allOverriddenSymbols.toList))),
384383
RENAME_NO_OVERRIDDEN -> (() => (Include.all.except(Include.overridden), syms)))
385-
).get.getOrElse((Include.empty, List.empty[Symbol]))
384+
).get.getOrElse((Include.empty, Nil))
386385
} else {
387386
(Include.all, syms)
388387
}
389388

390389
val names = allSymbols.map(_.name.sourceModuleName).toSet
391-
val trees = names.flatMap(name => driver.allTreesContaining(name.toString)).toList
392-
allSymbols.flatMap { sym =>
393-
Interactive.findTreesMatching(trees,
394-
include,
395-
sym,
396-
t => names.exists(Interactive.sameName(t.name, _)))
390+
val definitions = Interactive.findDefinitions(allSymbols, driver, include.isOverridden, includeExternal = true)
391+
val perProjectInfo = inProjectsSeeing(driver, definitions, allSymbols)
392+
393+
perProjectInfo.flatMap { (remoteDriver, ctx, definitions) =>
394+
definitions.flatMap { definition =>
395+
val name = definition.name(ctx).sourceModuleName.toString
396+
val trees = remoteDriver.sourceTreesContaining(name)(ctx)
397+
Interactive.findTreesMatching(trees,
398+
include,
399+
definition,
400+
t => names.exists(Interactive.sameName(t.name, _)))(ctx)
401+
}
397402
}
398403
}
399404

language-server/test/dotty/tools/languageserver/DefinitionTest.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,4 +322,26 @@ class DefinitionTest {
322322
.definition(m11 to m12, List(m3 to m4))
323323
}
324324

325+
@Test def definitionShowOverrides: Unit = {
326+
withSources(
327+
code"""class A { def ${m1}foo${m2}: Int = 0 }""",
328+
code"""class B extends A { def ${m3}foo${m4}: Int = 1 }""",
329+
code"""class C extends A { def ${m5}foo${m6}: Int = 2 }""",
330+
code"""class D extends C { def ${m7}foo${m8}: Int = 3 }""",
331+
code"""object O {
332+
val a = new A().${m9}foo${m10}
333+
val b = new B().${m11}foo${m12}
334+
val c = new C().${m13}foo${m14}
335+
val d = new D().${m15}foo${m16}
336+
}"""
337+
).definition(m1 to m2, List(m1 to m2, m3 to m4, m5 to m6, m7 to m8))
338+
.definition(m3 to m4, List(m1 to m2, m3 to m4))
339+
.definition(m5 to m6, List(m1 to m2, m5 to m6, m7 to m8))
340+
.definition(m7 to m8, List(m1 to m2, m5 to m6, m7 to m8))
341+
.definition(m9 to m10, List(m1 to m2, m3 to m4, m5 to m6, m7 to m8))
342+
.definition(m11 to m12, List(m3 to m4))
343+
.definition(m13 to m14, List(m5 to m6, m7 to m8))
344+
.definition(m15 to m16, List(m7 to m8))
345+
}
346+
325347
}

language-server/test/dotty/tools/languageserver/RenameTest.scala

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,85 @@ class RenameTest {
251251
testRename(m2)
252252
}
253253

254+
@Test def renameValMultiProject: Unit = {
255+
def testRename(m: CodeMarker, expectations: Set[CodeRange]) = {
256+
val p0 = Project.withSources(
257+
code"""object A { val ${m1}foo${m2} = 0 }"""
258+
)
259+
260+
val p1 = Project.dependingOn(p0).withSources(
261+
code"""object B { val ${m3}bar${m4} = A.${m5}foo${m6} }"""
262+
)
263+
264+
val p2 = Project.dependingOn(p1).withSources(
265+
code"""object C { val ${m7}baz${m8} = A.${m9}foo${m10} + B.${m11}bar${m12} }"""
266+
)
267+
268+
withProjects(p0, p1, p2).rename(m, "NewName", expectations)
269+
}
270+
271+
testRename(m1, Set(m1 to m2, m5 to m6, m9 to m10))
272+
testRename(m5, Set(m1 to m2, m5 to m6, m9 to m10))
273+
testRename(m9, Set(m1 to m2, m5 to m6, m9 to m10))
274+
275+
testRename(m3, Set(m3 to m4, m11 to m12))
276+
testRename(m11, Set(m3 to m4, m11 to m12))
277+
278+
testRename(m7, Set(m7 to m8))
279+
}
280+
281+
@Test def renameClassMultiProject: Unit = {
282+
val m21 = new CodeMarker("m21")
283+
val m22 = new CodeMarker("m22")
284+
val m23 = new CodeMarker("m23")
285+
val m24 = new CodeMarker("m24")
286+
val m25 = new CodeMarker("m25")
287+
val m26 = new CodeMarker("m26")
288+
val m27 = new CodeMarker("m27")
289+
val m28 = new CodeMarker("m28")
290+
def testRename(m: CodeMarker, expectations: Set[CodeRange]) = {
291+
val p0 = Project.withSources(
292+
code"""package a
293+
object ${m1}A${m2} { class ${m3}B${m4} }"""
294+
)
295+
296+
val p1 = Project.dependingOn(p0).withSources(
297+
code"""package b
298+
import a.${m5}A${m6}.{${m7}B${m8} => ${m9}AB${m10}}
299+
object ${m11}B${m12} { class ${m13}C${m14} extends ${m15}AB${m16} }"""
300+
)
301+
302+
val p2 = Project.dependingOn(p1).withSources(
303+
code"""package c
304+
import b.${m17}B${m18}.{${m19}C${m20} => ${m21}BC${m22}}
305+
object ${m23}C${m24} { class ${m25}D${m26} extends ${m27}BC${m28} }"""
306+
)
307+
308+
withProjects(p0, p1, p2).rename(m, "NewName", expectations)
309+
}
310+
311+
testRename(m1, Set(m1 to m2, m5 to m6))
312+
testRename(m5, Set(m1 to m2, m5 to m6))
313+
314+
testRename(m3, Set(m3 to m4, m7 to m8))
315+
testRename(m7, Set(m3 to m4, m7 to m8))
316+
317+
testRename(m9, Set(m9 to m10, m15 to m16))
318+
testRename(m15, Set(m9 to m10, m15 to m16))
319+
320+
testRename(m11, Set(m11 to m12, m17 to m18))
321+
testRename(m17, Set(m11 to m12, m17 to m18))
322+
323+
testRename(m13, Set(m13 to m14, m19 to m20))
324+
testRename(m19, Set(m13 to m14, m19 to m20))
325+
326+
testRename(m21, Set(m21 to m22, m27 to m28))
327+
testRename(m27, Set(m21 to m22, m27 to m28))
328+
329+
testRename(m23, Set(m23 to m24))
330+
331+
testRename(m25, Set(m25 to m26))
332+
333+
}
334+
254335
}

0 commit comments

Comments
 (0)