diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index f2b63cbec8d5..7882d635f84a 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -75,7 +75,7 @@ object Completion: customMatcher: Option[Name => Boolean] = None )(using Context): CompletionMap = val adjustedPath = typeCheckExtensionConstructPath(untpdPath, tpdPath, pos) - computeCompletions(pos, mode, rawPrefix, adjustedPath, customMatcher) + computeCompletions(pos, mode, rawPrefix, adjustedPath, untpdPath, customMatcher) /** * Inspect `path` to determine what kinds of symbols should be considered. @@ -199,12 +199,16 @@ object Completion: .flatten.getOrElse(tpdPath) private def computeCompletions( - pos: SourcePosition, mode: Mode, rawPrefix: String, adjustedPath: List[tpd.Tree], matches: Option[Name => Boolean] + pos: SourcePosition, + mode: Mode, rawPrefix: String, + adjustedPath: List[tpd.Tree], + untpdPath: List[untpd.Tree], + matches: Option[Name => Boolean] )(using Context): CompletionMap = val hasBackTick = rawPrefix.headOption.contains('`') val prefix = if hasBackTick then rawPrefix.drop(1) else rawPrefix val matches0 = matches.getOrElse(_.startsWith(prefix)) - val completer = new Completer(mode, pos, matches0) + val completer = new Completer(mode, pos, untpdPath, matches0) val result = adjustedPath match // Ignore synthetic select from `This` because in code it was `Ident` @@ -279,6 +283,12 @@ object Completion: if denot.isType then denot.symbol.showFullName else denot.info.widenTermRefExpr.show + + def isInNewContext(untpdPath: List[untpd.Tree]): Boolean = + untpdPath match + case _ :: untpd.New(selectOrIdent: (untpd.Select | untpd.Ident)) :: _ => true + case _ => false + /** Include in completion sets only symbols that * 1. is not absent (info is not NoType) * 2. are not a primary constructor, @@ -290,7 +300,11 @@ object Completion: * 8. symbol is not a constructor proxy module when in type completion mode * 9. have same term/type kind as name prefix given so far */ - def isValidCompletionSymbol(sym: Symbol, completionMode: Mode)(using Context): Boolean = + def isValidCompletionSymbol(sym: Symbol, completionMode: Mode, isNew: Boolean)(using Context): Boolean = + + lazy val isEnum = sym.is(Enum) || + (sym.companionClass.exists && sym.companionClass.is(Enum)) + sym.exists && !sym.isAbsent() && !sym.isPrimaryConstructor && @@ -300,6 +314,7 @@ object Completion: !sym.isPackageObject && !sym.is(Artifact) && !(completionMode.is(Mode.Type) && sym.isAllOf(ConstructorProxyModule)) && + !(isNew && isEnum) && ( (completionMode.is(Mode.Term) && (sym.isTerm || sym.is(ModuleClass)) || (completionMode.is(Mode.Type) && (sym.isType || sym.isStableMember))) @@ -323,7 +338,7 @@ object Completion: * For the results of all `xyzCompletions` methods term names and type names are always treated as different keys in the same map * and they never conflict with each other. */ - class Completer(val mode: Mode, pos: SourcePosition, matches: Name => Boolean): + class Completer(val mode: Mode, pos: SourcePosition, untpdPath: List[untpd.Tree], matches: Name => Boolean): /** Completions for terms and types that are currently in scope: * the members of the current class, local definitions and the symbols that have been imported, * recursively adding completions from outer scopes. @@ -530,7 +545,7 @@ object Completion: // There are four possible ways for an extension method to be applicable // 1. The extension method is visible under a simple name, by being defined or inherited or imported in a scope enclosing the reference. - val termCompleter = new Completer(Mode.Term, pos, matches) + val termCompleter = new Completer(Mode.Term, pos, untpdPath, matches) val extMethodsInScope = termCompleter.scopeCompletions.toList.flatMap: case (name, denots) => denots.collect: case d: SymDenotation if d.isTerm && d.termRef.symbol.is(Extension) => (d.termRef, name.asTermName) @@ -557,6 +572,8 @@ object Completion: } extMethodsWithAppliedReceiver.groupByName + lazy val isNew: Boolean = isInNewContext(untpdPath) + /** Include in completion sets only symbols that * 1. match the filter method, * 2. satisfy [[Completion.isValidCompletionSymbol]] @@ -564,7 +581,7 @@ object Completion: private def include(denot: SingleDenotation, nameInScope: Name)(using Context): Boolean = matches(nameInScope) && completionsFilter(NoType, nameInScope) && - isValidCompletionSymbol(denot.symbol, mode) + isValidCompletionSymbol(denot.symbol, mode, isNew) private def extractRefinements(site: Type)(using Context): Seq[SingleDenotation] = site match diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 6ef8bee8a5d2..d64bb44c1a5d 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -1704,4 +1704,23 @@ class CompletionTest { .completion(m1, Set( ("getOrElse", Method, "[V1 >: String](key: Int, default: => V1): V1"), )) + + @Test def noEnumCompletionInNewContext: Unit = + code"""|enum TestEnum: + | case TestCase + |object M: + | TestEnu$m1 + | TestEnum.TestCa$m2 + | val x: TestEnu$m3 + | val y: TestEnum.Tes$m4 + | new TestEnu$m5 + | new TestEnum.TestCas$m6 + |""" + .completion(m1, Set(("TestEnum", Module, "TestEnum"))) + .completion(m2, Set(("TestCase", Field, "TestEnum"))) + .completion(m3, Set(("TestEnum", Module, "TestEnum"), ("TestEnum", Class, "TestEnum"))) + .completion(m4, Set(("TestCase", Field, "TestEnum"))) + .completion(m5, Set()) + .completion(m6, Set()) + } diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index 052287193540..fb39102399ba 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -72,10 +72,7 @@ class Completions( case _ :: (_: (Import | Export)) :: _ => false case _ => true - private lazy val isNew: Boolean = - path match - case _ :: New(selectOrIdent: (Select | Ident)) :: _ => true - case _ => false + private lazy val isNew: Boolean = Completion.isInNewContext(adjustedPath) def includeSymbol(sym: Symbol)(using Context): Boolean = def hasSyntheticCursorSuffix: Boolean = @@ -537,7 +534,7 @@ class Completions( val query = completionPos.query if completionMode.is(Mode.Scope) && query.nonEmpty then val visitor = new CompilerSearchVisitor(sym => - if Completion.isValidCompletionSymbol(sym, completionMode) && + if Completion.isValidCompletionSymbol(sym, completionMode, isNew) && !(sym.is(Flags.ExtensionMethod) || (sym.maybeOwner.is(Flags.Implicit) && sym.maybeOwner.isClass)) then indexedContext.lookupSym(sym) match diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index f4f659db1541..b5db258601bc 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -1877,3 +1877,34 @@ class CompletionSuite extends BaseCompletionSuite: |""".stripMargin, topLines = Some(2) ) + + @Test def `no-enum-completions-in-new-context` = + check( + """enum TestEnum: + | case TestCase + |object M: + | new TestEnu@@ + |""".stripMargin, + "" + ) + + @Test def `no-enum-case-completions-in-new-context` = + check( + """enum TestEnum: + | case TestCase + |object M: + | new TestEnum.TestCas@@ + |""".stripMargin, + "" + ) + + @Test def `deduplicated-enum-completions` = + check( + """enum TestEnum: + | case TestCase + |object M: + | val x: TestEn@@ + |""".stripMargin, + """TestEnum test + |""".stripMargin, + )