From 5180c93e5d87e7fa11aa5c09ce107e97467320d0 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 29 Aug 2018 13:04:42 +0200 Subject: [PATCH] Support go-to-definition on named arguments --- .../tools/dotc/interactive/Interactive.scala | 9 +- .../languageserver/DottyLanguageServer.scala | 27 +++++- .../tools/languageserver/DefinitionTest.scala | 82 +++++++++++++++++++ 3 files changed, 114 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index e051a144b1b3..7e645bf078bc 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -39,10 +39,15 @@ object Interactive { else path.head.tpe } - /** The closest enclosing tree with a symbol containing position `pos`. + /** The closest enclosing tree with a symbol containing position `pos`, or the `EmptyTree`. */ def enclosingTree(trees: List[SourceTree], pos: SourcePosition)(implicit ctx: Context): Tree = - pathTo(trees, pos).dropWhile(!_.symbol.exists).headOption.getOrElse(tpd.EmptyTree) + enclosingTree(pathTo(trees, pos)) + + /** The closes enclosing tree with a symbol, or the `EmptyTree`. + */ + def enclosingTree(path: List[Tree])(implicit ctx: Context): Tree = + path.dropWhile(!_.symbol.exists).headOption.getOrElse(tpd.EmptyTree) /** The source symbol of the closest enclosing tree with a symbol containing position `pos`. * diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index b8aa84aec9fe..62eb2c7f373f 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -248,8 +248,31 @@ class DottyLanguageServer extends LanguageServer implicit val ctx = driver.currentCtx val pos = sourcePosition(driver, uri, params.getPosition) - val enclTree = Interactive.enclosingTree(driver.openedTrees(uri), pos) - val sym = Interactive.sourceSymbol(enclTree.symbol) + val path = Interactive.pathTo(driver.openedTrees(uri), pos) + val enclTree = Interactive.enclosingTree(path) + + val sym = { + val sym = 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 + } else { + val classTree = funSym.topLevelClass.asClass.tree + tpd.defPath(funSym, classTree).lastOption.flatMap { + case DefDef(_, _, paramss, _, _) => + paramss.flatten.find(_.name == name).map(_.symbol) + }.getOrElse(fn.symbol) + } + + case _ => + enclTree.symbol + } + Interactive.sourceSymbol(sym) + } if (sym == NoSymbol) Nil.asJava else { diff --git a/language-server/test/dotty/tools/languageserver/DefinitionTest.scala b/language-server/test/dotty/tools/languageserver/DefinitionTest.scala index a95efe2258f9..363f27c7d951 100644 --- a/language-server/test/dotty/tools/languageserver/DefinitionTest.scala +++ b/language-server/test/dotty/tools/languageserver/DefinitionTest.scala @@ -3,6 +3,7 @@ package dotty.tools.languageserver import org.junit.Test import dotty.tools.languageserver.util.Code._ +import dotty.tools.languageserver.util.embedded.CodeMarker class DefinitionTest { @@ -43,4 +44,85 @@ class DefinitionTest { ).definition(m3 to m4, List(m1 to m2)) } + @Test def goToDefNamedArg: Unit = { + code"""object Foo { + def foo(${m1}x${m2}: Int) = ${m3}x${m4} + foo(${m5}x${m6} = 2) + }""".withSource + .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 goToDefNamedArgOverload: Unit = { + val m9 = new CodeMarker("m9") + val m10 = new CodeMarker("m10") + val m11 = new CodeMarker("m11") + val m12 = new CodeMarker("m12") + val m13 = new CodeMarker("m13") + val m14 = new CodeMarker("m14") + + code"""object Foo { + def foo(${m1}x${m2}: String): String = ${m3}x${m4} + def foo(${m5}x${m6}: Int): String = foo(${m7}x${m8} = ${m9}x${m10}.toString) + foo(${m11}x${m12} = "a") + foo(${m13}x${m14} = 2) + }""".withSource + .definition(m1 to m2, List(m1 to m2)) + .definition(m3 to m4, List(m1 to m2)) + .definition(m5 to m6, List(m5 to m6)) + .definition(m7 to m8, List(m1 to m2)) + .definition(m9 to m10, List(m5 to m6)) + .definition(m11 to m12, List(m1 to m2)) + .definition(m13 to m14, List(m5 to m6)) + } + + @Test def goToConstructorNamedArg: Unit = { + withSources( + code"""class Foo(${m1}x${m2}: Int)""", + code"""class Bar extends Foo(${m3}x${m4} = 5)""", + code"""object Buzz { new Foo(${m5}x${m6} = 2) }""" + ) .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 goToConstructorNamedArgOverload: Unit = { + val m9 = new CodeMarker("m9") + val m10 = new CodeMarker("m10") + val m11 = new CodeMarker("m11") + val m12 = new CodeMarker("m12") + + withSources( + code"""class Foo(${m1}x${m2}: String) { + def this(${m3}x${m4}: Int) = this(${m5}x${m6} = ${m7}x${m8}.toString) + }""", + code"""object Bar { + new Foo(${m9}x${m10} = 1) + new Foo(${m11}x${m12} = "a") + }""" + ) .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(m3 to m4)) + .definition(m9 to m10, List(m3 to m4)) + .definition(m11 to m12, List(m1 to m2)) + } + + @Test def goToParamCopyMethod: Unit = { + val m9 = new CodeMarker("m9") + val m10 = new CodeMarker("m10") + + withSources( + code"""case class Foo(${m1}x${m2}: Int, ${m3}y${m4}: String)""", + code"""object Bar { + Foo(0, "a").copy(${m5}x${m6} = 1, ${m7}y${m8} = "b") + Foo(2, "c").copy(${m9}y${m10} = "d")""" + ) .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(m3 to m4)) + .definition(m9 to m10, List(m3 to m4)) + } + }