| 1 | //===- unittests/AST/StmtPrinterTest.cpp --- Statement printer tests ------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file contains tests for Stmt::printPretty() and related methods. |
| 10 | // |
| 11 | // Search this file for WRONG to see test cases that are producing something |
| 12 | // completely wrong, invalid C++ or just misleading. |
| 13 | // |
| 14 | // These tests have a coding convention: |
| 15 | // * statements to be printed should be contained within a function named 'A' |
| 16 | // unless it should have some special name (e.g., 'operator+'); |
| 17 | // * additional helper declarations are 'Z', 'Y', 'X' and so on. |
| 18 | // |
| 19 | //===----------------------------------------------------------------------===// |
| 20 | |
| 21 | #include "ASTPrint.h" |
| 22 | #include "clang/AST/ASTContext.h" |
| 23 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 24 | #include "clang/Tooling/Tooling.h" |
| 25 | #include "llvm/ADT/SmallString.h" |
| 26 | #include "gtest/gtest.h" |
| 27 | |
| 28 | using namespace clang; |
| 29 | using namespace ast_matchers; |
| 30 | using namespace tooling; |
| 31 | |
| 32 | namespace { |
| 33 | |
| 34 | enum class StdVer { CXX98, CXX11, CXX14, CXX17, CXX20 }; |
| 35 | |
| 36 | DeclarationMatcher FunctionBodyMatcher(StringRef ContainingFunction) { |
| 37 | return functionDecl(hasName(Name: ContainingFunction), |
| 38 | has(compoundStmt(has(stmt().bind(ID: "id" ))))); |
| 39 | } |
| 40 | |
| 41 | static void PrintStmt(raw_ostream &Out, const ASTContext *Context, |
| 42 | const Stmt *S, PrintingPolicyAdjuster PolicyAdjuster) { |
| 43 | assert(S != nullptr && "Expected non-null Stmt" ); |
| 44 | PrintingPolicy Policy = Context->getPrintingPolicy(); |
| 45 | if (PolicyAdjuster) |
| 46 | PolicyAdjuster(Policy); |
| 47 | S->printPretty(OS&: Out, /*Helper*/ nullptr, Policy); |
| 48 | } |
| 49 | |
| 50 | template <typename Matcher> |
| 51 | ::testing::AssertionResult |
| 52 | PrintedStmtMatches(StringRef Code, const std::vector<std::string> &Args, |
| 53 | const Matcher &NodeMatch, StringRef ExpectedPrinted, |
| 54 | PrintingPolicyAdjuster PolicyAdjuster = nullptr) { |
| 55 | return PrintedNodeMatches<Stmt>(Code, Args, NodeMatch, ExpectedPrinted, "" , |
| 56 | PrintStmt, PolicyAdjuster); |
| 57 | } |
| 58 | |
| 59 | template <typename T> |
| 60 | ::testing::AssertionResult |
| 61 | PrintedStmtCXXMatches(StdVer Standard, StringRef Code, const T &NodeMatch, |
| 62 | StringRef ExpectedPrinted, |
| 63 | PrintingPolicyAdjuster PolicyAdjuster = nullptr) { |
| 64 | const char *StdOpt; |
| 65 | switch (Standard) { |
| 66 | case StdVer::CXX98: StdOpt = "-std=c++98" ; break; |
| 67 | case StdVer::CXX11: StdOpt = "-std=c++11" ; break; |
| 68 | case StdVer::CXX14: StdOpt = "-std=c++14" ; break; |
| 69 | case StdVer::CXX17: StdOpt = "-std=c++17" ; break; |
| 70 | case StdVer::CXX20: |
| 71 | StdOpt = "-std=c++20" ; |
| 72 | break; |
| 73 | } |
| 74 | |
| 75 | std::vector<std::string> Args = { |
| 76 | StdOpt, |
| 77 | "-Wno-unused-value" , |
| 78 | }; |
| 79 | return PrintedStmtMatches(Code, Args, NodeMatch, ExpectedPrinted, |
| 80 | PolicyAdjuster); |
| 81 | } |
| 82 | |
| 83 | template <typename T> |
| 84 | ::testing::AssertionResult |
| 85 | PrintedStmtMSMatches(StringRef Code, const T &NodeMatch, |
| 86 | StringRef ExpectedPrinted, |
| 87 | PrintingPolicyAdjuster PolicyAdjuster = nullptr) { |
| 88 | std::vector<std::string> Args = { |
| 89 | "-std=c++98" , |
| 90 | "-target" , "i686-pc-win32" , |
| 91 | "-fms-extensions" , |
| 92 | "-Wno-unused-value" , |
| 93 | }; |
| 94 | return PrintedStmtMatches(Code, Args, NodeMatch, ExpectedPrinted, |
| 95 | PolicyAdjuster); |
| 96 | } |
| 97 | |
| 98 | template <typename T> |
| 99 | ::testing::AssertionResult |
| 100 | PrintedStmtObjCMatches(StringRef Code, const T &NodeMatch, |
| 101 | StringRef ExpectedPrinted, |
| 102 | PrintingPolicyAdjuster PolicyAdjuster = nullptr) { |
| 103 | std::vector<std::string> Args = { |
| 104 | "-ObjC" , |
| 105 | "-fobjc-runtime=macosx-10.12.0" , |
| 106 | }; |
| 107 | return PrintedStmtMatches(Code, Args, NodeMatch, ExpectedPrinted, |
| 108 | PolicyAdjuster); |
| 109 | } |
| 110 | |
| 111 | } // unnamed namespace |
| 112 | |
| 113 | TEST(StmtPrinter, TestIntegerLiteral) { |
| 114 | ASSERT_TRUE(PrintedStmtCXXMatches(StdVer::CXX98, |
| 115 | "void A() {" |
| 116 | " 1, -1, 1U, 1u," |
| 117 | " 1L, 1l, -1L, 1UL, 1ul," |
| 118 | " 1LL, -1LL, 1ULL;" |
| 119 | "}" , |
| 120 | FunctionBodyMatcher("A" ), |
| 121 | "1 , -1 , 1U , 1U , " |
| 122 | "1L , 1L , -1L , 1UL , 1UL , " |
| 123 | "1LL , -1LL , 1ULL" )); |
| 124 | // Should be: with semicolon |
| 125 | } |
| 126 | |
| 127 | TEST(StmtPrinter, TestMSIntegerLiteral) { |
| 128 | ASSERT_TRUE(PrintedStmtMSMatches( |
| 129 | "void A() {" |
| 130 | " 1i8, -1i8, 1ui8, " |
| 131 | " 1i16, -1i16, 1ui16, " |
| 132 | " 1i32, -1i32, 1ui32, " |
| 133 | " 1i64, -1i64, 1ui64;" |
| 134 | "}" , |
| 135 | FunctionBodyMatcher("A" ), |
| 136 | "1i8 , -1i8 , 1Ui8 , " |
| 137 | "1i16 , -1i16 , 1Ui16 , " |
| 138 | "1 , -1 , 1U , " |
| 139 | "1LL , -1LL , 1ULL" )); |
| 140 | // Should be: with semicolon |
| 141 | } |
| 142 | |
| 143 | TEST(StmtPrinter, TestFloatingPointLiteral) { |
| 144 | ASSERT_TRUE(PrintedStmtCXXMatches(StdVer::CXX98, |
| 145 | "void A() { 1.0f, -1.0f, 1.0, -1.0, 1.0l, -1.0l; }" , |
| 146 | FunctionBodyMatcher("A" ), |
| 147 | "1.F , -1.F , 1. , -1. , 1.L , -1.L" )); |
| 148 | // Should be: with semicolon |
| 149 | } |
| 150 | |
| 151 | TEST(StmtPrinter, TestStringLiteralOperatorTemplate_Pack) { |
| 152 | ASSERT_TRUE(PrintedStmtCXXMatches(StdVer::CXX11, |
| 153 | R"cpp( |
| 154 | template <char...> constexpr double operator""_c() { return 42; } |
| 155 | void A() { |
| 156 | constexpr auto waldo = 42_c; |
| 157 | } |
| 158 | )cpp" , |
| 159 | FunctionBodyMatcher("A" ), |
| 160 | "constexpr auto waldo = 42_c;\n" )); |
| 161 | } |
| 162 | |
| 163 | TEST(StmtPrinter, TestStringLiteralOperatorTemplate_Class) { |
| 164 | ASSERT_TRUE(PrintedStmtCXXMatches( |
| 165 | StdVer::CXX20, |
| 166 | R"cpp( |
| 167 | struct C { |
| 168 | template <unsigned N> constexpr C(const char (&)[N]) : n(N) {} |
| 169 | unsigned n; |
| 170 | }; |
| 171 | template <C c> constexpr auto operator""_c() { return c.n; } |
| 172 | void A() { |
| 173 | constexpr auto waldo = "abc"_c; |
| 174 | } |
| 175 | )cpp" , |
| 176 | FunctionBodyMatcher("A" ), |
| 177 | "constexpr auto waldo = operator\"\"_c<C{4}>();\n" )); |
| 178 | } |
| 179 | |
| 180 | TEST(StmtPrinter, TestCXXConversionDeclImplicit) { |
| 181 | ASSERT_TRUE(PrintedStmtCXXMatches( |
| 182 | StdVer::CXX98, |
| 183 | "struct A {" |
| 184 | "operator void *();" |
| 185 | "A operator&(A);" |
| 186 | "};" |
| 187 | "void bar(void *);" |
| 188 | "void foo(A a, A b) {" |
| 189 | " bar(a & b);" |
| 190 | "}" , |
| 191 | traverse(TK_AsIs, cxxMemberCallExpr(anything()).bind("id" )), "a & b" )); |
| 192 | } |
| 193 | |
| 194 | TEST(StmtPrinter, TestCXXConversionDeclExplicit) { |
| 195 | ASSERT_TRUE(PrintedStmtCXXMatches(StdVer::CXX11, |
| 196 | "struct A {" |
| 197 | "operator void *();" |
| 198 | "A operator&(A);" |
| 199 | "};" |
| 200 | "void bar(void *);" |
| 201 | "void foo(A a, A b) {" |
| 202 | " auto x = (a & b).operator void *();" |
| 203 | "}" , |
| 204 | cxxMemberCallExpr(anything()).bind("id" ), |
| 205 | "(a & b)" )); |
| 206 | // WRONG; Should be: (a & b).operator void *() |
| 207 | } |
| 208 | |
| 209 | TEST(StmtPrinter, TestCXXLamda) { |
| 210 | ASSERT_TRUE(PrintedStmtCXXMatches(StdVer::CXX11, |
| 211 | "void A() {" |
| 212 | " auto l = [] { };" |
| 213 | "}" , |
| 214 | lambdaExpr(anything()).bind("id" ), |
| 215 | "[] {\n" |
| 216 | "}" )); |
| 217 | |
| 218 | ASSERT_TRUE(PrintedStmtCXXMatches(StdVer::CXX11, |
| 219 | "void A() {" |
| 220 | " int a = 0, b = 1;" |
| 221 | " auto l = [a,b](int c, float d) { };" |
| 222 | "}" , |
| 223 | lambdaExpr(anything()).bind("id" ), |
| 224 | "[a, b](int c, float d) {\n" |
| 225 | "}" )); |
| 226 | |
| 227 | ASSERT_TRUE(PrintedStmtCXXMatches(StdVer::CXX14, |
| 228 | "void A() {" |
| 229 | " auto l = [](auto a, int b, auto c, int, auto) { };" |
| 230 | "}" , |
| 231 | lambdaExpr(anything()).bind("id" ), |
| 232 | "[](auto a, int b, auto c, int, auto) {\n" |
| 233 | "}" )); |
| 234 | |
| 235 | ASSERT_TRUE( |
| 236 | PrintedStmtCXXMatches(StdVer::CXX20, |
| 237 | "void A() {" |
| 238 | " auto l = []<typename T1, class T2, int I," |
| 239 | " template<class, typename> class T3>" |
| 240 | " (int a, auto, int, auto d) { };" |
| 241 | "}" , |
| 242 | lambdaExpr(anything()).bind("id" ), |
| 243 | "[]<typename T1, class T2, int I, template <class, " |
| 244 | "typename> class T3>(int a, auto, int, auto d) {\n" |
| 245 | "}" )); |
| 246 | } |
| 247 | |
| 248 | TEST(StmtPrinter, TestNoImplicitBases) { |
| 249 | const char *CPPSource = R"( |
| 250 | class A { |
| 251 | int field; |
| 252 | int member() { return field; } |
| 253 | }; |
| 254 | )" ; |
| 255 | // No implicit 'this'. |
| 256 | ASSERT_TRUE(PrintedStmtCXXMatches( |
| 257 | StdVer::CXX11, CPPSource, memberExpr(anything()).bind("id" ), "field" , |
| 258 | |
| 259 | [](PrintingPolicy &PP) { PP.SuppressImplicitBase = true; })); |
| 260 | // Print implicit 'this'. |
| 261 | ASSERT_TRUE(PrintedStmtCXXMatches(StdVer::CXX11, |
| 262 | CPPSource, memberExpr(anything()).bind("id" ), "this->field" )); |
| 263 | |
| 264 | const char *ObjCSource = R"( |
| 265 | @interface I { |
| 266 | int ivar; |
| 267 | } |
| 268 | @end |
| 269 | @implementation I |
| 270 | - (int) method { |
| 271 | return ivar; |
| 272 | } |
| 273 | @end |
| 274 | )" ; |
| 275 | // No implicit 'self'. |
| 276 | ASSERT_TRUE(PrintedStmtObjCMatches( |
| 277 | ObjCSource, returnStmt().bind("id" ), "return ivar;\n" , |
| 278 | |
| 279 | [](PrintingPolicy &PP) { PP.SuppressImplicitBase = true; })); |
| 280 | // Print implicit 'self'. |
| 281 | ASSERT_TRUE(PrintedStmtObjCMatches(ObjCSource, returnStmt().bind("id" ), |
| 282 | "return self->ivar;\n" )); |
| 283 | } |
| 284 | |
| 285 | TEST(StmtPrinter, TerseOutputWithLambdas) { |
| 286 | const char *CPPSource = "auto lamb = []{ return 0; };" ; |
| 287 | |
| 288 | // body is printed when TerseOutput is off(default). |
| 289 | ASSERT_TRUE(PrintedStmtCXXMatches(StdVer::CXX11, CPPSource, |
| 290 | lambdaExpr(anything()).bind("id" ), |
| 291 | "[] {\n return 0;\n}" )); |
| 292 | |
| 293 | // body not printed when TerseOutput is on. |
| 294 | ASSERT_TRUE(PrintedStmtCXXMatches( |
| 295 | StdVer::CXX11, CPPSource, lambdaExpr(anything()).bind("id" ), "[] {}" , |
| 296 | |
| 297 | [](PrintingPolicy &PP) { PP.TerseOutput = true; })); |
| 298 | } |
| 299 | |
| 300 | TEST(StmtPrinter, ParamsUglified) { |
| 301 | llvm::StringLiteral Code = R"cpp( |
| 302 | template <typename _T, int _I, template <typename> class _C> |
| 303 | auto foo(int __j) { |
| 304 | return typename _C<_T>::_F(_I, __j); |
| 305 | } |
| 306 | )cpp" ; |
| 307 | auto Clean = [](PrintingPolicy &Policy) { |
| 308 | Policy.CleanUglifiedParameters = true; |
| 309 | }; |
| 310 | |
| 311 | ASSERT_TRUE(PrintedStmtCXXMatches(StdVer::CXX14, Code, |
| 312 | returnStmt().bind("id" ), |
| 313 | "return typename _C<_T>::_F(_I, __j);\n" )); |
| 314 | ASSERT_TRUE( |
| 315 | PrintedStmtCXXMatches(StdVer::CXX14, Code, returnStmt().bind("id" ), |
| 316 | "return typename C<T>::_F(I, j);\n" , Clean)); |
| 317 | } |
| 318 | |