449 changes: 445 additions & 4 deletions clang/unittests/AST/ASTTraverserTest.cpp

Large diffs are not rendered by default.

347 changes: 347 additions & 0 deletions clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2331,6 +2331,353 @@ template<> bool timesTwo<bool>(bool){
}
}

TEST(Traversal, traverseNoImplicit) {
StringRef Code = R"cpp(
struct NonTrivial {
NonTrivial() {}
NonTrivial(const NonTrivial&) {}
NonTrivial& operator=(const NonTrivial&) { return *this; }
~NonTrivial() {}
};
struct NoSpecialMethods {
NonTrivial nt;
};
struct ContainsArray {
NonTrivial arr[2];
ContainsArray& operator=(const ContainsArray &other) = default;
};
void copyIt()
{
NoSpecialMethods nc1;
NoSpecialMethods nc2(nc1);
nc2 = nc1;
ContainsArray ca;
ContainsArray ca2;
ca2 = ca;
}
struct HasCtorInits : NoSpecialMethods, NonTrivial
{
int m_i;
NonTrivial m_nt;
HasCtorInits() : NoSpecialMethods(), m_i(42) {}
};
struct CtorInitsNonTrivial : NonTrivial
{
int m_i;
NonTrivial m_nt;
CtorInitsNonTrivial() : NonTrivial(), m_i(42) {}
};
)cpp";
{
auto M = cxxRecordDecl(hasName("NoSpecialMethods"),
has(cxxRecordDecl(hasName("NoSpecialMethods"))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));

M = cxxRecordDecl(hasName("NoSpecialMethods"),
has(cxxConstructorDecl(isCopyConstructor())));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));

M = cxxRecordDecl(hasName("NoSpecialMethods"),
has(cxxMethodDecl(isCopyAssignmentOperator())));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));

M = cxxRecordDecl(hasName("NoSpecialMethods"),
has(cxxConstructorDecl(isDefaultConstructor())));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));

M = cxxRecordDecl(hasName("NoSpecialMethods"), has(cxxDestructorDecl()));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));

M = cxxRecordDecl(hasName("NoSpecialMethods"),
hasMethod(cxxConstructorDecl(isCopyConstructor())));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));

M = cxxRecordDecl(hasName("NoSpecialMethods"),
hasMethod(cxxMethodDecl(isCopyAssignmentOperator())));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));

M = cxxRecordDecl(hasName("NoSpecialMethods"),
hasMethod(cxxConstructorDecl(isDefaultConstructor())));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));

M = cxxRecordDecl(hasName("NoSpecialMethods"),
hasMethod(cxxDestructorDecl()));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
// Because the copy-assignment operator is not spelled in the
// source (ie, isImplicit()), we don't match it
auto M =
cxxOperatorCallExpr(hasType(cxxRecordDecl(hasName("NoSpecialMethods"))),
callee(cxxMethodDecl(isCopyAssignmentOperator())));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
// Compiler generates a forStmt to copy the array
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, forStmt())));
EXPECT_FALSE(
matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, forStmt())));
}
{
// The defaulted method declaration can be matched, but not its
// definition, in IgnoreUnlessSpelledInSource mode
auto MDecl = cxxMethodDecl(ofClass(cxxRecordDecl(hasName("ContainsArray"))),
isCopyAssignmentOperator(), isDefaulted());

EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MDecl)));
EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MDecl)));

auto MDef = cxxMethodDecl(MDecl, has(compoundStmt()));

EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MDef)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MDef)));

auto MBody = cxxMethodDecl(MDecl, hasBody(compoundStmt()));

EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MBody)));
EXPECT_FALSE(
matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MBody)));

auto MIsDefn = cxxMethodDecl(MDecl, isDefinition());

EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MIsDefn)));
EXPECT_TRUE(
matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MIsDefn)));

auto MIsInline = cxxMethodDecl(MDecl, isInline());

EXPECT_FALSE(matches(Code, traverse(TK_AsIs, MIsInline)));
EXPECT_FALSE(
matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MIsInline)));

