@@ -14,12 +14,23 @@ import dotty.tools.dotc.core.Symbols.{defn, NoSymbol, Symbol}
14
14
import dotty .tools .dotc .core .Scopes
15
15
import dotty .tools .dotc .core .StdNames .{nme , tpnme }
16
16
import 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 }
18
18
import dotty .tools .dotc .printing .Texts ._
19
19
import dotty .tools .dotc .util .{NoSourcePosition , SourcePosition }
20
20
21
21
import scala .collection .mutable
22
22
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
+
23
34
object Completion {
24
35
25
36
import dotty .tools .dotc .ast .tpd ._
@@ -28,7 +39,7 @@ object Completion {
28
39
*
29
40
* @return offset and list of symbols for possible completions
30
41
*/
31
- def completions (pos : SourcePosition )(implicit ctx : Context ): (Int , List [Symbol ]) = {
42
+ def completions (pos : SourcePosition )(implicit ctx : Context ): (Int , List [Completion ]) = {
32
43
val path = Interactive .pathTo(ctx.compilationUnit.tpdTree, pos.pos)
33
44
computeCompletions(pos, path)(Interactive .contextOfPath(path))
34
45
}
@@ -100,7 +111,7 @@ object Completion {
100
111
new CompletionBuffer (mode, prefix, pos)
101
112
}
102
113
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 ]) = {
104
115
105
116
val offset = completionOffset(path)
106
117
val buffer = completionBuffer(path, pos)
@@ -126,20 +137,43 @@ object Completion {
126
137
127
138
private class CompletionBuffer (val mode : Mode , val prefix : String , pos : SourcePosition ) {
128
139
129
- private [this ] val completions = Scopes .newScope.openForMutations
140
+ private [this ] val completions = new RenameAwareScope
130
141
131
142
/**
132
143
* Return the list of symbols that shoudl be included in completion results.
133
144
*
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.
136
164
*/
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
+ }
143
177
}
144
178
145
179
/**
@@ -150,11 +184,11 @@ object Completion {
150
184
if (ctx.owner.isClass) {
151
185
addAccessibleMembers(ctx.owner.thisType)
152
186
ctx.owner.asClass.classInfo.selfInfo match {
153
- case selfSym : Symbol => add(selfSym)
187
+ case selfSym : Symbol => add(selfSym, selfSym.name )
154
188
case _ =>
155
189
}
156
190
}
157
- else if (ctx.scope != null ) ctx.scope.foreach(add)
191
+ else if (ctx.scope != null ) ctx.scope.foreach(s => add(s, s.name) )
158
192
159
193
addImportCompletions
160
194
@@ -185,32 +219,34 @@ object Completion {
185
219
* If `sym` exists, no symbol with the same name is already included, and it satisfies the
186
220
* inclusion filter, then add it to the completions.
187
221
*/
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)
191
228
}
192
229
193
230
/** 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
+ }
197
235
198
236
/** Include in completion sets only symbols that
199
237
* 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)
201
239
* 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
209
244
*/
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 &&
213
248
! sym.isPrimaryConstructor &&
249
+ sym.sourceSymbol.exists &&
214
250
(! sym.is(Package ) || ! sym.moduleClass.exists) &&
215
251
! sym.is(allOf(Mutable , Accessor )) &&
216
252
(
@@ -226,20 +262,21 @@ object Completion {
226
262
*/
227
263
private def accessibleMembers (site : Type )(implicit ctx : Context ): Seq [Symbol ] = site match {
228
264
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 ))
230
267
case _ =>
231
268
def appendMemberSyms (name : Name , buf : mutable.Buffer [SingleDenotation ]): Unit =
232
269
try buf ++= site.member(name).alternatives
233
270
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
236
273
case _ => NoSymbol
237
274
}.filter(_.exists)
238
275
}
239
276
240
277
/** Add all the accessible members of `site` in `info`. */
241
278
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 )
243
280
244
281
/**
245
282
* Add in `info` the symbols that are imported by `ctx.importInfo`. If this is a wildcard import,
@@ -248,17 +285,18 @@ object Completion {
248
285
private def addImportCompletions (implicit ctx : Context ): Unit = {
249
286
val imp = ctx.importInfo
250
287
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
+ }
254
296
}
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)
259
297
if (imp.isWildcardImport)
260
298
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 )
262
300
}
263
301
}
264
302
@@ -277,6 +315,12 @@ object Completion {
277
315
targets
278
316
}
279
317
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
+
280
324
}
281
325
282
326
/**
@@ -301,4 +345,23 @@ object Completion {
301
345
val Import : Mode = new Mode (4 ) | Term | Type
302
346
}
303
347
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
+
304
367
}
0 commit comments