summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam McCall <sam.mccall@gmail.com>2019-04-10 11:50:40 +0000
committerSam McCall <sam.mccall@gmail.com>2019-04-10 11:50:40 +0000
commit6f9978319faf097f535977a63a4d78b3bffbac11 (patch)
tree2171d7c878fb06f4df34993ba5ccb2e42ad3f79c
parentb96943b6a00d76e4bcf5b6d459d346cd2fb9e296 (diff)
downloadllvm-6f9978319faf097f535977a63a4d78b3bffbac11.tar.gz
[clangd] Refactor speculateCompletionFilter and also extract scope.
Summary: Intent is to use the heuristically-parsed scope in cases where we get bogus results from sema, such as in complex macro expansions. Added a motivating testcase we currently get wrong. Name changed because we (already) use this for things other than speculation. Reviewers: ioeric Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D60500 llvm-svn: 358074
-rw-r--r--clang-tools-extra/clangd/CodeComplete.cpp54
-rw-r--r--clang-tools-extra/clangd/CodeComplete.h19
-rw-r--r--clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp71
3 files changed, 88 insertions, 56 deletions
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index 7b455bbb0249..62ebe3e96f07 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -37,6 +37,7 @@
#include "index/Symbol.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
+#include "clang/Basic/CharInfo.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Format/Format.h"
@@ -1110,14 +1111,12 @@ llvm::Optional<FuzzyFindRequest>
speculativeFuzzyFindRequestForCompletion(FuzzyFindRequest CachedReq,
PathRef File, llvm::StringRef Content,
Position Pos) {
- auto Filter = speculateCompletionFilter(Content, Pos);
- if (!Filter) {
- elog("Failed to speculate filter text for code completion at Pos "
- "{0}:{1}: {2}",
- Pos.line, Pos.character, Filter.takeError());
- return None;
+ auto Offset = positionToOffset(Content, Pos);
+ if (!Offset) {
+ elog("No speculative filter: bad offset {0} in {1}", Pos, File);
+ return llvm::None;
}
- CachedReq.Query = *Filter;
+ CachedReq.Query = guessCompletionPrefix(Content, *Offset).Name;
return CachedReq;
}
@@ -1537,29 +1536,26 @@ clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
return Result;
}
-llvm::Expected<llvm::StringRef>
-speculateCompletionFilter(llvm::StringRef Content, Position Pos) {
- auto Offset = positionToOffset(Content, Pos);
- if (!Offset)
- return llvm::make_error<llvm::StringError>(
- "Failed to convert position to offset in content.",
- llvm::inconvertibleErrorCode());
- if (*Offset == 0)
- return "";
+CompletionPrefix
+guessCompletionPrefix(llvm::StringRef Content, unsigned Offset) {
+ assert(Offset <= Content.size());
+ StringRef Rest = Content.take_front(Offset);
+ CompletionPrefix Result;
+
+ // Consume the unqualified name. We only handle ASCII characters.
+ // isIdentifierBody will let us match "0invalid", but we don't mind.
+ while (!Rest.empty() && isIdentifierBody(Rest.back()))
+ Rest = Rest.drop_back();
+ Result.Name = Content.slice(Rest.size(), Offset);
+
+ // Consume qualifiers.
+ while (Rest.consume_back("::") && !Rest.endswith(":")) // reject ::::
+ while (!Rest.empty() && isIdentifierBody(Rest.back()))
+ Rest = Rest.drop_back();
+ Result.Qualifier =
+ Content.slice(Rest.size(), Result.Name.begin() - Content.begin());
- // Start from the character before the cursor.
- int St = *Offset - 1;
- // FIXME(ioeric): consider UTF characters?
- auto IsValidIdentifierChar = [](char C) {
- return ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') ||
- (C >= '0' && C <= '9') || (C == '_'));
- };
- size_t Len = 0;
- for (; (St >= 0) && IsValidIdentifierChar(Content[St]); --St, ++Len) {
- }
- if (Len > 0)
- St++; // Shift to the first valid character.
- return Content.substr(St, Len);
+ return Result;
}
CodeCompleteResult
diff --git a/clang-tools-extra/clangd/CodeComplete.h b/clang-tools-extra/clangd/CodeComplete.h
index d4a413c48047..aced6bef7d5b 100644
--- a/clang-tools-extra/clangd/CodeComplete.h
+++ b/clang-tools-extra/clangd/CodeComplete.h
@@ -254,10 +254,21 @@ SignatureHelp signatureHelp(PathRef FileName,
// completion.
bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx);
-/// Retrives a speculative code completion filter text before the cursor.
-/// Exposed for testing only.
-llvm::Expected<llvm::StringRef>
-speculateCompletionFilter(llvm::StringRef Content, Position Pos);
+// Text immediately before the completion point that should be completed.
+// This is heuristically derived from the source code, and is used when:
+// - semantic analysis fails
+// - semantic analysis may be slow, and we speculatively query the index
+struct CompletionPrefix {
+ // The unqualified partial name.
+ // If there is none, begin() == end() == completion position.
+ llvm::StringRef Name;
+ // The spelled scope qualifier, such as Foo::.
+ // If there is none, begin() == end() == Name.begin().
+ llvm::StringRef Qualifier;
+};
+// Heuristically parses before Offset to determine what should be completed.
+CompletionPrefix guessCompletionPrefix(llvm::StringRef Content,
+ unsigned Offset);
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp b/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp
index 16d8efcf8105..28c2e6be5f69 100644
--- a/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp
+++ b/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp
@@ -32,7 +32,6 @@ namespace {
using ::llvm::Failed;
using ::testing::AllOf;
using ::testing::Contains;
-using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::HasSubstr;
@@ -1915,28 +1914,37 @@ TEST(CompletionTest, OverridesNonIdentName) {
)cpp");
}
-TEST(SpeculateCompletionFilter, Filters) {
- Annotations F(R"cpp($bof^
- $bol^
- ab$ab^
- x.ab$dot^
- x.$dotempty^
- x::ab$scoped^
- x::$scopedempty^
-
- )cpp");
- auto speculate = [&](StringRef PointName) {
- auto Filter = speculateCompletionFilter(F.code(), F.point(PointName));
- assert(Filter);
- return *Filter;
- };
- EXPECT_EQ(speculate("bof"), "");
- EXPECT_EQ(speculate("bol"), "");
- EXPECT_EQ(speculate("ab"), "ab");
- EXPECT_EQ(speculate("dot"), "ab");
- EXPECT_EQ(speculate("dotempty"), "");
- EXPECT_EQ(speculate("scoped"), "ab");
- EXPECT_EQ(speculate("scopedempty"), "");
+TEST(GuessCompletionPrefix, Filters) {
+ for (llvm::StringRef Case : {
+ "[[scope::]][[ident]]^",
+ "[[]][[]]^",
+ "\n[[]][[]]^",
+ "[[]][[ab]]^",
+ "x.[[]][[ab]]^",
+ "x.[[]][[]]^",
+ "[[x::]][[ab]]^",
+ "[[x::]][[]]^",
+ "[[::x::]][[ab]]^",
+ "some text [[scope::more::]][[identif]]^ier",
+ "some text [[scope::]][[mor]]^e::identifier",
+ "weird case foo::[[::bar::]][[baz]]^",
+ }) {
+ Annotations F(Case);
+ auto Offset = cantFail(positionToOffset(F.code(), F.point()));
+ auto ToStringRef = [&](Range R) {
+ return F.code().slice(cantFail(positionToOffset(F.code(), R.start)),
+ cantFail(positionToOffset(F.code(), R.end)));
+ };
+ auto WantQualifier = ToStringRef(F.ranges()[0]),
+ WantName = ToStringRef(F.ranges()[1]);
+
+ auto Prefix = guessCompletionPrefix(F.code(), Offset);
+ // Even when components are empty, check their offsets are correct.
+ EXPECT_EQ(WantQualifier, Prefix.Qualifier) << Case;
+ EXPECT_EQ(WantQualifier.begin(), Prefix.Qualifier.begin()) << Case;
+ EXPECT_EQ(WantName, Prefix.Name) << Case;
+ EXPECT_EQ(WantName.begin(), Prefix.Name.begin()) << Case;
+ }
}
TEST(CompletionTest, EnableSpeculativeIndexRequest) {
@@ -2366,6 +2374,23 @@ TEST(CompletionTest, NestedScopeIsUnresolved) {
UnorderedElementsAre(AllOf(Qualifier(""), Named("XYZ"))));
}
+// Regression test: clang parser gets confused here and doesn't report the ns::
+// prefix - naive behavior is to insert it again.
+// However we can recognize this from the source code.
+// Test disabled until we can make it pass.
+TEST(CompletionTest, DISABLED_NamespaceDoubleInsertion) {
+ clangd::CodeCompleteOptions Opts = {};
+
+ auto Results = completions(R"cpp(
+ namespace ns {}
+ #define M(X) < X
+ M(ns::ABC^
+ )cpp",
+ {cls("ns::ABCDE")}, Opts);
+ EXPECT_THAT(Results.Completions,
+ UnorderedElementsAre(AllOf(Qualifier(""), Named("ABCDE"))));
+}
+
} // namespace
} // namespace clangd
} // namespace clang