| 1 | //===- unittests/AST/TypePrinterTest.cpp --- Type 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 QualType::print() and related methods. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "ASTPrint.h" |
| 14 | #include "clang/AST/ASTContext.h" |
| 15 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 16 | #include "clang/Tooling/Tooling.h" |
| 17 | #include "llvm/ADT/SmallString.h" |
| 18 | #include "gtest/gtest.h" |
| 19 | |
| 20 | using namespace clang; |
| 21 | using namespace ast_matchers; |
| 22 | using namespace tooling; |
| 23 | |
| 24 | namespace { |
| 25 | |
| 26 | static void PrintType(raw_ostream &Out, const ASTContext *Context, |
| 27 | const QualType *T, |
| 28 | PrintingPolicyAdjuster PolicyAdjuster) { |
| 29 | assert(T && !T->isNull() && "Expected non-null Type" ); |
| 30 | PrintingPolicy Policy = Context->getPrintingPolicy(); |
| 31 | if (PolicyAdjuster) |
| 32 | PolicyAdjuster(Policy); |
| 33 | T->print(OS&: Out, Policy); |
| 34 | } |
| 35 | |
| 36 | ::testing::AssertionResult |
| 37 | PrintedTypeMatches(StringRef Code, const std::vector<std::string> &Args, |
| 38 | const DeclarationMatcher &NodeMatch, |
| 39 | StringRef ExpectedPrinted, |
| 40 | PrintingPolicyAdjuster PolicyAdjuster) { |
| 41 | return PrintedNodeMatches<QualType>(Code, Args, NodeMatch, ExpectedPrinted, |
| 42 | FileName: "" , Printer: PrintType, PolicyAdjuster); |
| 43 | } |
| 44 | |
| 45 | } // unnamed namespace |
| 46 | |
| 47 | TEST(TypePrinter, TemplateId) { |
| 48 | std::string Code = R"cpp( |
| 49 | namespace N { |
| 50 | template <typename> struct Type {}; |
| 51 | |
| 52 | template <typename T> |
| 53 | void Foo(const Type<T> &Param); |
| 54 | } |
| 55 | )cpp" ; |
| 56 | auto Matcher = parmVarDecl(hasType(InnerMatcher: qualType().bind(ID: "id" ))); |
| 57 | |
| 58 | ASSERT_TRUE(PrintedTypeMatches( |
| 59 | Code, {}, Matcher, "const Type<T> &" , |
| 60 | [](PrintingPolicy &Policy) { Policy.FullyQualifiedName = false; })); |
| 61 | |
| 62 | ASSERT_TRUE(PrintedTypeMatches( |
| 63 | Code, {}, Matcher, "const Type<T> &" , |
| 64 | [](PrintingPolicy &Policy) { Policy.FullyQualifiedName = true; })); |
| 65 | } |
| 66 | |
| 67 | TEST(TypePrinter, TemplateId2) { |
| 68 | std::string Code = R"cpp( |
| 69 | template <template <typename ...> class TemplatedType> |
| 70 | void func(TemplatedType<int> Param); |
| 71 | )cpp" ; |
| 72 | auto Matcher = parmVarDecl(hasType(InnerMatcher: qualType().bind(ID: "id" ))); |
| 73 | |
| 74 | // Regression test ensuring we do not segfault getting the QualType as a |
| 75 | // string. |
| 76 | ASSERT_TRUE(PrintedTypeMatches(Code, {}, Matcher, "<int>" , |
| 77 | [](PrintingPolicy &Policy) { |
| 78 | Policy.FullyQualifiedName = true; |
| 79 | Policy.PrintAsCanonical = true; |
| 80 | })); |
| 81 | } |
| 82 | |
| 83 | TEST(TypePrinter, ParamsUglified) { |
| 84 | llvm::StringLiteral Code = R"cpp( |
| 85 | template <typename _Tp, template <typename> class __f> |
| 86 | const __f<_Tp&> *A = nullptr; |
| 87 | )cpp" ; |
| 88 | auto Clean = [](PrintingPolicy &Policy) { |
| 89 | Policy.CleanUglifiedParameters = true; |
| 90 | }; |
| 91 | |
| 92 | ASSERT_TRUE(PrintedTypeMatches(Code, {}, |
| 93 | varDecl(hasType(qualType().bind("id" ))), |
| 94 | "const __f<_Tp &> *" , nullptr)); |
| 95 | ASSERT_TRUE(PrintedTypeMatches(Code, {}, |
| 96 | varDecl(hasType(qualType().bind("id" ))), |
| 97 | "const f<Tp &> *" , Clean)); |
| 98 | } |
| 99 | |
| 100 | TEST(TypePrinter, SuppressElaboration) { |
| 101 | llvm::StringLiteral Code = R"cpp( |
| 102 | namespace shared { |
| 103 | namespace a { |
| 104 | template <typename T> |
| 105 | struct S {}; |
| 106 | } // namespace a |
| 107 | namespace b { |
| 108 | struct Foo {}; |
| 109 | } // namespace b |
| 110 | using Alias = a::S<b::Foo>; |
| 111 | } // namespace shared |
| 112 | )cpp" ; |
| 113 | |
| 114 | auto Matcher = typedefNameDecl(hasName(Name: "::shared::Alias" ), |
| 115 | hasType(InnerMatcher: qualType().bind(ID: "id" ))); |
| 116 | ASSERT_TRUE(PrintedTypeMatches( |
| 117 | Code, {}, Matcher, "a::S<b::Foo>" , |
| 118 | [](PrintingPolicy &Policy) { Policy.FullyQualifiedName = true; })); |
| 119 | ASSERT_TRUE(PrintedTypeMatches(Code, {}, Matcher, |
| 120 | "shared::a::S<shared::b::Foo>" , |
| 121 | [](PrintingPolicy &Policy) { |
| 122 | Policy.SuppressElaboration = true; |
| 123 | Policy.FullyQualifiedName = true; |
| 124 | })); |
| 125 | } |
| 126 | |
| 127 | TEST(TypePrinter, TemplateIdWithNTTP) { |
| 128 | constexpr char Code[] = R"cpp( |
| 129 | template <int N> |
| 130 | struct Str { |
| 131 | constexpr Str(char const (&s)[N]) { __builtin_memcpy(value, s, N); } |
| 132 | char value[N]; |
| 133 | }; |
| 134 | template <Str> class ASCII {}; |
| 135 | |
| 136 | ASCII<"this nontype template argument is too long to print"> x; |
| 137 | )cpp" ; |
| 138 | auto Matcher = classTemplateSpecializationDecl( |
| 139 | hasName(Name: "ASCII" ), has(cxxConstructorDecl( |
| 140 | isMoveConstructor(), |
| 141 | has(parmVarDecl(hasType(InnerMatcher: qualType().bind(ID: "id" ))))))); |
| 142 | |
| 143 | ASSERT_TRUE(PrintedTypeMatches( |
| 144 | Code, {"-std=c++20" }, Matcher, |
| 145 | R"(ASCII<Str<52>{"this nontype template argument is [...]"}> &&)" , |
| 146 | [](PrintingPolicy &Policy) { |
| 147 | Policy.EntireContentsOfLargeArray = false; |
| 148 | })); |
| 149 | |
| 150 | ASSERT_TRUE(PrintedTypeMatches( |
| 151 | Code, {"-std=c++20" }, Matcher, |
| 152 | R"(ASCII<Str<52>{"this nontype template argument is too long to print"}> &&)" , |
| 153 | [](PrintingPolicy &Policy) { |
| 154 | Policy.EntireContentsOfLargeArray = true; |
| 155 | })); |
| 156 | } |
| 157 | |
| 158 | TEST(TypePrinter, TemplateArgumentsSubstitution) { |
| 159 | constexpr char Code[] = R"cpp( |
| 160 | template <typename Y> class X {}; |
| 161 | typedef X<int> A; |
| 162 | int foo() { |
| 163 | return sizeof(A); |
| 164 | } |
| 165 | )cpp" ; |
| 166 | auto Matcher = typedefNameDecl(hasName(Name: "A" ), hasType(InnerMatcher: qualType().bind(ID: "id" ))); |
| 167 | ASSERT_TRUE(PrintedTypeMatches(Code, {}, Matcher, "X<int>" , |
| 168 | [](PrintingPolicy &Policy) { |
| 169 | Policy.SuppressTagKeyword = false; |
| 170 | Policy.SuppressScope = true; |
| 171 | })); |
| 172 | } |
| 173 | |
| 174 | TEST(TypePrinter, TemplateArgumentsSubstitution_Expressions) { |
| 175 | /// Tests clang::isSubstitutedDefaultArgument on TemplateArguments |
| 176 | /// that are of kind TemplateArgument::Expression |
| 177 | constexpr char Code[] = R"cpp( |
| 178 | constexpr bool func() { return true; } |
| 179 | |
| 180 | template <typename T1 = int, |
| 181 | int T2 = 42, |
| 182 | T1 T3 = 43, |
| 183 | int T4 = sizeof(T1), |
| 184 | bool T5 = func() |
| 185 | > |
| 186 | struct Foo { |
| 187 | }; |
| 188 | |
| 189 | Foo<int, 40 + 2> X; |
| 190 | )cpp" ; |
| 191 | |
| 192 | auto AST = tooling::buildASTFromCodeWithArgs(Code, /*Args=*/{"-std=c++20" }); |
| 193 | ASTContext &Ctx = AST->getASTContext(); |
| 194 | |
| 195 | auto const *CTD = selectFirst<ClassTemplateDecl>( |
| 196 | BoundTo: "id" , Results: match(Matcher: classTemplateDecl(hasName(Name: "Foo" )).bind(ID: "id" ), Context&: Ctx)); |
| 197 | ASSERT_NE(CTD, nullptr); |
| 198 | auto const *CTSD = *CTD->specializations().begin(); |
| 199 | ASSERT_NE(CTSD, nullptr); |
| 200 | auto const *Params = CTD->getTemplateParameters(); |
| 201 | ASSERT_NE(Params, nullptr); |
| 202 | auto const &ArgList = CTSD->getTemplateArgs(); |
| 203 | |
| 204 | auto createBinOpExpr = [&](uint32_t LHS, uint32_t RHS, |
| 205 | uint32_t Result) -> ConstantExpr * { |
| 206 | const int numBits = 32; |
| 207 | clang::APValue ResultVal{llvm::APSInt(llvm::APInt(numBits, Result))}; |
| 208 | auto *LHSInt = IntegerLiteral::Create(Ctx, llvm::APInt(numBits, LHS), |
| 209 | Ctx.UnsignedIntTy, {}); |
| 210 | auto *RHSInt = IntegerLiteral::Create(Ctx, llvm::APInt(numBits, RHS), |
| 211 | Ctx.UnsignedIntTy, {}); |
| 212 | auto *BinOp = BinaryOperator::Create( |
| 213 | C: Ctx, lhs: LHSInt, rhs: RHSInt, opc: BinaryOperatorKind::BO_Add, ResTy: Ctx.UnsignedIntTy, |
| 214 | VK: ExprValueKind::VK_PRValue, OK: ExprObjectKind::OK_Ordinary, opLoc: {}, FPFeatures: {}); |
| 215 | return ConstantExpr::Create(Ctx, dyn_cast<Expr>(BinOp), ResultVal); |
| 216 | }; |
| 217 | |
| 218 | { |
| 219 | // Arg is an integral '42' |
| 220 | auto const &Arg = ArgList.get(Idx: 1); |
| 221 | ASSERT_EQ(Arg.getKind(), TemplateArgument::Integral); |
| 222 | |
| 223 | // Param has default expr which evaluates to '42' |
| 224 | auto const *Param = Params->getParam(1); |
| 225 | |
| 226 | EXPECT_TRUE(clang::isSubstitutedDefaultArgument( |
| 227 | Ctx, Arg, Param, ArgList.asArray(), Params->getDepth())); |
| 228 | } |
| 229 | |
| 230 | { |
| 231 | // Arg is an integral '41' |
| 232 | llvm::APInt Int(32, 41); |
| 233 | TemplateArgument Arg(Ctx, llvm::APSInt(Int), Ctx.UnsignedIntTy); |
| 234 | |
| 235 | // Param has default expr which evaluates to '42' |
| 236 | auto const *Param = Params->getParam(1); |
| 237 | |
| 238 | EXPECT_FALSE(clang::isSubstitutedDefaultArgument( |
| 239 | Ctx, Arg, Param, ArgList.asArray(), Params->getDepth())); |
| 240 | } |
| 241 | |
| 242 | { |
| 243 | // Arg is an integral '4' |
| 244 | llvm::APInt Int(32, 4); |
| 245 | TemplateArgument Arg(Ctx, llvm::APSInt(Int), Ctx.UnsignedIntTy); |
| 246 | |
| 247 | // Param has is value-dependent expression (i.e., sizeof(T)) |
| 248 | auto const *Param = Params->getParam(3); |
| 249 | |
| 250 | EXPECT_FALSE(clang::isSubstitutedDefaultArgument( |
| 251 | Ctx, Arg, Param, ArgList.asArray(), Params->getDepth())); |
| 252 | } |
| 253 | |
| 254 | { |
| 255 | const int LHS = 40; |
| 256 | const int RHS = 2; |
| 257 | const int Result = 42; |
| 258 | auto *ConstExpr = createBinOpExpr(LHS, RHS, Result); |
| 259 | // Arg is instantiated with '40 + 2' |
| 260 | TemplateArgument Arg(ConstExpr, /*IsCanonical=*/false); |
| 261 | |
| 262 | // Param has default expr of '42' |
| 263 | auto const *Param = Params->getParam(1); |
| 264 | |
| 265 | EXPECT_TRUE(clang::isSubstitutedDefaultArgument( |
| 266 | Ctx, Arg, Param, ArgList.asArray(), Params->getDepth())); |
| 267 | } |
| 268 | |
| 269 | { |
| 270 | const int LHS = 40; |
| 271 | const int RHS = 1; |
| 272 | const int Result = 41; |
| 273 | auto *ConstExpr = createBinOpExpr(LHS, RHS, Result); |
| 274 | |
| 275 | // Arg is instantiated with '40 + 1' |
| 276 | TemplateArgument Arg(ConstExpr, /*IsCanonical=*/false); |
| 277 | |
| 278 | // Param has default expr of '42' |
| 279 | auto const *Param = Params->getParam(1); |
| 280 | |
| 281 | EXPECT_FALSE(clang::isSubstitutedDefaultArgument( |
| 282 | Ctx, Arg, Param, ArgList.asArray(), Params->getDepth())); |
| 283 | } |
| 284 | |
| 285 | { |
| 286 | const int LHS = 4; |
| 287 | const int RHS = 0; |
| 288 | const int Result = 4; |
| 289 | auto *ConstExpr = createBinOpExpr(LHS, RHS, Result); |
| 290 | |
| 291 | // Arg is instantiated with '4 + 0' |
| 292 | TemplateArgument Arg(ConstExpr, /*IsCanonical=*/false); |
| 293 | |
| 294 | // Param has is value-dependent expression (i.e., sizeof(T)) |
| 295 | auto const *Param = Params->getParam(3); |
| 296 | |
| 297 | EXPECT_FALSE(clang::isSubstitutedDefaultArgument( |
| 298 | Ctx, Arg, Param, ArgList.asArray(), Params->getDepth())); |
| 299 | } |
| 300 | } |
| 301 | |