Skip to content

Commit 0deb555

Browse files
committed
Fix completion mode filtering + optimize scopeCompletions
1 parent c481e91 commit 0deb555

File tree

2 files changed

+89
-94
lines changed

2 files changed

+89
-94
lines changed

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

Lines changed: 82 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ object Completion:
370370
* For the results of all `xyzCompletions` methods term names and type names are always treated as different keys in the same map
371371
* and they never conflict with each other.
372372
*/
373-
class Completer(val mode: Mode, pos: SourcePosition, untpdPath: List[untpd.Tree], matches: Name => Boolean):
373+
class Completer(val mode: Mode, pos: SourcePosition, untpdPath: List[untpd.Tree], matches: Name => Boolean)(using Context):
374374
/** Completions for terms and types that are currently in scope:
375375
* the members of the current class, local definitions and the symbols that have been imported,
376376
* recursively adding completions from outer scopes.
@@ -384,7 +384,52 @@ object Completion:
384384
* (even if the import follows it syntactically)
385385
* - a more deeply nested import shadowing a member or a local definition causes an ambiguity
386386
*/
387-
def scopeCompletions(using context: Context): CompletionResult =
387+
lazy val scopeCompletions: CompletionResult =
388+
389+
/** Completions introduced by imports directly in this context.
390+
* Completions from outer contexts are not included.
391+
*/
392+
def importedCompletions(using Context): CompletionResult =
393+
val imp = ctx.importInfo
394+
val renames = collection.mutable.Map.empty[Symbol, Name]
395+
396+
if imp == null then
397+
CompletionResult(Map.empty, Map.empty)
398+
else
399+
def fromImport(name: Name, nameInScope: Name): Seq[(Name, SingleDenotation)] =
400+
imp.site.member(name).alternatives
401+
.collect:
402+
case denot if include(denot, nameInScope) =>
403+
if name != nameInScope then renames(denot.symbol) = nameInScope
404+
nameInScope -> denot
405+
406+
val givenImports = imp.importedImplicits
407+
.map { ref => (ref.implicitName: Name, ref.underlyingRef.denot.asSingleDenotation) }
408+
.filter((name, denot) => include(denot, name))
409+
.groupByName
410+
411+
val wildcardMembers =
412+
if imp.selectors.exists(_.imported.name == nme.WILDCARD) then
413+
val denots = accessibleMembers(imp.site)
414+
.filter(mbr => !mbr.symbol.is(Given) && !imp.excluded.contains(mbr.name.toTermName))
415+
denots.groupByName
416+
else
417+
Map.empty
418+
419+
val explicitMembers =
420+
val importNamesInScope = imp.forwardMapping.toList.map(_._2)
421+
val duplicatedNames = importNamesInScope.diff(importNamesInScope.distinct)
422+
val discardedNames = duplicatedNames ++ imp.excluded
423+
imp.reverseMapping.toList
424+
.filter { (nameInScope, _) => !discardedNames.contains(nameInScope) }
425+
.flatMap: (nameInScope, original) =>
426+
fromImport(original, nameInScope) ++
427+
fromImport(original.toTypeName, nameInScope.toTypeName)
428+
.toSeq.groupByName
429+
430+
val results = givenImports ++ wildcardMembers ++ explicitMembers
431+
CompletionResult(results, renames.toMap)
432+
end importedCompletions
388433

