@@ -106,31 +106,25 @@ object Completion {
106
106
case _ => 0
107
107
}
108
108
109
- /** Create a new `CompletionBuffer` for completing at `pos`. */
110
- private def completionBuffer (path : List [Tree ], pos : SourcePosition ): CompletionBuffer = {
109
+ private def computeCompletions (pos : SourcePosition , path : List [Tree ])(using Context ): (Int , List [Completion ]) = {
111
110
val mode = completionMode(path, pos)
112
111
val prefix = completionPrefix(path, pos)
113
- new CompletionBuffer (mode, prefix, pos)
114
- }
115
-
116
- private def computeCompletions (pos : SourcePosition , path : List [Tree ])(using Context ): (Int , List [Completion ]) = {
117
-
118
- val offset = completionOffset(path)
119
- val buffer = completionBuffer(path, pos)
112
+ val completer = new Completer (mode, prefix, pos)
120
113
121
114
val scope = path match {
122
- case Select (qual, _) :: _ => buffer .selectionCompletions(path, qual)
123
- case Import (expr, _) :: _ => buffer .directMemberCompletions(expr)
124
- case (_ : untpd.ImportSelector ) :: Import (expr, _) :: _ => buffer .directMemberCompletions(expr)
125
- case _ => buffer .scopeCompletions
115
+ case Select (qual, _) :: _ => completer .selectionCompletions(path, qual)
116
+ case Import (expr, _) :: _ => completer .directMemberCompletions(expr)
117
+ case (_ : untpd.ImportSelector ) :: Import (expr, _) :: _ => completer .directMemberCompletions(expr)
118
+ case _ => completer .scopeCompletions
126
119
}
127
120
128
121
val completionList = scope.getCompletions
122
+ val offset = completionOffset(path)
129
123
130
124
interactiv.println(i """ completion with pos = $pos,
131
- | prefix = ${buffer .prefix},
132
- | term = ${buffer .mode.is(Mode .Term )},
133
- | type = ${buffer .mode.is(Mode .Type )}
125
+ | prefix = ${completer .prefix},
126
+ | term = ${completer .mode.is(Mode .Term )},
127
+ | type = ${completer .mode.is(Mode .Type )}
134
128
| results = $completionList%, % """ )
135
129
(offset, completionList)
136
130
}
@@ -156,7 +150,12 @@ object Completion {
156
150
" "
157
151
}
158
152
159
- private class CompletionBuffer (val mode : Mode , val prefix : String , pos : SourcePosition ) {
153
+ /** Computes code completions depending on the context in which completion is requested
154
+ * @param mode Should complete names of terms, types or both
155
+ * @param prefix The prefix that all suggested completions should start with
156
+ * @param pos Cursor position where completion was requested
157
+ */
158
+ private class Completer (val mode : Mode , val prefix : String , pos : SourcePosition ) {
160
159
/** Completions for terms and types that are currently in scope:
161
160
* the members of the current class and the symbols that have been imported, recursively adding completions from outer scopes
162
161
*/
@@ -167,13 +166,15 @@ object Completion {
167
166
val grouped = elems.groupBy(f)
168
167
keys.map(key => key -> grouped(key))
169
168
170
- val grouped = ctx.outersIterator.toList.reverse.orderedGroupBy(_.owner).filter(_._1.exists)
171
- val imported = grouped.map { (owner, contexts) =>
169
+ val contextsByOwner = ctx.outersIterator.toList
170
+ .reverse.orderedGroupBy(_.owner)
171
+ .filter(_._1.exists)
172
+ val imported = contextsByOwner.map { (_, contexts) =>
172
173
contexts.collect { case context if context.isImportContext =>
173
174
importedCompletions(using context)
174
175
}.foldLeft(CompletionScope .empty)(_.mergeDiscardingAmbiguities(_))
175
176
}
176
- val members = grouped .map { (owner, _) =>
177
+ val members = contextsByOwner .map { (owner, _) =>
177
178
if owner.isClass then
178
179
CompletionScope .from(accessibleMembers(owner.thisType))
179
180
else CompletionScope .empty
@@ -209,7 +210,10 @@ object Completion {
209
210
val types = imp.site.member(name.toTypeName).alternatives.map(denot => nameInScope.toTypeName -> denot)
210
211
CompletionScope .fromNamed(terms ++ types)
211
212
212
- val givenImports = CompletionScope .fromNamed(imp.importedImplicits.map(x => (x.implicitName, x.underlyingRef.denot.asSingleDenotation)))
213
+ val namedImportedImplicits = imp.importedImplicits.map { ref =>
214
+ (ref.implicitName, ref.underlyingRef.denot.asSingleDenotation)
215
+ }
216
+ val givenImports = CompletionScope .fromNamed(namedImportedImplicits)
213
217
214
218
val wildcardMembers =
215
219
if imp.selectors.exists(_.imported.name == nme.WILDCARD ) then
@@ -243,8 +247,8 @@ object Completion {
243
247
case _ : MethodOrPoly => tpe
244
248
case _ => ExprType (tpe)
245
249
246
- def tryApplyingReceiver (termRef : TermRef ): Option [SingleDenotation ] =
247
- ctx.typer.tryApplyingReceiver (termRef, qual)
250
+ def tryApplyingReceiverToExtension (termRef : TermRef ): Option [SingleDenotation ] =
251
+ ctx.typer.tryApplyingExtensionMethod (termRef, qual)
248
252
.map { tree =>
249
253
val tpe = asDefLikeType(tree.tpe.dealias)
250
254
termRef.denot.asSingleDenotation.mapInfo(_ => tpe)
@@ -259,15 +263,16 @@ object Completion {
259
263
case name : TermName if name.startsWith(matchingNamePrefix) => Some ((denot, name))
260
264
case _ => None
261
265
262
- types.flatMap{ tpe =>
266
+ types.flatMap { tpe =>
263
267
tpe.membersBasedOnFlags(required = ExtensionMethod , excluded = EmptyFlags )
264
268
.collect { case DenotWithMatchingName (denot, name) => TermRef (tpe, denot.symbol) -> name }
265
269
}
266
270
267
271
// There are four possible ways for an extension method to be applicable
268
272
269
273
// 1. The extension method is visible under a simple name, by being defined or inherited or imported in a scope enclosing the reference.
270
- val extMethodsInScope = scopeCompletions.nameToDenots.toList.flatMap {
274
+ val termCompleter = new Completer (Mode .Term , prefix, pos)
275
+ val extMethodsInScope = termCompleter.scopeCompletions.nameToDenots.toList.flatMap {
271
276
case (name, denots) => denots.collect { case d : SymDenotation => (d.termRef, name.asTermName) }
272
277
}
273
278
@@ -287,8 +292,8 @@ object Completion {
287
292
val extMethodsWithAppliedReceiver = availableExtMethods.flatMap {
288
293
case (termRef, termName) =>
289
294
if termRef.symbol.is(ExtensionMethod ) && ! qual.tpe.isBottomType then
290
- val applied = tryApplyingReceiver (termRef)
291
- applied.map{ denot =>
295
+ val applied = tryApplyingReceiverToExtension (termRef)
296
+ applied.map { denot =>
292
297
val sym = denot.symbol.asTerm.copy(name = termName)
293
298
denot.derivedSingleDenotation(sym, denot.info)
294
299
}
@@ -326,23 +331,17 @@ object Completion {
326
331
/** @param site The type to inspect.
327
332
* @return The members of `site` that are accessible and pass the include filter.
328
333
*/
329
- private def accessibleMembers (site : Type )(using Context ): Seq [SingleDenotation ] = site match {
330
- case site : NamedType if site.symbol.is(Package ) =>
331
- extension (tpe : Type )
332
- def accessibleMembers = tpe.allMembers.toList.filter(denot => denot.symbol.isAccessibleFrom(site, superAccess = false ))
333
-
334
- val packageDecls = site.accessibleMembers
335
- val packageObjectsDecls = packageDecls.filter(_.symbol.isPackageObject).flatMap(_.symbol.thisType.accessibleMembers)
336
-
337
- packageDecls ++ packageObjectsDecls
338
- case _ =>
339
- def appendMemberSyms (name : Name , buf : mutable.Buffer [SingleDenotation ]): Unit =
340
- try buf ++= site.member(name).alternatives
341
- catch { case ex : TypeError => }
342
- site.memberDenots(completionsFilter, appendMemberSyms).collect {
343
- case mbr if include(mbr.symbol, mbr.symbol.name)
344
- && mbr.symbol.isAccessibleFrom(site, superAccess = true ) => mbr
345
- }
334
+ private def accessibleMembers (site : Type )(using Context ): Seq [SingleDenotation ] = {
335
+ def appendMemberSyms (name : Name , buf : mutable.Buffer [SingleDenotation ]): Unit =
336
+ try
337
+ buf ++= site.member(name).alternatives
338
+ catch
339
+ case ex : TypeError =>
340
+
341
+ site.memberDenots(completionsFilter, appendMemberSyms).collect {
342
+ case mbr if include(mbr.symbol, mbr.symbol.name)
343
+ && mbr.symbol.isAccessibleFrom(site) => mbr
344
+ }
346
345
}
347
346
348
347
/**
@@ -367,27 +366,60 @@ object Completion {
367
366
def isStable = true
368
367
}
369
368
370
- extension (scope : CompletionScope )
371
- private def withDenots (denotations : Seq [SingleDenotation ], name : Name )(using Context ): CompletionScope = {
372
- val denots = denotations.filter(denot => include(denot.symbol, name))
373
- val shortName = name.stripModuleClassSuffix
374
-
375
- if denots.nonEmpty then
376
- CompletionScope (scope.nameToDenots + (shortName -> denots.toList))
377
- else
378
- scope
369
+ object CompletionScope {
370
+ def from (denotations : Seq [SingleDenotation ])(using Context ): CompletionScope = {
371
+ val mappings = denotations
372
+ .filter(den => include(den.symbol, den.name))
373
+ .toList
374
+ .groupBy(_.name)
375
+ .map((name, denots) => name.stripModuleClassSuffix -> denots)
376
+ CompletionScope (mappings)
379
377
}
380
378
381
- extension (scope : CompletionScope .type )
382
- private def from (denots : Seq [SingleDenotation ])(using Context ): CompletionScope = {
383
- val mappings = denots.filter(den => include(den.symbol, den.name)).toList.groupBy(_.name).map( (name, denots) => name.stripModuleClassSuffix -> denots)
379
+ def fromNamed (namedDenotations : Seq [(Name , SingleDenotation )])(using Context ): CompletionScope = {
380
+ val mappings = namedDenotations
381
+ .filter((name, denot) => include(denot.symbol, name))
382
+ .toList
383
+ .groupBy(_._1)
384
+ .map((name, namedDenots) => name.stripModuleClassSuffix -> namedDenots.map(_._2))
384
385
CompletionScope (mappings)
385
386
}
386
387
387
- private def fromNamed (namedDenots : Seq [(Name , SingleDenotation )])(using Context ): CompletionScope = {
388
- val mappings = namedDenots.filter((name, den) => include(den.symbol, name)).toList.groupBy(_._1).map( (name, namedDens) => name.stripModuleClassSuffix -> namedDens.map(_._2))
388
+ val empty = CompletionScope ()
389
+ }
390
+
391
+ /** A wrapper over a map from names to their denotations
392
+ * taking care of filtering out not matching completions
393
+ * and enabling easy resolving of conflicts between different denotations with the same name
394
+ */
395
+ case class CompletionScope private (nameToDenots : Map [Name , List [SingleDenotation ]] = Map .empty) {
396
+
397
+ // Merge two scopes using mappings from `that` instead of from `this` in case of name clashes
398
+ def mergeShadowedBy (that : CompletionScope ) = CompletionScope (this .nameToDenots ++ that.nameToDenots)
399
+
400
+ // Merge two scopes but discard mappings for names that appear in both scopes
401
+ def mergeDiscardingAmbiguities (that : CompletionScope ) =
402
+ val mappings = (this .nameToDenots.toList ++ that.nameToDenots.toList)
403
+ .groupBy(_._1)
404
+ .collect {
405
+ case (name, (_, denots) :: Nil ) => name -> denots
406
+ }
389
407
CompletionScope (mappings)
408
+
409
+ /**
410
+ * Return the list of symbols that should be included in completion results.
411
+ *
412
+ * If several symbols share the same name, the type symbols appear before term symbols inside
413
+ * the same `Completion`.
414
+ */
415
+ def getCompletions (using Context ): List [Completion ] = {
416
+ nameToDenots.toList.groupBy(_._1.toTermName.show).map { (name, namedDenots) =>
417
+ val typesFirst = namedDenots.flatMap(_._2).sortWith((s1, s2) => s1.isType && ! s2.isType)
418
+ val desc = description(typesFirst)
419
+ Completion (name, desc, typesFirst.map(_.symbol))
420
+ }.toList
390
421
}
422
+ }
391
423
}
392
424
393
425
/**
@@ -411,35 +443,5 @@ object Completion {
411
443
/** Both term and type symbols are allowed */
412
444
val Import : Mode = new Mode (4 ) | Term | Type
413
445
}
414
-
415
- private object CompletionScope {
416
- val empty = CompletionScope ()
417
- }
418
-
419
- private case class CompletionScope (nameToDenots : Map [Name , List [SingleDenotation ]] = Map .empty) {
420
- def mergeShadowedBy (that : CompletionScope ) = CompletionScope (this .nameToDenots ++ that.nameToDenots)
421
-
422
- def mergeDiscardingAmbiguities (that : CompletionScope ) =
423
- val mappings = (this .nameToDenots.toList ++ that.nameToDenots.toList)
424
- .groupBy(_._1)
425
- .collect {
426
- case (name, (_, denots) :: Nil ) => name -> denots
427
- }
428
- CompletionScope (mappings)
429
-
430
- /**
431
- * Return the list of symbols that should be included in completion results.
432
- *
433
- * If several symbols share the same name, the type symbols appear before term symbols inside
434
- * the same `Completion`.
435
- */
436
- def getCompletions (using Context ): List [Completion ] = {
437
- nameToDenots.toList.groupBy(_._1.toTermName.show).map { (name, namedDenots) =>
438
- val typesFirst = namedDenots.flatMap(_._2).sortWith((s1, s2) => s1.isType && ! s2.isType)
439
- val desc = description(typesFirst)
440
- Completion (name, desc, typesFirst.map(_.symbol))
441
- }.toList
442
- }
443
- }
444
446
}
445
447
0 commit comments