diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -305,7 +305,8 @@ // 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 AccessibleScopes, const IncludeInserter &Includes, @@ -360,14 +361,39 @@ // If the completion was visible to Sema, no qualifier is needed. This // avoids unneeded qualifiers in cases like with `using ns::X`. if (Completion.RequiredQualifier.empty() && !C.SemaResult) { - llvm::StringRef ShortestQualifier = C.IndexResult->Scope; - for (llvm::StringRef Scope : AccessibleScopes) { - llvm::StringRef Qualifier = C.IndexResult->Scope; - if (Qualifier.consume_front(Scope) && - Qualifier.size() < ShortestQualifier.size()) - ShortestQualifier = Qualifier; + // 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. + if (C.IndexResult->SymInfo.Kind == index::SymbolKind::EnumConstant && + SemaDeclCtx && ASTCtx) { + for (auto *Ctx = SemaDeclCtx; Ctx; Ctx = Ctx->getParent()) { + auto MatchingUsingDecl = llvm::find_if(Ctx->decls(), [&C](Decl *D) { + if (const auto *UD = dyn_cast(D)) + if (const auto *ED = dyn_cast(UD->getTargetDecl())) + if (ED->isScoped() && + printQualifiedName(*ED) + "::" == C.IndexResult->Scope) + return true; + return false; + }); + if (MatchingUsingDecl != Ctx->decls_end()) { + assert(MatchingUsingDecl->isNamed()); + Completion.RequiredQualifier = + dyn_cast(*MatchingUsingDecl)->getName().str() + + "::"; + break; + } + } + } else { + llvm::StringRef ShortestQualifier = C.IndexResult->Scope; + for (llvm::StringRef Scope : AccessibleScopes) { + llvm::StringRef Qualifier = C.IndexResult->Scope; + if (Qualifier.consume_front(Scope) && + Qualifier.size() < ShortestQualifier.size()) + ShortestQualifier = Qualifier; + } + Completion.RequiredQualifier = std::string(ShortestQualifier); } - Completion.RequiredQualifier = std::string(ShortestQualifier); } } if (C.IdentifierResult) { @@ -1983,7 +2009,8 @@ : 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); diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -3059,6 +3059,25 @@ 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;