389434
/** Temporary data structure representing denotations with the same name introduced in a given scope
390435
* as a member of a type, by a local definition or by an import clause
@@ -399,31 +444,27 @@ object Completion:
399444
def addMapping(name: Name, denots: ScopedDenotations) =
400445
mappings(name) = mappings(name) :+ denots
401446

402-
ctx.outersIterator.foreach { case ctx @ given Context =>
403-
if ctx.isImportContext then
404-
val imported = importedCompletions
405-
imported.names.foreach { (name, denots) =>
406-
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
407-
}
408-
imported.renames.foreach { (name, newName) =>
409-
renames(name) = newName
410-
}
411-
else if ctx.owner.isClass then
412-
accessibleMembers(ctx.owner.thisType)
413-
.groupByName.foreach { (name, denots) =>
447+
ctx.outersIterator.foreach:
448+
case ctx @ given Context =>
449+
if ctx.isImportContext then
450+
val imported = importedCompletions
451+
imported.names.foreach: (name, denots) =>
414452
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
415-
}
416-
else if ctx.scope ne EmptyScope then
417-
ctx.scope.toList.filter(symbol => include(symbol, symbol.name))
418-
.flatMap(_.alternatives)
419-
.groupByName.foreach { (name, denots) =>
420-
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
421-
}
422-
}
453+
imported.renames.foreach: (name, newName) =>
454+
renames(name) = newName
455+
else if ctx.owner.isClass then
456+
accessibleMembers(ctx.owner.thisType)
457+
.groupByName.foreach: (name, denots) =>
458+
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
459+
else if ctx.scope ne EmptyScope then
460+
ctx.scope.toList.filter(symbol => include(symbol, symbol.name))
461+
.flatMap(_.alternatives)
462+
.groupByName.foreach: (name, denots) =>
463+
addMapping(name, ScopedDenotations(denots, ctx, include(_, name)))
423464

424465
var resultMappings = Map.empty[Name, Seq[SingleDenotation]]
425466

426-
mappings.foreach { (name, denotss) =>
467+
mappings.foreach: (name, denotss) =>
427468
val first = denotss.head
428469

429470
// import a.c
@@ -447,7 +488,7 @@ object Completion:
447488
def notConflictingWithDefaults = // is imported symbol
448489
denotss.filterNot(_.denots.exists(denot => Interactive.isImportedByDefault(denot.symbol))).size <= 1
449490

450-
denotss.find(!_.ctx.isImportContext) match {
491+
denotss.find(!_.ctx.isImportContext) match
451492
// most deeply nested member or local definition if not shadowed by an import
452493
case Some(local) if local.ctx.scope == first.ctx.scope =>
453494
resultMappings += name -> local.denots
@@ -457,15 +498,14 @@ object Completion:
457498
val ordered = denotss.map(_.denots).sorted
458499
resultMappings += name -> ordered.head
459500
case _ =>
460-
}
461-
}
501+
462502

463503
CompletionResult(resultMappings, renames.toMap)
464504
end scopeCompletions
465505

466506
/** Widen only those types which are applied or are exactly nothing
467507
*/
468-
def widenQualifier(qual: tpd.Tree)(using Context): tpd.Tree =
508+
def widenQualifier(qual: tpd.Tree): tpd.Tree =
469509
qual.typeOpt.widenDealias match
470510
case widenedType if widenedType.isExactlyNothing => qual.withType(widenedType)
471511
case appliedType: AppliedType => qual.withType(appliedType)
@@ -475,7 +515,7 @@ object Completion:
475515
* Direct members take priority over members from extensions
476516
* and so do members from extensions over members from implicit conversions
477517
*/
478-
def selectionCompletions(qual: tpd.Tree)(using Context): CompletionMap =
518+
def selectionCompletions(qual: tpd.Tree): CompletionMap =
479519
val adjustedQual = widenQualifier(qual)
480520

481521
val implicitConversionMembers = implicitConversionMemberCompletions(adjustedQual)
@@ -493,60 +533,14 @@ object Completion:
493533
/** Completions for members of `qual`'s type.
494534
* These include inherited definitions but not members added by extensions or implicit conversions
495535
*/
496-
def directMemberCompletions(qual: tpd.Tree)(using Context): CompletionMap =
536+
def directMemberCompletions(qual: tpd.Tree): CompletionMap =
497537
if qual.typeOpt.isExactlyNothing then
498538
Map.empty
499539
else
500540
accessibleMembers(qual.typeOpt).groupByName
501541

