xref: /llvm-project/clang/unittests/AST/TypePrinterTest.cpp (revision 6503b015d4a8971dcb69a63de7d8d203e4be79b6)
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 
PrintType(raw_ostream & Out,const ASTContext * Context,const QualType * T,PrintingPolicyAdjuster PolicyAdjuster)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(Out, Policy);
34 }
35 
36 ::testing::AssertionResult
PrintedTypeMatches(StringRef Code,const std::vector<std::string> & Args,const DeclarationMatcher & NodeMatch,StringRef ExpectedPrinted,PrintingPolicyAdjuster PolicyAdjuster)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                                       "", PrintType, PolicyAdjuster);
43 }
44 
45 } // unnamed namespace
46 
TEST(TypePrinter,TemplateId)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(qualType().bind("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 
TEST(TypePrinter,TemplateId2)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(qualType().bind("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.PrintCanonicalTypes = true;
80                                  }));
81 }
82 
TEST(TypePrinter,ParamsUglified)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 
TEST(TypePrinter,SuppressElaboration)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("::shared::Alias"),
115                                  hasType(qualType().bind("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 
TEST(TypePrinter,TemplateIdWithNTTP)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("ASCII"), has(cxxConstructorDecl(
140                             isMoveConstructor(),
141                             has(parmVarDecl(hasType(qualType().bind("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 
TEST(TypePrinter,TemplateArgumentsSubstitution)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("A"), hasType(qualType().bind("id")));
167   ASSERT_TRUE(PrintedTypeMatches(Code, {}, Matcher, "X<int>",
168                                  [](PrintingPolicy &Policy) {
169                                    Policy.SuppressTagKeyword = false;
170                                    Policy.SuppressScope = true;
171                                  }));
172 }
173 
TEST(TypePrinter,TemplateArgumentsSubstitution_Expressions)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       "id", match(classTemplateDecl(hasName("Foo")).bind("id"), 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         Ctx, LHSInt, RHSInt, BinaryOperatorKind::BO_Add, Ctx.UnsignedIntTy,
214         ExprValueKind::VK_PRValue, ExprObjectKind::OK_Ordinary, {}, {});
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(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);
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);
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);
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