llvm-project/clang/unittests/Sema/CodeCompleteTest.cpp

481 lines
13 KiB
C++

//=== unittests/Sema/CodeCompleteTest.cpp - Code Complete tests ==============//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Parse/ParseAST.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/SemaDiagnostic.h"
#include "clang/Tooling/Tooling.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <cstddef>
#include <string>
namespace {
using namespace clang;
using namespace clang::tooling;
using ::testing::Each;
using ::testing::UnorderedElementsAre;
const char TestCCName[] = "test.cc";
struct CompletionContext {
std::vector<std::string> VisitedNamespaces;
std::string PreferredType;
// String representation of std::ptrdiff_t on a given platform. This is a hack
// to properly account for different configurations of clang.
std::string PtrDiffType;
};
class VisitedContextFinder : public CodeCompleteConsumer {
public:
VisitedContextFinder(CompletionContext &ResultCtx)
: CodeCompleteConsumer(/*CodeCompleteOpts=*/{}), ResultCtx(ResultCtx),
CCTUInfo(std::make_shared<GlobalCodeCompletionAllocator>()) {}
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
CodeCompletionResult *Results,
unsigned NumResults) override {
ResultCtx.VisitedNamespaces =
getVisitedNamespace(Context.getVisitedContexts());
ResultCtx.PreferredType = Context.getPreferredType().getAsString();
ResultCtx.PtrDiffType =
S.getASTContext().getPointerDiffType().getAsString();
}
CodeCompletionAllocator &getAllocator() override {
return CCTUInfo.getAllocator();
}
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
private:
std::vector<std::string> getVisitedNamespace(
CodeCompletionContext::VisitedContextSet VisitedContexts) const {
std::vector<std::string> NSNames;
for (const auto *Context : VisitedContexts)
if (const auto *NS = llvm::dyn_cast<NamespaceDecl>(Context))
NSNames.push_back(NS->getQualifiedNameAsString());
return NSNames;
}
CompletionContext &ResultCtx;
CodeCompletionTUInfo CCTUInfo;
};
class CodeCompleteAction : public SyntaxOnlyAction {
public:
CodeCompleteAction(ParsedSourceLocation P, CompletionContext &ResultCtx)
: CompletePosition(std::move(P)), ResultCtx(ResultCtx) {}
bool BeginInvocation(CompilerInstance &CI) override {
CI.getFrontendOpts().CodeCompletionAt = CompletePosition;
CI.setCodeCompletionConsumer(new VisitedContextFinder(ResultCtx));
return true;
}
private:
// 1-based code complete position <Line, Col>;
ParsedSourceLocation CompletePosition;
CompletionContext &ResultCtx;
};
ParsedSourceLocation offsetToPosition(llvm::StringRef Code, size_t Offset) {
Offset = std::min(Code.size(), Offset);
StringRef Before = Code.substr(0, Offset);
int Lines = Before.count('\n');
size_t PrevNL = Before.rfind('\n');
size_t StartOfLine = (PrevNL == StringRef::npos) ? 0 : (PrevNL + 1);
return {TestCCName, static_cast<unsigned>(Lines + 1),
static_cast<unsigned>(Offset - StartOfLine + 1)};
}
CompletionContext runCompletion(StringRef Code, size_t Offset) {
CompletionContext ResultCtx;
auto Action = llvm::make_unique<CodeCompleteAction>(
offsetToPosition(Code, Offset), ResultCtx);
clang::tooling::runToolOnCodeWithArgs(Action.release(), Code, {"-std=c++11"},
TestCCName);
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,
std::string *PtrDiffType = nullptr) {
ParsedAnnotations P = parseAnnotations(AnnotatedCode);
std::vector<std::string> Types;
for (size_t Point : P.Points) {
auto Results = runCompletion(P.Code, Point);
if (PtrDiffType) {
assert(PtrDiffType->empty() || *PtrDiffType == Results.PtrDiffType);
*PtrDiffType = Results.PtrDiffType;
}
Types.push_back(Results.PreferredType);
}
return Types;
}
TEST(SemaCodeCompleteTest, VisitedNSForValidQualifiedId) {
auto VisitedNS = runCodeCompleteOnCode(R"cpp(
namespace ns1 {}
namespace ns2 {}
namespace ns3 {}
namespace ns3 { namespace nns3 {} }
namespace foo {
using namespace ns1;
namespace ns4 {} // not visited
namespace { using namespace ns2; }
inline namespace bar { using namespace ns3::nns3; }
} // foo
namespace ns { foo::^ }
)cpp")
.VisitedNamespaces;
EXPECT_THAT(VisitedNS, UnorderedElementsAre("foo", "ns1", "ns2", "ns3::nns3",
"foo::(anonymous)"));
}
TEST(SemaCodeCompleteTest, VisitedNSForInvalidQualifiedId) {
auto VisitedNS = runCodeCompleteOnCode(R"cpp(
namespace na {}
namespace ns1 {
using namespace na;
foo::^
}
)cpp")
.VisitedNamespaces;
EXPECT_THAT(VisitedNS, UnorderedElementsAre("ns1", "na"));
}
TEST(SemaCodeCompleteTest, VisitedNSWithoutQualifier) {
auto VisitedNS = runCodeCompleteOnCode(R"cpp(
namespace n1 {
namespace n2 {
void f(^) {}
}
}
)cpp")
.VisitedNamespaces;
EXPECT_THAT(VisitedNS, UnorderedElementsAre("n1", "n1::n2"));
}
TEST(PreferredTypeTest, BinaryExpr) {
// Check various operations for arithmetic types.
StringRef Code = 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";
EXPECT_THAT(collectPreferredTypes(Code), Each("int"));
Code = 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";
EXPECT_THAT(collectPreferredTypes(Code), Each("float"));
// Pointer types.
Code = R"cpp(
void test(int *ptr) {
ptr - ^ptr;
ptr = ^ptr;
})cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("int *"));
Code = R"cpp(
void test(int *ptr) {
ptr + ^10;
ptr += ^10;
ptr -= ^10;
})cpp";
{
std::string PtrDiff;
auto Types = collectPreferredTypes(Code, &PtrDiff);
EXPECT_THAT(Types, Each(PtrDiff));
}
// Comparison operators.
Code = R"cpp(
void test(int i) {
i <= ^1; i < ^1; i >= ^1; i > ^1; i == ^1; i != ^1;
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("int"));
Code = R"cpp(
void test(int *ptr) {
ptr <= ^ptr; ptr < ^ptr; ptr >= ^ptr; ptr > ^ptr;
ptr == ^ptr; ptr != ^ptr;
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("int *"));
// Relational operations.
Code = R"cpp(
void test(int i, int *ptr) {
i && ^1; i || ^1;
ptr && ^1; ptr || ^1;
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("_Bool"));
// Bitwise operations.
Code = R"cpp(
void test(long long ll) {
ll | ^1; ll & ^1;
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("long long"));
Code = R"cpp(
enum A {};
void test(A a) {
a | ^1; a & ^1;
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("enum A"));
Code = 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";
EXPECT_THAT(collectPreferredTypes(Code), Each("enum A"));
// Binary shifts.
Code = 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";
EXPECT_THAT(collectPreferredTypes(Code), Each("int"));
// Comma does not provide any useful information.
Code = R"cpp(
class Cls {};
void test(int i, int* ptr, Cls x) {
(i, ^i);
(ptr, ^ptr);
(x, ^x);
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("NULL TYPE"));
// User-defined types do not take operator overloading into account.
// However, they provide heuristics for some common cases.
Code = 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";
EXPECT_THAT(collectPreferredTypes(Code), Each("class Cls"));
Code = R"cpp(
class Cls {};
void test(Cls c) {
// we assume relational ops operate on bools.
c && ^c; c || ^c;
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("_Bool"));
Code = 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";
EXPECT_THAT(collectPreferredTypes(Code), Each("NULL TYPE"));
}
TEST(PreferredTypeTest, Members) {
StringRef Code = R"cpp(
struct vector {
int *begin();
vector clone();
};
void test(int *a) {
a = ^vector().^clone().^begin();
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("int *"));
}
TEST(PreferredTypeTest, Conditions) {
StringRef Code = R"cpp(
struct vector {
bool empty();
};
void test() {
if (^vector().^empty()) {}
while (^vector().^empty()) {}
for (; ^vector().^empty();) {}
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("_Bool"));
}
TEST(PreferredTypeTest, InitAndAssignment) {
StringRef Code = R"cpp(
struct vector {
int* begin();
};
void test() {
const int* x = ^vector().^begin();
x = ^vector().^begin();
if (const int* y = ^vector().^begin()) {}
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("const int *"));
}
TEST(PreferredTypeTest, UnaryExprs) {
StringRef Code = R"cpp(
void test(long long a) {
a = +^a;
a = -^a
a = ++^a;
a = --^a;
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("long long"));
Code = R"cpp(
void test(int a, int *ptr) {
!^a;
!^ptr;
!!!^a;
a = !^a;
a = !^ptr;
a = !!!^a;
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("_Bool"));
Code = R"cpp(
void test(int a) {
const int* x = &^a;
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("const int"));
Code = R"cpp(
void test(int *a) {
int x = *^a;
int &r = *^a;
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("int *"));
Code = R"cpp(
void test(int a) {
*^a;
&^a;
}
)cpp";
}
TEST(PreferredTypeTest, ParenExpr) {
StringRef Code = R"cpp(
const int *i = ^(^(^(^10)));
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("const int *"));
}
TEST(PreferredTypeTest, FunctionArguments) {
StringRef Code = R"cpp(
void foo(const int*);
void bar(const int*);
void bar(const int*, int b);
struct vector {
const int *data();
};
void test() {
foo(^(^(^(^vec^tor^().^da^ta^()))));
bar(^(^(^(^vec^tor^().^da^ta^()))));
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("const int *"));
Code = R"cpp(
void bar(int, volatile double *);
void bar(int, volatile double *, int, int);
struct vector {
double *data();
};
struct class_members {
void bar(int, volatile double *);
void bar(int, volatile double *, int, int);
};
void test() {
bar(10, ^(^(^(^vec^tor^().^da^ta^()))));
class_members().bar(10, ^(^(^(^vec^tor^().^da^ta^()))));
}
)cpp";
EXPECT_THAT(collectPreferredTypes(Code), Each("volatile double *"));
}
} // namespace