502-
/** Completions introduced by imports directly in this context.
503-
* Completions from outer contexts are not included.
504-
*/
505-
private def importedCompletions(using Context): CompletionResult =
506-
val imp = ctx.importInfo
507-
val renames = collection.mutable.Map.empty[Symbol, Name]
508-
509-
if imp == null then
510-
CompletionResult(Map.empty, Map.empty)
511-
else
512-
def fromImport(name: Name, nameInScope: Name): Seq[(Name, SingleDenotation)] =
513-
imp.site.member(name).alternatives
514-
.collect { case denot if include(denot, nameInScope) =>
515-
if name != nameInScope then
516-
renames(denot.symbol) = nameInScope
517-
nameInScope -> denot
518-
}
519-
520-
val givenImports = imp.importedImplicits
521-
.map { ref => (ref.implicitName: Name, ref.underlyingRef.denot.asSingleDenotation) }
522-
.filter((name, denot) => include(denot, name))
523-
.groupByName
524-
525-
val wildcardMembers =
526-
if imp.selectors.exists(_.imported.name == nme.WILDCARD) then
527-
val denots = accessibleMembers(imp.site)
528-
.filter(mbr => !mbr.symbol.is(Given) && !imp.excluded.contains(mbr.name.toTermName))
529-
denots.groupByName
530-
else
531-
Map.empty
532-
533-
val explicitMembers =
534-
val importNamesInScope = imp.forwardMapping.toList.map(_._2)
535-
val duplicatedNames = importNamesInScope.diff(importNamesInScope.distinct)
536-
val discardedNames = duplicatedNames ++ imp.excluded
537-
imp.reverseMapping.toList
538-
.filter { (nameInScope, _) => !discardedNames.contains(nameInScope) }
539-
.flatMap { (nameInScope, original) =>
540-
fromImport(original, nameInScope) ++
541-
fromImport(original.toTypeName, nameInScope.toTypeName)
542-
}.toSeq.groupByName
543-
544-
val results = givenImports ++ wildcardMembers ++ explicitMembers
545-
CompletionResult(results, renames.toMap)
546-
end importedCompletions
547-
548542
/** Completions from implicit conversions including old style extensions using implicit classes */
549-
private def implicitConversionMemberCompletions(qual: tpd.Tree)(using Context): CompletionMap =
543+
private def implicitConversionMemberCompletions(qual: tpd.Tree): CompletionMap =
550544

551545
def tryToInstantiateTypeVars(conversionTarget: SearchSuccess): Type =
552546
try
@@ -567,16 +561,15 @@ object Completion:
567561
.groupByName
568562

569563
/** Completions for named tuples */
570-
private def namedTupleCompletions(qual: tpd.Tree)(using Context): CompletionMap =
564+
private def namedTupleCompletions(qual: tpd.Tree): CompletionMap =
571565
def namedTupleCompletionsFromType(tpe: Type): CompletionMap =
572566
val freshCtx = ctx.fresh.setExploreTyperState()
573567
inContext(freshCtx):
574568
tpe.namedTupleElementTypes(true)
575-
.map { (name, tpe) =>
569+
.map: (name, tpe) =>
576570
val symbol = newSymbol(owner = NoSymbol, name, EmptyFlags, tpe)
577571
val denot = SymDenotation(symbol, NoSymbol, name, EmptyFlags, tpe)
578572
name -> denot
579-
}
580573
.toSeq
581574
.filter((name, denot) => include(denot, name))
582575
.groupByName
@@ -591,17 +584,16 @@ object Completion:
591584
else Map.empty
592585

593586
/** Completions from extension methods */
594-
private def extensionCompletions(qual: tpd.Tree)(using Context): CompletionMap =
587+
private def extensionCompletions(qual: tpd.Tree): CompletionMap =
595588
def asDefLikeType(tpe: Type): Type = tpe match
596589
case _: MethodOrPoly => tpe
597590
case _ => ExprType(tpe)
598591

599592
def tryApplyingReceiverToExtension(termRef: TermRef): Option[SingleDenotation] =
600593
ctx.typer.tryApplyingExtensionMethod(termRef, qual)
601-
.map { tree =>
594+
.map: tree =>
602595
val tpe = asDefLikeType(tree.typeOpt.dealias)
603596
termRef.denot.asSingleDenotation.mapInfo(_ => tpe)
604-
}
605597

