|
@@ -14,31 +14,39 @@
|
|
#include "clang/Sema/Sema.h"
|
|
#include "clang/Sema/Sema.h"
|
|
#include "clang/Sema/SemaDiagnostic.h"
|
|
#include "clang/Sema/SemaDiagnostic.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
-#include "gtest/gtest.h"
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gmock/gmock.h"
|
|
|
|
+#include "gtest/gtest.h"
|
|
|
|
+#include <cstddef>
|
|
|
|
+#include <string>
|
|
|
|
|
|
namespace {
|
|
namespace {
|
|
|
|
|
|
using namespace clang;
|
|
using namespace clang;
|
|
using namespace clang::tooling;
|
|
using namespace clang::tooling;
|
|
|
|
+using ::testing::Each;
|
|
using ::testing::UnorderedElementsAre;
|
|
using ::testing::UnorderedElementsAre;
|
|
|
|
|
|
const char TestCCName[] = "test.cc";
|
|
const char TestCCName[] = "test.cc";
|
|
-using VisitedContextResults = std::vector<std::string>;
|
|
|
|
|
|
|
|
-class VisitedContextFinder: public CodeCompleteConsumer {
|
|
|
|
|
|
+struct CompletionContext {
|
|
|
|
+ std::vector<std::string> VisitedNamespaces;
|
|
|
|
+ std::string PreferredType;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+class VisitedContextFinder : public CodeCompleteConsumer {
|
|
public:
|
|
public:
|
|
- VisitedContextFinder(VisitedContextResults &Results)
|
|
|
|
|
|
+ VisitedContextFinder(CompletionContext &ResultCtx)
|
|
: CodeCompleteConsumer(/*CodeCompleteOpts=*/{},
|
|
: CodeCompleteConsumer(/*CodeCompleteOpts=*/{},
|
|
/*CodeCompleteConsumer*/ false),
|
|
/*CodeCompleteConsumer*/ false),
|
|
- VCResults(Results),
|
|
|
|
|
|
+ ResultCtx(ResultCtx),
|
|
CCTUInfo(std::make_shared<GlobalCodeCompletionAllocator>()) {}
|
|
CCTUInfo(std::make_shared<GlobalCodeCompletionAllocator>()) {}
|
|
|
|
|
|
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
|
|
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
|
|
CodeCompletionResult *Results,
|
|
CodeCompletionResult *Results,
|
|
unsigned NumResults) override {
|
|
unsigned NumResults) override {
|
|
- VisitedContexts = Context.getVisitedContexts();
|
|
|
|
- VCResults = getVisitedNamespace();
|
|
|
|
|
|
+ ResultCtx.VisitedNamespaces =
|
|
|
|
+ getVisitedNamespace(Context.getVisitedContexts());
|
|
|
|
+ ResultCtx.PreferredType = Context.getPreferredType().getAsString();
|
|
}
|
|
}
|
|
|
|
|
|
CodeCompletionAllocator &getAllocator() override {
|
|
CodeCompletionAllocator &getAllocator() override {
|
|
@@ -47,7 +55,9 @@ public:
|
|
|
|
|
|
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
|
|
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
|
|
|
|
|
|
- std::vector<std::string> getVisitedNamespace() const {
|
|
|
|
|
|
+private:
|
|
|
|
+ std::vector<std::string> getVisitedNamespace(
|
|
|
|
+ CodeCompletionContext::VisitedContextSet VisitedContexts) const {
|
|
std::vector<std::string> NSNames;
|
|
std::vector<std::string> NSNames;
|
|
for (const auto *Context : VisitedContexts)
|
|
for (const auto *Context : VisitedContexts)
|
|
if (const auto *NS = llvm::dyn_cast<NamespaceDecl>(Context))
|
|
if (const auto *NS = llvm::dyn_cast<NamespaceDecl>(Context))
|
|
@@ -55,27 +65,25 @@ public:
|
|
return NSNames;
|
|
return NSNames;
|
|
}
|
|
}
|
|
|
|
|
|
-private:
|
|
|
|
- VisitedContextResults& VCResults;
|
|
|
|
|
|
+ CompletionContext &ResultCtx;
|
|
CodeCompletionTUInfo CCTUInfo;
|
|
CodeCompletionTUInfo CCTUInfo;
|
|
- CodeCompletionContext::VisitedContextSet VisitedContexts;
|
|
|
|
};
|
|
};
|
|
|
|
|
|
class CodeCompleteAction : public SyntaxOnlyAction {
|
|
class CodeCompleteAction : public SyntaxOnlyAction {
|
|
public:
|
|
public:
|
|
- CodeCompleteAction(ParsedSourceLocation P, VisitedContextResults &Results)
|
|
|
|
- : CompletePosition(std::move(P)), VCResults(Results) {}
|
|
|
|
|
|
+ CodeCompleteAction(ParsedSourceLocation P, CompletionContext &ResultCtx)
|
|
|
|
+ : CompletePosition(std::move(P)), ResultCtx(ResultCtx) {}
|
|
|
|
|
|
bool BeginInvocation(CompilerInstance &CI) override {
|
|
bool BeginInvocation(CompilerInstance &CI) override {
|
|
CI.getFrontendOpts().CodeCompletionAt = CompletePosition;
|
|
CI.getFrontendOpts().CodeCompletionAt = CompletePosition;
|
|
- CI.setCodeCompletionConsumer(new VisitedContextFinder(VCResults));
|
|
|
|
|
|
+ CI.setCodeCompletionConsumer(new VisitedContextFinder(ResultCtx));
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
private:
|
|
private:
|
|
// 1-based code complete position <Line, Col>;
|
|
// 1-based code complete position <Line, Col>;
|
|
ParsedSourceLocation CompletePosition;
|
|
ParsedSourceLocation CompletePosition;
|
|
- VisitedContextResults& VCResults;
|
|
|
|
|
|
+ CompletionContext &ResultCtx;
|
|
};
|
|
};
|
|
|
|
|
|
ParsedSourceLocation offsetToPosition(llvm::StringRef Code, size_t Offset) {
|
|
ParsedSourceLocation offsetToPosition(llvm::StringRef Code, size_t Offset) {
|
|
@@ -88,21 +96,49 @@ ParsedSourceLocation offsetToPosition(llvm::StringRef Code, size_t Offset) {
|
|
static_cast<unsigned>(Offset - StartOfLine + 1)};
|
|
static_cast<unsigned>(Offset - StartOfLine + 1)};
|
|
}
|
|
}
|
|
|
|
|
|
-VisitedContextResults runCodeCompleteOnCode(StringRef Code) {
|
|
|
|
- VisitedContextResults Results;
|
|
|
|
- auto TokenOffset = Code.find('^');
|
|
|
|
- assert(TokenOffset != StringRef::npos &&
|
|
|
|
- "Completion token ^ wasn't found in Code.");
|
|
|
|
- std::string WithoutToken = Code.take_front(TokenOffset);
|
|
|
|
- WithoutToken += Code.drop_front(WithoutToken.size() + 1);
|
|
|
|
- assert(StringRef(WithoutToken).find('^') == StringRef::npos &&
|
|
|
|
- "expected exactly one completion token ^ inside the code");
|
|
|
|
-
|
|
|
|
|
|
+CompletionContext runCompletion(StringRef Code, size_t Offset) {
|
|
|
|
+ CompletionContext ResultCtx;
|
|
auto Action = llvm::make_unique<CodeCompleteAction>(
|
|
auto Action = llvm::make_unique<CodeCompleteAction>(
|
|
- offsetToPosition(WithoutToken, TokenOffset), Results);
|
|
|
|
|
|
+ offsetToPosition(Code, Offset), ResultCtx);
|
|
clang::tooling::runToolOnCodeWithArgs(Action.release(), Code, {"-std=c++11"},
|
|
clang::tooling::runToolOnCodeWithArgs(Action.release(), Code, {"-std=c++11"},
|
|
TestCCName);
|
|
TestCCName);
|
|
- return Results;
|
|
|
|
|
|
+ return ResultCtx;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+struct ParsedAnnotations {
|
|
|
|
+ std::vector<size_t> Points;
|
|
|
|
+ std::string Code;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+ParsedAnnotations parseAnnotations(StringRef AnnotatedCode) {
|
|
|
|
+ ParsedAnnotations R;
|
|
|
|
+ while (!AnnotatedCode.empty()) {
|
|
|
|
+ size_t NextPoint = AnnotatedCode.find('^');
|
|
|
|
+ if (NextPoint == StringRef::npos) {
|
|
|
|
+ R.Code += AnnotatedCode;
|
|
|
|
+ AnnotatedCode = "";
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ R.Code += AnnotatedCode.substr(0, NextPoint);
|
|
|
|
+ R.Points.push_back(R.Code.size());
|
|
|
|
+
|
|
|
|
+ AnnotatedCode = AnnotatedCode.substr(NextPoint + 1);
|
|
|
|
+ }
|
|
|
|
+ return R;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+CompletionContext runCodeCompleteOnCode(StringRef AnnotatedCode) {
|
|
|
|
+ ParsedAnnotations P = parseAnnotations(AnnotatedCode);
|
|
|
|
+ assert(P.Points.size() == 1 && "expected exactly one annotation point");
|
|
|
|
+ return runCompletion(P.Code, P.Points.front());
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+std::vector<std::string> collectPreferredTypes(StringRef AnnotatedCode) {
|
|
|
|
+ ParsedAnnotations P = parseAnnotations(AnnotatedCode);
|
|
|
|
+ std::vector<std::string> Types;
|
|
|
|
+ for (size_t Point : P.Points)
|
|
|
|
+ Types.push_back(runCompletion(P.Code, Point).PreferredType);
|
|
|
|
+ return Types;
|
|
}
|
|
}
|
|
|
|
|
|
TEST(SemaCodeCompleteTest, VisitedNSForValidQualifiedId) {
|
|
TEST(SemaCodeCompleteTest, VisitedNSForValidQualifiedId) {
|
|
@@ -119,7 +155,8 @@ TEST(SemaCodeCompleteTest, VisitedNSForValidQualifiedId) {
|
|
inline namespace bar { using namespace ns3::nns3; }
|
|
inline namespace bar { using namespace ns3::nns3; }
|
|
} // foo
|
|
} // foo
|
|
namespace ns { foo::^ }
|
|
namespace ns { foo::^ }
|
|
- )cpp");
|
|
|
|
|
|
+ )cpp")
|
|
|
|
+ .VisitedNamespaces;
|
|
EXPECT_THAT(VisitedNS, UnorderedElementsAre("foo", "ns1", "ns2", "ns3::nns3",
|
|
EXPECT_THAT(VisitedNS, UnorderedElementsAre("foo", "ns1", "ns2", "ns3::nns3",
|
|
"foo::(anonymous)"));
|
|
"foo::(anonymous)"));
|
|
}
|
|
}
|
|
@@ -127,7 +164,8 @@ TEST(SemaCodeCompleteTest, VisitedNSForValidQualifiedId) {
|
|
TEST(SemaCodeCompleteTest, VisitedNSForInvalideQualifiedId) {
|
|
TEST(SemaCodeCompleteTest, VisitedNSForInvalideQualifiedId) {
|
|
auto VisitedNS = runCodeCompleteOnCode(R"cpp(
|
|
auto VisitedNS = runCodeCompleteOnCode(R"cpp(
|
|
namespace ns { foo::^ }
|
|
namespace ns { foo::^ }
|
|
- )cpp");
|
|
|
|
|
|
+ )cpp")
|
|
|
|
+ .VisitedNamespaces;
|
|
EXPECT_TRUE(VisitedNS.empty());
|
|
EXPECT_TRUE(VisitedNS.empty());
|
|
}
|
|
}
|
|
|
|
|
|
@@ -138,8 +176,150 @@ TEST(SemaCodeCompleteTest, VisitedNSWithoutQualifier) {
|
|
void f(^) {}
|
|
void f(^) {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- )cpp");
|
|
|
|
|
|
+ )cpp")
|
|
|
|
+ .VisitedNamespaces;
|
|
EXPECT_THAT(VisitedNS, UnorderedElementsAre("n1", "n1::n2"));
|
|
EXPECT_THAT(VisitedNS, UnorderedElementsAre("n1", "n1::n2"));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+TEST(PreferredTypeTest, BinaryExpr) {
|
|
|
|
+ // Check various operations for arithmetic types.
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ void test(int x) {
|
|
|
|
+ x = ^10;
|
|
|
|
+ x += ^10; x -= ^10; x *= ^10; x /= ^10; x %= ^10;
|
|
|
|
+ x + ^10; x - ^10; x * ^10; x / ^10; x % ^10;
|
|
|
|
+ })cpp"),
|
|
|
|
+ Each("int"));
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ void test(float x) {
|
|
|
|
+ x = ^10;
|
|
|
|
+ x += ^10; x -= ^10; x *= ^10; x /= ^10; x %= ^10;
|
|
|
|
+ x + ^10; x - ^10; x * ^10; x / ^10; x % ^10;
|
|
|
|
+ })cpp"),
|
|
|
|
+ Each("float"));
|
|
|
|
+
|
|
|
|
+ // Pointer types.
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ void test(int *ptr) {
|
|
|
|
+ ptr - ^ptr;
|
|
|
|
+ ptr = ^ptr;
|
|
|
|
+ })cpp"),
|
|
|
|
+ Each("int *"));
|
|
|
|
+
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ void test(int *ptr) {
|
|
|
|
+ ptr + ^10;
|
|
|
|
+ ptr += ^10;
|
|
|
|
+ ptr -= ^10;
|
|
|
|
+ })cpp"),
|
|
|
|
+ Each("long")); // long is normalized 'ptrdiff_t'.
|
|
|
|
+
|
|
|
|
+ // Comparison operators.
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ void test(int i) {
|
|
|
|
+ i <= ^1; i < ^1; i >= ^1; i > ^1; i == ^1; i != ^1;
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("int"));
|
|
|
|
+
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ void test(int *ptr) {
|
|
|
|
+ ptr <= ^ptr; ptr < ^ptr; ptr >= ^ptr; ptr > ^ptr;
|
|
|
|
+ ptr == ^ptr; ptr != ^ptr;
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("int *"));
|
|
|
|
+
|
|
|
|
+ // Relational operations.
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ void test(int i, int *ptr) {
|
|
|
|
+ i && ^1; i || ^1;
|
|
|
|
+ ptr && ^1; ptr || ^1;
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("_Bool"));
|
|
|
|
+
|
|
|
|
+ // Bitwise operations.
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ void test(long long ll) {
|
|
|
|
+ ll | ^1; ll & ^1;
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("long long"));
|
|
|
|
+
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ enum A {};
|
|
|
|
+ void test(A a) {
|
|
|
|
+ a | ^1; a & ^1;
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("enum A"));
|
|
|
|
+
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ enum class A {};
|
|
|
|
+ void test(A a) {
|
|
|
|
+ // This is technically illegal with the 'enum class' without overloaded
|
|
|
|
+ // operators, but we pretend it's fine.
|
|
|
|
+ a | ^a; a & ^a;
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("enum A"));
|
|
|
|
+
|
|
|
|
+ // Binary shifts.
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ void test(int i, long long ll) {
|
|
|
|
+ i << ^1; ll << ^1;
|
|
|
|
+ i <<= ^1; i <<= ^1;
|
|
|
|
+ i >> ^1; ll >> ^1;
|
|
|
|
+ i >>= ^1; i >>= ^1;
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("int"));
|
|
|
|
+
|
|
|
|
+ // Comma does not provide any useful information.
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ class Cls {};
|
|
|
|
+ void test(int i, int* ptr, Cls x) {
|
|
|
|
+ (i, ^i);
|
|
|
|
+ (ptr, ^ptr);
|
|
|
|
+ (x, ^x);
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("NULL TYPE"));
|
|
|
|
+
|
|
|
|
+ // User-defined types do not take operator overloading into account.
|
|
|
|
+ // However, they provide heuristics for some common cases.
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ class Cls {};
|
|
|
|
+ void test(Cls c) {
|
|
|
|
+ // we assume arithmetic and comparions ops take the same type.
|
|
|
|
+ c + ^c; c - ^c; c * ^c; c / ^c; c % ^c;
|
|
|
|
+ c == ^c; c != ^c; c < ^c; c <= ^c; c > ^c; c >= ^c;
|
|
|
|
+ // same for the assignments.
|
|
|
|
+ c = ^c; c += ^c; c -= ^c; c *= ^c; c /= ^c; c %= ^c;
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("class Cls"));
|
|
|
|
+
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ class Cls {};
|
|
|
|
+ void test(Cls c) {
|
|
|
|
+ // we assume relational ops operate on bools.
|
|
|
|
+ c && ^c; c || ^c;
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("_Bool"));
|
|
|
|
+
|
|
|
|
+ EXPECT_THAT(collectPreferredTypes(R"cpp(
|
|
|
|
+ class Cls {};
|
|
|
|
+ void test(Cls c) {
|
|
|
|
+ // we make no assumptions about the following operators, since they are
|
|
|
|
+ // often overloaded with a non-standard meaning.
|
|
|
|
+ c << ^c; c >> ^c; c | ^c; c & ^c;
|
|
|
|
+ c <<= ^c; c >>= ^c; c |= ^c; c &= ^c;
|
|
|
|
+ }
|
|
|
|
+ )cpp"),
|
|
|
|
+ Each("NULL TYPE"));
|
|
|
|
+}
|
|
|
|
+
|
|
} // namespace
|
|
} // namespace
|