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.PrintCanonicalTypes = 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); |
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 | |