//===-- 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(N->ASTNode.get())) 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() : ""; } std::vector allNodes(const SelectionTree &T) { std::vector 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 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()! }, // 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 void foo() { [[^T]] t; }", "TemplateTypeParmTypeLoc"}, // No crash { R"cpp( template struct Foo {}; template <[[template class /*cursor here*/^U]]> struct Foo*> {}; )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 class Vector {}; template