Skip to content

Commit 5d3b040

Browse files
committed
Redo completions
1 parent 9747f6b commit 5d3b040

File tree

5 files changed

+173
-10
lines changed

5 files changed

+173
-10
lines changed

compiler/src/dotty/tools/dotc/config/Printers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ object Printers {
1919
val cyclicErrors: Printer = noPrinter
2020
val dottydoc: Printer = noPrinter
2121
val exhaustivity: Printer = noPrinter
22-
val incremental: Printer = noPrinter
2322
val gadts: Printer = noPrinter
2423
val hk: Printer = noPrinter
2524
val implicits: Printer = noPrinter
2625
val implicitsDetailed: Printer = noPrinter
2726
val inlining: Printer = noPrinter
27+
val interactiv: Printer = new Printer
2828
val overload: Printer = noPrinter
2929
val patmatch: Printer = noPrinter
3030
val pickling: Printer = noPrinter

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import util.SimpleIdentityMap
1818
import util.Stats
1919
import java.util.WeakHashMap
2020
import config.Config
21-
import config.Printers.{incremental, noPrinter}
21+
import config.Printers.noPrinter
2222
import reporting.diagnostic.Message
2323
import reporting.diagnostic.messages.BadSymbolicReference
2424
import reporting.trace

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

Lines changed: 166 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Contexts._, Flags._, Names._, NameOps._, Symbols._, SymDenotations._, Tre
1111
import util.Positions._, util.SourcePosition
1212
import core.Denotations.SingleDenotation
1313
import NameKinds.SimpleNameKind
14+
import config.Printers.interactiv
1415

1516
/** High-level API to get information out of typed trees, designed to be used by IDEs.
1617
*
@@ -36,6 +37,7 @@ object Interactive {
3637
if (path.isEmpty) NoType
3738
else path.head.tpe
3839
}
40+
3941
/** The closest enclosing tree with a symbol containing position `pos`.
4042
*/
4143
def enclosingTree(trees: List[SourceTree], pos: SourcePosition)(implicit ctx: Context): Tree =
@@ -95,6 +97,7 @@ object Interactive {
9597
*
9698
* @return offset and list of symbols for possible completions
9799
*/
100+
// deprecated
98101
def completions(trees: List[SourceTree], pos: SourcePosition)(implicit ctx: Context): (Int, List[Symbol]) = {
99102
val path = pathTo(trees, pos)
100103
val boundary = enclosingDefinitionInPath(path).symbol
@@ -122,6 +125,104 @@ object Interactive {
122125
.getOrElse((0, Nil))
123126
}
124127

128+
/** Get possible completions from tree at `pos`
129+
*
130+
* @return offset and list of symbols for possible completions
131+
*/
132+
def completions(pos: SourcePosition)(implicit ctx: Context): (Int, List[Symbol]) = {
133+
val path = pathTo(ctx.compilationUnit.tpdTree, pos.pos)
134+
computeCompletions(pos, path)(contextOfPath(path))
135+
}
136+
137+
private def computeCompletions(pos: SourcePosition, path: List[Tree])(implicit ctx: Context): (Int, List[Symbol]) = {
138+
val completions = Scopes.newScope.openForMutations
139+
140+
val (completionPos, prefix) = path match {
141+
case (ref: RefTree) :: _ =>
142+
(ref.pos.point, ref.name.toString.take(ref.pos.end - ref.pos.point - 1))
143+
case (id @ Ident(name)) :: _ =>
144+
if.pos
145+
getScopeCompletions(ctx)
146+
id.pos.point
147+
case _ =>
148+
getScopeCompletions(ctx)
149+
0
150+
151+
def add(sym: Symbol) =
152+
if (sym.exists && !completions.lookup(sym.name).exists)
153+
completions.enter(sym)
154+
155+
def addMember(site: Type, name: Name) =
156+
if (!completions.lookup(name).exists)
157+
for (alt <- site.member(name).alternatives)
158+
completions.enter(alt.symbol)
159+
160+
def allMembers(site: Type, superAccess: Boolean = true) =
161+
site.membersBasedOnFlags(EmptyFlags, EmptyFlags).map(_.accessibleFrom(site, superAccess))
162+
163+
def getImportCompletions(ictx: Context): Unit = {
164+
implicit val ctx = ictx
165+
val imp = ctx.importInfo
166+
if (imp != null) {
167+
def addImport(name: TermName) = {
168+
addMember(imp.site, name)
169+
addMember(imp.site, name.toTypeName)
170+
}
171+
for (renamed <- imp.reverseMapping.keys) addImport(renamed)
172+
for (imported <- imp.originals if !imp.excluded.contains(imported)) addImport(imported)
173+
if (imp.isWildcardImport)
174+
for (mbr <- allMembers(imp.site) if !imp.excluded.contains(mbr.name.toTermName))
175+
addMember(imp.site, mbr.name)
176+
}
177+
}
178+
179+
def getScopeCompletions(ictx: Context): Unit = {
180+
implicit val ctx = ictx
181+
182+
if (ctx.owner.isClass) {
183+
for (sym <- ctx.owner.info.decls) // decls in same class first
184+
addMember(ctx.owner.thisType, sym.name)
185+
if (!ctx.owner.is(Package)) {
186+
for (mbr <- allMembers(ctx.owner.thisType)) // all other members second
187+
addMember(ctx.owner.thisType, mbr.name)
188+
ctx.owner.asClass.classInfo.selfInfo match {
189+
case selfSym: Symbol => add(selfSym)
190+
case _ =>
191+
}
192+
}
193+
}
194+
else if (ctx.scope != null) ctx.scope.foreach(add)
195+
196+
getImportCompletions(ctx)
197+
198+
var outer = ctx.outer
199+
while ((outer.owner `eq` ctx.owner) && (outer.scope `eq` ctx.scope)) {
200+
getImportCompletions(outer)
201+
outer = outer.outer
202+
}
203+
if (outer `ne` NoContext) getScopeCompletions(outer)
204+
}
205+
206+
def getMemberCompletions(site: Type): Unit = {
207+
for (mbr <- allMembers(site)) addMember(site, mbr.name)
208+
}
209+
210+
val completionPos = path match {
211+
case (sel @ Select(qual, name)) :: _ =>
212+
getMemberCompletions(qual.tpe)
213+
// When completing "`a.foo`, return the members of `a`
214+
sel.pos.point
215+
case (id: Ident) :: _ =>
216+
getScopeCompletions(ctx)
217+
id.pos.point
218+
case _ =>
219+
getScopeCompletions(ctx)
220+
0
221+
}
222+
interactiv.println(i"completion = ${completions.toList}%, %")
223+
(completionPos, completions.toList)
224+
}
225+
125226
/** Possible completions of members of `prefix` which are accessible when called inside `boundary` */
126227
def completions(prefix: Type, boundary: Symbol)(implicit ctx: Context): List[Symbol] =
127228
safely {
@@ -131,7 +232,7 @@ object Interactive {
131232
def addMember(name: Name, buf: mutable.Buffer[SingleDenotation]): Unit =
132233
buf ++= prefix.member(name).altsWith(d =>
133234
!exclude(d) && d.symbol.isAccessibleFrom(prefix)(boundaryCtx))
134-
prefix.memberDenots(completionsFilter, addMember).map(_.symbol).toList
235+
prefix.memberDenots(completionsFilter, addMember).map(_.symbol).toList
135236
}
136237
else Nil
137238
}
@@ -203,14 +304,72 @@ object Interactive {
203304
*/
204305
def pathTo(trees: List[SourceTree], pos: SourcePosition)(implicit ctx: Context): List[Tree] =
205306
trees.find(_.pos.contains(pos)) match {
206-
case Some(tree) =>
207-
// FIXME: We shouldn't need a cast. Change NavigateAST.pathTo to return a List of Tree?
208-
val path = NavigateAST.pathTo(pos.pos, tree.tree, skipZeroExtent = true).asInstanceOf[List[untpd.Tree]]
307+
case Some(tree) => pathTo(tree.tree, pos.pos)
308+
case None => Nil
309+
}
209310

210-
path.dropWhile(!_.hasType).asInstanceOf[List[tpd.Tree]]
211-
case None =>
212-
Nil
311+
def pathTo(tree: Tree, pos: Position)(implicit ctx: Context): List[Tree] =
312+
if (tree.pos.contains(pos)) {
313+
// FIXME: We shouldn't need a cast. Change NavigateAST.pathTo to return a List of Tree?
314+
val path = NavigateAST.pathTo(pos, tree, skipZeroExtent = true).asInstanceOf[List[untpd.Tree]]
315+
path.dropWhile(!_.hasType).asInstanceOf[List[tpd.Tree]]
213316
}
317+
else Nil
318+
319+
def contextOfStat(stats: List[Tree], stat: Tree, exprOwner: Symbol, ctx: Context): Context = stats match {
320+
case Nil =>
321+
ctx
322+
case first :: _ if first eq stat =>
323+
ctx.exprContext(stat, exprOwner)
324+
case (imp: Import) :: rest =>
325+
contextOfStat(rest, stat, exprOwner, ctx.importContext(imp, imp.symbol(ctx)))
326+
case _ =>
327+
ctx
328+
}
329+
330+
def contextOfPath(path: List[Tree])(implicit ctx: Context): Context = path match {
331+
case Nil | _ :: Nil =>
332+
ctx.run.runContext.fresh.setCompilationUnit(ctx.compilationUnit)
333+
case nested :: encl :: rest =>
334+
import typer.Typer._
335+
val outer = contextOfPath(encl :: rest)
336+
encl match {
337+
case tree @ PackageDef(pkg, stats) =>
338+
assert(tree.symbol.exists)
339+
if (nested `eq` pkg) outer
340+
else contextOfStat(stats, nested, pkg.symbol.moduleClass, outer.packageContext(tree, tree.symbol))
341+
case tree: DefDef =>
342+
assert(tree.symbol.exists)
343+
val localCtx = outer.localContext(tree, tree.symbol).setNewScope
344+
for (tparam <- tree.tparams) localCtx.enter(tparam.symbol)
345+
for (vparams <- tree.vparamss; vparam <- vparams) localCtx.enter(vparam.symbol)
346+
// Note: this overapproximates visibility a bit, since value parameters are only visible
347+
// in subsequent parameter sections
348+
localCtx
349+
case tree: MemberDef =>
350+
assert(tree.symbol.exists)
351+
outer.localContext(tree, tree.symbol)
352+
case tree @ Block(stats, expr) =>
353+
val localCtx = outer.fresh.setNewScope
354+
stats.foreach {
355+
case stat: MemberDef => localCtx.enter(stat.symbol)
356+
case _ =>
357+
}
358+
contextOfStat(stats, nested, ctx.owner, localCtx)
359+
case tree @ CaseDef(pat, guard, rhs) if nested `eq` rhs =>
360+
val localCtx = outer.fresh.setNewScope
361+
pat.foreachSubTree {
362+
case bind: Bind => localCtx.enter(bind.symbol)
363+
case _ =>
364+
}
365+
localCtx
366+
case tree @ Template(constr, parents, self, _) =>
367+
if ((constr :: self :: parents).exists(nested `eq` _)) ctx
368+
else contextOfStat(tree.body, nested, tree.symbol, outer.inClassContext(self.symbol))
369+
case _ =>
370+
outer
371+
}
372+
}
214373

215374
/** The first tree in the path that is a definition. */
216375
def enclosingDefinitionInPath(path: List[Tree])(implicit ctx: Context): Tree =

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ case class SourceTree(tree: tpd.NameTree, source: SourceFile) {
3737
}
3838
}
3939
}
40+
4041
object SourceTree {
4142
def fromSymbol(sym: ClassSymbol, id: String = "")(implicit ctx: Context): Option[SourceTree] = {
4243
if (sym == defn.SourceFileAnnot || // FIXME: No SourceFile annotation on SourceFile itself

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,10 @@ class DottyLanguageServer extends LanguageServer
202202
implicit val ctx = driver.currentCtx
203203

204204
val pos = sourcePosition(driver, uri, params.getPosition)
205-
val items = Interactive.completions(driver.openedTrees(uri), pos)._2
205+
val items = driver.compilationUnits.get(uri) match {
206+
case Some(unit) => Interactive.completions(pos)(ctx.fresh.setCompilationUnit(unit))._2
207+
case None => Nil
208+
}
206209

207210
JEither.forRight(new CompletionList(
208211
/*isIncomplete = */ false, items.map(completionItem).asJava))

0 commit comments

Comments
 (0)