//=== 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 #include namespace { using namespace clang; using namespace clang::tooling; using ::testing::Each; using ::testing::UnorderedElementsAre; const char TestCCName[] = "test.cc"; struct CompletionContext { std::vector 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()) {} 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 getVisitedNamespace( CodeCompletionContext::VisitedContextSet VisitedContexts) const { std::vector NSNames; for (const auto *Context : VisitedContexts) if (const auto *NS = llvm::dyn_cast(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 ; 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(Lines + 1), static_cast(Offset - StartOfLine + 1)}; } CompletionContext runCompletion(StringRef Code, size_t Offset) { CompletionContext ResultCtx; clang::tooling::runToolOnCodeWithArgs( std::make_unique(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 collectPreferredTypes(StringRef AnnotatedCode, std::string *PtrDiffType = nullptr) { llvm::Annotations A(AnnotatedCode); std::vector 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 struct vector { using self = vector; }; void accepts_vector(vector); int foo(int); void test() { accepts_vector(^::^vector::^self); } )cpp"; EXPECT_THAT(collectPreferredTypes(Code), Each("vector")); } 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