1 | //===- unittest/Tooling/SourceCodeBuildersTest.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/SourceCodeBuilders.h" |
10 | #include "clang/AST/Type.h" |
11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
12 | #include "clang/ASTMatchers/ASTMatchers.h" |
13 | #include "clang/Tooling/Tooling.h" |
14 | #include "llvm/Testing/Support/SupportHelpers.h" |
15 | #include "gmock/gmock.h" |
16 | #include "gtest/gtest.h" |
17 | #include <optional> |
18 | |
19 | using namespace clang; |
20 | using namespace tooling; |
21 | using namespace ast_matchers; |
22 | |
23 | namespace { |
24 | using MatchResult = MatchFinder::MatchResult; |
25 | using llvm::ValueIs; |
26 | |
27 | // Create a valid translation unit from a statement. |
28 | static std::string wrapSnippet(StringRef StatementCode) { |
29 | return ("namespace std {\n" |
30 | "template <typename T> struct unique_ptr {\n" |
31 | " T* operator->() const;\n" |
32 | " T& operator*() const;\n" |
33 | "};\n" |
34 | "template <typename T> struct shared_ptr {\n" |
35 | " T* operator->() const;\n" |
36 | " T& operator*() const;\n" |
37 | "};\n" |
38 | "}\n" |
39 | "struct A { void super(); };\n" |
40 | "struct S : public A { S(); S(int); int Field; };\n" |
41 | "S operator+(const S &a, const S &b);\n" |
42 | "struct Smart {\n" |
43 | " S* operator->() const;\n" |
44 | " S& operator*() const;\n" |
45 | "};\n" |
46 | "auto test_snippet = []{" + |
47 | StatementCode + "};" ) |
48 | .str(); |
49 | } |
50 | |
51 | static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) { |
52 | return varDecl(hasName(Name: "test_snippet" ), |
53 | hasDescendant(compoundStmt(hasAnySubstatement(InnerMatcher: Matcher)))); |
54 | } |
55 | |
56 | struct TestMatch { |
57 | // The AST unit from which `result` is built. We bundle it because it backs |
58 | // the result. Users are not expected to access it. |
59 | std::unique_ptr<ASTUnit> AstUnit; |
60 | // The result to use in the test. References `ast_unit`. |
61 | MatchResult Result; |
62 | }; |
63 | |
64 | // Matches `Matcher` against the statement `StatementCode` and returns the |
65 | // result. Handles putting the statement inside a function and modifying the |
66 | // matcher correspondingly. `Matcher` should match one of the statements in |
67 | // `StatementCode` exactly -- that is, produce exactly one match. However, |
68 | // `StatementCode` may contain other statements not described by `Matcher`. |
69 | static std::optional<TestMatch> matchStmt(StringRef StatementCode, |
70 | StatementMatcher Matcher) { |
71 | auto AstUnit = buildASTFromCodeWithArgs(Code: wrapSnippet(StatementCode), |
72 | Args: {"-Wno-unused-value" }); |
73 | if (AstUnit == nullptr) { |
74 | ADD_FAILURE() << "AST construction failed" ; |
75 | return std::nullopt; |
76 | } |
77 | ASTContext &Context = AstUnit->getASTContext(); |
78 | auto Matches = ast_matchers::match(Matcher: wrapMatcher(Matcher), Context); |
79 | // We expect a single, exact match for the statement. |
80 | if (Matches.size() != 1) { |
81 | ADD_FAILURE() << "Wrong number of matches: " << Matches.size(); |
82 | return std::nullopt; |
83 | } |
84 | return TestMatch{.AstUnit: std::move(AstUnit), .Result: MatchResult(Matches[0], &Context)}; |
85 | } |
86 | |
87 | static void testPredicate(bool (*Pred)(const Expr &), StringRef Snippet, |
88 | bool Expected) { |
89 | auto StmtMatch = matchStmt(StatementCode: Snippet, Matcher: expr().bind(ID: "expr" )); |
90 | ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; |
91 | EXPECT_EQ(Expected, Pred(*StmtMatch->Result.Nodes.getNodeAs<Expr>("expr" ))) |
92 | << "Snippet: " << Snippet; |
93 | } |
94 | |
95 | // Tests the predicate on the call argument, assuming `Snippet` is a function |
96 | // call. |
97 | static void testPredicateOnArg(bool (*Pred)(const Expr &), StringRef Snippet, |
98 | bool Expected) { |
99 | auto StmtMatch = matchStmt( |
100 | StatementCode: Snippet, Matcher: expr(ignoringImplicit(InnerMatcher: callExpr(hasArgument( |
101 | N: 0, InnerMatcher: ignoringElidableConstructorCall(InnerMatcher: expr().bind(ID: "arg" ))))))); |
102 | ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; |
103 | EXPECT_EQ(Expected, Pred(*StmtMatch->Result.Nodes.getNodeAs<Expr>("arg" ))) |
104 | << "Snippet: " << Snippet; |
105 | } |
106 | |
107 | TEST(SourceCodeBuildersTest, needParensAfterUnaryOperator) { |
108 | testPredicate(Pred: needParensAfterUnaryOperator, Snippet: "3 + 5;" , Expected: true); |
109 | testPredicate(Pred: needParensAfterUnaryOperator, Snippet: "true ? 3 : 5;" , Expected: true); |
110 | testPredicate(Pred: needParensAfterUnaryOperator, Snippet: "S(3) + S(5);" , Expected: true); |
111 | |
112 | testPredicate(Pred: needParensAfterUnaryOperator, Snippet: "int x; x;" , Expected: false); |
113 | testPredicate(Pred: needParensAfterUnaryOperator, Snippet: "int(3.0);" , Expected: false); |
114 | testPredicate(Pred: needParensAfterUnaryOperator, Snippet: "void f(); f();" , Expected: false); |
115 | testPredicate(Pred: needParensAfterUnaryOperator, Snippet: "int a[3]; a[0];" , Expected: false); |
116 | testPredicate(Pred: needParensAfterUnaryOperator, Snippet: "S x; x.Field;" , Expected: false); |
117 | testPredicate(Pred: needParensAfterUnaryOperator, Snippet: "int x = 1; --x;" , Expected: false); |
118 | testPredicate(Pred: needParensAfterUnaryOperator, Snippet: "int x = 1; -x;" , Expected: false); |
119 | } |
120 | |
121 | TEST(SourceCodeBuildersTest, needParensAfterUnaryOperatorInImplicitConversion) { |
122 | // The binary operation will be embedded in various implicit |
123 | // expressions. Verify they are ignored. |
124 | testPredicateOnArg(Pred: needParensAfterUnaryOperator, Snippet: "void f(S); f(3 + 5);" , |
125 | Expected: true); |
126 | } |
127 | |
128 | TEST(SourceCodeBuildersTest, mayEverNeedParens) { |
129 | testPredicate(Pred: mayEverNeedParens, Snippet: "3 + 5;" , Expected: true); |
130 | testPredicate(Pred: mayEverNeedParens, Snippet: "true ? 3 : 5;" , Expected: true); |
131 | testPredicate(Pred: mayEverNeedParens, Snippet: "int x = 1; --x;" , Expected: true); |
132 | testPredicate(Pred: mayEverNeedParens, Snippet: "int x = 1; -x;" , Expected: true); |
133 | |
134 | testPredicate(Pred: mayEverNeedParens, Snippet: "int x; x;" , Expected: false); |
135 | testPredicate(Pred: mayEverNeedParens, Snippet: "int(3.0);" , Expected: false); |
136 | testPredicate(Pred: mayEverNeedParens, Snippet: "void f(); f();" , Expected: false); |
137 | testPredicate(Pred: mayEverNeedParens, Snippet: "int a[3]; a[0];" , Expected: false); |
138 | testPredicate(Pred: mayEverNeedParens, Snippet: "S x; x.Field;" , Expected: false); |
139 | } |
140 | |
141 | TEST(SourceCodeBuildersTest, mayEverNeedParensInImplictConversion) { |
142 | // The binary operation will be embedded in various implicit |
143 | // expressions. Verify they are ignored. |
144 | testPredicateOnArg(Pred: mayEverNeedParens, Snippet: "void f(S); f(3 + 5);" , Expected: true); |
145 | } |
146 | |
147 | TEST(SourceCodeBuildersTest, isKnownPointerLikeTypeUniquePtr) { |
148 | std::string Snippet = "std::unique_ptr<int> P; P;" ; |
149 | auto StmtMatch = |
150 | matchStmt(StatementCode: Snippet, Matcher: declRefExpr(hasType(InnerMatcher: qualType().bind(ID: "ty" )))); |
151 | ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; |
152 | EXPECT_TRUE( |
153 | isKnownPointerLikeType(*StmtMatch->Result.Nodes.getNodeAs<QualType>("ty" ), |
154 | *StmtMatch->Result.Context)) |
155 | << "Snippet: " << Snippet; |
156 | } |
157 | |
158 | TEST(SourceCodeBuildersTest, isKnownPointerLikeTypeSharedPtr) { |
159 | std::string Snippet = "std::shared_ptr<int> P; P;" ; |
160 | auto StmtMatch = |
161 | matchStmt(StatementCode: Snippet, Matcher: declRefExpr(hasType(InnerMatcher: qualType().bind(ID: "ty" )))); |
162 | ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; |
163 | EXPECT_TRUE( |
164 | isKnownPointerLikeType(*StmtMatch->Result.Nodes.getNodeAs<QualType>("ty" ), |
165 | *StmtMatch->Result.Context)) |
166 | << "Snippet: " << Snippet; |
167 | } |
168 | |
169 | TEST(SourceCodeBuildersTest, isKnownPointerLikeTypeUnknownTypeFalse) { |
170 | std::string Snippet = "Smart P; P;" ; |
171 | auto StmtMatch = |
172 | matchStmt(StatementCode: Snippet, Matcher: declRefExpr(hasType(InnerMatcher: qualType().bind(ID: "ty" )))); |
173 | ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; |
174 | EXPECT_FALSE( |
175 | isKnownPointerLikeType(*StmtMatch->Result.Nodes.getNodeAs<QualType>("ty" ), |
176 | *StmtMatch->Result.Context)) |
177 | << "Snippet: " << Snippet; |
178 | } |
179 | |
180 | TEST(SourceCodeBuildersTest, isKnownPointerLikeTypeNormalTypeFalse) { |
181 | std::string Snippet = "int *P; P;" ; |
182 | auto StmtMatch = |
183 | matchStmt(StatementCode: Snippet, Matcher: declRefExpr(hasType(InnerMatcher: qualType().bind(ID: "ty" )))); |
184 | ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; |
185 | EXPECT_FALSE( |
186 | isKnownPointerLikeType(*StmtMatch->Result.Nodes.getNodeAs<QualType>("ty" ), |
187 | *StmtMatch->Result.Context)) |
188 | << "Snippet: " << Snippet; |
189 | } |
190 | |
191 | static void testBuilder( |
192 | std::optional<std::string> (*Builder)(const Expr &, const ASTContext &), |
193 | StringRef Snippet, StringRef Expected) { |
194 | auto StmtMatch = matchStmt(StatementCode: Snippet, Matcher: expr().bind(ID: "expr" )); |
195 | ASSERT_TRUE(StmtMatch); |
196 | EXPECT_THAT(Builder(*StmtMatch->Result.Nodes.getNodeAs<Expr>("expr" ), |
197 | *StmtMatch->Result.Context), |
198 | ValueIs(std::string(Expected))); |
199 | } |
200 | |
201 | static void testBuildAccess(StringRef Snippet, StringRef Expected, |
202 | PLTClass C = PLTClass::Pointer) { |
203 | auto StmtMatch = matchStmt(StatementCode: Snippet, Matcher: expr().bind(ID: "expr" )); |
204 | ASSERT_TRUE(StmtMatch); |
205 | EXPECT_THAT(buildAccess(*StmtMatch->Result.Nodes.getNodeAs<Expr>("expr" ), |
206 | *StmtMatch->Result.Context, C), |
207 | ValueIs(std::string(Expected))); |
208 | } |
209 | |
210 | TEST(SourceCodeBuildersTest, BuildParensUnaryOp) { |
211 | testBuilder(Builder: buildParens, Snippet: "-4;" , Expected: "(-4)" ); |
212 | } |
213 | |
214 | TEST(SourceCodeBuildersTest, BuildParensBinOp) { |
215 | testBuilder(Builder: buildParens, Snippet: "4 + 4;" , Expected: "(4 + 4)" ); |
216 | } |
217 | |
218 | TEST(SourceCodeBuildersTest, BuildParensValue) { |
219 | testBuilder(Builder: buildParens, Snippet: "4;" , Expected: "4" ); |
220 | } |
221 | |
222 | TEST(SourceCodeBuildersTest, BuildParensSubscript) { |
223 | testBuilder(Builder: buildParens, Snippet: "int a[3]; a[0];" , Expected: "a[0]" ); |
224 | } |
225 | |
226 | TEST(SourceCodeBuildersTest, BuildParensCall) { |
227 | testBuilder(Builder: buildParens, Snippet: "int f(int); f(4);" , Expected: "f(4)" ); |
228 | } |
229 | |
230 | TEST(SourceCodeBuildersTest, BuildAddressOfValue) { |
231 | testBuilder(Builder: buildAddressOf, Snippet: "S x; x;" , Expected: "&x" ); |
232 | } |
233 | |
234 | TEST(SourceCodeBuildersTest, BuildAddressOfPointerDereference) { |
235 | testBuilder(Builder: buildAddressOf, Snippet: "S *x; *x;" , Expected: "x" ); |
236 | } |
237 | |
238 | TEST(SourceCodeBuildersTest, BuildAddressOfPointerDereferenceIgnoresParens) { |
239 | testBuilder(Builder: buildAddressOf, Snippet: "S *x; *(x);" , Expected: "x" ); |
240 | } |
241 | |
242 | TEST(SourceCodeBuildersTest, BuildAddressOfBinaryOperation) { |
243 | testBuilder(Builder: buildAddressOf, Snippet: "S x; x + x;" , Expected: "&(x + x)" ); |
244 | } |
245 | |
246 | TEST(SourceCodeBuildersTest, BuildAddressOfImplicitThis) { |
247 | StringRef Snippet = R"cc( |
248 | struct Struct { |
249 | void foo() {} |
250 | void bar() { |
251 | foo(); |
252 | } |
253 | }; |
254 | )cc" ; |
255 | auto StmtMatch = matchStmt( |
256 | StatementCode: Snippet, |
257 | Matcher: cxxMemberCallExpr(onImplicitObjectArgument(InnerMatcher: cxxThisExpr().bind(ID: "expr" )))); |
258 | ASSERT_TRUE(StmtMatch); |
259 | EXPECT_THAT(buildAddressOf(*StmtMatch->Result.Nodes.getNodeAs<Expr>("expr" ), |
260 | *StmtMatch->Result.Context), |
261 | ValueIs(std::string("this" ))); |
262 | } |
263 | |
264 | TEST(SourceCodeBuildersTest, BuildDereferencePointer) { |
265 | testBuilder(Builder: buildDereference, Snippet: "S *x; x;" , Expected: "*x" ); |
266 | } |
267 | |
268 | TEST(SourceCodeBuildersTest, BuildDereferenceValueAddress) { |
269 | testBuilder(Builder: buildDereference, Snippet: "S x; &x;" , Expected: "x" ); |
270 | } |
271 | |
272 | TEST(SourceCodeBuildersTest, BuildDereferenceValueAddressIgnoresParens) { |
273 | testBuilder(Builder: buildDereference, Snippet: "S x; &(x);" , Expected: "x" ); |
274 | } |
275 | |
276 | TEST(SourceCodeBuildersTest, BuildDereferenceBinaryOperation) { |
277 | testBuilder(Builder: buildDereference, Snippet: "S *x; x + 1;" , Expected: "*(x + 1)" ); |
278 | } |
279 | |
280 | TEST(SourceCodeBuildersTest, BuildDotValue) { |
281 | testBuilder(Builder: buildDot, Snippet: "S x; x;" , Expected: "x." ); |
282 | } |
283 | |
284 | TEST(SourceCodeBuildersTest, BuildDotPointerDereference) { |
285 | testBuilder(Builder: buildDot, Snippet: "S *x; *x;" , Expected: "x->" ); |
286 | } |
287 | |
288 | TEST(SourceCodeBuildersTest, BuildDotPointerDereferenceIgnoresParens) { |
289 | testBuilder(Builder: buildDot, Snippet: "S *x; *(x);" , Expected: "x->" ); |
290 | } |
291 | |
292 | TEST(SourceCodeBuildersTest, BuildDotBinaryOperation) { |
293 | testBuilder(Builder: buildDot, Snippet: "S x; x + x;" , Expected: "(x + x)." ); |
294 | } |
295 | |
296 | TEST(SourceCodeBuildersTest, BuildDotPointerDereferenceExprWithParens) { |
297 | testBuilder(Builder: buildDot, Snippet: "S *x; *(x + 1);" , Expected: "(x + 1)->" ); |
298 | } |
299 | |
300 | TEST(SourceCodeBuildersTest, BuildArrowPointer) { |
301 | testBuilder(Builder: buildArrow, Snippet: "S *x; x;" , Expected: "x->" ); |
302 | } |
303 | |
304 | TEST(SourceCodeBuildersTest, BuildArrowValueAddress) { |
305 | testBuilder(Builder: buildArrow, Snippet: "S x; &x;" , Expected: "x." ); |
306 | } |
307 | |
308 | TEST(SourceCodeBuildersTest, BuildArrowValueAddressIgnoresParens) { |
309 | testBuilder(Builder: buildArrow, Snippet: "S x; &(x);" , Expected: "x." ); |
310 | } |
311 | |
312 | TEST(SourceCodeBuildersTest, BuildArrowBinaryOperation) { |
313 | testBuilder(Builder: buildArrow, Snippet: "S *x; x + 1;" , Expected: "(x + 1)->" ); |
314 | } |
315 | |
316 | TEST(SourceCodeBuildersTest, BuildArrowValueAddressWithParens) { |
317 | testBuilder(Builder: buildArrow, Snippet: "S x; &(true ? x : x);" , Expected: "(true ? x : x)." ); |
318 | } |
319 | |
320 | TEST(SourceCodeBuildersTest, BuildAccessValue) { |
321 | testBuildAccess(Snippet: "S x; x;" , Expected: "x." ); |
322 | } |
323 | |
324 | TEST(SourceCodeBuildersTest, BuildAccessPointerDereference) { |
325 | testBuildAccess(Snippet: "S *x; *x;" , Expected: "x->" ); |
326 | } |
327 | |
328 | TEST(SourceCodeBuildersTest, BuildAccessPointerDereferenceIgnoresParens) { |
329 | testBuildAccess(Snippet: "S *x; *(x);" , Expected: "x->" ); |
330 | } |
331 | |
332 | TEST(SourceCodeBuildersTest, BuildAccessValueBinaryOperation) { |
333 | testBuildAccess(Snippet: "S x; x + x;" , Expected: "(x + x)." ); |
334 | } |
335 | |
336 | TEST(SourceCodeBuildersTest, BuildAccessPointerDereferenceExprWithParens) { |
337 | testBuildAccess(Snippet: "S *x; *(x + 1);" , Expected: "(x + 1)->" ); |
338 | } |
339 | |
340 | TEST(SourceCodeBuildersTest, BuildAccessPointer) { |
341 | testBuildAccess(Snippet: "S *x; x;" , Expected: "x->" ); |
342 | } |
343 | |
344 | TEST(SourceCodeBuildersTest, BuildAccessValueAddress) { |
345 | testBuildAccess(Snippet: "S x; &x;" , Expected: "x." ); |
346 | } |
347 | |
348 | TEST(SourceCodeBuildersTest, BuildAccessValueAddressIgnoresParens) { |
349 | testBuildAccess(Snippet: "S x; &(x);" , Expected: "x." ); |
350 | } |
351 | |
352 | TEST(SourceCodeBuildersTest, BuildAccessPointerBinaryOperation) { |
353 | testBuildAccess(Snippet: "S *x; x + 1;" , Expected: "(x + 1)->" ); |
354 | } |
355 | |
356 | TEST(SourceCodeBuildersTest, BuildAccessValueAddressWithParens) { |
357 | testBuildAccess(Snippet: "S x; &(true ? x : x);" , Expected: "(true ? x : x)." ); |
358 | } |
359 | |
360 | TEST(SourceCodeBuildersTest, BuildAccessSmartPointer) { |
361 | testBuildAccess(Snippet: "std::unique_ptr<int> x; x;" , Expected: "x->" ); |
362 | } |
363 | |
364 | TEST(SourceCodeBuildersTest, BuildAccessSmartPointerAsValue) { |
365 | testBuildAccess(Snippet: "std::unique_ptr<int> x; x;" , Expected: "x." , C: PLTClass::Value); |
366 | } |
367 | |
368 | TEST(SourceCodeBuildersTest, BuildAccessSmartPointerDeref) { |
369 | testBuildAccess(Snippet: "std::unique_ptr<int> x; *x;" , Expected: "x->" ); |
370 | } |
371 | |
372 | TEST(SourceCodeBuildersTest, BuildAccessSmartPointerDerefAsValue) { |
373 | testBuildAccess(Snippet: "std::unique_ptr<int> x; *x;" , Expected: "(*x)." , C: PLTClass::Value); |
374 | } |
375 | |
376 | TEST(SourceCodeBuildersTest, BuildAccessSmartPointerMemberCall) { |
377 | StringRef Snippet = R"cc( |
378 | Smart x; |
379 | x->Field; |
380 | )cc" ; |
381 | auto StmtMatch = |
382 | matchStmt(StatementCode: Snippet, Matcher: memberExpr(hasObjectExpression(InnerMatcher: expr().bind(ID: "expr" )))); |
383 | ASSERT_TRUE(StmtMatch); |
384 | EXPECT_THAT(buildAccess(*StmtMatch->Result.Nodes.getNodeAs<Expr>("expr" ), |
385 | *StmtMatch->Result.Context), |
386 | ValueIs(std::string("x->" ))); |
387 | } |
388 | |
389 | TEST(SourceCodeBuildersTest, BuildAccessIgnoreImplicit) { |
390 | StringRef Snippet = R"cc( |
391 | S x; |
392 | A *a; |
393 | a = &x; |
394 | )cc" ; |
395 | auto StmtMatch = |
396 | matchStmt(StatementCode: Snippet, Matcher: binaryOperator(isAssignmentOperator(), |
397 | hasRHS(InnerMatcher: expr().bind(ID: "expr" )))); |
398 | ASSERT_TRUE(StmtMatch); |
399 | EXPECT_THAT(buildAccess(*StmtMatch->Result.Nodes.getNodeAs<Expr>("expr" ), |
400 | *StmtMatch->Result.Context), |
401 | ValueIs(std::string("x." ))); |
402 | } |
403 | |
404 | TEST(SourceCodeBuildersTest, BuildAccessImplicitThis) { |
405 | StringRef Snippet = R"cc( |
406 | struct Struct { |
407 | void foo() {} |
408 | void bar() { |
409 | foo(); |
410 | } |
411 | }; |
412 | )cc" ; |
413 | auto StmtMatch = matchStmt( |
414 | StatementCode: Snippet, |
415 | Matcher: cxxMemberCallExpr(onImplicitObjectArgument(InnerMatcher: cxxThisExpr().bind(ID: "expr" )))); |
416 | ASSERT_TRUE(StmtMatch); |
417 | EXPECT_THAT(buildAccess(*StmtMatch->Result.Nodes.getNodeAs<Expr>("expr" ), |
418 | *StmtMatch->Result.Context), |
419 | ValueIs(std::string())); |
420 | } |
421 | |
422 | TEST(SourceCodeBuildersTest, BuildAccessImplicitThisIgnoreImplicitCasts) { |
423 | StringRef Snippet = "struct B : public A { void f() { super(); } };" ; |
424 | auto StmtMatch = matchStmt( |
425 | StatementCode: Snippet, |
426 | Matcher: cxxMemberCallExpr(onImplicitObjectArgument(InnerMatcher: expr().bind(ID: "expr" )))); |
427 | ASSERT_TRUE(StmtMatch); |
428 | EXPECT_THAT(buildAccess(*StmtMatch->Result.Nodes.getNodeAs<Expr>("expr" ), |
429 | *StmtMatch->Result.Context), |
430 | ValueIs(std::string())); |
431 | } |
432 | } // namespace |
433 | |