
Preferred types are used by code completion for ranking. This commit considerably increases the number of points in code where those types are propagated. In order to avoid complicating signatures of Parser's methods, a preferred type is kept as a member variable in the parser and updated during parsing. Differential revision: https://reviews.llvm.org/D56723 llvm-svn: 352788
442 lines
12 KiB
C++
442 lines
12 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=*/{},
|
|
/*CodeCompleteConsumer*/ false),
|
|
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, VisitedNSForInvalideQualifiedId) {
|
|
auto VisitedNS = runCodeCompleteOnCode(R"cpp(
|
|
namespace ns { foo::^ }
|
|
)cpp")
|
|
.VisitedNamespaces;
|
|
EXPECT_TRUE(VisitedNS.empty());
|
|
}
|
|
|
|
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 *"));
|
|
}
|
|
} // namespace
|