Skip to content

Commit 614265d

Browse files
authored
Merge pull request #4199 from dotty-staging/topic/go-to-definition-imports
IDE: Support jump to definition in imports
2 parents 4bd5115 + f88ad86 commit 614265d

File tree

17 files changed

+933
-186
lines changed

17 files changed

+933
-186
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,5 +1147,91 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
11471147
case _ =>
11481148
EmptyTree
11491149
}
1150+
1151+
/**
1152+
* The symbols that are imported with `expr.name`
1153+
*
1154+
* @param expr The base of the import statement
1155+
* @param name The name that is being imported.
1156+
* @return All the symbols that would be imported with `expr.name`.
1157+
*/
1158+
def importedSymbols(expr: Tree, name: Name)(implicit ctx: Context): List[Symbol] = {
1159+
def lookup(name: Name): Symbol = expr.tpe.member(name).symbol
1160+
val symbols =
1161+
List(lookup(name.toTermName),
1162+
lookup(name.toTypeName),
1163+
lookup(name.moduleClassName),
1164+
lookup(name.sourceModuleName))
1165+
1166+
symbols.map(_.sourceSymbol).filter(_.exists).distinct
1167+
}
1168+
1169+
/**
1170+
* All the symbols that are imported by the first selector of `imp` that matches
1171+
* `selectorPredicate`.
1172+
*
1173+
* @param imp The import statement to analyze
1174+
* @param selectorPredicate A test to find the selector to use.
1175+
* @return The symbols imported.
1176+
*/
1177+
def importedSymbols(imp: Import,
1178+
selectorPredicate: untpd.Tree => Boolean = util.common.alwaysTrue)
1179+
(implicit ctx: Context): List[Symbol] = {
1180+
imp.selectors.find(selectorPredicate) match {
1181+
case Some(id: untpd.Ident) =>
1182+
importedSymbols(imp.expr, id.name)
1183+
case Some(Thicket((id: untpd.Ident) :: (_: untpd.Ident) :: Nil)) =>
1184+
importedSymbols(imp.expr, id.name)
1185+
case _ =>
1186+
Nil
1187+
}
1188+
}
1189+
1190+
/**
1191+
* The list of select trees that resolve to the same symbols as the ones that are imported
1192+
* by `imp`.
1193+
*/
1194+
def importSelections(imp: Import)(implicit ctx: Context): List[Select] = {
1195+
def imported(sym: Symbol, id: untpd.Ident, rename: Option[untpd.Ident]): List[Select] = {
1196+
// Give a zero-extent position to the qualifier to prevent it from being included several
1197+
// times in results in the language server.
1198+
val noPosExpr = focusPositions(imp.expr)
1199+
val selectTree = Select(noPosExpr, sym.name).withPos(id.pos)
1200+
rename match {
1201+
case None =>
1202+
selectTree :: Nil
1203+
case Some(rename) =>
1204+
// Get the type of the symbol that is actually selected, and construct a select
1205+
// node with the new name and the type of the real symbol.
1206+
val name = if (sym.name.isTypeName) rename.name.toTypeName else rename.name
1207+
val actual = Select(noPosExpr, sym.name)
1208+
val renameTree = Select(noPosExpr, name).withPos(rename.pos).withType(actual.tpe)
1209+
selectTree :: renameTree :: Nil
1210+
}
1211+
}
1212+
1213+
imp.selectors.flatMap {
1214+
case Ident(nme.WILDCARD) =>
1215+
Nil
1216+
case id: untpd.Ident =>
1217+
importedSymbols(imp.expr, id.name).flatMap { sym =>
1218+
imported(sym, id, None)
1219+
}
1220+
case Thicket((id: untpd.Ident) :: (newName: untpd.Ident) :: Nil) =>
1221+
importedSymbols(imp.expr, id.name).flatMap { sym =>
1222+
imported(sym, id, Some(newName))
1223+
}
1224+
}
1225+
}
1226+
1227+
/** Replaces all positions in `tree` with zero-extent positions */
1228+
private def focusPositions(tree: Tree)(implicit ctx: Context): Tree = {
1229+
val transformer = new tpd.TreeMap {
1230+
override def transform(tree: Tree)(implicit ctx: Context): Tree = {
1231+
super.transform(tree).withPos(tree.pos.focus)
1232+
}
1233+
}
1234+
transformer.transform(tree)
1235+
}
11501236
}
11511237

compiler/src/dotty/tools/dotc/core/Symbols.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,26 @@ object Symbols {
624624
}
625625
}
626626

627+
/** A symbol related to `sym` that is defined in source code.
628+
*
629+
* @see enclosingSourceSymbols
630+
*/
631+
@annotation.tailrec final def sourceSymbol(implicit ctx: Context): Symbol =
632+
if (!denot.exists)
633+
this
634+
else if (denot.is(ModuleVal))
635+
this.moduleClass.sourceSymbol // The module val always has a zero-extent position
636+
else if (denot.is(Synthetic)) {
637+
val linked = denot.linkedClass
638+
if (linked.exists && !linked.is(Synthetic))
639+
linked
640+
else
641+
denot.owner.sourceSymbol
642+
}
643+
else if (denot.isPrimaryConstructor)
644+
denot.owner.sourceSymbol
645+
else this
646+
627647
/** The position of this symbol, or NoPosition if the symbol was not loaded
628648
* from source or from TASTY. This is always a zero-extent position.
629649
*

0 commit comments

Comments
 (0)