Added AST matcher for ignoring elidable constructors

Summary: Added AST matcher for ignoring elidable move constructors

Reviewers: hokein, gribozavr

Reviewed By: hokein, gribozavr

Subscribers: cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D63149

Patch by Johan Vikström.

llvm-svn: 363262
This commit is contained in:
Dmitri Gribenko 2019-06-13 13:48:24 +00:00
parent 0eb763c559
commit 31d68804fd
5 changed files with 210 additions and 9 deletions

View File

@ -5728,6 +5728,32 @@ Example matches x (matcher = expr(hasType(cxxRecordDecl(hasName("X")))))
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Expr.html">Expr</a>&gt;</td><td class="name" onclick="toggle('ignoringElidableConstructorCall0')"><a name="ignoringElidableConstructorCall0Anchor">ignoringElidableConstructorCall</a></td><td>ast_matchers::Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Expr.html">Expr</a>&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="ignoringElidableConstructorCall0"><pre>Matches expressions that match InnerMatcher that are possibly wrapped in an
elidable constructor.
In C++17 copy elidable constructors are no longer being
generated in the AST as it is not permitted by the standard. They are
however part of the AST in C++14 and earlier. Therefore, to write a matcher
that works in all language modes, the matcher has to skip elidable
constructor AST nodes if they appear in the AST. This matcher can be used to
skip those elidable constructors.
Given
struct H {};
H G();
void f() {
H D = G();
}
``varDecl(hasInitializer(any(
ignoringElidableConstructorCall(callExpr()),
exprWithCleanups(ignoringElidableConstructorCall(callExpr()))))``
matches ``H D = G()``
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Expr.html">Expr</a>&gt;</td><td class="name" onclick="toggle('ignoringImpCasts0')"><a name="ignoringImpCasts0Anchor">ignoringImpCasts</a></td><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Expr.html">Expr</a>&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="ignoringImpCasts0"><pre>Matches expressions that match InnerMatcher after any implicit casts
are stripped off.

View File

@ -6452,6 +6452,44 @@ AST_MATCHER(FunctionDecl, hasTrailingReturn) {
return false;
}
/// Matches expressions that match InnerMatcher that are possibly wrapped in an
/// elidable constructor.
///
/// In C++17 copy elidable constructors are no longer being
/// generated in the AST as it is not permitted by the standard. They are
/// however part of the AST in C++14 and earlier. Therefore, to write a matcher
/// that works in all language modes, the matcher has to skip elidable
/// constructor AST nodes if they appear in the AST. This matcher can be used to
/// skip those elidable constructors.
///
/// Given
///
/// \code
/// struct H {};
/// H G();
/// void f() {
/// H D = G();
/// }
/// \endcode
///
/// ``varDecl(hasInitializer(any(
/// ignoringElidableConstructorCall(callExpr()),
/// exprWithCleanups(ignoringElidableConstructorCall(callExpr()))))``
/// matches ``H D = G()``
AST_MATCHER_P(Expr, ignoringElidableConstructorCall,
ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
if (const auto *CtorExpr = dyn_cast<CXXConstructExpr>(&Node)) {
if (CtorExpr->isElidable()) {
if (const auto *MaterializeTemp =
dyn_cast<MaterializeTemporaryExpr>(CtorExpr->getArg(0))) {
return InnerMatcher.matches(*MaterializeTemp->GetTemporaryExpr(),
Finder, Builder);
}
}
}
return InnerMatcher.matches(Node, Finder, Builder);
}
//----------------------------------------------------------------------------//
// OpenMP handling.
//----------------------------------------------------------------------------//

View File

@ -320,6 +320,7 @@ RegistryMaps::RegistryMaps() {
REGISTER_MATCHER(hasUnqualifiedDesugaredType);
REGISTER_MATCHER(hasValueType);
REGISTER_MATCHER(ifStmt);
REGISTER_MATCHER(ignoringElidableConstructorCall);
REGISTER_MATCHER(ignoringImpCasts);
REGISTER_MATCHER(ignoringImplicit);
REGISTER_MATCHER(ignoringParenCasts);

View File

@ -566,6 +566,74 @@ TEST(Matcher, BindMatchedNodes) {
llvm::make_unique<VerifyIdIsBoundTo<CXXMemberCallExpr>>("x")));
}
TEST(Matcher, IgnoresElidableConstructors) {
EXPECT_TRUE(
matches("struct H {};"
"template<typename T> H B(T A);"
"void f() {"
" H D1;"
" D1 = B(B(1));"
"}",
cxxOperatorCallExpr(hasArgument(
1, callExpr(hasArgument(
0, ignoringElidableConstructorCall(callExpr()))))),
LanguageMode::Cxx11OrLater));
EXPECT_TRUE(
matches("struct H {};"
"template<typename T> H B(T A);"
"void f() {"
" H D1;"
" D1 = B(1);"
"}",
cxxOperatorCallExpr(hasArgument(
1, callExpr(hasArgument(0, ignoringElidableConstructorCall(
integerLiteral()))))),
LanguageMode::Cxx11OrLater));
EXPECT_TRUE(matches(
"struct H {};"
"H G();"
"void f() {"
" H D = G();"
"}",
varDecl(hasInitializer(anyOf(
ignoringElidableConstructorCall(callExpr()),
exprWithCleanups(has(ignoringElidableConstructorCall(callExpr())))))),
LanguageMode::Cxx11OrLater));
}
TEST(Matcher, IgnoresElidableInReturn) {
auto matcher = expr(ignoringElidableConstructorCall(declRefExpr()));
EXPECT_TRUE(matches("struct H {};"
"H f() {"
" H g;"
" return g;"
"}",
matcher, LanguageMode::Cxx11OrLater));
EXPECT_TRUE(notMatches("struct H {};"
"H f() {"
" return H();"
"}",
matcher, LanguageMode::Cxx11OrLater));
}
TEST(Matcher, IgnoreElidableConstructorDoesNotMatchConstructors) {
EXPECT_TRUE(matches("struct H {};"
"void f() {"
" H D;"
"}",
varDecl(hasInitializer(
ignoringElidableConstructorCall(cxxConstructExpr()))),
LanguageMode::Cxx11OrLater));
}
TEST(Matcher, IgnoresElidableDoesNotPreventMatches) {
EXPECT_TRUE(matches("void f() {"
" int D = 10;"
"}",
expr(ignoringElidableConstructorCall(integerLiteral())),
LanguageMode::Cxx11OrLater));
}
TEST(Matcher, BindTheSameNameInAlternatives) {
StatementMatcher matcher = anyOf(
binaryOperator(hasOperatorName("+"),
@ -914,10 +982,10 @@ TEST(isConstexpr, MatchesConstexprDeclarations) {
varDecl(hasName("foo"), isConstexpr())));
EXPECT_TRUE(matches("constexpr int bar();",
functionDecl(hasName("bar"), isConstexpr())));
EXPECT_TRUE(matchesConditionally("void baz() { if constexpr(1 > 0) {} }",
ifStmt(isConstexpr()), true, "-std=c++17"));
EXPECT_TRUE(matchesConditionally("void baz() { if (1 > 0) {} }",
ifStmt(isConstexpr()), false, "-std=c++17"));
EXPECT_TRUE(matches("void baz() { if constexpr(1 > 0) {} }",
ifStmt(isConstexpr()), LanguageMode::Cxx17OrLater));
EXPECT_TRUE(notMatches("void baz() { if (1 > 0) {} }", ifStmt(isConstexpr()),
LanguageMode::Cxx17OrLater));
}
TEST(TemplateArgumentCountIs, Matches) {

View File

@ -57,6 +57,17 @@ private:
const std::unique_ptr<BoundNodesCallback> FindResultReviewer;
};
enum class LanguageMode {
Cxx11,
Cxx14,
Cxx17,
Cxx2a,
Cxx11OrLater,
Cxx14OrLater,
Cxx17OrLater,
Cxx2aOrLater
};
template <typename T>
testing::AssertionResult matchesConditionally(
const std::string &Code, const T &AMatcher, bool ExpectMatch,
@ -116,14 +127,71 @@ testing::AssertionResult matchesConditionally(
}
template <typename T>
testing::AssertionResult matches(const std::string &Code, const T &AMatcher) {
return matchesConditionally(Code, AMatcher, true, "-std=c++11");
testing::AssertionResult
matchesConditionally(const std::string &Code, const T &AMatcher,
bool ExpectMatch, const LanguageMode &Mode) {
std::vector<LanguageMode> LangModes;
switch (Mode) {
case LanguageMode::Cxx11:
case LanguageMode::Cxx14:
case LanguageMode::Cxx17:
case LanguageMode::Cxx2a:
LangModes = {Mode};
break;
case LanguageMode::Cxx11OrLater:
LangModes = {LanguageMode::Cxx11, LanguageMode::Cxx14, LanguageMode::Cxx17,
LanguageMode::Cxx2a};
break;
case LanguageMode::Cxx14OrLater:
LangModes = {LanguageMode::Cxx14, LanguageMode::Cxx17, LanguageMode::Cxx2a};
break;
case LanguageMode::Cxx17OrLater:
LangModes = {LanguageMode::Cxx17, LanguageMode::Cxx2a};
break;
case LanguageMode::Cxx2aOrLater:
LangModes = {LanguageMode::Cxx2a};
}
for (auto Mode : LangModes) {
std::string LangModeArg;
switch (Mode) {
case LanguageMode::Cxx11:
LangModeArg = "-std=c++11";
break;
case LanguageMode::Cxx14:
LangModeArg = "-std=c++14";
break;
case LanguageMode::Cxx17:
LangModeArg = "-std=c++17";
break;
case LanguageMode::Cxx2a:
LangModeArg = "-std=c++2a";
break;
default:
llvm_unreachable("Invalid language mode");
}
auto Result =
matchesConditionally(Code, AMatcher, ExpectMatch, LangModeArg);
if (!Result)
return Result;
}
return testing::AssertionSuccess();
}
template <typename T>
testing::AssertionResult notMatches(const std::string &Code,
const T &AMatcher) {
return matchesConditionally(Code, AMatcher, false, "-std=c++11");
testing::AssertionResult
matches(const std::string &Code, const T &AMatcher,
const LanguageMode &Mode = LanguageMode::Cxx11) {
return matchesConditionally(Code, AMatcher, true, Mode);
}
template <typename T>
testing::AssertionResult
notMatches(const std::string &Code, const T &AMatcher,
const LanguageMode &Mode = LanguageMode::Cxx11) {
return matchesConditionally(Code, AMatcher, false, Mode);
}
template <typename T>