// The parameter of the defaulted method can still be matched.
auto MParm =
cxxMethodDecl(MDecl, hasParameter(0, parmVarDecl(hasName("other"))));

EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MParm)));
EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MParm)));
}
{
auto M =
cxxConstructorDecl(hasName("HasCtorInits"),
has(cxxCtorInitializer(forField(hasName("m_i")))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M =
cxxConstructorDecl(hasName("HasCtorInits"),
has(cxxCtorInitializer(forField(hasName("m_nt")))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = cxxConstructorDecl(hasName("HasCtorInits"),
hasAnyConstructorInitializer(cxxCtorInitializer(
forField(hasName("m_nt")))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M =
cxxConstructorDecl(hasName("HasCtorInits"),
forEachConstructorInitializer(
cxxCtorInitializer(forField(hasName("m_nt")))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = cxxConstructorDecl(
hasName("CtorInitsNonTrivial"),
has(cxxCtorInitializer(withInitializer(cxxConstructExpr(
hasDeclaration(cxxConstructorDecl(hasName("NonTrivial"))))))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = cxxConstructorDecl(
hasName("HasCtorInits"),
has(cxxCtorInitializer(withInitializer(cxxConstructExpr(hasDeclaration(
cxxConstructorDecl(hasName("NoSpecialMethods"))))))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = cxxCtorInitializer(forField(hasName("m_nt")));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}

Code = R"cpp(
void rangeFor()
{
int arr[2];
for (auto i : arr)
{
}
}
)cpp";
{
auto M = cxxForRangeStmt(has(binaryOperator(hasOperatorName("!="))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M =
cxxForRangeStmt(hasDescendant(binaryOperator(hasOperatorName("+"))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M =
cxxForRangeStmt(hasDescendant(unaryOperator(hasOperatorName("++"))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = cxxForRangeStmt(has(declStmt()));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M =
cxxForRangeStmt(hasLoopVariable(varDecl(hasName("i"))),
hasRangeInit(declRefExpr(to(varDecl(hasName("arr"))))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = cxxForRangeStmt(unless(hasInitStatement(stmt())));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = cxxForRangeStmt(hasBody(stmt()));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}

Code = R"cpp(
void rangeFor()
{
int arr[2];
for (auto& a = arr; auto i : a)
{
}
}
)cpp";
{
auto M = cxxForRangeStmt(has(binaryOperator(hasOperatorName("!="))));
EXPECT_TRUE(
matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"}));
EXPECT_FALSE(
matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M),
true, {"-std=c++20"}));
}
{
auto M =
cxxForRangeStmt(hasDescendant(binaryOperator(hasOperatorName("+"))));
EXPECT_TRUE(
matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"}));
EXPECT_FALSE(
matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M),
true, {"-std=c++20"}));
}
{
auto M =
cxxForRangeStmt(hasDescendant(unaryOperator(hasOperatorName("++"))));
EXPECT_TRUE(
matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"}));
EXPECT_FALSE(
matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M),
true, {"-std=c++20"}));
}
{
auto M = cxxForRangeStmt(has(declStmt()));
EXPECT_TRUE(
matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"}));
EXPECT_FALSE(
matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M),
true, {"-std=c++20"}));
}
{
auto M = cxxForRangeStmt(
hasInitStatement(declStmt(hasSingleDecl(varDecl(
hasName("a"),
hasInitializer(declRefExpr(to(varDecl(hasName("arr"))))))))),
hasLoopVariable(varDecl(hasName("i"))),
hasRangeInit(declRefExpr(to(varDecl(hasName("a"))))));
EXPECT_TRUE(
matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"}));
EXPECT_TRUE(
matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M),
true, {"-std=c++20"}));
}
Code = R"cpp(
void hasDefaultArg(int i, int j = 0)
{
}
void callDefaultArg()
{
hasDefaultArg(42);
}
)cpp";
auto hasDefaultArgCall = [](auto InnerMatcher) {
return callExpr(callee(functionDecl(hasName("hasDefaultArg"))),
InnerMatcher);
};
{
auto M = hasDefaultArgCall(has(integerLiteral(equals(42))));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = hasDefaultArgCall(has(cxxDefaultArgExpr()));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = hasDefaultArgCall(argumentCountIs(2));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = hasDefaultArgCall(argumentCountIs(1));
EXPECT_FALSE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = hasDefaultArgCall(hasArgument(1, cxxDefaultArgExpr()));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
{
auto M = hasDefaultArgCall(hasAnyArgument(cxxDefaultArgExpr()));
EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
}
}

template <typename MatcherT>
bool matcherTemplateWithBinding(StringRef Code, const MatcherT &M) {
return matchAndVerifyResultTrue(
Expand Down
206 changes: 206 additions & 0 deletions clang/unittests/Tooling/TransformerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ class ClangRefactoringTestBase : public testing::Test {
compareSnippets(Expected, rewrite(Input));
}

template <typename R> void testRuleFailure(R Rule, StringRef Input) {
Transformers.push_back(
std::make_unique<Transformer>(std::move(Rule), consumer()));
Transformers.back()->registerMatchers(&MatchFinder);
ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code";
}

// Transformers are referenced by MatchFinder.
std::vector<std::unique_ptr<Transformer>> Transformers;
clang::ast_matchers::MatchFinder MatchFinder;
Expand Down Expand Up @@ -1067,6 +1074,205 @@ TEST_F(TransformerTest, ErrorOccurredMatchSkipped) {
EXPECT_EQ(ErrorCount, 0);
}

TEST_F(TransformerTest, ImplicitNodes_ConstructorDecl) {

std::string OtherStructPrefix = R"cpp(
struct Other {
)cpp";
std::string OtherStructSuffix = "};";

std::string CopyableStructName = "struct Copyable";
std::string BrokenStructName = "struct explicit Copyable";

std::string CodeSuffix = R"cpp(
{
Other m_i;
Copyable();
};
)cpp";

std::string CopyCtor = "Other(const Other&) = default;";
std::string ExplicitCopyCtor = "explicit Other(const Other&) = default;";
std::string BrokenExplicitCopyCtor =
"explicit explicit explicit Other(const Other&) = default;";

std::string RewriteInput = OtherStructPrefix + CopyCtor + OtherStructSuffix +
CopyableStructName + CodeSuffix;
std::string ExpectedRewriteOutput = OtherStructPrefix + ExplicitCopyCtor +
OtherStructSuffix + CopyableStructName +
CodeSuffix;
std::string BrokenRewriteOutput = OtherStructPrefix + BrokenExplicitCopyCtor +
OtherStructSuffix + BrokenStructName +
CodeSuffix;

auto MatchedRecord =
cxxConstructorDecl(isCopyConstructor()).bind("copyConstructor");

auto RewriteRule =
changeTo(before(node("copyConstructor")), cat("explicit "));

testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedRecord),
RewriteRule),
RewriteInput, ExpectedRewriteOutput);

testRule(makeRule(traverse(TK_AsIs, MatchedRecord), RewriteRule),
RewriteInput, BrokenRewriteOutput);
}

TEST_F(TransformerTest, ImplicitNodes_RangeFor) {

std::string CodePrefix = R"cpp(
struct Container
{
int* begin() const;
int* end() const;
int* cbegin() const;
int* cend() const;
};
void foo()
{
const Container c;
)cpp";

std::string BeginCallBefore = " c.begin();";
std::string BeginCallAfter = " c.cbegin();";

std::string ForLoop = "for (auto i : c)";
std::string BrokenForLoop = "for (auto i :.cbegin() c)";

std::string CodeSuffix = R"cpp(
{
}
}
)cpp";

std::string RewriteInput =
CodePrefix + BeginCallBefore + ForLoop + CodeSuffix;
std::string ExpectedRewriteOutput =
CodePrefix + BeginCallAfter + ForLoop + CodeSuffix;
std::string BrokenRewriteOutput =
CodePrefix + BeginCallAfter + BrokenForLoop + CodeSuffix;

auto MatchedRecord =
cxxMemberCallExpr(on(expr(hasType(qualType(isConstQualified(),
hasDeclaration(cxxRecordDecl(
hasName("Container"))))))
.bind("callTarget")),
callee(cxxMethodDecl(hasName("begin"))))
.bind("constBeginCall");

auto RewriteRule =
changeTo(node("constBeginCall"), cat(name("callTarget"), ".cbegin()"));

testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedRecord),
RewriteRule),
RewriteInput, ExpectedRewriteOutput);

testRule(makeRule(traverse(TK_AsIs, MatchedRecord), RewriteRule),
RewriteInput, BrokenRewriteOutput);
}

TEST_F(TransformerTest, ImplicitNodes_ForStmt) {

std::string CodePrefix = R"cpp(
struct NonTrivial {
NonTrivial() {}
NonTrivial(NonTrivial&) {}
NonTrivial& operator=(NonTrivial const&) { return *this; }
~NonTrivial() {}
};
struct ContainsArray {
NonTrivial arr[2];
ContainsArray& operator=(ContainsArray const&) = default;
};
void testIt()
{
ContainsArray ca1;
ContainsArray ca2;
ca2 = ca1;
)cpp";

auto CodeSuffix = "}";

auto LoopBody = R"cpp(
{
}
)cpp";

auto RawLoop = "for (auto i = 0; i != 5; ++i)";

auto RangeLoop = "for (auto i : boost::irange(5))";

// Expect to rewrite the raw loop to the ranged loop.
// This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs
// mode also matches the hidden for loop generated in the copy assignment
// operator of ContainsArray. Transformer then fails to transform the code at
// all.

auto RewriteInput =
CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix;

auto RewriteOutput =
CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix;
{
auto MatchedLoop = forStmt(
has(declStmt(
hasSingleDecl(varDecl(hasInitializer(integerLiteral(equals(0))))
.bind("loopVar")))),
has(binaryOperator(hasOperatorName("!="),
hasLHS(ignoringImplicit(declRefExpr(
to(varDecl(equalsBoundNode("loopVar")))))),
hasRHS(expr().bind("upperBoundExpr")))),
has(unaryOperator(hasOperatorName("++"),
hasUnaryOperand(declRefExpr(
to(varDecl(equalsBoundNode("loopVar"))))))
.bind("incrementOp")));

auto RewriteRule =
changeTo(transformer::enclose(node("loopVar"), node("incrementOp")),
cat("auto ", name("loopVar"), " : boost::irange(",
node("upperBoundExpr"), ")"));

testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedLoop),
RewriteRule),
RewriteInput, RewriteOutput);

testRuleFailure(makeRule(traverse(TK_AsIs, MatchedLoop), RewriteRule),
RewriteInput);
}
{
auto MatchedLoop = forStmt(
hasLoopInit(declStmt(
hasSingleDecl(varDecl(hasInitializer(integerLiteral(equals(0))))
.bind("loopVar")))),
hasCondition(binaryOperator(hasOperatorName("!="),
hasLHS(ignoringImplicit(declRefExpr(to(
varDecl(equalsBoundNode("loopVar")))))),
hasRHS(expr().bind("upperBoundExpr")))),
hasIncrement(unaryOperator(hasOperatorName("++"),
hasUnaryOperand(declRefExpr(to(
varDecl(equalsBoundNode("loopVar"))))))
.bind("incrementOp")));

auto RewriteRule =
changeTo(transformer::enclose(node("loopVar"), node("incrementOp")),
cat("auto ", name("loopVar"), " : boost::irange(",
node("upperBoundExpr"), ")"));

testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedLoop),
RewriteRule),
RewriteInput, RewriteOutput);

testRuleFailure(makeRule(traverse(TK_AsIs, MatchedLoop), RewriteRule),
RewriteInput);
}
}

TEST_F(TransformerTest, TemplateInstantiation) {

std::string NonTemplatesInput = R"cpp(
Expand Down