@@ -106,31 +106,25 @@ object Completion {
106106 case _ => 0
107107 }
108108
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 ]) = {
111110 val mode = completionMode(path, pos)
112111 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)
120113
121114 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
126119 }
127120
128121 val completionList = scope.getCompletions
122+ val offset = completionOffset(path)
129123
130124 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 )}
134128 | results = $completionList%, % """ )
135129 (offset, completionList)
136130 }
@@ -156,7 +150,12 @@ object Completion {
156150 " "
157151 }
158152
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 ) {
160159 /** Completions for terms and types that are currently in scope:
161160 * the members of the current class and the symbols that have been imported, recursively adding completions from outer scopes
162161 */
@@ -167,13 +166,15 @@ object Completion {
167166 val grouped = elems.groupBy(f)
168167 keys.map(key => key -> grouped(key))
169168
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) =>
172173 contexts.collect { case context if context.isImportContext =>
173174 importedCompletions(using context)
174175 }.foldLeft(CompletionScope .empty)(_.mergeDiscardingAmbiguities(_))
175176 }
176- val members = grouped .map { (owner, _) =>
177+ val members = contextsByOwner .map { (owner, _) =>
177178 if owner.isClass then
178179 CompletionScope .from(accessibleMembers(owner.thisType))
179180 else CompletionScope .empty
@@ -209,7 +210,10 @@ object Completion {
209210 val types = imp.site.member(name.toTypeName).alternatives.map(denot => nameInScope.toTypeName -> denot)
210211 CompletionScope .fromNamed(terms ++ types)
211212
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)
213217
214218 val wildcardMembers =
215219 if imp.selectors.exists(_.imported.name == nme.WILDCARD ) then
@@ -243,8 +247,8 @@ object Completion {
243247 case _ : MethodOrPoly => tpe
244248 case _ => ExprType (tpe)
245249
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)
248252 .map { tree =>
249253 val tpe = asDefLikeType(tree.tpe.dealias)
250254 termRef.denot.asSingleDenotation.mapInfo(_ => tpe)
@@ -259,15 +263,16 @@ object Completion {
259263 case name : TermName if name.startsWith(matchingNamePrefix) => Some ((denot, name))
260264 case _ => None
261265
262- types.flatMap{ tpe =>
266+ types.flatMap { tpe =>
263267 tpe.membersBasedOnFlags(required = ExtensionMethod , excluded = EmptyFlags )
264268 .collect { case DenotWithMatchingName (denot, name) => TermRef (tpe, denot.symbol) -> name }
265269 }
266270
267271 // There are four possible ways for an extension method to be applicable
268272
269273 // 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 {
271276 case (name, denots) => denots.collect { case d : SymDenotation => (d.termRef, name.asTermName) }
272277 }
273278
@@ -287,8 +292,8 @@ object Completion {
287292 val extMethodsWithAppliedReceiver = availableExtMethods.flatMap {
288293 case (termRef, termName) =>
289294 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 =>
292297 val sym = denot.symbol.asTerm.copy(name = termName)
293298 denot.derivedSingleDenotation(sym, denot.info)
294299 }
@@ -326,23 +331,17 @@ object Completion {
326331 /** @param site The type to inspect.
327332 * @return The members of `site` that are accessible and pass the include filter.
328333 */
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+ }
346345 }
347346
348347 /**
@@ -367,27 +366,60 @@ object Completion {
367366 def isStable = true
368367 }
369368
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)
379377 }
380378
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))
384385 CompletionScope (mappings)
385386 }
386387
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+ }
389407 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
390421 }
422+ }
391423 }
392424
393425 /**
@@ -411,35 +443,5 @@ object Completion {
411443 /** Both term and type symbols are allowed */
412444 val Import : Mode = new Mode (4 ) | Term | Type
413445 }
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- }
444446}
445447
0 commit comments