forked from OSchip/llvm-project
715 lines
22 KiB
C++
715 lines
22 KiB
C++
//===-- 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"}};
|
|
|
|
for (const Case &C : Cases) {
|
|
trace::TestTracer Tracer;
|
|
Annotations Test(C.Code);
|
|
|
|
TestTU TU;
|
|
TU.Code = std::string(Test.code());
|
|
|
|
// FIXME: Auto-completion in a template requires disabling delayed template
|
|
// parsing.
|
|
TU.ExtraArgs.push_back("-fno-delayed-template-parsing");
|
|
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";
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|