606598
def extractMemberExtensionMethods(types: Seq[Type]): Seq[(TermRef, TermName)] =
607599
object DenotWithMatchingName:
@@ -610,17 +602,15 @@ object Completion:
610602
case name: TermName if include(denot, name) => Some((denot, name))
611603
case _ => None
612604

613-
types.flatMap { tp =>
605+
types.flatMap: tp =>
614606
val tpe = tp.widenExpr
615607
tpe.membersBasedOnFlags(required = ExtensionMethod, excluded = EmptyFlags)
616608
.collect { case DenotWithMatchingName(denot, name) => TermRef(tpe, denot.symbol) -> name }
617-
}
618609

619610
// There are four possible ways for an extension method to be applicable
620611

621612
// 1. The extension method is visible under a simple name, by being defined or inherited or imported in a scope enclosing the reference.
622-
val termCompleter = new Completer(Mode.Term, pos, untpdPath, matches)
623-
val extMethodsInScope = termCompleter.scopeCompletions.names.toList.flatMap:
613+
val extMethodsInScope = scopeCompletions.names.toList.flatMap:
624614
case (name, denots) => denots.collect:
625615
case d: SymDenotation if d.isTerm && d.termRef.symbol.is(Extension) => (d.termRef, name.asTermName)
626616

@@ -637,13 +627,13 @@ object Completion:
637627
val extMethodsFromGivensInImplicitScope = extractMemberExtensionMethods(givensInImplicitScope)
638628

639629
val availableExtMethods = extMethodsFromGivensInImplicitScope ++ extMethodsFromImplicitScope ++ extMethodsFromGivensInScope ++ extMethodsInScope
640-
val extMethodsWithAppliedReceiver = availableExtMethods.flatMap {
630+
val extMethodsWithAppliedReceiver = availableExtMethods.flatMap:
641631
case (termRef, termName) =>
642632
if termRef.symbol.is(ExtensionMethod) && !qual.typeOpt.isBottomType then
643633
tryApplyingReceiverToExtension(termRef)
644634
.map(denot => termName -> denot)
645635
else None
646-
}
636+
647637
extMethodsWithAppliedReceiver.groupByName
648638

649639
lazy val isNew: Boolean = isInNewContext(untpdPath)
@@ -683,10 +673,9 @@ object Completion:
683673
catch
684674
case ex: TypeError =>
685675

686-
val members = site.memberDenots(completionsFilter, appendMemberSyms).collect {
687-
case mbr if include(mbr, mbr.name)
688-
&& mbr.symbol.isAccessibleFrom(site) => mbr
689-
}
676+
val members = site.memberDenots(completionsFilter, appendMemberSyms).collect:
677+
case mbr if include(mbr, mbr.name) && mbr.symbol.isAccessibleFrom(site) => mbr
678+
690679
val refinements = extractRefinements(site).filter(mbr => include(mbr, mbr.name))
691680

692681
members ++ refinements
@@ -699,13 +688,12 @@ object Completion:
699688
* @param qual The argument to which the implicit conversion should be applied.
700689
* @return The set of types after `qual` implicit conversion.
701690
*/
702-
private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] = {
691+
private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] =
703692
val typer = ctx.typer
704693
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span, Set.empty).allImplicits
705694

706695
interactiv.println(i"implicit conversion targets considered: ${conversions.toList}%, %")
707696
conversions
708-
}
709697

710698
/** Filter for names that should appear when looking for completions. */
711699
private object completionsFilter extends NameFilter:

presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2250,3 +2250,10 @@ class CompletionSuite extends BaseCompletionSuite:
22502250
|""".stripMargin,
22512251
""
22522252
)
2253+
2254+
@Test def `no-completions-on-package-selection` =
2255+
check(
2256+
"""package one.@@
2257+
|""".stripMargin,
2258+
""
2259+
)

0 commit comments

Comments
 (0)