summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Liu <ioeric@google.com>2017-12-19 16:50:37 +0000
committerEric Liu <ioeric@google.com>2017-12-19 16:50:37 +0000
commita50f7cf406f7279f4bc875147e72aec4bd423f21 (patch)
tree3d74035ae2436a2da653635dd6c994f6ec7c3509
parent81a5eb03e612b2aeb88bb6a4d32a26e0b2d9cdb8 (diff)
[clangd] Index-based code completion.
Summary: Use symbol index to populate completion results for qualfified IDs e.g. "nx::A^". Reviewers: ilya-biryukov, sammccall Reviewed By: ilya-biryukov, sammccall Subscribers: rwols, klimek, mgorny, cfe-commits, sammccall Differential Revision: https://reviews.llvm.org/D41281 git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/trunk@321083 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--clangd/CodeComplete.cpp197
-rw-r--r--clangd/CodeComplete.h8
-rw-r--r--clangd/index/FileIndex.cpp4
-rw-r--r--clangd/index/FileIndex.h4
-rw-r--r--clangd/index/Index.h2
-rw-r--r--clangd/index/MemIndex.cpp2
-rw-r--r--clangd/index/MemIndex.h2
-rw-r--r--unittests/clangd/CodeCompleteTests.cpp101
-rw-r--r--unittests/clangd/IndexTests.cpp9
9 files changed, 297 insertions, 32 deletions
diff --git a/clangd/CodeComplete.cpp b/clangd/CodeComplete.cpp
index 66bce09b..226e8f8e 100644
--- a/clangd/CodeComplete.cpp
+++ b/clangd/CodeComplete.cpp
@@ -16,6 +16,8 @@
#include "CodeComplete.h"
#include "Compiler.h"
+#include "Logger.h"
+#include "index/Index.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Sema/CodeCompleteConsumer.h"
@@ -26,26 +28,28 @@ namespace clang {
namespace clangd {
namespace {
-CompletionItemKind getKindOfDecl(CXCursorKind CursorKind) {
+CompletionItemKind toCompletionItemKind(CXCursorKind CursorKind) {
switch (CursorKind) {
case CXCursor_MacroInstantiation:
case CXCursor_MacroDefinition:
return CompletionItemKind::Text;
case CXCursor_CXXMethod:
+ case CXCursor_Destructor:
return CompletionItemKind::Method;
case CXCursor_FunctionDecl:
case CXCursor_FunctionTemplate:
return CompletionItemKind::Function;
case CXCursor_Constructor:
- case CXCursor_Destructor:
return CompletionItemKind::Constructor;
case CXCursor_FieldDecl:
return CompletionItemKind::Field;
case CXCursor_VarDecl:
case CXCursor_ParmDecl:
return CompletionItemKind::Variable;
- case CXCursor_ClassDecl:
+ // FIXME(ioeric): use LSP struct instead of class when it is suppoted in the
+ // protocol.
case CXCursor_StructDecl:
+ case CXCursor_ClassDecl:
case CXCursor_UnionDecl:
case CXCursor_ClassTemplate:
case CXCursor_ClassTemplatePartialSpecialization:
@@ -58,6 +62,7 @@ CompletionItemKind getKindOfDecl(CXCursorKind CursorKind) {
return CompletionItemKind::Value;
case CXCursor_EnumDecl:
return CompletionItemKind::Enum;
+ // FIXME(ioeric): figure out whether reference is the right type for aliases.
case CXCursor_TypeAliasDecl:
case CXCursor_TypeAliasTemplateDecl:
case CXCursor_TypedefDecl:
@@ -69,11 +74,12 @@ CompletionItemKind getKindOfDecl(CXCursorKind CursorKind) {
}
}
-CompletionItemKind getKind(CodeCompletionResult::ResultKind ResKind,
- CXCursorKind CursorKind) {
+CompletionItemKind
+toCompletionItemKind(CodeCompletionResult::ResultKind ResKind,
+ CXCursorKind CursorKind) {
switch (ResKind) {
case CodeCompletionResult::RK_Declaration:
- return getKindOfDecl(CursorKind);
+ return toCompletionItemKind(CursorKind);
case CodeCompletionResult::RK_Keyword:
return CompletionItemKind::Keyword;
case CodeCompletionResult::RK_Macro:
@@ -85,6 +91,59 @@ CompletionItemKind getKind(CodeCompletionResult::ResultKind ResKind,
llvm_unreachable("Unhandled CodeCompletionResult::ResultKind.");
}
+CompletionItemKind toCompletionItemKind(index::SymbolKind Kind) {
+ using SK = index::SymbolKind;
+ switch (Kind) {
+ case SK::Unknown:
+ return CompletionItemKind::Missing;
+ case SK::Module:
+ case SK::Namespace:
+ case SK::NamespaceAlias:
+ return CompletionItemKind::Module;
+ case SK::Macro:
+ return CompletionItemKind::Text;
+ case SK::Enum:
+ return CompletionItemKind::Enum;
+ // FIXME(ioeric): use LSP struct instead of class when it is suppoted in the
+ // protocol.
+ case SK::Struct:
+ case SK::Class:
+ case SK::Protocol:
+ case SK::Extension:
+ case SK::Union:
+ return CompletionItemKind::Class;
+ // FIXME(ioeric): figure out whether reference is the right type for aliases.
+ case SK::TypeAlias:
+ case SK::Using:
+ return CompletionItemKind::Reference;
+ case SK::Function:
+ // FIXME(ioeric): this should probably be an operator. This should be fixed
+ // when `Operator` is support type in the protocol.
+ case SK::ConversionFunction:
+ return CompletionItemKind::Function;
+ case SK::Variable:
+ case SK::Parameter:
+ return CompletionItemKind::Variable;
+ case SK::Field:
+ return CompletionItemKind::Field;
+ // FIXME(ioeric): use LSP enum constant when it is supported in the protocol.
+ case SK::EnumConstant:
+ return CompletionItemKind::Value;
+ case SK::InstanceMethod:
+ case SK::ClassMethod:
+ case SK::StaticMethod:
+ case SK::Destructor:
+ return CompletionItemKind::Method;
+ case SK::InstanceProperty:
+ case SK::ClassProperty:
+ case SK::StaticProperty:
+ return CompletionItemKind::Property;
+ case SK::Constructor:
+ return CompletionItemKind::Constructor;
+ }
+ llvm_unreachable("Unhandled clang::index::SymbolKind.");
+}
+
std::string escapeSnippet(const llvm::StringRef Text) {
std::string Result;
Result.reserve(Text.size()); // Assume '$', '}' and '\\' are rare.
@@ -228,20 +287,48 @@ private:
}
};
+/// \brief Information about the scope specifier in the qualified-id code
+/// completion (e.g. "ns::ab?").
+struct SpecifiedScope {
+ /// The scope specifier as written. For example, for completion "ns::ab?", the
+ /// written scope specifier is "ns".
+ std::string Written;
+ // If this scope specifier is recognized in Sema (e.g. as a namespace
+ // context), this will be set to the fully qualfied name of the corresponding
+ // context.
+ std::string Resolved;
+};
+
+/// \brief Information from sema about (parital) symbol names to be completed.
+/// For example, for completion "ns::ab^", this stores the scope specifier
+/// "ns::" and the completion filter text "ab".
+struct NameToComplete {
+ // The partial identifier being completed, without qualifier.
+ std::string Filter;
+
+ /// This is set if the completion is for qualified IDs, e.g. "abc::x^".
+ llvm::Optional<SpecifiedScope> SSInfo;
+};
+
+SpecifiedScope extraCompletionScope(Sema &S, const CXXScopeSpec &SS);
+
class CompletionItemsCollector : public CodeCompleteConsumer {
public:
CompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
- CompletionList &Items)
+ CompletionList &Items, NameToComplete &CompletedName)
: CodeCompleteConsumer(CodeCompleteOpts.getClangCompleteOpts(),
/*OutputIsBinary=*/false),
ClangdOpts(CodeCompleteOpts), Items(Items),
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
- CCTUInfo(Allocator) {}
+ CCTUInfo(Allocator), CompletedName(CompletedName) {}
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
CodeCompletionResult *Results,
unsigned NumResults) override final {
- StringRef Filter = S.getPreprocessor().getCodeCompletionFilter();
+ if (auto SS = Context.getCXXScopeSpecifier())
+ CompletedName.SSInfo = extraCompletionScope(S, **SS);
+
+ CompletedName.Filter = S.getPreprocessor().getCodeCompletionFilter();
std::priority_queue<CompletionCandidate> Candidates;
for (unsigned I = 0; I < NumResults; ++I) {
auto &Result = Results[I];
@@ -249,7 +336,8 @@ public:
(Result.Availability == CXAvailability_NotAvailable ||
Result.Availability == CXAvailability_NotAccessible))
continue;
- if (!Filter.empty() && !fuzzyMatch(S, Context, Filter, Result))
+ if (!CompletedName.Filter.empty() &&
+ !fuzzyMatch(S, Context, CompletedName.Filter, Result))
continue;
Candidates.emplace(Result);
if (ClangdOpts.Limit && Candidates.size() > ClangdOpts.Limit) {
@@ -324,7 +412,8 @@ private:
ProcessChunks(CCS, Item);
// Fill in the kind field of the CompletionItem.
- Item.kind = getKind(Candidate.Result->Kind, Candidate.Result->CursorKind);
+ Item.kind = toCompletionItemKind(Candidate.Result->Kind,
+ Candidate.Result->CursorKind);
return Item;
}
@@ -336,7 +425,7 @@ private:
CompletionList &Items;
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
CodeCompletionTUInfo CCTUInfo;
-
+ NameToComplete &CompletedName;
}; // CompletionItemsCollector
bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) {
@@ -349,8 +438,9 @@ class PlainTextCompletionItemsCollector final
public:
PlainTextCompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
- CompletionList &Items)
- : CompletionItemsCollector(CodeCompleteOpts, Items) {}
+ CompletionList &Items,
+ NameToComplete &CompletedName)
+ : CompletionItemsCollector(CodeCompleteOpts, Items, CompletedName) {}
private:
void ProcessChunks(const CodeCompletionString &CCS,
@@ -385,8 +475,9 @@ class SnippetCompletionItemsCollector final : public CompletionItemsCollector {
public:
SnippetCompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
- CompletionList &Items)
- : CompletionItemsCollector(CodeCompleteOpts, Items) {}
+ CompletionList &Items,
+ NameToComplete &CompletedName)
+ : CompletionItemsCollector(CodeCompleteOpts, Items, CompletedName) {}
private:
void ProcessChunks(const CodeCompletionString &CCS,
@@ -648,6 +739,61 @@ bool invokeCodeComplete(const Context &Ctx,
return true;
}
+CompletionItem indexCompletionItem(const Symbol &Sym, llvm::StringRef Filter,
+ const SpecifiedScope &SSInfo) {
+ CompletionItem Item;
+ Item.kind = toCompletionItemKind(Sym.SymInfo.Kind);
+ Item.label = Sym.Name;
+ // FIXME(ioeric): support inserting/replacing scope qualifiers.
+ Item.insertText = Sym.Name;
+ // FIXME(ioeric): support snippets.
+ Item.insertTextFormat = InsertTextFormat::PlainText;
+ Item.filterText = Filter;
+
+ // FIXME(ioeric): sort symbols appropriately.
+ Item.sortText = "";
+
+ // FIXME(ioeric): use more symbol information (e.g. documentation, label) to
+ // populate the completion item.
+
+ return Item;
+}
+
+void completeWithIndex(const Context &Ctx, const SymbolIndex &Index,
+ llvm::StringRef Code, const SpecifiedScope &SSInfo,
+ llvm::StringRef Filter, CompletionList *Items) {
+ FuzzyFindRequest Req;
+ Req.Query = Filter;
+ // FIXME(ioeric): add more possible scopes based on using namespaces and
+ // containing namespaces.
+ StringRef Scope = SSInfo.Resolved.empty() ? SSInfo.Written : SSInfo.Resolved;
+ Req.Scopes = {Scope.trim(':').str()};
+
+ Items->isIncomplete = !Index.fuzzyFind(Ctx, Req, [&](const Symbol &Sym) {
+ Items->items.push_back(indexCompletionItem(Sym, Filter, SSInfo));
+ });
+}
+
+SpecifiedScope extraCompletionScope(Sema &S, const CXXScopeSpec &SS) {
+ SpecifiedScope Info;
+ auto &SM = S.getSourceManager();
+ auto SpecifierRange = SS.getRange();
+ Info.Written = Lexer::getSourceText(
+ CharSourceRange::getCharRange(SpecifierRange), SM, clang::LangOptions());
+ if (SS.isValid()) {
+ DeclContext *DC = S.computeDeclContext(SS);
+ if (auto *NS = llvm::dyn_cast<NamespaceDecl>(DC)) {
+ Info.Resolved = NS->getQualifiedNameAsString();
+ } else if (auto *TU = llvm::dyn_cast<TranslationUnitDecl>(DC)) {
+ Info.Resolved = "::";
+ // Sema does not include the suffix "::" in the range of SS, so we add
+ // it back here.
+ Info.Written = "::";
+ }
+ }
+ return Info;
+}
+
} // namespace
clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
@@ -657,6 +803,9 @@ clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
Result.IncludeGlobals = IncludeGlobals;
Result.IncludeBriefComments = IncludeBriefComments;
+ // Enable index-based code completion when Index is provided.
+ Result.IncludeNamespaceLevelDecls = !Index;
+
return Result;
}
@@ -669,16 +818,24 @@ CompletionList codeComplete(const Context &Ctx, PathRef FileName,
CodeCompleteOptions Opts) {
CompletionList Results;
std::unique_ptr<CodeCompleteConsumer> Consumer;
+ NameToComplete CompletedName;
if (Opts.EnableSnippets) {
- Consumer =
- llvm::make_unique<SnippetCompletionItemsCollector>(Opts, Results);
+ Consumer = llvm::make_unique<SnippetCompletionItemsCollector>(
+ Opts, Results, CompletedName);
} else {
- Consumer =
- llvm::make_unique<PlainTextCompletionItemsCollector>(Opts, Results);
+ Consumer = llvm::make_unique<PlainTextCompletionItemsCollector>(
+ Opts, Results, CompletedName);
}
invokeCodeComplete(Ctx, std::move(Consumer), Opts.getClangCompleteOpts(),
FileName, Command, Preamble, Contents, Pos, std::move(VFS),
std::move(PCHs));
+ if (Opts.Index && CompletedName.SSInfo) {
+ log(Ctx, "WARNING: Got completion results from sema for completion on "
+ "qualified ID while symbol index is provided.");
+ Results.items.clear();
+ completeWithIndex(Ctx, *Opts.Index, Contents, *CompletedName.SSInfo,
+ CompletedName.Filter, &Results);
+ }
return Results;
}
diff --git a/clangd/CodeComplete.h b/clangd/CodeComplete.h
index 10e03368..52ec0188 100644
--- a/clangd/CodeComplete.h
+++ b/clangd/CodeComplete.h
@@ -19,6 +19,7 @@
#include "Logger.h"
#include "Path.h"
#include "Protocol.h"
+#include "index/Index.h"
#include "clang/Frontend/PrecompiledPreamble.h"
#include "clang/Sema/CodeCompleteOptions.h"
#include "clang/Tooling/CompilationDatabase.h"
@@ -59,6 +60,13 @@ struct CodeCompleteOptions {
/// Limit the number of results returned (0 means no limit).
/// If more results are available, we set CompletionList.isIncomplete.
size_t Limit = 0;
+
+ // Populated internally by clangd, do not set.
+ /// If `Index` is set, it is used to augment the code completion
+ /// results.
+ /// FIXME(ioeric): we might want a better way to pass the index around inside
+ /// clangd.
+ const SymbolIndex *Index = nullptr;
};
/// Get code completions at a specified \p Pos in \p FileName.
diff --git a/clangd/index/FileIndex.cpp b/clangd/index/FileIndex.cpp
index 1e0016a2..5f4e157b 100644
--- a/clangd/index/FileIndex.cpp
+++ b/clangd/index/FileIndex.cpp
@@ -63,7 +63,7 @@ std::shared_ptr<std::vector<const Symbol *>> FileSymbols::allSymbols() {
return {std::move(Snap), Pointers};
}
-void FileIndex::update(Context &Ctx, PathRef Path, ParsedAST *AST) {
+void FileIndex::update(const Context &Ctx, PathRef Path, ParsedAST *AST) {
if (!AST) {
FSymbols.update(Path, nullptr);
} else {
@@ -74,7 +74,7 @@ void FileIndex::update(Context &Ctx, PathRef Path, ParsedAST *AST) {
Index.build(std::move(Symbols));
}
-bool FileIndex::fuzzyFind(Context &Ctx, const FuzzyFindRequest &Req,
+bool FileIndex::fuzzyFind(const Context &Ctx, const FuzzyFindRequest &Req,
std::function<void(const Symbol &)> Callback) const {
return Index.fuzzyFind(Ctx, Req, std::move(Callback));
}
diff --git a/clangd/index/FileIndex.h b/clangd/index/FileIndex.h
index e904b8d1..6408a25b 100644
--- a/clangd/index/FileIndex.h
+++ b/clangd/index/FileIndex.h
@@ -58,9 +58,9 @@ class FileIndex : public SymbolIndex {
public:
/// \brief Update symbols in \p Path with symbols in \p AST. If \p AST is
/// nullptr, this removes all symbols in the file
- void update(Context &Ctx, PathRef Path, ParsedAST *AST);
+ void update(const Context &Ctx, PathRef Path, ParsedAST *AST);
- bool fuzzyFind(Context &Ctx, const FuzzyFindRequest &Req,
+ bool fuzzyFind(const Context &Ctx, const FuzzyFindRequest &Req,
std::function<void(const Symbol &)> Callback) const override;
private:
diff --git a/clangd/index/Index.h b/clangd/index/Index.h
index 1cb8943a..60aaebae 100644
--- a/clangd/index/Index.h
+++ b/clangd/index/Index.h
@@ -153,7 +153,7 @@ public:
/// Returns true if the result list is complete, false if it was truncated due
/// to MaxCandidateCount
virtual bool
- fuzzyFind(Context &Ctx, const FuzzyFindRequest &Req,
+ fuzzyFind(const Context &Ctx, const FuzzyFindRequest &Req,
std::function<void(const Symbol &)> Callback) const = 0;
// FIXME: add interfaces for more index use cases:
diff --git a/clangd/index/MemIndex.cpp b/clangd/index/MemIndex.cpp
index cc4c0ed8..11beae4a 100644
--- a/clangd/index/MemIndex.cpp
+++ b/clangd/index/MemIndex.cpp
@@ -26,7 +26,7 @@ void MemIndex::build(std::shared_ptr<std::vector<const Symbol *>> Syms) {
}
}
-bool MemIndex::fuzzyFind(Context &Ctx, const FuzzyFindRequest &Req,
+bool MemIndex::fuzzyFind(const Context &Ctx, const FuzzyFindRequest &Req,
std::function<void(const Symbol &)> Callback) const {
assert(!StringRef(Req.Query).contains("::") &&
"There must be no :: in query.");
diff --git a/clangd/index/MemIndex.h b/clangd/index/MemIndex.h
index 24601294..26ae34cf 100644
--- a/clangd/index/MemIndex.h
+++ b/clangd/index/MemIndex.h
@@ -24,7 +24,7 @@ public:
/// accessible as long as `Symbols` is kept alive.
void build(std::shared_ptr<std::vector<const Symbol *>> Symbols);
- bool fuzzyFind(Context &Ctx, const FuzzyFindRequest &Req,
+ bool fuzzyFind(const Context &Ctx, const FuzzyFindRequest &Req,
std::function<void(const Symbol &)> Callback) const override;
private:
diff --git a/unittests/clangd/CodeCompleteTests.cpp b/unittests/clangd/CodeCompleteTests.cpp
index 8ba9ebb3..4e598d35 100644
--- a/unittests/clangd/CodeCompleteTests.cpp
+++ b/unittests/clangd/CodeCompleteTests.cpp
@@ -6,6 +6,7 @@
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
+
#include "ClangdServer.h"
#include "Compiler.h"
#include "Context.h"
@@ -13,6 +14,7 @@
#include "Protocol.h"
#include "SourceCode.h"
#include "TestFS.h"
+#include "index/MemIndex.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -83,6 +85,7 @@ StringWithPos parseTextMarker(StringRef Text) {
MATCHER_P(Named, Name, "") { return arg.insertText == Name; }
MATCHER_P(Labeled, Label, "") { return arg.label == Label; }
MATCHER_P(Kind, K, "") { return arg.kind == K; }
+MATCHER_P(Filter, F, "") { return arg.filterText == F; }
MATCHER_P(PlainText, Text, "") {
return arg.insertTextFormat == clangd::InsertTextFormat::PlainText &&
arg.insertText == Text;
@@ -457,6 +460,104 @@ TEST(SignatureHelpTest, ActiveArg) {
EXPECT_EQ(1, Results.activeParameter);
}
+std::unique_ptr<SymbolIndex> simpleIndexFromSymbols(
+ std::vector<std::pair<std::string, index::SymbolKind>> Symbols) {
+ auto I = llvm::make_unique<MemIndex>();
+ struct Snapshot {
+ SymbolSlab Slab;
+ std::vector<const Symbol *> Pointers;
+ };
+ auto Snap = std::make_shared<Snapshot>();
+ for (const auto &Pair : Symbols) {
+ Symbol Sym;
+ Sym.ID = SymbolID(Pair.first);
+ llvm::StringRef QName = Pair.first;
+ size_t Pos = QName.rfind("::");
+ if (Pos == llvm::StringRef::npos) {
+ Sym.Name = QName;
+ Sym.Scope = "";
+ } else {
+ Sym.Name = QName.substr(Pos + 2);
+ Sym.Scope = QName.substr(0, Pos);
+ }
+ Sym.SymInfo.Kind = Pair.second;
+ Snap->Slab.insert(std::move(Sym));
+ }
+ for (auto &Iter : Snap->Slab)
+ Snap->Pointers.push_back(&Iter.second);
+ auto S = std::shared_ptr<std::vector<const Symbol *>>(std::move(Snap),
+ &Snap->Pointers);
+ I->build(std::move(S));
+ return I;
+}
+
+TEST(CompletionTest, NoIndex) {
+ clangd::CodeCompleteOptions Opts;
+ Opts.Index = nullptr;
+
+ auto Results = completions(R"cpp(
+ namespace ns { class No {}; }
+ void f() { ns::^ }
+ )cpp",
+ Opts);
+ EXPECT_THAT(Results.items, Has("No"));
+}
+
+TEST(CompletionTest, SimpleIndexBased) {
+ clangd::CodeCompleteOptions Opts;
+ auto I = simpleIndexFromSymbols({{"ns::XYZ", index::SymbolKind::Class},
+ {"nx::XYZ", index::SymbolKind::Class},
+ {"ns::foo", index::SymbolKind::Function}});
+ Opts.Index = I.get();
+
+ auto Results = completions(R"cpp(
+ namespace ns { class No {}; }
+ void f() { ns::^ }
+ )cpp",
+ Opts);
+ EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class));
+ EXPECT_THAT(Results.items, Has("foo", CompletionItemKind::Function));
+ EXPECT_THAT(Results.items, Not(Has("No")));
+}
+
+TEST(CompletionTest, IndexBasedWithFilter) {
+ clangd::CodeCompleteOptions Opts;
+ auto I = simpleIndexFromSymbols({{"ns::XYZ", index::SymbolKind::Class},
+ {"ns::foo", index::SymbolKind::Function}});
+ Opts.Index = I.get();
+
+ auto Results = completions(R"cpp(
+ void f() { ns::x^ }
+ )cpp",
+ Opts);
+ EXPECT_THAT(Results.items, Contains(AllOf(Named("XYZ"), Filter("x"))));
+ EXPECT_THAT(Results.items, Not(Has("foo")));
+}
+
+TEST(CompletionTest, GlobalQualified) {
+ clangd::CodeCompleteOptions Opts;
+ auto I = simpleIndexFromSymbols({{"XYZ", index::SymbolKind::Class}});
+ Opts.Index = I.get();
+
+ auto Results = completions(R"cpp(
+ void f() { ::^ }
+ )cpp",
+ Opts);
+ EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class));
+}
+
+TEST(CompletionTest, FullyQualifiedScope) {
+ clangd::CodeCompleteOptions Opts;
+ auto I = simpleIndexFromSymbols({{"ns::XYZ", index::SymbolKind::Class}});
+ Opts.Index = I.get();
+
+ auto Results = completions(R"cpp(
+ void f() { ::ns::^ }
+ )cpp",
+ Opts);
+ EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/unittests/clangd/IndexTests.cpp b/unittests/clangd/IndexTests.cpp
index 2852da0b..ea0ea794 100644
--- a/unittests/clangd/IndexTests.cpp
+++ b/unittests/clangd/IndexTests.cpp
@@ -143,7 +143,7 @@ TEST(MemIndexTest, MatchQualifiedNamesWithGlobalScope) {
I.build(generateSymbols({"a::xyz", "b::yz", "yz"}));
FuzzyFindRequest Req;
Req.Query = "y";
- Req.Scopes.push_back("");
+ Req.Scopes = {""};
auto Matches = match(I, Req);
EXPECT_THAT(match(I, Req), UnorderedElementsAre("yz"));
}
@@ -153,7 +153,7 @@ TEST(MemIndexTest, MatchQualifiedNamesWithOneScope) {
I.build(generateSymbols({"a::xyz", "a::yy", "a::xz", "b::yz", "yz"}));
FuzzyFindRequest Req;
Req.Query = "y";
- Req.Scopes.push_back("a");
+ Req.Scopes = {"a"};
auto Matches = match(I, Req);
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz", "a::yy"));
}
@@ -163,8 +163,7 @@ TEST(MemIndexTest, MatchQualifiedNamesWithMultipleScopes) {
I.build(generateSymbols({"a::xyz", "a::yy", "a::xz", "b::yz", "yz"}));
FuzzyFindRequest Req;
Req.Query = "y";
- Req.Scopes.push_back("a");
- Req.Scopes.push_back("b");
+ Req.Scopes = {"a", "b"};
auto Matches = match(I, Req);
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz", "a::yy", "b::yz"));
}
@@ -174,7 +173,7 @@ TEST(MemIndexTest, NoMatchNestedScopes) {
I.build(generateSymbols({"a::xyz", "a::b::yy"}));
FuzzyFindRequest Req;
Req.Query = "y";
- Req.Scopes.push_back("a");
+ Req.Scopes = {"a"};
auto Matches = match(I, Req);
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz"));
}