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
20using namespace clang;
21using namespace ast_matchers;
22using namespace tooling;
23
24namespace {
25
26static 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
37PrintedTypeMatches(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
47TEST(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
67TEST(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.PrintCanonicalTypes = true;
80 }));
81}
82
83TEST(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
100TEST(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
127TEST(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
158TEST(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
174TEST(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);
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

source code of clang/unittests/AST/TypePrinterTest.cpp