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 @@ -311,7 +311,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 QueryScopes, const IncludeInserter &Includes, @@ -374,6 +375,8 @@ ShortestQualifier = Qualifier; } Completion.RequiredQualifier = std::string(ShortestQualifier); + + stripNamespaceForEnumConstantIfUsingDecl(*C.IndexResult, SemaDeclCtx); } } if (C.IdentifierResult) { @@ -430,6 +433,30 @@ }); } + // 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(D); + if (!UD) + continue; + const auto *ED = dyn_cast(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) { assert(bool(C.SemaResult) == bool(SemaCCS)); Bundled.emplace_back(); @@ -1963,7 +1990,8 @@ : nullptr; if (!Builder) Builder.emplace(Recorder ? &Recorder->CCSema->getASTContext() : nullptr, - Item, SemaCCS, QueryScopes, *Inserter, FileName, + Recorder ? Recorder->CCSema->CurContext : nullptr, Item, + SemaCCS, QueryScopes, *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 @@ -3063,6 +3063,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;