[clang-tools-extra] [clangd] Fix qualifier not being dropped for using declaration referring to scoped enum (PR #77766)

Tom Praschan via cfe-commits cfe-commits at lists.llvm.org
Thu Jan 11 05:31:47 PST 2024


https://github.com/tom-anders created https://github.com/llvm/llvm-project/pull/77766

Relevant issue: https://github.com/clangd/clangd/issues/1361

The problem here was that writing something like `using ns::ScopedEnum` does not cause Sema to visit this scope, to it won't be added into `CodeCompletionBuilder`'s `AccessibleScopes`, leading to the qualifier not being dropped.

To detect this, walk up the DeclContext to check if we have a matching using declaration. If so, drop the qualifiers.

Differential Revision: https://reviews.llvm.org/D141800

>From 660bca0eab80c9f1c4c4cc380701ede4250d4f90 Mon Sep 17 00:00:00 2001
From: Tom Praschan <13141438+tom-anders at users.noreply.github.com>
Date: Sat, 4 Feb 2023 13:15:47 +0100
Subject: [PATCH] [clangd] Fix qualifier not being dropped for using
 declaration referring to scoped enum

Relevant issue: https://github.com/clangd/clangd/issues/1361

The problem here was that writing something like `using ns::ScopedEnum`
does not cause Sema to visit this scope, to it won't be added into
`CodeCompletionBuilder`'s `AccessibleScopes`, leading to the qualifier not
being dropped.

To detect this, walk up the DeclContext to check if we have a matching
using declaration. If so, drop the qualifiers.

Differential Revision: https://reviews.llvm.org/D141800
---
 clang-tools-extra/clangd/CodeComplete.cpp     | 32 +++++++++++++++++--
 .../clangd/unittests/CodeCompleteTests.cpp    | 19 +++++++++++
 2 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index 0e5f08cec440ce..0345c014982134 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -333,7 +333,8 @@ std::string removeFirstTemplateArg(llvm::StringRef Signature) {
 // computed from the first candidate, in the constructor.
 // Others vary per candidate, so add() must be called for remaining candidates.
 struct CodeCompletionBuilder {
-  CodeCompletionBuilder(ASTContext *ASTCtx, const CompletionCandidate &C,
+  CodeCompletionBuilder(ASTContext *ASTCtx, DeclContext *SemaDeclCtx,
+                        const CompletionCandidate &C,
                         CodeCompletionString *SemaCCS,
                         llvm::ArrayRef<std::string> AccessibleScopes,
                         const IncludeInserter &Includes,
@@ -395,6 +396,8 @@ struct CodeCompletionBuilder {
             ShortestQualifier = Qualifier;
         }
         Completion.RequiredQualifier = std::string(ShortestQualifier);
+
+        stripNamespaceForEnumConstantIfUsingDecl(*C.IndexResult, SemaDeclCtx);
       }
     }
     if (C.IdentifierResult) {
@@ -452,6 +455,30 @@ struct CodeCompletionBuilder {
                           });
   }
 
+  // With all-scopes-completion, we can complete enum constants of scoped
+  // enums, in which case the completion might not be visible to Sema.
+  // So, if there's a using declaration for the enum class, manually
+  // drop the qualifiers.
+  void stripNamespaceForEnumConstantIfUsingDecl(const Symbol &IndexResult,
+                                                DeclContext *SemaDeclCtx) {
+    if (IndexResult.SymInfo.Kind != index::SymbolKind::EnumConstant)
+      return;
+    for (auto *Ctx = SemaDeclCtx; Ctx; Ctx = Ctx->getParent())
+      for (auto *D : Ctx->decls()) {
+        const auto *UD = dyn_cast<UsingShadowDecl>(D);
+        if (!UD)
+          continue;
+        const auto *ED = dyn_cast<EnumDecl>(UD->getTargetDecl());
+        if (!ED || !ED->isScoped())
+          continue;
+        auto EnumName = printQualifiedName(*ED) + "::";
+        if (EnumName == IndexResult.Scope) {
+          Completion.RequiredQualifier = ED->getName().str() + "::";
+          return;
+        }
+      }
+  }
+
   void add(const CompletionCandidate &C, CodeCompletionString *SemaCCS,
            CodeCompletionContext::Kind ContextKind) {
     assert(bool(C.SemaResult) == bool(SemaCCS));
@@ -2086,7 +2113,8 @@ class CodeCompleteFlow {
                           : nullptr;
       if (!Builder)
         Builder.emplace(Recorder ? &Recorder->CCSema->getASTContext() : nullptr,
-                        Item, SemaCCS, AccessibleScopes, *Inserter, FileName,
+                        Recorder ? Recorder->CCSema->CurContext : nullptr, Item,
+                        SemaCCS, AccessibleScopes, *Inserter, FileName,
                         CCContextKind, Opts, IsUsingDeclaration, NextTokenKind);
       else
         Builder->add(Item, SemaCCS, CCContextKind);
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 6d387fec9b3851..0c3eed7c35e578 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -3133,6 +3133,25 @@ TEST(CompletionTest, NoAllScopesCompletionWhenQualified) {
                   AllOf(qualifier(""), scope("na::"), named("ClangdA"))));
 }
 
+// https://github.com/clangd/clangd/issues/1361
+TEST(CompletionTest, ScopedEnumUsingDecl) {
+  clangd::CodeCompleteOptions Opts = {};
+  Opts.AllScopes = true;
+
+  auto Results = completions(
+      R"cpp(
+    namespace ns { enum class Scoped { FooBar }; }
+    using ns::Scoped;
+    void f() { 
+        Foo^ 
+    }
+  )cpp",
+      {enmConstant("ns::Scoped::FooBar")}, Opts);
+  EXPECT_THAT(Results.Completions, UnorderedElementsAre(AllOf(
+                                       qualifier("Scoped::"), named("FooBar"),
+                                       kind(CompletionItemKind::EnumMember))));
+}
+
 TEST(CompletionTest, AllScopesCompletion) {
   clangd::CodeCompleteOptions Opts = {};
   Opts.AllScopes = true;



More information about the cfe-commits mailing list