1 | //===- unittest/Tooling/StencilTest.cpp -----------------------------------===// |
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 | #include "clang/Tooling/Transformer/Stencil.h" |
10 | #include "clang/AST/ASTTypeTraits.h" |
11 | #include "clang/AST/Expr.h" |
12 | #include "clang/ASTMatchers/ASTMatchers.h" |
13 | #include "clang/Tooling/FixIt.h" |
14 | #include "clang/Tooling/Tooling.h" |
15 | #include "llvm/Support/Error.h" |
16 | #include "llvm/Testing/Support/Error.h" |
17 | #include "gmock/gmock.h" |
18 | #include "gtest/gtest.h" |
19 | #include <optional> |
20 | |
21 | using namespace clang; |
22 | using namespace transformer; |
23 | using namespace ast_matchers; |
24 | |
25 | namespace { |
26 | using ::llvm::Failed; |
27 | using ::llvm::HasValue; |
28 | using ::llvm::StringError; |
29 | using ::testing::AllOf; |
30 | using ::testing::HasSubstr; |
31 | using MatchResult = MatchFinder::MatchResult; |
32 | |
33 | // Create a valid translation-unit from a statement. |
34 | static std::string wrapSnippet(StringRef , |
35 | StringRef StatementCode) { |
36 | constexpr char Preface[] = R"cc( |
37 | namespace N { class C {}; } |
38 | namespace { class AnonC {}; } |
39 | struct S { int Field; }; |
40 | namespace std { |
41 | template <typename T> |
42 | struct unique_ptr { |
43 | T* operator->() const; |
44 | T& operator*() const; |
45 | }; |
46 | } |
47 | template<class T> T desugar() { return T(); }; |
48 | )cc" ; |
49 | return (Preface + ExtraPreface + "auto stencil_test_snippet = []{" + |
50 | StatementCode + "};" ) |
51 | .str(); |
52 | } |
53 | |
54 | static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) { |
55 | return varDecl(hasName(Name: "stencil_test_snippet" ), |
56 | hasDescendant(compoundStmt(hasAnySubstatement(InnerMatcher: Matcher)))); |
57 | } |
58 | |
59 | struct TestMatch { |
60 | // The AST unit from which `result` is built. We bundle it because it backs |
61 | // the result. Users are not expected to access it. |
62 | std::unique_ptr<ASTUnit> AstUnit; |
63 | // The result to use in the test. References `ast_unit`. |
64 | MatchResult Result; |
65 | }; |
66 | |
67 | // Matches `Matcher` against the statement `StatementCode` and returns the |
68 | // result. Handles putting the statement inside a function and modifying the |
69 | // matcher correspondingly. `Matcher` should match one of the statements in |
70 | // `StatementCode` exactly -- that is, produce exactly one match. However, |
71 | // `StatementCode` may contain other statements not described by `Matcher`. |
72 | // `ExtraPreface` (optionally) adds extra decls to the TU, before the code. |
73 | static std::optional<TestMatch> matchStmt(StringRef StatementCode, |
74 | StatementMatcher Matcher, |
75 | StringRef = "" ) { |
76 | auto AstUnit = tooling::buildASTFromCodeWithArgs( |
77 | Code: wrapSnippet(ExtraPreface, StatementCode), Args: {"-Wno-unused-value" }); |
78 | if (AstUnit == nullptr) { |
79 | ADD_FAILURE() << "AST construction failed" ; |
80 | return std::nullopt; |
81 | } |
82 | ASTContext &Context = AstUnit->getASTContext(); |
83 | auto Matches = ast_matchers::match(Matcher: wrapMatcher(Matcher), Context); |
84 | // We expect a single, exact match for the statement. |
85 | if (Matches.size() != 1) { |
86 | ADD_FAILURE() << "Wrong number of matches: " << Matches.size(); |
87 | return std::nullopt; |
88 | } |
89 | return TestMatch{.AstUnit: std::move(AstUnit), .Result: MatchResult(Matches[0], &Context)}; |
90 | } |
91 | |
92 | class StencilTest : public ::testing::Test { |
93 | protected: |
94 | // Verifies that the given stencil fails when evaluated on a valid match |
95 | // result. Binds a statement to "stmt", a (non-member) ctor-initializer to |
96 | // "init", an expression to "expr" and a (nameless) declaration to "decl". |
97 | void testError(const Stencil &Stencil, |
98 | ::testing::Matcher<std::string> Matcher) { |
99 | const std::string Snippet = R"cc( |
100 | struct A {}; |
101 | class F : public A { |
102 | public: |
103 | F(int) {} |
104 | }; |
105 | F(1); |
106 | )cc" ; |
107 | auto StmtMatch = matchStmt( |
108 | StatementCode: Snippet, |
109 | Matcher: stmt(hasDescendant( |
110 | cxxConstructExpr( |
111 | hasDeclaration(InnerMatcher: decl(hasDescendant(cxxCtorInitializer( |
112 | isBaseInitializer()) |
113 | .bind(ID: "init" ))) |
114 | .bind(ID: "decl" ))) |
115 | .bind(ID: "expr" ))) |
116 | .bind(ID: "stmt" )); |
117 | ASSERT_TRUE(StmtMatch); |
118 | if (auto ResultOrErr = Stencil->eval(R: StmtMatch->Result)) { |
119 | ADD_FAILURE() << "Expected failure but succeeded: " << *ResultOrErr; |
120 | } else { |
121 | auto Err = llvm::handleErrors(E: ResultOrErr.takeError(), |
122 | Hs: [&Matcher](const StringError &Err) { |
123 | EXPECT_THAT(Err.getMessage(), Matcher); |
124 | }); |
125 | if (Err) { |
126 | ADD_FAILURE() << "Unhandled error: " << llvm::toString(E: std::move(Err)); |
127 | } |
128 | } |
129 | } |
130 | |
131 | // Tests failures caused by references to unbound nodes. `unbound_id` is the |
132 | // id that will cause the failure. |
133 | void testUnboundNodeError(const Stencil &Stencil, StringRef UnboundId) { |
134 | testError(Stencil, |
135 | Matcher: AllOf(matchers: HasSubstr(substring: std::string(UnboundId)), matchers: HasSubstr(substring: "not bound" ))); |
136 | } |
137 | }; |
138 | |
139 | TEST_F(StencilTest, SingleStatement) { |
140 | StringRef Condition("C" ), Then("T" ), Else("E" ); |
141 | const std::string Snippet = R"cc( |
142 | if (true) |
143 | return 1; |
144 | else |
145 | return 0; |
146 | )cc" ; |
147 | auto StmtMatch = matchStmt( |
148 | StatementCode: Snippet, Matcher: ifStmt(hasCondition(InnerMatcher: expr().bind(ID: Condition)), |
149 | hasThen(InnerMatcher: stmt().bind(ID: Then)), hasElse(InnerMatcher: stmt().bind(ID: Else)))); |
150 | ASSERT_TRUE(StmtMatch); |
151 | // Invert the if-then-else. |
152 | auto Stencil = |
153 | cat(Parts: "if (!" , Parts: node(ID: std::string(Condition)), Parts: ") " , |
154 | Parts: statement(ID: std::string(Else)), Parts: " else " , Parts: statement(ID: std::string(Then))); |
155 | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), |
156 | HasValue("if (!true) return 0; else return 1;" )); |
157 | } |
158 | |
159 | TEST_F(StencilTest, UnboundNode) { |
160 | const std::string Snippet = R"cc( |
161 | if (true) |
162 | return 1; |
163 | else |
164 | return 0; |
165 | )cc" ; |
166 | auto StmtMatch = matchStmt(StatementCode: Snippet, Matcher: ifStmt(hasCondition(InnerMatcher: stmt().bind(ID: "a1" )), |
167 | hasThen(InnerMatcher: stmt().bind(ID: "a2" )))); |
168 | ASSERT_TRUE(StmtMatch); |
169 | auto Stencil = cat(Parts: "if(!" , Parts: node(ID: "a1" ), Parts: ") " , Parts: node(ID: "UNBOUND" ), Parts: ";" ); |
170 | auto ResultOrErr = Stencil->eval(R: StmtMatch->Result); |
171 | EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError())) |
172 | << "Expected unbound node, got " << *ResultOrErr; |
173 | } |
174 | |
175 | // Tests that a stencil with a single parameter (`Id`) evaluates to the expected |
176 | // string, when `Id` is bound to the expression-statement in `Snippet`. |
177 | void testExpr(StringRef Id, StringRef Snippet, const Stencil &Stencil, |
178 | StringRef Expected) { |
179 | auto StmtMatch = matchStmt(StatementCode: Snippet, Matcher: expr().bind(ID: Id)); |
180 | ASSERT_TRUE(StmtMatch); |
181 | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), |
182 | HasValue(std::string(Expected))); |
183 | } |
184 | |
185 | void testFailure(StringRef Id, StringRef Snippet, const Stencil &Stencil, |
186 | testing::Matcher<std::string> MessageMatcher) { |
187 | auto StmtMatch = matchStmt(StatementCode: Snippet, Matcher: expr().bind(ID: Id)); |
188 | ASSERT_TRUE(StmtMatch); |
189 | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), |
190 | Failed<StringError>(testing::Property( |
191 | &StringError::getMessage, MessageMatcher))); |
192 | } |
193 | |
194 | TEST_F(StencilTest, SelectionOp) { |
195 | StringRef Id = "id" ; |
196 | testExpr(Id, Snippet: "3;" , Stencil: cat(Parts: node(ID: std::string(Id))), Expected: "3" ); |
197 | } |
198 | |
199 | TEST_F(StencilTest, IfBoundOpBound) { |
200 | StringRef Id = "id" ; |
201 | testExpr(Id, Snippet: "3;" , Stencil: ifBound(Id, TrueStencil: cat(Parts: "5" ), FalseStencil: cat(Parts: "7" )), Expected: "5" ); |
202 | } |
203 | |
204 | TEST_F(StencilTest, IfBoundOpUnbound) { |
205 | StringRef Id = "id" ; |
206 | testExpr(Id, Snippet: "3;" , Stencil: ifBound(Id: "other" , TrueStencil: cat(Parts: "5" ), FalseStencil: cat(Parts: "7" )), Expected: "7" ); |
207 | } |
208 | |
209 | static auto selectMatcher() { |
210 | // The `anything` matcher is not bound, to test for none of the cases |
211 | // matching. |
212 | return expr(anyOf(integerLiteral().bind(ID: "int" ), cxxBoolLiteral().bind(ID: "bool" ), |
213 | floatLiteral().bind(ID: "float" ), anything())); |
214 | } |
215 | |
216 | static auto selectStencil() { |
217 | return selectBound(CaseStencils: { |
218 | {"int" , cat(Parts: "I" )}, |
219 | {"bool" , cat(Parts: "B" )}, |
220 | {"bool" , cat(Parts: "redundant" )}, |
221 | {"float" , cat(Parts: "F" )}, |
222 | }); |
223 | } |
224 | |
225 | TEST_F(StencilTest, SelectBoundChooseDetectedMatch) { |
226 | std::string Input = "3;" ; |
227 | auto StmtMatch = matchStmt(StatementCode: Input, Matcher: selectMatcher()); |
228 | ASSERT_TRUE(StmtMatch); |
229 | EXPECT_THAT_EXPECTED(selectStencil()->eval(StmtMatch->Result), |
230 | HasValue(std::string("I" ))); |
231 | } |
232 | |
233 | TEST_F(StencilTest, SelectBoundChooseFirst) { |
234 | std::string Input = "true;" ; |
235 | auto StmtMatch = matchStmt(StatementCode: Input, Matcher: selectMatcher()); |
236 | ASSERT_TRUE(StmtMatch); |
237 | EXPECT_THAT_EXPECTED(selectStencil()->eval(StmtMatch->Result), |
238 | HasValue(std::string("B" ))); |
239 | } |
240 | |
241 | TEST_F(StencilTest, SelectBoundDiesOnExhaustedCases) { |
242 | std::string Input = "\"string\";" ; |
243 | auto StmtMatch = matchStmt(StatementCode: Input, Matcher: selectMatcher()); |
244 | ASSERT_TRUE(StmtMatch); |
245 | EXPECT_THAT_EXPECTED( |
246 | selectStencil()->eval(StmtMatch->Result), |
247 | Failed<StringError>(testing::Property( |
248 | &StringError::getMessage, |
249 | AllOf(HasSubstr("selectBound failed" ), HasSubstr("no default" ))))); |
250 | } |
251 | |
252 | TEST_F(StencilTest, SelectBoundSucceedsWithDefault) { |
253 | std::string Input = "\"string\";" ; |
254 | auto StmtMatch = matchStmt(StatementCode: Input, Matcher: selectMatcher()); |
255 | ASSERT_TRUE(StmtMatch); |
256 | auto Stencil = selectBound(CaseStencils: {{"int" , cat(Parts: "I" )}}, DefaultStencil: cat(Parts: "D" )); |
257 | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), |
258 | HasValue(std::string("D" ))); |
259 | } |
260 | |
261 | TEST_F(StencilTest, ExpressionOpNoParens) { |
262 | StringRef Id = "id" ; |
263 | testExpr(Id, Snippet: "3;" , Stencil: expression(Id), Expected: "3" ); |
264 | } |
265 | |
266 | // Don't parenthesize a parens expression. |
267 | TEST_F(StencilTest, ExpressionOpNoParensParens) { |
268 | StringRef Id = "id" ; |
269 | testExpr(Id, Snippet: "(3);" , Stencil: expression(Id), Expected: "(3)" ); |
270 | } |
271 | |
272 | TEST_F(StencilTest, ExpressionOpBinaryOpParens) { |
273 | StringRef Id = "id" ; |
274 | testExpr(Id, Snippet: "3+4;" , Stencil: expression(Id), Expected: "(3+4)" ); |
275 | } |
276 | |
277 | // `expression` shares code with other ops, so we get sufficient coverage of the |
278 | // error handling code with this test. If that changes in the future, more error |
279 | // tests should be added. |
280 | TEST_F(StencilTest, ExpressionOpUnbound) { |
281 | StringRef Id = "id" ; |
282 | testFailure(Id, Snippet: "3;" , Stencil: expression(Id: "ACACA" ), |
283 | MessageMatcher: AllOf(matchers: HasSubstr(substring: "ACACA" ), matchers: HasSubstr(substring: "not bound" ))); |
284 | } |
285 | |
286 | TEST_F(StencilTest, DerefPointer) { |
287 | StringRef Id = "id" ; |
288 | testExpr(Id, Snippet: "int *x; x;" , Stencil: deref(ExprId: Id), Expected: "*x" ); |
289 | } |
290 | |
291 | TEST_F(StencilTest, DerefBinOp) { |
292 | StringRef Id = "id" ; |
293 | testExpr(Id, Snippet: "int *x; x + 1;" , Stencil: deref(ExprId: Id), Expected: "*(x + 1)" ); |
294 | } |
295 | |
296 | TEST_F(StencilTest, DerefAddressExpr) { |
297 | StringRef Id = "id" ; |
298 | testExpr(Id, Snippet: "int x; &x;" , Stencil: deref(ExprId: Id), Expected: "x" ); |
299 | } |
300 | |
301 | TEST_F(StencilTest, AddressOfValue) { |
302 | StringRef Id = "id" ; |
303 | testExpr(Id, Snippet: "int x; x;" , Stencil: addressOf(ExprId: Id), Expected: "&x" ); |
304 | } |
305 | |
306 | TEST_F(StencilTest, AddressOfDerefExpr) { |
307 | StringRef Id = "id" ; |
308 | testExpr(Id, Snippet: "int *x; *x;" , Stencil: addressOf(ExprId: Id), Expected: "x" ); |
309 | } |
310 | |
311 | TEST_F(StencilTest, MaybeDerefValue) { |
312 | StringRef Id = "id" ; |
313 | testExpr(Id, Snippet: "int x; x;" , Stencil: maybeDeref(ExprId: Id), Expected: "x" ); |
314 | } |
315 | |
316 | TEST_F(StencilTest, MaybeDerefPointer) { |
317 | StringRef Id = "id" ; |
318 | testExpr(Id, Snippet: "int *x; x;" , Stencil: maybeDeref(ExprId: Id), Expected: "*x" ); |
319 | } |
320 | |
321 | TEST_F(StencilTest, MaybeDerefBinOp) { |
322 | StringRef Id = "id" ; |
323 | testExpr(Id, Snippet: "int *x; x + 1;" , Stencil: maybeDeref(ExprId: Id), Expected: "*(x + 1)" ); |
324 | } |
325 | |
326 | TEST_F(StencilTest, MaybeDerefAddressExpr) { |
327 | StringRef Id = "id" ; |
328 | testExpr(Id, Snippet: "int x; &x;" , Stencil: maybeDeref(ExprId: Id), Expected: "x" ); |
329 | } |
330 | |
331 | TEST_F(StencilTest, MaybeDerefSmartPointer) { |
332 | StringRef Id = "id" ; |
333 | std::string Snippet = R"cc( |
334 | std::unique_ptr<S> x; |
335 | x; |
336 | )cc" ; |
337 | testExpr(Id, Snippet, Stencil: maybeDeref(ExprId: Id), Expected: "*x" ); |
338 | } |
339 | |
340 | TEST_F(StencilTest, MaybeDerefSmartPointerFromMemberExpr) { |
341 | StringRef Id = "id" ; |
342 | std::string Snippet = "std::unique_ptr<S> x; x->Field;" ; |
343 | auto StmtMatch = |
344 | matchStmt(StatementCode: Snippet, Matcher: memberExpr(hasObjectExpression(InnerMatcher: expr().bind(ID: Id)))); |
345 | ASSERT_TRUE(StmtMatch); |
346 | const Stencil Stencil = maybeDeref(ExprId: Id); |
347 | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("*x" )); |
348 | } |
349 | |
350 | TEST_F(StencilTest, MaybeAddressOfPointer) { |
351 | StringRef Id = "id" ; |
352 | testExpr(Id, Snippet: "int *x; x;" , Stencil: maybeAddressOf(ExprId: Id), Expected: "x" ); |
353 | } |
354 | |
355 | TEST_F(StencilTest, MaybeAddressOfValue) { |
356 | StringRef Id = "id" ; |
357 | testExpr(Id, Snippet: "int x; x;" , Stencil: addressOf(ExprId: Id), Expected: "&x" ); |
358 | } |
359 | |
360 | TEST_F(StencilTest, MaybeAddressOfBinOp) { |
361 | StringRef Id = "id" ; |
362 | testExpr(Id, Snippet: "int x; x + 1;" , Stencil: maybeAddressOf(ExprId: Id), Expected: "&(x + 1)" ); |
363 | } |
364 | |
365 | TEST_F(StencilTest, MaybeAddressOfDerefExpr) { |
366 | StringRef Id = "id" ; |
367 | testExpr(Id, Snippet: "int *x; *x;" , Stencil: addressOf(ExprId: Id), Expected: "x" ); |
368 | } |
369 | |
370 | TEST_F(StencilTest, MaybeAddressOfSmartPointer) { |
371 | StringRef Id = "id" ; |
372 | testExpr(Id, Snippet: "std::unique_ptr<S> x; x;" , Stencil: maybeAddressOf(ExprId: Id), Expected: "x" ); |
373 | } |
374 | |
375 | TEST_F(StencilTest, MaybeAddressOfSmartPointerFromMemberCall) { |
376 | StringRef Id = "id" ; |
377 | std::string Snippet = "std::unique_ptr<S> x; x->Field;" ; |
378 | auto StmtMatch = |
379 | matchStmt(StatementCode: Snippet, Matcher: memberExpr(hasObjectExpression(InnerMatcher: expr().bind(ID: Id)))); |
380 | ASSERT_TRUE(StmtMatch); |
381 | const Stencil Stencil = maybeAddressOf(ExprId: Id); |
382 | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("x" )); |
383 | } |
384 | |
385 | TEST_F(StencilTest, MaybeAddressOfSmartPointerDerefNoCancel) { |
386 | StringRef Id = "id" ; |
387 | testExpr(Id, Snippet: "std::unique_ptr<S> x; *x;" , Stencil: maybeAddressOf(ExprId: Id), Expected: "&*x" ); |
388 | } |
389 | |
390 | TEST_F(StencilTest, AccessOpValue) { |
391 | StringRef Snippet = R"cc( |
392 | S x; |
393 | x; |
394 | )cc" ; |
395 | StringRef Id = "id" ; |
396 | testExpr(Id, Snippet, Stencil: access(BaseId: Id, Member: "field" ), Expected: "x.field" ); |
397 | } |
398 | |
399 | TEST_F(StencilTest, AccessOpValueExplicitText) { |
400 | StringRef Snippet = R"cc( |
401 | S x; |
402 | x; |
403 | )cc" ; |
404 | StringRef Id = "id" ; |
405 | testExpr(Id, Snippet, Stencil: access(BaseId: Id, Member: cat(Parts: "field" )), Expected: "x.field" ); |
406 | } |
407 | |
408 | TEST_F(StencilTest, AccessOpValueAddress) { |
409 | StringRef Snippet = R"cc( |
410 | S x; |
411 | &x; |
412 | )cc" ; |
413 | StringRef Id = "id" ; |
414 | testExpr(Id, Snippet, Stencil: access(BaseId: Id, Member: "field" ), Expected: "x.field" ); |
415 | } |
416 | |
417 | TEST_F(StencilTest, AccessOpPointer) { |
418 | StringRef Snippet = R"cc( |
419 | S *x; |
420 | x; |
421 | )cc" ; |
422 | StringRef Id = "id" ; |
423 | testExpr(Id, Snippet, Stencil: access(BaseId: Id, Member: "field" ), Expected: "x->field" ); |
424 | } |
425 | |
426 | TEST_F(StencilTest, AccessOpPointerDereference) { |
427 | StringRef Snippet = R"cc( |
428 | S *x; |
429 | *x; |
430 | )cc" ; |
431 | StringRef Id = "id" ; |
432 | testExpr(Id, Snippet, Stencil: access(BaseId: Id, Member: "field" ), Expected: "x->field" ); |
433 | } |
434 | |
435 | TEST_F(StencilTest, AccessOpSmartPointer) { |
436 | StringRef Snippet = R"cc( |
437 | std::unique_ptr<S> x; |
438 | x; |
439 | )cc" ; |
440 | StringRef Id = "id" ; |
441 | testExpr(Id, Snippet, Stencil: access(BaseId: Id, Member: "field" ), Expected: "x->field" ); |
442 | } |
443 | |
444 | TEST_F(StencilTest, AccessOpSmartPointerDereference) { |
445 | StringRef Snippet = R"cc( |
446 | std::unique_ptr<S> x; |
447 | *x; |
448 | )cc" ; |
449 | StringRef Id = "id" ; |
450 | testExpr(Id, Snippet, Stencil: access(BaseId: Id, Member: "field" ), Expected: "x->field" ); |
451 | } |
452 | |
453 | TEST_F(StencilTest, AccessOpSmartPointerMemberCall) { |
454 | StringRef Snippet = R"cc( |
455 | std::unique_ptr<S> x; |
456 | x->Field; |
457 | )cc" ; |
458 | StringRef Id = "id" ; |
459 | auto StmtMatch = |
460 | matchStmt(StatementCode: Snippet, Matcher: memberExpr(hasObjectExpression(InnerMatcher: expr().bind(ID: Id)))); |
461 | ASSERT_TRUE(StmtMatch); |
462 | EXPECT_THAT_EXPECTED(access(Id, "field" )->eval(StmtMatch->Result), |
463 | HasValue("x->field" )); |
464 | } |
465 | |
466 | TEST_F(StencilTest, AccessOpExplicitThis) { |
467 | using clang::ast_matchers::hasObjectExpression; |
468 | using clang::ast_matchers::memberExpr; |
469 | |
470 | // Set up the code so we can bind to a use of this. |
471 | StringRef Snippet = R"cc( |
472 | class C { |
473 | public: |
474 | int x; |
475 | int foo() { return this->x; } |
476 | }; |
477 | )cc" ; |
478 | auto StmtMatch = matchStmt( |
479 | StatementCode: Snippet, |
480 | Matcher: traverse(TK: TK_AsIs, InnerMatcher: returnStmt(hasReturnValue(InnerMatcher: ignoringImplicit(InnerMatcher: memberExpr( |
481 | hasObjectExpression(InnerMatcher: expr().bind(ID: "obj" )))))))); |
482 | ASSERT_TRUE(StmtMatch); |
483 | const Stencil Stencil = access(BaseId: "obj" , Member: "field" ); |
484 | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), |
485 | HasValue("this->field" )); |
486 | } |
487 | |
488 | TEST_F(StencilTest, AccessOpImplicitThis) { |
489 | using clang::ast_matchers::hasObjectExpression; |
490 | using clang::ast_matchers::memberExpr; |
491 | |
492 | // Set up the code so we can bind to a use of (implicit) this. |
493 | StringRef Snippet = R"cc( |
494 | class C { |
495 | public: |
496 | int x; |
497 | int foo() { return x; } |
498 | }; |
499 | )cc" ; |
500 | auto StmtMatch = |
501 | matchStmt(StatementCode: Snippet, Matcher: returnStmt(hasReturnValue(InnerMatcher: ignoringImplicit(InnerMatcher: memberExpr( |
502 | hasObjectExpression(InnerMatcher: expr().bind(ID: "obj" ))))))); |
503 | ASSERT_TRUE(StmtMatch); |
504 | const Stencil Stencil = access(BaseId: "obj" , Member: "field" ); |
505 | EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("field" )); |
506 | } |
507 | |
508 | TEST_F(StencilTest, DescribeType) { |
509 | std::string Snippet = "int *x; x;" ; |
510 | std::string Expected = "int *" ; |
511 | auto StmtMatch = |
512 | matchStmt(StatementCode: Snippet, Matcher: declRefExpr(hasType(InnerMatcher: qualType().bind(ID: "type" )))); |
513 | ASSERT_TRUE(StmtMatch); |
514 | EXPECT_THAT_EXPECTED(describe("type" )->eval(StmtMatch->Result), |
515 | HasValue(std::string(Expected))); |
516 | } |
517 | |
518 | TEST_F(StencilTest, DescribeSugaredType) { |
519 | std::string Snippet = "using Ty = int; Ty *x; x;" ; |
520 | std::string Expected = "Ty *" ; |
521 | auto StmtMatch = |
522 | matchStmt(StatementCode: Snippet, Matcher: declRefExpr(hasType(InnerMatcher: qualType().bind(ID: "type" )))); |
523 | ASSERT_TRUE(StmtMatch); |
524 | EXPECT_THAT_EXPECTED(describe("type" )->eval(StmtMatch->Result), |
525 | HasValue(std::string(Expected))); |
526 | } |
527 | |
528 | TEST_F(StencilTest, DescribeDeclType) { |
529 | std::string Snippet = "S s; s;" ; |
530 | std::string Expected = "S" ; |
531 | auto StmtMatch = |
532 | matchStmt(StatementCode: Snippet, Matcher: declRefExpr(hasType(InnerMatcher: qualType().bind(ID: "type" )))); |
533 | ASSERT_TRUE(StmtMatch); |
534 | EXPECT_THAT_EXPECTED(describe("type" )->eval(StmtMatch->Result), |
535 | HasValue(std::string(Expected))); |
536 | } |
537 | |
538 | TEST_F(StencilTest, DescribeQualifiedType) { |
539 | std::string Snippet = "N::C c; c;" ; |
540 | std::string Expected = "N::C" ; |
541 | auto StmtMatch = |
542 | matchStmt(StatementCode: Snippet, Matcher: declRefExpr(hasType(InnerMatcher: qualType().bind(ID: "type" )))); |
543 | ASSERT_TRUE(StmtMatch); |
544 | EXPECT_THAT_EXPECTED(describe("type" )->eval(StmtMatch->Result), |
545 | HasValue(std::string(Expected))); |
546 | } |
547 | |
548 | TEST_F(StencilTest, DescribeUnqualifiedType) { |
549 | std::string Snippet = "using N::C; C c; c;" ; |
550 | std::string Expected = "C" ; |
551 | auto StmtMatch = |
552 | matchStmt(StatementCode: Snippet, Matcher: declRefExpr(hasType(InnerMatcher: qualType().bind(ID: "type" )))); |
553 | ASSERT_TRUE(StmtMatch); |
554 | EXPECT_THAT_EXPECTED(describe("type" )->eval(StmtMatch->Result), |
555 | HasValue(std::string(Expected))); |
556 | } |
557 | |
558 | TEST_F(StencilTest, DescribeAnonNamespaceType) { |
559 | std::string Snippet = "auto c = desugar<AnonC>(); c;" ; |
560 | std::string Expected = "(anonymous namespace)::AnonC" ; |
561 | auto StmtMatch = |
562 | matchStmt(StatementCode: Snippet, Matcher: declRefExpr(hasType(InnerMatcher: qualType().bind(ID: "type" )))); |
563 | ASSERT_TRUE(StmtMatch); |
564 | EXPECT_THAT_EXPECTED(describe("type" )->eval(StmtMatch->Result), |
565 | HasValue(std::string(Expected))); |
566 | } |
567 | |
568 | TEST_F(StencilTest, RunOp) { |
569 | StringRef Id = "id" ; |
570 | auto SimpleFn = [Id](const MatchResult &R) { |
571 | return std::string(R.Nodes.getNodeAs<Stmt>(ID: Id) != nullptr ? "Bound" |
572 | : "Unbound" ); |
573 | }; |
574 | testExpr(Id, Snippet: "3;" , Stencil: run(C: SimpleFn), Expected: "Bound" ); |
575 | } |
576 | |
577 | TEST_F(StencilTest, CatOfMacroRangeSucceeds) { |
578 | StringRef Snippet = R"cpp( |
579 | #define MACRO 3.77 |
580 | double foo(double d); |
581 | foo(MACRO);)cpp" ; |
582 | |
583 | auto StmtMatch = |
584 | matchStmt(StatementCode: Snippet, Matcher: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "foo" ))), |
585 | argumentCountIs(N: 1), |
586 | hasArgument(N: 0, InnerMatcher: expr().bind(ID: "arg" )))); |
587 | ASSERT_TRUE(StmtMatch); |
588 | Stencil S = cat(Parts: node(ID: "arg" )); |
589 | EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("MACRO" )); |
590 | } |
591 | |
592 | TEST_F(StencilTest, CatOfMacroArgRangeSucceeds) { |
593 | StringRef Snippet = R"cpp( |
594 | #define MACRO(a, b) a + b |
595 | MACRO(2, 3);)cpp" ; |
596 | |
597 | auto StmtMatch = |
598 | matchStmt(StatementCode: Snippet, Matcher: binaryOperator(hasRHS(InnerMatcher: expr().bind(ID: "rhs" )))); |
599 | ASSERT_TRUE(StmtMatch); |
600 | Stencil S = cat(Parts: node(ID: "rhs" )); |
601 | EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("3" )); |
602 | } |
603 | |
604 | TEST_F(StencilTest, CatOfMacroArgSubRangeSucceeds) { |
605 | StringRef Snippet = R"cpp( |
606 | #define MACRO(a, b) a + b |
607 | int foo(int); |
608 | MACRO(2, foo(3));)cpp" ; |
609 | |
610 | auto StmtMatch = matchStmt( |
611 | StatementCode: Snippet, Matcher: binaryOperator(hasRHS(InnerMatcher: callExpr( |
612 | callee(InnerMatcher: functionDecl(hasName(Name: "foo" ))), argumentCountIs(N: 1), |
613 | hasArgument(N: 0, InnerMatcher: expr().bind(ID: "arg" )))))); |
614 | ASSERT_TRUE(StmtMatch); |
615 | Stencil S = cat(Parts: node(ID: "arg" )); |
616 | EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("3" )); |
617 | } |
618 | |
619 | TEST_F(StencilTest, CatOfInvalidRangeFails) { |
620 | StringRef Snippet = R"cpp( |
621 | #define MACRO (3.77) |
622 | double foo(double d); |
623 | foo(MACRO);)cpp" ; |
624 | |
625 | auto StmtMatch = |
626 | matchStmt(StatementCode: Snippet, Matcher: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "foo" ))), |
627 | argumentCountIs(N: 1), |
628 | hasArgument(N: 0, InnerMatcher: expr().bind(ID: "arg" )))); |
629 | ASSERT_TRUE(StmtMatch); |
630 | Stencil S = cat(Parts: node(ID: "arg" )); |
631 | Expected<std::string> Result = S->eval(R: StmtMatch->Result); |
632 | ASSERT_FALSE(Result); |
633 | llvm::handleAllErrors(E: Result.takeError(), Handlers: [](const llvm::StringError &E) { |
634 | EXPECT_THAT(E.getMessage(), AllOf(HasSubstr("selected range" ), |
635 | HasSubstr("macro expansion" ))); |
636 | }); |
637 | } |
638 | |
639 | // The `StencilToStringTest` tests verify that the string representation of the |
640 | // stencil combinator matches (as best possible) the spelling of the |
641 | // combinator's construction. Exceptions include those combinators that have no |
642 | // explicit spelling (like raw text) and those supporting non-printable |
643 | // arguments (like `run`, `selection`). |
644 | |
645 | TEST(StencilToStringTest, RawTextOp) { |
646 | auto S = cat(Parts: "foo bar baz" ); |
647 | StringRef Expected = R"("foo bar baz")" ; |
648 | EXPECT_EQ(S->toString(), Expected); |
649 | } |
650 | |
651 | TEST(StencilToStringTest, RawTextOpEscaping) { |
652 | auto S = cat(Parts: "foo \"bar\" baz\\n" ); |
653 | StringRef Expected = R"("foo \"bar\" baz\\n")" ; |
654 | EXPECT_EQ(S->toString(), Expected); |
655 | } |
656 | |
657 | TEST(StencilToStringTest, DescribeOp) { |
658 | auto S = describe(Id: "Id" ); |
659 | StringRef Expected = R"repr(describe("Id"))repr" ; |
660 | EXPECT_EQ(S->toString(), Expected); |
661 | } |
662 | |
663 | TEST(StencilToStringTest, DebugPrintNodeOp) { |
664 | auto S = dPrint(Id: "Id" ); |
665 | StringRef Expected = R"repr(dPrint("Id"))repr" ; |
666 | EXPECT_EQ(S->toString(), Expected); |
667 | } |
668 | |
669 | TEST(StencilToStringTest, ExpressionOp) { |
670 | auto S = expression(Id: "Id" ); |
671 | StringRef Expected = R"repr(expression("Id"))repr" ; |
672 | EXPECT_EQ(S->toString(), Expected); |
673 | } |
674 | |
675 | TEST(StencilToStringTest, DerefOp) { |
676 | auto S = deref(ExprId: "Id" ); |
677 | StringRef Expected = R"repr(deref("Id"))repr" ; |
678 | EXPECT_EQ(S->toString(), Expected); |
679 | } |
680 | |
681 | TEST(StencilToStringTest, AddressOfOp) { |
682 | auto S = addressOf(ExprId: "Id" ); |
683 | StringRef Expected = R"repr(addressOf("Id"))repr" ; |
684 | EXPECT_EQ(S->toString(), Expected); |
685 | } |
686 | |
687 | TEST(StencilToStringTest, SelectionOp) { |
688 | auto S1 = cat(Parts: node(ID: "node1" )); |
689 | EXPECT_EQ(S1->toString(), "selection(...)" ); |
690 | } |
691 | |
692 | TEST(StencilToStringTest, AccessOpText) { |
693 | auto S = access(BaseId: "Id" , Member: "memberData" ); |
694 | StringRef Expected = R"repr(access("Id", "memberData"))repr" ; |
695 | EXPECT_EQ(S->toString(), Expected); |
696 | } |
697 | |
698 | TEST(StencilToStringTest, AccessOpSelector) { |
699 | auto S = access(BaseId: "Id" , Member: cat(Parts: name(ID: "otherId" ))); |
700 | StringRef Expected = R"repr(access("Id", selection(...)))repr" ; |
701 | EXPECT_EQ(S->toString(), Expected); |
702 | } |
703 | |
704 | TEST(StencilToStringTest, AccessOpStencil) { |
705 | auto S = access(BaseId: "Id" , Member: cat(Parts: "foo_" , Parts: "bar" )); |
706 | StringRef Expected = R"repr(access("Id", seq("foo_", "bar")))repr" ; |
707 | EXPECT_EQ(S->toString(), Expected); |
708 | } |
709 | |
710 | TEST(StencilToStringTest, IfBoundOp) { |
711 | auto S = ifBound(Id: "Id" , TrueStencil: cat(Parts: "trueText" ), FalseStencil: access(BaseId: "exprId" , Member: "memberData" )); |
712 | StringRef Expected = |
713 | R"repr(ifBound("Id", "trueText", access("exprId", "memberData")))repr" ; |
714 | EXPECT_EQ(S->toString(), Expected); |
715 | } |
716 | |
717 | TEST(StencilToStringTest, SelectBoundOp) { |
718 | auto S = selectBound(CaseStencils: { |
719 | {"int" , cat(Parts: "I" )}, |
720 | {"float" , cat(Parts: "F" )}, |
721 | }); |
722 | StringRef Expected = R"repr(selectBound({{"int", "I"}, {"float", "F"}}))repr" ; |
723 | EXPECT_EQ(S->toString(), Expected); |
724 | } |
725 | |
726 | TEST(StencilToStringTest, SelectBoundOpWithOneCase) { |
727 | auto S = selectBound(CaseStencils: {{"int" , cat(Parts: "I" )}}); |
728 | StringRef Expected = R"repr(selectBound({{"int", "I"}}))repr" ; |
729 | EXPECT_EQ(S->toString(), Expected); |
730 | } |
731 | |
732 | TEST(StencilToStringTest, SelectBoundOpWithDefault) { |
733 | auto S = selectBound(CaseStencils: {{"int" , cat(Parts: "I" )}, {"float" , cat(Parts: "F" )}}, DefaultStencil: cat(Parts: "D" )); |
734 | StringRef Expected = |
735 | R"cc(selectBound({{"int", "I"}, {"float", "F"}}, "D"))cc" ; |
736 | EXPECT_EQ(S->toString(), Expected); |
737 | } |
738 | |
739 | TEST(StencilToStringTest, RunOp) { |
740 | auto F1 = [](const MatchResult &R) { return "foo" ; }; |
741 | auto S1 = run(C: F1); |
742 | EXPECT_EQ(S1->toString(), "run(...)" ); |
743 | } |
744 | |
745 | TEST(StencilToStringTest, Sequence) { |
746 | auto S = cat(Parts: "foo" , Parts: access(BaseId: "x" , Member: "m()" ), Parts: "bar" , |
747 | Parts: ifBound(Id: "x" , TrueStencil: cat(Parts: "t" ), FalseStencil: access(BaseId: "e" , Member: "f" ))); |
748 | StringRef Expected = R"repr(seq("foo", access("x", "m()"), "bar", )repr" |
749 | R"repr(ifBound("x", "t", access("e", "f"))))repr" ; |
750 | EXPECT_EQ(S->toString(), Expected); |
751 | } |
752 | |
753 | TEST(StencilToStringTest, SequenceEmpty) { |
754 | auto S = cat(); |
755 | StringRef Expected = "seq()" ; |
756 | EXPECT_EQ(S->toString(), Expected); |
757 | } |
758 | |
759 | TEST(StencilToStringTest, SequenceSingle) { |
760 | auto S = cat(Parts: "foo" ); |
761 | StringRef Expected = "\"foo\"" ; |
762 | EXPECT_EQ(S->toString(), Expected); |
763 | } |
764 | |
765 | TEST(StencilToStringTest, SequenceFromVector) { |
766 | auto S = catVector(Parts: {cat(Parts: "foo" ), access(BaseId: "x" , Member: "m()" ), cat(Parts: "bar" ), |
767 | ifBound(Id: "x" , TrueStencil: cat(Parts: "t" ), FalseStencil: access(BaseId: "e" , Member: "f" ))}); |
768 | StringRef Expected = R"repr(seq("foo", access("x", "m()"), "bar", )repr" |
769 | R"repr(ifBound("x", "t", access("e", "f"))))repr" ; |
770 | EXPECT_EQ(S->toString(), Expected); |
771 | } |
772 | } // namespace |
773 | |