| //===-- SelectionTests.cpp - ----------------------------------------------===// |
| // |
| // 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 "Annotations.h" |
| #include "Selection.h" |
| #include "SourceCode.h" |
| #include "TestTU.h" |
| #include "support/TestTracer.h" |
| #include "clang/AST/Decl.h" |
| #include "llvm/Support/Casting.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| namespace clang { |
| namespace clangd { |
| namespace { |
| using ::testing::ElementsAreArray; |
| using ::testing::UnorderedElementsAreArray; |
| |
| // Create a selection tree corresponding to a point or pair of points. |
| // This uses the precisely-defined createRight semantics. The fuzzier |
| // createEach is tested separately. |
| SelectionTree makeSelectionTree(const StringRef MarkedCode, ParsedAST &AST) { |
| Annotations Test(MarkedCode); |
| switch (Test.points().size()) { |
| case 1: { // Point selection. |
| unsigned Offset = cantFail(positionToOffset(Test.code(), Test.point())); |
| return SelectionTree::createRight(AST.getASTContext(), AST.getTokens(), |
| Offset, Offset); |
| } |
| case 2: // Range selection. |
| return SelectionTree::createRight( |
| AST.getASTContext(), AST.getTokens(), |
| cantFail(positionToOffset(Test.code(), Test.points()[0])), |
| cantFail(positionToOffset(Test.code(), Test.points()[1]))); |
| default: |
| ADD_FAILURE() << "Expected 1-2 points for selection.\n" << MarkedCode; |
| return SelectionTree::createRight(AST.getASTContext(), AST.getTokens(), 0u, |
| 0u); |
| } |
| } |
| |
| Range nodeRange(const SelectionTree::Node *N, ParsedAST &AST) { |
| if (!N) |
| return Range{}; |
| const SourceManager &SM = AST.getSourceManager(); |
| const LangOptions &LangOpts = AST.getLangOpts(); |
| StringRef Buffer = SM.getBufferData(SM.getMainFileID()); |
| if (llvm::isa_and_nonnull<TranslationUnitDecl>(N->ASTNode.get<Decl>())) |
| return Range{Position{}, offsetToPosition(Buffer, Buffer.size())}; |
| auto FileRange = |
| toHalfOpenFileRange(SM, LangOpts, N->ASTNode.getSourceRange()); |
| assert(FileRange && "We should be able to get the File Range"); |
| return Range{ |
| offsetToPosition(Buffer, SM.getFileOffset(FileRange->getBegin())), |
| offsetToPosition(Buffer, SM.getFileOffset(FileRange->getEnd()))}; |
| } |
| |
| std::string nodeKind(const SelectionTree::Node *N) { |
| return N ? N->kind() : "<null>"; |
| } |
| |
| std::vector<const SelectionTree::Node *> allNodes(const SelectionTree &T) { |
| std::vector<const SelectionTree::Node *> Result = {&T.root()}; |
| for (unsigned I = 0; I < Result.size(); ++I) { |
| const SelectionTree::Node *N = Result[I]; |
| Result.insert(Result.end(), N->Children.begin(), N->Children.end()); |
| } |
| return Result; |
| } |
| |
| // Returns true if Common is a descendent of Root. |
| // Verifies nothing is selected above Common. |
| bool verifyCommonAncestor(const SelectionTree::Node &Root, |
| const SelectionTree::Node *Common, |
| StringRef MarkedCode) { |
| if (&Root == Common) |
| return true; |
| if (Root.Selected) |
| ADD_FAILURE() << "Selected nodes outside common ancestor\n" << MarkedCode; |
| bool Seen = false; |
| for (const SelectionTree::Node *Child : Root.Children) |
| if (verifyCommonAncestor(*Child, Common, MarkedCode)) { |
| if (Seen) |
| ADD_FAILURE() << "Saw common ancestor twice\n" << MarkedCode; |
| Seen = true; |
| } |
| return Seen; |
| } |
| |
| TEST(SelectionTest, CommonAncestor) { |
| struct Case { |
| // Selection is between ^marks^. |
| // common ancestor marked with a [[range]]. |
| const char *Code; |
| const char *CommonAncestorKind; |
| }; |
| Case Cases[] = { |
| { |
| R"cpp( |
| template <typename T> |
| int x = [[T::^U::]]ccc(); |
| )cpp", |
| "NestedNameSpecifierLoc", |
| }, |
| { |
| R"cpp( |
| struct AAA { struct BBB { static int ccc(); };}; |
| int x = AAA::[[B^B^B]]::ccc(); |
| )cpp", |
| "RecordTypeLoc", |
| }, |
| { |
| R"cpp( |
| struct AAA { struct BBB { static int ccc(); };}; |
| int x = AAA::[[B^BB^]]::ccc(); |
| )cpp", |
| "RecordTypeLoc", |
| }, |
| { |
| R"cpp( |
| struct AAA { struct BBB { static int ccc(); };}; |
| int x = [[AAA::BBB::c^c^c]](); |
| )cpp", |
| "DeclRefExpr", |
| }, |
| { |
| R"cpp( |
| struct AAA { struct BBB { static int ccc(); };}; |
| int x = [[AAA::BBB::cc^c(^)]]; |
| )cpp", |
| "CallExpr", |
| }, |
| |
| { |
| R"cpp( |
| void foo() { [[if (1^11) { return; } else {^ }]] } |
| )cpp", |
| "IfStmt", |
| }, |
| { |
| R"cpp( |
| int x(int); |
| #define M(foo) x(foo) |
| int a = 42; |
| int b = M([[^a]]); |
| )cpp", |
| "DeclRefExpr", |
| }, |
| { |
| R"cpp( |
| void foo(); |
| #define CALL_FUNCTION(X) X() |
| void bar() { CALL_FUNCTION([[f^o^o]]); } |
| )cpp", |
| "DeclRefExpr", |
| }, |
| { |
| R"cpp( |
| void foo(); |
| #define CALL_FUNCTION(X) X() |
| void bar() { [[CALL_FUNC^TION(fo^o)]]; } |
| )cpp", |
| "CallExpr", |
| }, |
| { |
| R"cpp( |
| void foo(); |
| #define CALL_FUNCTION(X) X() |
| void bar() { [[C^ALL_FUNC^TION(foo)]]; } |
| )cpp", |
| "CallExpr", |
| }, |
| { |
| R"cpp( |
| void foo(); |
| #^define CALL_FUNCTION(X) X(^) |
| void bar() { CALL_FUNCTION(foo); } |
| )cpp", |
| nullptr, |
| }, |
| { |
| R"cpp( |
| void foo(); |
| #define CALL_FUNCTION(X) X() |
| void bar() { CALL_FUNCTION(foo^)^; } |
| )cpp", |
| nullptr, |
| }, |
| { |
| R"cpp( |
| namespace ns { |
| #if 0 |
| void fo^o() {} |
| #endif |
| } |
| )cpp", |
| nullptr, |
| }, |
| { |
| R"cpp( |
| struct S { S(const char*); }; |
| S [[s ^= "foo"]]; |
| )cpp", |
| "CXXConstructExpr", |
| }, |
| { |
| R"cpp( |
| struct S { S(const char*); }; |
| [[S ^s = "foo"]]; |
| )cpp", |
| "VarDecl", |
| }, |
| { |
| R"cpp( |
| [[^void]] (*S)(int) = nullptr; |
| )cpp", |
| "BuiltinTypeLoc", |
| }, |
| { |
| R"cpp( |
| [[void (*S)^(int)]] = nullptr; |
| )cpp", |
| "FunctionProtoTypeLoc", |
| }, |
| { |
| R"cpp( |
| [[void (^*S)(int)]] = nullptr; |
| )cpp", |
| "FunctionProtoTypeLoc", |
| }, |
| { |
| R"cpp( |
| [[void (*^S)(int) = nullptr]]; |
| )cpp", |
| "VarDecl", |
| }, |
| { |
| R"cpp( |
| [[void ^(*S)(int)]] = nullptr; |
| )cpp", |
| "FunctionProtoTypeLoc", |
| }, |
| { |
| R"cpp( |
| struct S { |
| int foo() const; |
| int bar() { return [[f^oo]](); } |
| }; |
| )cpp", |
| "MemberExpr", // Not implicit CXXThisExpr, or its implicit cast! |
| }, |
| { |
| R"cpp( |
| auto lambda = [](const char*){ return 0; }; |
| int x = lambda([["y^"]]); |
| )cpp", |
| "StringLiteral", // Not DeclRefExpr to operator()! |
| }, |
| { |
| R"cpp( |
| struct Foo {}; |
| struct Bar : [[v^ir^tual private Foo]] {}; |
| )cpp", |
| "CXXBaseSpecifier", |
| }, |
| { |
| R"cpp( |
| struct Foo {}; |
| struct Bar : private [[Fo^o]] {}; |
| )cpp", |
| "RecordTypeLoc", |
| }, |
| { |
| R"cpp( |
| struct Foo {}; |
| struct Bar : [[Fo^o]] {}; |
| )cpp", |
| "RecordTypeLoc", |
| }, |
| |
| // Point selections. |
| {"void foo() { [[^foo]](); }", "DeclRefExpr"}, |
| {"void foo() { [[f^oo]](); }", "DeclRefExpr"}, |
| {"void foo() { [[fo^o]](); }", "DeclRefExpr"}, |
| {"void foo() { [[foo^()]]; }", "CallExpr"}, |
| {"void foo() { [[foo^]] (); }", "DeclRefExpr"}, |
| {"int bar; void foo() [[{ foo (); }]]^", "CompoundStmt"}, |
| {"int x = [[42]]^;", "IntegerLiteral"}, |
| |
| // Ignores whitespace, comments, and semicolons in the selection. |
| {"void foo() { [[foo^()]]; /*comment*/^}", "CallExpr"}, |
| |
| // Tricky case: FunctionTypeLoc in FunctionDecl has a hole in it. |
| {"[[^void]] foo();", "BuiltinTypeLoc"}, |
| {"[[void foo^()]];", "FunctionProtoTypeLoc"}, |
| {"[[^void foo^()]];", "FunctionDecl"}, |
| {"[[void ^foo()]];", "FunctionDecl"}, |
| // Tricky case: two VarDecls share a specifier. |
| {"[[int ^a]], b;", "VarDecl"}, |
| {"[[int a, ^b]];", "VarDecl"}, |
| // Tricky case: CXXConstructExpr wants to claim the whole init range. |
| { |
| R"cpp( |
| struct X { X(int); }; |
| class Y { |
| X x; |
| Y() : [[^x(4)]] {} |
| }; |
| )cpp", |
| "CXXCtorInitializer", // Not the CXXConstructExpr! |
| }, |
| // Tricky case: anonymous struct is a sibling of the VarDecl. |
| {"[[st^ruct {int x;}]] y;", "CXXRecordDecl"}, |
| {"[[struct {int x;} ^y]];", "VarDecl"}, |
| {"struct {[[int ^x]];} y;", "FieldDecl"}, |
| // FIXME: the AST has no location info for qualifiers. |
| {"const [[a^uto]] x = 42;", "AutoTypeLoc"}, |
| {"[[co^nst auto x = 42]];", "VarDecl"}, |
| |
| {"^", nullptr}, |
| {"void foo() { [[foo^^]] (); }", "DeclRefExpr"}, |
| |
| // FIXME: Ideally we'd get a declstmt or the VarDecl itself here. |
| // This doesn't happen now; the RAV doesn't traverse a node containing ;. |
| {"int x = 42;^", nullptr}, |
| |
| // Common ancestor is logically TUDecl, but we never return that. |
| {"^int x; int y;^", nullptr}, |
| |
| // Node types that have caused problems in the past. |
| {"template <typename T> void foo() { [[^T]] t; }", |
| "TemplateTypeParmTypeLoc"}, |
| |
| // No crash |
| { |
| R"cpp( |
| template <class T> struct Foo {}; |
| template <[[template<class> class /*cursor here*/^U]]> |
| struct Foo<U<int>*> {}; |
| )cpp", |
| "TemplateTemplateParmDecl"}, |
| |
| // Foreach has a weird AST, ensure we can select parts of the range init. |
| // This used to fail, because the DeclStmt for C claimed the whole range. |
| { |
| R"cpp( |
| struct Str { |
| const char *begin(); |
| const char *end(); |
| }; |
| Str makeStr(const char*); |
| void loop() { |
| for (const char C : [[mak^eStr("foo"^)]]) |
| ; |
| } |
| )cpp", |
| "CallExpr"}, |
| |
| // User-defined literals are tricky: is 12_i one token or two? |
| // For now we treat it as one, and the UserDefinedLiteral as a leaf. |
| { |
| R"cpp( |
| struct Foo{}; |
| Foo operator""_ud(unsigned long long); |
| Foo x = [[^12_ud]]; |
| )cpp", |
| "UserDefinedLiteral"}, |
| |
| { |
| R"cpp( |
| int a; |
| decltype([[^a]] + a) b; |
| )cpp", |
| "DeclRefExpr"}, |
| |
| // Objective-C nullability attributes. |
| { |
| R"cpp( |
| @interface I{} |
| @property(nullable) [[^I]] *x; |
| @end |
| )cpp", |
| "ObjCInterfaceTypeLoc"}, |
| { |
| R"cpp( |
| @interface I{} |
| - (void)doSomething:(nonnull [[i^d]])argument; |
| @end |
| )cpp", |
| "TypedefTypeLoc"}, |
| |
| // Objective-C OpaqueValueExpr/PseudoObjectExpr has weird ASTs. |
| // Need to traverse the contents of the OpaqueValueExpr to the POE, |
| // and ensure we traverse only the syntactic form of the PseudoObjectExpr. |
| { |
| R"cpp( |
| @interface I{} |
| @property(retain) I*x; |
| @property(retain) I*y; |
| @end |
| void test(I *f) { [[^f]].x.y = 0; } |
| )cpp", |
| "DeclRefExpr"}, |
| { |
| R"cpp( |
| @interface I{} |
| @property(retain) I*x; |
| @property(retain) I*y; |
| @end |
| void test(I *f) { [[f.^x]].y = 0; } |
| )cpp", |
| "ObjCPropertyRefExpr"}, |
| // Examples with implicit properties. |
| { |
| R"cpp( |
| @interface I{} |
| -(int)foo; |
| @end |
| int test(I *f) { return 42 + [[^f]].foo; } |
| )cpp", |
| "DeclRefExpr"}, |
| { |
| R"cpp( |
| @interface I{} |
| -(int)foo; |
| @end |
| int test(I *f) { return 42 + [[f.^foo]]; } |
| )cpp", |
| "ObjCPropertyRefExpr"}, |
| {"struct foo { [[int has^h<:32:>]]; };", "FieldDecl"}, |
| {"struct foo { [[op^erator int()]]; };", "CXXConversionDecl"}, |
| {"struct foo { [[^~foo()]]; };", "CXXDestructorDecl"}, |
| // FIXME: The following to should be class itself instead. |
| {"struct foo { [[fo^o(){}]] };", "CXXConstructorDecl"}, |
| |
| {R"cpp( |
| struct S1 { void f(); }; |
| struct S2 { S1 * operator->(); }; |
| void test(S2 s2) { |
| s2[[-^>]]f(); |
| } |
| )cpp", |
| "DeclRefExpr"}, // DeclRefExpr to the "operator->" method. |
| |
| // Template template argument. |
| {R"cpp( |
| template <typename> class Vector {}; |
| template <template <typename> class Container> class A {}; |
| A<[[V^ector]]> a; |
| )cpp", |
| "TemplateArgumentLoc"}, |
| |
| // Attributes |
| {R"cpp( |
| void f(int * __attribute__(([[no^nnull]])) ); |
| )cpp", |
| "NonNullAttr"}, |
| |
| {R"cpp( |
| // Digraph syntax for attributes to avoid accidental annotations. |
| class <:[gsl::Owner([[in^t]])]:> X{}; |
| )cpp", |
| "BuiltinTypeLoc"}, |
| |
| // This case used to crash - AST has a null Attr |
| {R"cpp( |
| @interface I |
| [[@property(retain, nonnull) <:[My^Object2]:> *x]]; // error-ok |
| @end |
| )cpp", |
| "ObjCPropertyDecl"}, |
| |
| {R"cpp( |
| typedef int Foo; |
| enum Bar : [[Fo^o]] {}; |
| )cpp", |
| "TypedefTypeLoc"}, |
| {R"cpp( |
| typedef int Foo; |
| enum Bar : [[Fo^o]]; |
| )cpp", |
| "TypedefTypeLoc"}, |
| }; |
| |
| for (const Case &C : Cases) { |
| trace::TestTracer Tracer; |
| Annotations Test(C.Code); |
| |
| TestTU TU; |
| TU.Code = std::string(Test.code()); |
| |
| TU.ExtraArgs.push_back("-xobjective-c++"); |
| |
| auto AST = TU.build(); |
| auto T = makeSelectionTree(C.Code, AST); |
| EXPECT_EQ("TranslationUnitDecl", nodeKind(&T.root())) << C.Code; |
| |
| if (Test.ranges().empty()) { |
| // If no [[range]] is marked in the example, there should be no selection. |
| EXPECT_FALSE(T.commonAncestor()) << C.Code << "\n" << T; |
| EXPECT_THAT(Tracer.takeMetric("selection_recovery", "C++"), |
| testing::IsEmpty()); |
| } else { |
| // If there is an expected selection, common ancestor should exist |
| // with the appropriate node type. |
| EXPECT_EQ(C.CommonAncestorKind, nodeKind(T.commonAncestor())) |
| << C.Code << "\n" |
| << T; |
| // Convert the reported common ancestor to a range and verify it. |
| EXPECT_EQ(nodeRange(T.commonAncestor(), AST), Test.range()) |
| << C.Code << "\n" |
| << T; |
| |
| // Check that common ancestor is reachable on exactly one path from root, |
| // and no nodes outside it are selected. |
| EXPECT_TRUE(verifyCommonAncestor(T.root(), T.commonAncestor(), C.Code)) |
| << C.Code; |
| EXPECT_THAT(Tracer.takeMetric("selection_recovery", "C++"), |
| ElementsAreArray({0})); |
| } |
| } |
| } |
| |
| // Regression test: this used to match the injected X, not the outer X. |
| TEST(SelectionTest, InjectedClassName) { |
| const char *Code = "struct ^X { int x; };"; |
| auto AST = TestTU::withCode(Annotations(Code).code()).build(); |
| auto T = makeSelectionTree(Code, AST); |
| ASSERT_EQ("CXXRecordDecl", nodeKind(T.commonAncestor())) << T; |
| auto *D = dyn_cast<CXXRecordDecl>(T.commonAncestor()->ASTNode.get<Decl>()); |
| EXPECT_FALSE(D->isInjectedClassName()); |
| } |
| |
| TEST(SelectionTree, Metrics) { |
| const char *Code = R"cpp( |
| // error-ok: testing behavior on recovery expression |
| int foo(); |
| int foo(int, int); |
| int x = fo^o(42); |
| )cpp"; |
| auto AST = TestTU::withCode(Annotations(Code).code()).build(); |
| trace::TestTracer Tracer; |
| auto T = makeSelectionTree(Code, AST); |
| EXPECT_THAT(Tracer.takeMetric("selection_recovery", "C++"), |
| ElementsAreArray({1})); |
| EXPECT_THAT(Tracer.takeMetric("selection_recovery_type", "C++"), |
| ElementsAreArray({1})); |
| } |
| |
| // FIXME: Doesn't select the binary operator node in |
| // #define FOO(X) X + 1 |
| // int a, b = [[FOO(a)]]; |
| TEST(SelectionTest, Selected) { |
| // Selection with ^marks^. |
| // Partially selected nodes marked with a [[range]]. |
| // Completely selected nodes marked with a $C[[range]]. |
| const char *Cases[] = { |
| R"cpp( int abc, xyz = [[^ab^c]]; )cpp", |
| R"cpp( int abc, xyz = [[a^bc^]]; )cpp", |
| R"cpp( int abc, xyz = $C[[^abc^]]; )cpp", |
| R"cpp( |
| void foo() { |
| [[if ([[1^11]]) $C[[{ |
| $C[[return]]; |
| }]] else [[{^ |
| }]]]] |
| char z; |
| } |
| )cpp", |
| R"cpp( |
| template <class T> |
| struct unique_ptr {}; |
| void foo(^$C[[unique_ptr<$C[[unique_ptr<$C[[int]]>]]>]]^ a) {} |
| )cpp", |
| R"cpp(int a = [[5 >^> 1]];)cpp", |
| R"cpp( |
| #define ECHO(X) X |
| ECHO(EC^HO($C[[int]]) EC^HO(a)); |
| )cpp", |
| R"cpp( $C[[^$C[[int]] a^]]; )cpp", |
| R"cpp( $C[[^$C[[int]] a = $C[[5]]^]]; )cpp", |
| }; |
| for (const char *C : Cases) { |
| Annotations Test(C); |
| auto AST = TestTU::withCode(Test.code()).build(); |
| auto T = makeSelectionTree(C, AST); |
| |
| std::vector<Range> Complete, Partial; |
| for (const SelectionTree::Node *N : allNodes(T)) |
| if (N->Selected == SelectionTree::Complete) |
| Complete.push_back(nodeRange(N, AST)); |
| else if (N->Selected == SelectionTree::Partial) |
| Partial.push_back(nodeRange(N, AST)); |
| EXPECT_THAT(Complete, UnorderedElementsAreArray(Test.ranges("C"))) << C; |
| EXPECT_THAT(Partial, UnorderedElementsAreArray(Test.ranges())) << C; |
| } |
| } |
| |
| TEST(SelectionTest, PathologicalPreprocessor) { |
| const char *Case = R"cpp( |
| #define MACRO while(1) |
| void test() { |
| #include "Expand.inc" |
| br^eak; |
| } |
| )cpp"; |
| Annotations Test(Case); |
| auto TU = TestTU::withCode(Test.code()); |
| TU.AdditionalFiles["Expand.inc"] = "MACRO\n"; |
| auto AST = TU.build(); |
| EXPECT_THAT(*AST.getDiagnostics(), ::testing::IsEmpty()); |
| auto T = makeSelectionTree(Case, AST); |
| |
| EXPECT_EQ("BreakStmt", T.commonAncestor()->kind()); |
| EXPECT_EQ("WhileStmt", T.commonAncestor()->Parent->kind()); |
| } |
| |
| TEST(SelectionTest, IncludedFile) { |
| const char *Case = R"cpp( |
| void test() { |
| #include "Exp^and.inc" |
| break; |
| } |
| )cpp"; |
| Annotations Test(Case); |
| auto TU = TestTU::withCode(Test.code()); |
| TU.AdditionalFiles["Expand.inc"] = "while(1)\n"; |
| auto AST = TU.build(); |
| auto T = makeSelectionTree(Case, AST); |
| |
| EXPECT_EQ(nullptr, T.commonAncestor()); |
| } |
| |
| TEST(SelectionTest, MacroArgExpansion) { |
| // If a macro arg is expanded several times, we only consider the first one |
| // selected. |
| const char *Case = R"cpp( |
| int mul(int, int); |
| #define SQUARE(X) mul(X, X); |
| int nine = SQUARE(^3); |
| )cpp"; |
| Annotations Test(Case); |
| auto AST = TestTU::withCode(Test.code()).build(); |
| auto T = makeSelectionTree(Case, AST); |
| EXPECT_EQ("IntegerLiteral", T.commonAncestor()->kind()); |
| EXPECT_TRUE(T.commonAncestor()->Selected); |
| |
| // Verify that the common assert() macro doesn't suffer from this. |
| // (This is because we don't associate the stringified token with the arg). |
| Case = R"cpp( |
| void die(const char*); |
| #define assert(x) (x ? (void)0 : die(#x)) |
| void foo() { assert(^42); } |
| )cpp"; |
| Test = Annotations(Case); |
| AST = TestTU::withCode(Test.code()).build(); |
| T = makeSelectionTree(Case, AST); |
| |
| EXPECT_EQ("IntegerLiteral", T.commonAncestor()->kind()); |
| } |
| |
| TEST(SelectionTest, Implicit) { |
| const char *Test = R"cpp( |
| struct S { S(const char*); }; |
| int f(S); |
| int x = f("^"); |
| )cpp"; |
| auto AST = TestTU::withCode(Annotations(Test).code()).build(); |
| auto T = makeSelectionTree(Test, AST); |
| |
| const SelectionTree::Node *Str = T.commonAncestor(); |
| EXPECT_EQ("StringLiteral", nodeKind(Str)) << "Implicit selected?"; |
| EXPECT_EQ("ImplicitCastExpr", nodeKind(Str->Parent)); |
| EXPECT_EQ("CXXConstructExpr", nodeKind(Str->Parent->Parent)); |
| EXPECT_EQ(Str, &Str->Parent->Parent->ignoreImplicit()) |
| << "Didn't unwrap " << nodeKind(&Str->Parent->Parent->ignoreImplicit()); |
| |
| EXPECT_EQ("CXXConstructExpr", nodeKind(&Str->outerImplicit())); |
| } |
| |
| TEST(SelectionTest, CreateAll) { |
| llvm::Annotations Test("int$unique^ a=1$ambiguous^+1; $empty^"); |
| auto AST = TestTU::withCode(Test.code()).build(); |
| unsigned Seen = 0; |
| SelectionTree::createEach( |
| AST.getASTContext(), AST.getTokens(), Test.point("ambiguous"), |
| Test.point("ambiguous"), [&](SelectionTree T) { |
| // Expect to see the right-biased tree first. |
| if (Seen == 0) { |
| EXPECT_EQ("BinaryOperator", nodeKind(T.commonAncestor())); |
| } else if (Seen == 1) { |
| EXPECT_EQ("IntegerLiteral", nodeKind(T.commonAncestor())); |
| } |
| ++Seen; |
| return false; |
| }); |
| EXPECT_EQ(2u, Seen); |
| |
| Seen = 0; |
| SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), |
| Test.point("ambiguous"), Test.point("ambiguous"), |
| [&](SelectionTree T) { |
| ++Seen; |
| return true; |
| }); |
| EXPECT_EQ(1u, Seen) << "Return true --> stop iterating"; |
| |
| Seen = 0; |
| SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), |
| Test.point("unique"), Test.point("unique"), |
| [&](SelectionTree T) { |
| ++Seen; |
| return false; |
| }); |
| EXPECT_EQ(1u, Seen) << "no ambiguity --> only one tree"; |
| |
| Seen = 0; |
| SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), |
| Test.point("empty"), Test.point("empty"), |
| [&](SelectionTree T) { |
| EXPECT_FALSE(T.commonAncestor()); |
| ++Seen; |
| return false; |
| }); |
| EXPECT_EQ(1u, Seen) << "empty tree still created"; |
| |
| Seen = 0; |
| SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), |
| Test.point("unique"), Test.point("ambiguous"), |
| [&](SelectionTree T) { |
| ++Seen; |
| return false; |
| }); |
| EXPECT_EQ(1u, Seen) << "one tree for nontrivial selection"; |
| } |
| |
| TEST(SelectionTest, DeclContextIsLexical) { |
| llvm::Annotations Test("namespace a { void $1^foo(); } void a::$2^foo();"); |
| auto AST = TestTU::withCode(Test.code()).build(); |
| { |
| auto ST = SelectionTree::createRight(AST.getASTContext(), AST.getTokens(), |
| Test.point("1"), Test.point("1")); |
| EXPECT_FALSE(ST.commonAncestor()->getDeclContext().isTranslationUnit()); |
| } |
| { |
| auto ST = SelectionTree::createRight(AST.getASTContext(), AST.getTokens(), |
| Test.point("2"), Test.point("2")); |
| EXPECT_TRUE(ST.commonAncestor()->getDeclContext().isTranslationUnit()); |
| } |
| } |
| |
| } // namespace |
| } // namespace clangd |
| } // namespace clang |