@@ -14,12 +14,23 @@ import dotty.tools.dotc.core.Symbols.{defn, NoSymbol, Symbol}
1414import dotty .tools .dotc .core .Scopes
1515import dotty .tools .dotc .core .StdNames .{nme , tpnme }
1616import dotty .tools .dotc .core .TypeError
17- import dotty .tools .dotc .core .Types .{NamedType , Type , takeAllFilter }
17+ import dotty .tools .dotc .core .Types .{NameFilter , NamedType , Type , NoType }
1818import dotty .tools .dotc .printing .Texts ._
1919import dotty .tools .dotc .util .{NoSourcePosition , SourcePosition }
2020
2121import scala .collection .mutable
2222
23+ /**
24+ * One of the results of a completion query.
25+ *
26+ * @param label The label of this completion result, or the text that this completion result
27+ * should insert in the scope where the completion request happened.
28+ * @param description The description of this completion result: the fully qualified name for
29+ * types, or the type for terms.
30+ * @param symbols The symbols that are matched by this completion result.
31+ */
32+ case class Completion (label : String , description : String , symbols : List [Symbol ])
33+
2334object Completion {
2435
2536 import dotty .tools .dotc .ast .tpd ._
@@ -28,7 +39,7 @@ object Completion {
2839 *
2940 * @return offset and list of symbols for possible completions
3041 */
31- def completions (pos : SourcePosition )(implicit ctx : Context ): (Int , List [Symbol ]) = {
42+ def completions (pos : SourcePosition )(implicit ctx : Context ): (Int , List [Completion ]) = {
3243 val path = Interactive .pathTo(ctx.compilationUnit.tpdTree, pos.pos)
3344 computeCompletions(pos, path)(Interactive .contextOfPath(path))
3445 }
@@ -100,7 +111,7 @@ object Completion {
100111 new CompletionBuffer (mode, prefix, pos)
101112 }
102113
103- private def computeCompletions (pos : SourcePosition , path : List [Tree ])(implicit ctx : Context ): (Int , List [Symbol ]) = {
114+ private def computeCompletions (pos : SourcePosition , path : List [Tree ])(implicit ctx : Context ): (Int , List [Completion ]) = {
104115
105116 val offset = completionOffset(path)
106117 val buffer = completionBuffer(path, pos)
@@ -126,20 +137,43 @@ object Completion {
126137
127138 private class CompletionBuffer (val mode : Mode , val prefix : String , pos : SourcePosition ) {
128139
129- private [this ] val completions = Scopes .newScope.openForMutations
140+ private [this ] val completions = new RenameAwareScope
130141
131142 /**
132143 * Return the list of symbols that shoudl be included in completion results.
133144 *
134- * If the mode is `Import` and several symbols share the same name, the type symbols are
135- * preferred over term symbols.
145+ * If several symbols share the same name, the type symbols appear before term symbols inside
146+ * the same `Completion`.
147+ */
148+ def getCompletions (implicit ctx : Context ): List [Completion ] = {
149+ val nameToSymbols = completions.mappings.toList
150+ nameToSymbols.map { case (name, symbols) =>
151+ val typesFirst = symbols.sortWith((s1, s2) => s1.isType && ! s2.isType)
152+ val desc = description(typesFirst)
153+ Completion (name.toString, desc, typesFirst)
154+ }
155+ }
156+
157+ /**
158+ * A description for completion result that represents `symbols`.
159+ *
160+ * If `symbols` contains a single symbol, show its full name in case it's a type, or its type if
161+ * it's a term.
162+ *
163+ * When there are multiple symbols, show their kinds.
136164 */
137- def getCompletions (implicit ctx : Context ): List [Symbol ] = {
138- // Show only the type symbols when there are multiple options with the same name
139- completions.toList.groupBy(_.name.stripModuleClassSuffix.toSimpleName).mapValues {
140- case sym :: Nil => sym :: Nil
141- case syms => syms.filter(_.isType)
142- }.values.flatten.toList
165+ private def description (symbols : List [Symbol ])(implicit ctx : Context ): String = {
166+ symbols match {
167+ case sym :: Nil =>
168+ if (sym.isType) sym.showFullName
169+ else sym.info.widenTermRefExpr.show
170+
171+ case sym :: _ =>
172+ symbols.map(ctx.printer.kindString).mkString(" " , " and " , s " ${sym.name.show}" )
173+
174+ case Nil =>
175+ " "
176+ }
143177 }
144178
145179 /**
@@ -150,11 +184,11 @@ object Completion {
150184 if (ctx.owner.isClass) {
151185 addAccessibleMembers(ctx.owner.thisType)
152186 ctx.owner.asClass.classInfo.selfInfo match {
153- case selfSym : Symbol => add(selfSym)
187+ case selfSym : Symbol => add(selfSym, selfSym.name )
154188 case _ =>
155189 }
156190 }
157- else if (ctx.scope != null ) ctx.scope.foreach(add)
191+ else if (ctx.scope != null ) ctx.scope.foreach(s => add(s, s.name) )
158192
159193 addImportCompletions
160194
@@ -185,32 +219,34 @@ object Completion {
185219 * If `sym` exists, no symbol with the same name is already included, and it satisfies the
186220 * inclusion filter, then add it to the completions.
187221 */
188- private def add (sym : Symbol )(implicit ctx : Context ) =
189- if (sym.exists && ! completions.lookup(sym.name).exists && include(sym)) {
190- completions.enter(sym)
222+ private def add (sym : Symbol , nameInScope : Name )(implicit ctx : Context ) =
223+ if (sym.exists &&
224+ completionsFilter(NoType , nameInScope) &&
225+ ! completions.lookup(nameInScope).exists &&
226+ include(sym, nameInScope)) {
227+ completions.enter(sym, nameInScope)
191228 }
192229
193230 /** Lookup members `name` from `site`, and try to add them to the completion list. */
194- private def addMember (site : Type , name : Name )(implicit ctx : Context ) =
195- if (! completions.lookup(name).exists)
196- for (alt <- site.member(name).alternatives) add(alt.symbol)
231+ private def addMember (site : Type , name : Name , nameInScope : Name )(implicit ctx : Context ) =
232+ if (! completions.lookup(nameInScope).exists) {
233+ for (alt <- site.member(name).alternatives) add(alt.symbol, nameInScope)
234+ }
197235
198236 /** Include in completion sets only symbols that
199237 * 1. start with given name prefix, and
200- * 2. do not contain '$' except in prefix where it is explicitly written by user, and
238+ * 2. is not absent (info is not NoType)
201239 * 3. are not a primary constructor,
202- * 4. are the module class in case of packages,
203- * 5. are mutable accessors, to exclude setters for `var`,
204- * 6. have same term/type kind as name prefix given so far
205- *
206- * The reason for (2) is that we do not want to present compiler-synthesized identifiers
207- * as completion results. However, if a user explicitly writes all '$' characters in an
208- * identifier, we should complete the rest.
240+ * 4. have an existing source symbol,
241+ * 5. are the module class in case of packages,
242+ * 6. are mutable accessors, to exclude setters for `var`,
243+ * 7. have same term/type kind as name prefix given so far
209244 */
210- private def include (sym : Symbol )(implicit ctx : Context ): Boolean =
211- sym.name .startsWith(prefix) &&
212- ! sym.name.toString.drop(prefix.length).contains( '$' ) &&
245+ private def include (sym : Symbol , nameInScope : Name )(implicit ctx : Context ): Boolean =
246+ nameInScope .startsWith(prefix) &&
247+ ! sym.isAbsent &&
213248 ! sym.isPrimaryConstructor &&
249+ sym.sourceSymbol.exists &&
214250 (! sym.is(Package ) || ! sym.moduleClass.exists) &&
215251 ! sym.is(allOf(Mutable , Accessor )) &&
216252 (
@@ -226,20 +262,21 @@ object Completion {
226262 */
227263 private def accessibleMembers (site : Type )(implicit ctx : Context ): Seq [Symbol ] = site match {
228264 case site : NamedType if site.symbol.is(Package ) =>
229- site.decls.toList.filter(include) // Don't look inside package members -- it's too expensive.
265+ // Don't look inside package members -- it's too expensive.
266+ site.decls.toList.filter(sym => sym.isAccessibleFrom(site, superAccess = false ))
230267 case _ =>
231268 def appendMemberSyms (name : Name , buf : mutable.Buffer [SingleDenotation ]): Unit =
232269 try buf ++= site.member(name).alternatives
233270 catch { case ex : TypeError => }
234- site.memberDenots(takeAllFilter , appendMemberSyms).collect {
235- case mbr if include(mbr.symbol) => mbr.accessibleFrom(site, superAccess = true ).symbol
271+ site.memberDenots(completionsFilter , appendMemberSyms).collect {
272+ case mbr if include(mbr.symbol, mbr.symbol.name ) => mbr.accessibleFrom(site, superAccess = true ).symbol
236273 case _ => NoSymbol
237274 }.filter(_.exists)
238275 }
239276
240277 /** Add all the accessible members of `site` in `info`. */
241278 private def addAccessibleMembers (site : Type )(implicit ctx : Context ): Unit =
242- for (mbr <- accessibleMembers(site)) addMember(site, mbr.name)
279+ for (mbr <- accessibleMembers(site)) addMember(site, mbr.name, mbr.name )
243280
244281 /**
245282 * Add in `info` the symbols that are imported by `ctx.importInfo`. If this is a wildcard import,
@@ -248,17 +285,18 @@ object Completion {
248285 private def addImportCompletions (implicit ctx : Context ): Unit = {
249286 val imp = ctx.importInfo
250287 if (imp != null ) {
251- def addImport (name : TermName ) = {
252- addMember(imp.site, name)
253- addMember(imp.site, name.toTypeName)
288+ def addImport (name : TermName , nameInScope : TermName ) = {
289+ addMember(imp.site, name, nameInScope)
290+ addMember(imp.site, name.toTypeName, nameInScope.toTypeName)
291+ }
292+ imp.reverseMapping.foreachBinding { (nameInScope, original) =>
293+ if (original != nameInScope || ! imp.excluded.contains(original)) {
294+ addImport(original, nameInScope)
295+ }
254296 }
255- // FIXME: We need to also take renamed items into account for completions,
256- // That means we have to return list of a pairs (Name, Symbol) instead of a list
257- // of symbols from `completions`.!=
258- for (imported <- imp.originals if ! imp.excluded.contains(imported)) addImport(imported)
259297 if (imp.isWildcardImport)
260298 for (mbr <- accessibleMembers(imp.site) if ! imp.excluded.contains(mbr.name.toTermName))
261- addMember(imp.site, mbr.name)
299+ addMember(imp.site, mbr.name, mbr.name )
262300 }
263301 }
264302
@@ -277,6 +315,12 @@ object Completion {
277315 targets
278316 }
279317
318+ /** Filter for names that should appear when looking for completions. */
319+ private [this ] object completionsFilter extends NameFilter {
320+ def apply (pre : Type , name : Name )(implicit ctx : Context ): Boolean =
321+ ! name.isConstructorName && name.toTermName.info.kind == SimpleNameKind
322+ }
323+
280324 }
281325
282326 /**
@@ -301,4 +345,23 @@ object Completion {
301345 val Import : Mode = new Mode (4 ) | Term | Type
302346 }
303347
348+ /** A scope that tracks renames of the entered symbols.
349+ * Useful for providing completions for renamed symbols
350+ * in the REPL and the IDE.
351+ */
352+ private class RenameAwareScope extends Scopes .MutableScope {
353+ private [this ] val nameToSymbols : mutable.Map [TermName , List [Symbol ]] = mutable.Map .empty
354+
355+ /** Enter the symbol `sym` in this scope, recording a potential renaming. */
356+ def enter [T <: Symbol ](sym : T , name : Name )(implicit ctx : Context ): T = {
357+ val termName = name.stripModuleClassSuffix.toTermName
358+ nameToSymbols += termName -> (sym :: nameToSymbols.getOrElse(termName, Nil ))
359+ newScopeEntry(name, sym)
360+ sym
361+ }
362+
363+ /** Get the names that are known in this scope, along with the list of symbols they refer to. */
364+ def mappings : Map [TermName , List [Symbol ]] = nameToSymbols.toMap
365+ }
366+
304367}
0 commit comments