| //=== 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 "llvm/Testing/Support/Annotations.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; |
| clang::tooling::runToolOnCodeWithArgs( |
| std::make_unique<CodeCompleteAction>(offsetToPosition(Code, Offset), |
| ResultCtx), |
| Code, {"-std=c++11"}, TestCCName); |
| return ResultCtx; |
| } |
| |
| CompletionContext runCodeCompleteOnCode(StringRef AnnotatedCode) { |
| llvm::Annotations A(AnnotatedCode); |
| return runCompletion(A.code(), A.point()); |
| } |
| |
| std::vector<std::string> |
| collectPreferredTypes(StringRef AnnotatedCode, |
| std::string *PtrDiffType = nullptr) { |
| llvm::Annotations A(AnnotatedCode); |
| std::vector<std::string> Types; |
| for (size_t Point : A.points()) { |
| auto Results = runCompletion(A.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 *")); |
| |
| Code = R"cpp( |
| namespace ns { |
| struct vector { |
| }; |
| } |
| void accepts_vector(ns::vector); |
| |
| void test() { |
| accepts_vector(^::^ns::^vector()); |
| } |
| )cpp"; |
| EXPECT_THAT(collectPreferredTypes(Code), Each("ns::vector")); |
| |
| Code = R"cpp( |
| template <class T> |
| struct vector { using self = vector; }; |
| |
| void accepts_vector(vector<int>); |
| int foo(int); |
| |
| void test() { |
| accepts_vector(^::^vector<decltype(foo(1))>::^self); |
| } |
| )cpp"; |
| EXPECT_THAT(collectPreferredTypes(Code), Each("vector<int>")); |
| } |
| |
| TEST(PreferredTypeTest, NoCrashOnInvalidTypes) { |
| StringRef Code = R"cpp( |
| auto x = decltype(&1)(^); |
| auto y = new decltype(&1)(^); |
| // GNU decimal type extension is not supported in clang. |
| auto z = new _Decimal128(^); |
| void foo() { (void)(foo)(^); } |
| )cpp"; |
| EXPECT_THAT(collectPreferredTypes(Code), Each("NULL TYPE")); |
| } |
| |
| } // namespace |