clang-tools 24.0.0git
ExtractFunctionTests.cpp
Go to the documentation of this file.
1//===-- ExtractFunctionTests.cpp --------------------------------*- C++ -*-===//
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 "TweakTesting.h"
10#include "gmock/gmock.h"
11#include "gtest/gtest.h"
12
13using ::testing::AllOf;
14using ::testing::HasSubstr;
15using ::testing::Not;
16using ::testing::StartsWith;
17
18namespace clang {
19namespace clangd {
20namespace {
21
22TWEAK_TEST(ExtractFunction);
23
24TEST_F(ExtractFunctionTest, FunctionTest) {
26
27 // Root statements should have common parent.
28 EXPECT_EQ(apply("for(;;) [[1+2; 1+2;]]"), "unavailable");
29 // Single expression-statements can be extracted.
30 EXPECT_THAT(apply("int x = 0; [[x++;]]"), HasSubstr("extracted"));
31 // We don't support extraction from lambdas.
32 EXPECT_EQ(apply("auto lam = [](){ [[int x;]] }; "), "unavailable");
33 // Partial statements aren't extracted.
34 EXPECT_THAT(apply("int [[x = 0]];"), "unavailable");
35 // FIXME: Support hoisting.
36 EXPECT_THAT(apply(" [[int a = 5;]] a++; "), "unavailable");
37
38 // Ensure that end of Zone and Beginning of PostZone being adjacent doesn't
39 // lead to break being included in the extraction zone.
40 EXPECT_THAT(apply("for(;;) { [[int x;]]break; }"), HasSubstr("extracted"));
41 // A loop's initializer has its value discarded just like an ordinary
42 // statement, so it remains extractable (unlike the condition; see
43 // ControlFlowConditions below).
44 EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("extracted"));
45 // ...but if the declared name is used later (in the condition,
46 // increment, or body), extraction is unavailable regardless -- not
47 // because of any condition/init-specific logic, but because
48 // requiresHoisting() (checked in ExtractFunction::prepare(), independent
49 // of what kind of statement is being extracted) catches it.
50 EXPECT_EQ(apply("void use(int); for([[int i = 0;]] i < 10; ++i) use(i);"),
51 "unavailable");
52 // Extract certain return
53 EXPECT_THAT(apply(" if(true) [[{ return; }]] "), HasSubstr("extracted"));
54 // Don't extract uncertain return
55 EXPECT_THAT(apply(" if(true) [[if (false) return;]] "),
56 StartsWith("unavailable"));
57 EXPECT_THAT(
58 apply("#define RETURN_IF_ERROR(x) if (x) return\nRETU^RN_IF_ERROR(4);"),
59 StartsWith("unavailable"));
60}
61
62TEST_F(ExtractFunctionTest, FileTest) {
63 // Check all parameters are in order. `a` and `ptr` are mutated in the
64 // zone (`+=` and postfix `++` respectively), so stay non-const; `b` is
65 // an unmutated scalar, so becomes a by-value parameter; `foo` is an
66 // unmutated class type, so becomes a const reference.
67 std::string ParameterCheckInput = R"cpp(
68struct Foo {
69 int x;
70};
71void f(int a) {
72 int b;
73 int *ptr = &a;
74 Foo foo;
75 [[a += foo.x + b;
76 *ptr++;]]
77})cpp";
78 std::string ParameterCheckOutput = R"cpp(
79struct Foo {
80 int x;
81};
82void extracted(int &a, int b, int *&ptr, const Foo &foo) {
83a += foo.x + b;
84 *ptr++;
85}
86void f(int a) {
87 int b;
88 int *ptr = &a;
89 Foo foo;
90 extracted(a, b, ptr, foo);
91})cpp";
92 EXPECT_EQ(apply(ParameterCheckInput), ParameterCheckOutput);
93
94 // Check const qualifier
95 std::string ConstCheckInput = R"cpp(
96void f(const int c) {
97 [[while(c) {}]]
98})cpp";
99 std::string ConstCheckOutput = R"cpp(
100void extracted(const int c) {
101while(c) {}
102}
103void f(const int c) {
104 extracted(c);
105})cpp";
106 EXPECT_EQ(apply(ConstCheckInput), ConstCheckOutput);
107
108 // Check const qualifier: kept for a by-reference non-scalar.
109 std::string ConstNamespaceCheckInput = R"cpp(
110namespace X { struct Y { int z; }; }
111int f(const X::Y &y) {
112 [[return y.z + y.z;]]
113})cpp";
114 std::string ConstNamespaceCheckOutput = R"cpp(
115namespace X { struct Y { int z; }; }
116int extracted(const X::Y &y) {
117return y.z + y.z;
118}
119int f(const X::Y &y) {
120 return extracted(y);
121})cpp";
122 EXPECT_EQ(apply(ConstNamespaceCheckInput), ConstNamespaceCheckOutput);
123
124 // Don't extract when we need to make a function as a parameter.
125 EXPECT_THAT(apply("void f() { [[int a; f();]] }"), StartsWith("fail"));
126
127 std::string MethodInput = R"cpp(
128 class T {
129 void f() {
130 [[int x;]]
131 }
132 };
133 )cpp";
134 std::string MethodCheckOutput = R"cpp(
135 class T {
136 void extracted() {
137int x;
138}
139void f() {
140 extracted();
141 }
142 };
143 )cpp";
144 EXPECT_EQ(apply(MethodInput), MethodCheckOutput);
145
146 std::string OutOfLineMethodInput = R"cpp(
147 class T {
148 void f();
149 };
150
151 void T::f() {
152 [[int x;]]
153 }
154 )cpp";
155 std::string OutOfLineMethodCheckOutput = R"cpp(
156 class T {
157 void extracted();
158void f();
159 };
160
161 void T::extracted() {
162int x;
163}
164void T::f() {
165 extracted();
166 }
167 )cpp";
168 EXPECT_EQ(apply(OutOfLineMethodInput), OutOfLineMethodCheckOutput);
169
170 // We don't extract from templated functions for now as templates are hard
171 // to deal with.
172 std::string TemplateFailInput = R"cpp(
173 template<typename T>
174 void f() {
175 [[int x;]]
176 }
177 )cpp";
178 EXPECT_EQ(apply(TemplateFailInput), "unavailable");
179
180 std::string MacroInput = R"cpp(
181 #define F(BODY) void f() { BODY }
182 F ([[int x = 0;]])
183 )cpp";
184 std::string MacroOutput = R"cpp(
185 #define F(BODY) void f() { BODY }
186 void extracted() {
187int x = 0;
188}
189F (extracted();)
190 )cpp";
191 EXPECT_EQ(apply(MacroInput), MacroOutput);
192
193 // Shouldn't crash.
194 EXPECT_EQ(apply("void f([[int a]]);"), "unavailable");
195 EXPECT_EQ(apply("void f(int a = [[1]]);"), "unavailable");
196 // Don't extract if we select the entire function body (CompoundStmt).
197 std::string CompoundFailInput = R"cpp(
198 void f() [[{
199 int a;
200 }]]
201 )cpp";
202 EXPECT_EQ(apply(CompoundFailInput), "unavailable");
203
204 ExtraArgs.push_back("-std=c++14");
205 // A bare expression-statement can be extracted (the semicolon isn't part
206 // of the selection either way, since it isn't owned by any AST node).
207 EXPECT_THAT(apply(R"cpp(
208 void call() { [[1+1]]; }
209 )cpp"),
210 HasSubstr("extracted"));
211 EXPECT_THAT(apply(R"cpp(
212 void call() { [[1+1;]] }
213 )cpp"),
214 HasSubstr("extracted"));
215}
216
217TEST_F(ExtractFunctionTest, DifferentHeaderSourceTest) {
218 Header = R"cpp(
219 class SomeClass {
220 void f();
221 };
222 )cpp";
223
224 std::string OutOfLineSource = R"cpp(
225 void SomeClass::f() {
226 [[int x;]]
227 }
228 )cpp";
229
230 std::string OutOfLineSourceOutputCheck = R"cpp(
231 void SomeClass::extracted() {
232int x;
233}
234void SomeClass::f() {
235 extracted();
236 }
237 )cpp";
238
239 std::string HeaderOutputCheck = R"cpp(
240 class SomeClass {
241 void extracted();
242void f();
243 };
244 )cpp";
245
246 llvm::StringMap<std::string> EditedFiles;
247
248 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
249 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
250}
251
252TEST_F(ExtractFunctionTest, DifferentFilesNestedTest) {
253 Header = R"cpp(
254 class T {
255 class SomeClass {
256 void f();
257 };
258 };
259 )cpp";
260
261 std::string NestedOutOfLineSource = R"cpp(
262 void T::SomeClass::f() {
263 [[int x;]]
264 }
265 )cpp";
266
267 std::string NestedOutOfLineSourceOutputCheck = R"cpp(
268 void T::SomeClass::extracted() {
269int x;
270}
271void T::SomeClass::f() {
272 extracted();
273 }
274 )cpp";
275
276 std::string NestedHeaderOutputCheck = R"cpp(
277 class T {
278 class SomeClass {
279 void extracted();
280void f();
281 };
282 };
283 )cpp";
284
285 llvm::StringMap<std::string> EditedFiles;
286
287 EXPECT_EQ(apply(NestedOutOfLineSource, &EditedFiles),
288 NestedOutOfLineSourceOutputCheck);
289 EXPECT_EQ(EditedFiles.begin()->second, NestedHeaderOutputCheck);
290}
291
292TEST_F(ExtractFunctionTest, ConstexprDifferentHeaderSourceTest) {
293 Header = R"cpp(
294 class SomeClass {
295 constexpr void f() const;
296 };
297 )cpp";
298
299 std::string OutOfLineSource = R"cpp(
300 constexpr void SomeClass::f() const {
301 [[int x;]]
302 }
303 )cpp";
304
305 std::string OutOfLineSourceOutputCheck = R"cpp(
306 constexpr void SomeClass::extracted() const {
307int x;
308}
309constexpr void SomeClass::f() const {
310 extracted();
311 }
312 )cpp";
313
314 std::string HeaderOutputCheck = R"cpp(
315 class SomeClass {
316 constexpr void extracted() const;
317constexpr void f() const;
318 };
319 )cpp";
320
321 llvm::StringMap<std::string> EditedFiles;
322
323 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
324 EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
325 << "The header should be edited and receives the declaration of the new "
326 "function";
327
328 if (EditedFiles.begin() != EditedFiles.end()) {
329 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
330 }
331}
332
333TEST_F(ExtractFunctionTest, ConstevalDifferentHeaderSourceTest) {
334 ExtraArgs.push_back("--std=c++20");
335 Header = R"cpp(
336 class SomeClass {
337 consteval void f() const;
338 };
339 )cpp";
340
341 std::string OutOfLineSource = R"cpp(
342 consteval void SomeClass::f() const {
343 [[int x;]]
344 }
345 )cpp";
346
347 std::string OutOfLineSourceOutputCheck = R"cpp(
348 consteval void SomeClass::extracted() const {
349int x;
350}
351consteval void SomeClass::f() const {
352 extracted();
353 }
354 )cpp";
355
356 std::string HeaderOutputCheck = R"cpp(
357 class SomeClass {
358 consteval void extracted() const;
359consteval void f() const;
360 };
361 )cpp";
362
363 llvm::StringMap<std::string> EditedFiles;
364
365 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
366 EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
367 << "The header should be edited and receives the declaration of the new "
368 "function";
369
370 if (EditedFiles.begin() != EditedFiles.end()) {
371 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
372 }
373}
374
375TEST_F(ExtractFunctionTest, ConstDifferentHeaderSourceTest) {
376 Header = R"cpp(
377 class SomeClass {
378 void f() const;
379 };
380 )cpp";
381
382 std::string OutOfLineSource = R"cpp(
383 void SomeClass::f() const {
384 [[int x;]]
385 }
386 )cpp";
387
388 std::string OutOfLineSourceOutputCheck = R"cpp(
389 void SomeClass::extracted() const {
390int x;
391}
392void SomeClass::f() const {
393 extracted();
394 }
395 )cpp";
396
397 std::string HeaderOutputCheck = R"cpp(
398 class SomeClass {
399 void extracted() const;
400void f() const;
401 };
402 )cpp";
403
404 llvm::StringMap<std::string> EditedFiles;
405
406 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
407 EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
408 << "The header should be edited and receives the declaration of the new "
409 "function";
410
411 if (EditedFiles.begin() != EditedFiles.end()) {
412 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
413 }
414}
415
416TEST_F(ExtractFunctionTest, StaticDifferentHeaderSourceTest) {
417 Header = R"cpp(
418 class SomeClass {
419 static void f();
420 };
421 )cpp";
422
423 std::string OutOfLineSource = R"cpp(
424 void SomeClass::f() {
425 [[int x;]]
426 }
427 )cpp";
428
429 std::string OutOfLineSourceOutputCheck = R"cpp(
430 void SomeClass::extracted() {
431int x;
432}
433void SomeClass::f() {
434 extracted();
435 }
436 )cpp";
437
438 std::string HeaderOutputCheck = R"cpp(
439 class SomeClass {
440 static void extracted();
441static void f();
442 };
443 )cpp";
444
445 llvm::StringMap<std::string> EditedFiles;
446
447 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
448 EXPECT_NE(EditedFiles.begin(), EditedFiles.end())
449 << "The header should be edited and receives the declaration of the new "
450 "function";
451
452 if (EditedFiles.begin() != EditedFiles.end()) {
453 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
454 }
455}
456
457TEST_F(ExtractFunctionTest, DifferentContextHeaderSourceTest) {
458 Header = R"cpp(
459 namespace ns{
460 class A {
461 class C {
462 public:
463 class RType {};
464 };
465
466 class T {
467 class SomeClass {
468 static C::RType f();
469 };
470 };
471 };
472 } // ns
473 )cpp";
474
475 std::string OutOfLineSource = R"cpp(
476 ns::A::C::RType ns::A::T::SomeClass::f() {
477 [[A::C::RType x;
478 return x;]]
479 }
480 )cpp";
481
482 std::string OutOfLineSourceOutputCheck = R"cpp(
483 ns::A::C::RType ns::A::T::SomeClass::extracted() {
484A::C::RType x;
485 return x;
486}
487ns::A::C::RType ns::A::T::SomeClass::f() {
488 return extracted();
489 }
490 )cpp";
491
492 std::string HeaderOutputCheck = R"cpp(
493 namespace ns{
494 class A {
495 class C {
496 public:
497 class RType {};
498 };
499
500 class T {
501 class SomeClass {
502 static ns::A::C::RType extracted();
503static C::RType f();
504 };
505 };
506 };
507 } // ns
508 )cpp";
509
510 llvm::StringMap<std::string> EditedFiles;
511
512 EXPECT_EQ(apply(OutOfLineSource, &EditedFiles), OutOfLineSourceOutputCheck);
513 EXPECT_EQ(EditedFiles.begin()->second, HeaderOutputCheck);
514}
515
516TEST_F(ExtractFunctionTest, DifferentSyntacticContextNamespace) {
517 std::string OutOfLineSource = R"cpp(
518 namespace ns {
519 void f();
520 }
521
522 void ns::f() {
523 [[int x;]]
524 }
525 )cpp";
526
527 std::string OutOfLineSourceOutputCheck = R"cpp(
528 namespace ns {
529 void extracted();
530void f();
531 }
532
533 void ns::extracted() {
534int x;
535}
536void ns::f() {
537 extracted();
538 }
539 )cpp";
540
541 EXPECT_EQ(apply(OutOfLineSource), OutOfLineSourceOutputCheck);
542}
543
544TEST_F(ExtractFunctionTest, ControlFlow) {
546 // We should be able to extract break/continue with a parent loop/switch.
547 EXPECT_THAT(apply(" [[for(;;) if(1) break;]] "), HasSubstr("extracted"));
548 EXPECT_THAT(apply(" for(;;) [[while(1) break;]] "), HasSubstr("extracted"));
549 EXPECT_THAT(apply(" [[switch(1) { break; }]]"), HasSubstr("extracted"));
550 EXPECT_THAT(apply(" [[while(1) switch(1) { continue; }]]"),
551 HasSubstr("extracted"));
552 // Don't extract break and continue without a loop/switch parent.
553 EXPECT_THAT(apply(" for(;;) [[if(1) continue;]] "), StartsWith("fail"));
554 EXPECT_THAT(apply(" while(1) [[if(1) break;]] "), StartsWith("fail"));
555 EXPECT_THAT(apply(" switch(1) { [[break;]] }"), StartsWith("fail"));
556 EXPECT_THAT(apply(" for(;;) { [[while(1) break; break;]] }"),
557 StartsWith("fail"));
558}
559
560TEST_F(ExtractFunctionTest, ExistingReturnStatement) {
561 Context = File;
562 const char *Before = R"cpp(
563 bool lucky(int N);
564 int getNum(bool Superstitious, int Min, int Max) {
565 if (Superstitious) [[{
566 for (int I = Min; I <= Max; ++I)
567 if (lucky(I))
568 return I;
569 return -1;
570 }]] else {
571 return (Min + Max) / 2;
572 }
573 }
574 )cpp";
575 // FIXME: avoid emitting redundant braces
576 const char *After = R"cpp(
577 bool lucky(int N);
578 int extracted(int Min, int Max) {
579{
580 for (int I = Min; I <= Max; ++I)
581 if (lucky(I))
582 return I;
583 return -1;
584 }
585}
586int getNum(bool Superstitious, int Min, int Max) {
587 if (Superstitious) return extracted(Min, Max); else {
588 return (Min + Max) / 2;
589 }
590 }
591 )cpp";
592 EXPECT_EQ(apply(Before), After);
593}
594
595TEST_F(ExtractFunctionTest, OverloadedOperators) {
596 Context = File;
597 std::string Before = R"cpp(struct A {
598 int operator+(int x) { return x; }
599 };
600 A &operator<<(A &, int);
601 A &operator|(A &, int);
602
603 A stream{};
604
605 void foo(int, int);
606
607 int main() {
608 [[foo(1, 2);
609 foo(3, 4);
610 stream << 42;
611 stream + 42;
612 stream | 42;
613 foo(1, 2);
614 foo(3, 4);]]
615 })cpp";
616 std::string After =
617 R"cpp(struct A {
618 int operator+(int x) { return x; }
619 };
620 A &operator<<(A &, int);
621 A &operator|(A &, int);
622
623 A stream{};
624
625 void foo(int, int);
626
627 void extracted() {
628foo(1, 2);
629 foo(3, 4);
630 stream << 42;
631 stream + 42;
632 stream | 42;
633 foo(1, 2);
634 foo(3, 4);
635}
636int main() {
637 extracted();
638 })cpp";
639 EXPECT_EQ(apply(Before), After);
640}
641
642TEST_F(ExtractFunctionTest, SingleStatement) {
643 Context = File;
644 // https://github.com/clangd/clangd/issues/698
645 // A single call-expression-statement can be extracted.
646 EXPECT_THAT(apply(R"cpp(
647 void foo(int, int);
648 void bar() {
649 [[foo(1, 2);]]
650 })cpp"),
651 HasSubstr("extracted"));
652 // https://github.com/clangd/clangd/issues/1254
653 // A single statement consisting of an overloaded binary operator call can
654 // be extracted, even though the SelectionTree marks the
655 // CXXOperatorCallExpr itself as Unselected (its operands claim all the
656 // characters).
657 EXPECT_THAT(apply(R"cpp(
658 struct Stream {};
659 Stream &operator<<(Stream &, const char *);
660 Stream stream;
661 int main() {
662 [[stream << "x";]]
663 })cpp"),
664 HasSubstr("extracted"));
665 // Selecting a subexpression of an operator call (rather than the whole
666 // statement) must not be extracted: it is not a standalone statement, and
667 // "extracting" it would replace only part of the expression.
668 EXPECT_EQ(apply(R"cpp(
669 struct Stream {};
670 Stream &operator<<(Stream &, int);
671 Stream stream;
672 void test() {
673 stream << [[3]];
674 })cpp"),
675 "unavailable");
676 // Same as above, but the selected subexpression is itself an
677 // (Unselected-but-fully-covered) CXXOperatorCallExpr nested as an argument
678 // of an outer call, rather than a Complete leaf expression.
679 EXPECT_EQ(apply(R"cpp(
680 struct Stream {};
681 Stream &operator<<(Stream &, int);
682 void foo(Stream &, int);
683 Stream stream;
684 void test() {
685 foo([[stream << 3]], 4);
686 })cpp"),
687 "unavailable");
688}
689
690TEST_F(ExtractFunctionTest, ControlFlowConditions) {
691 Context = File;
692 // The condition of an `if` is not a discardable statement -- its value is
693 // consumed by the `if` itself.
694 EXPECT_EQ(apply(R"cpp(
695 int example(int event1, bool event2, double event3) {
696 if ([[event1 == 2 && event2 && event3 == 10.3]])
697 return 1;
698 return 0;
699 })cpp"),
700 "unavailable");
701 // Same, but for other control-flow constructs' conditions.
702 EXPECT_EQ(apply("void f(int x) { while ([[x > 0]]) --x; }"), "unavailable");
703 EXPECT_EQ(apply("void f(int x) { do {} while ([[x > 0]]); }"), "unavailable");
704 EXPECT_EQ(apply("void f(int x) { for (; [[x > 0]];) ; }"), "unavailable");
705 EXPECT_EQ(apply("void f(int x) { switch ([[x + 1]]) {} }"), "unavailable");
706 // A condition-variable declaration (`if (T x = ...)`) is likewise not a
707 // discardable statement: its truthiness *is* the condition.
708 EXPECT_EQ(apply("bool cond(); void f() { if ([[bool b = cond()]]) ; }"),
709 "unavailable");
710 // Unlike the condition, a loop's initializer and increment clauses have
711 // their value discarded just like an ordinary statement, so they remain
712 // extractable (any hazard from extracting a declaration used later is
713 // already caught by ExtractionZone::requiresHoisting, independently of
714 // this).
715 EXPECT_THAT(apply("void f(int x) { for ([[x = 0]]; x < 10; ++x) ; }"),
716 HasSubstr("extracted"));
717 EXPECT_THAT(apply("void f(int x) { for (;; [[--x]]) ; }"),
718 HasSubstr("extracted"));
719 // Likewise, an `if`/`switch` init-statement (C++17) is extractable.
720 ExtraArgs.push_back("-std=c++17");
721 EXPECT_THAT(apply("void f(int x) { if ([[x = 0]]; x > 0) ; }"),
722 HasSubstr("extracted"));
723 EXPECT_THAT(apply("void f(int x) { switch ([[x = 0]]; x) {} }"),
724 HasSubstr("extracted"));
725 // Sanity check: extraction from the *body* of these constructs (as opposed
726 // to their condition) is unaffected.
727 EXPECT_THAT(apply("void f(int x) { if (x > 0) [[x = x * 2;]] }"),
728 HasSubstr("extracted"));
729}
730
731TEST_F(ExtractFunctionTest, RangeBasedFor) {
732 Context = File;
733 // The range-expression of a range-based for is consumed to build the
734 // hidden begin/end iterators, so it's not a discardable statement either
735 // (same category as an ordinary condition).
736 EXPECT_EQ(apply(R"cpp(
737 struct Vec { int *begin(); int *end(); };
738 Vec V;
739 void f() { for (auto X : [[V]]) {} }
740 )cpp"),
741 "unavailable");
742 // Extraction from the body is unaffected.
743 EXPECT_THAT(apply(R"cpp(
744 struct Vec { int *begin(); int *end(); };
745 Vec V;
746 void foo(int);
747 void f() { for (auto X : V) { [[foo(X + 1);]] } }
748 )cpp"),
749 HasSubstr("extracted"));
750}
751
752TEST_F(ExtractFunctionTest, VarDeclInitializer) {
753 Context = File;
754 // The initializer of a variable declaration is not a discardable
755 // statement either: its value is required to initialize the variable, so
756 // replacing it with a call to a void-returning extracted function would
757 // not compile. Ascending from the initializer lands on the VarDecl itself (a
758 // Decl, not a Stmt or Expr), which the general "is this a genuine statement"
759 // check must also reject.
760 EXPECT_EQ(apply(R"cpp(
761 int func();
762 void f() { auto A = [[func()]]; }
763 )cpp"),
764 "unavailable");
765 // Same without `auto`: the type doesn't matter, only that a value is
766 // required.
767 EXPECT_EQ(apply(R"cpp(
768 int func();
769 void f() { int A = [[func()]]; }
770 )cpp"),
771 "unavailable");
772 // Sanity check: extracting the whole declaration statement (as opposed to
773 // just its initializer) is unaffected.
774 EXPECT_THAT(apply(R"cpp(
775 int func();
776 void f() { [[int A = func();]] }
777 )cpp"),
778 HasSubstr("extracted"));
779}
780
781TEST_F(ExtractFunctionTest, ConstParameters) {
782 Context = File;
783 // A captured scalar that's only read becomes a by-value parameter;
784 // non-scalars instead become a const reference (see the `S` cases
785 // below). The duplicated calls below are there to prevent "pure forwarding"
786 // suppression.
787 EXPECT_THAT(apply(R"cpp(
788 void use(int);
789 void f(int x) { [[use(x); use(x);]] }
790 )cpp"),
791 HasSubstr("void extracted(int x)"));
792 // Direct assignment: stays non-const.
793 EXPECT_THAT(apply("void f(int x) { [[x = 1;]] }"),
794 HasSubstr("void extracted(int &x)"));
795 // Compound assignment: stays non-const.
796 EXPECT_THAT(apply("void f(int x) { [[x += 1;]] }"),
797 HasSubstr("void extracted(int &x)"));
798 // Increment/decrement: stays non-const.
799 EXPECT_THAT(apply("void f(int x) { [[++x;]] }"),
800 HasSubstr("void extracted(int &x)"));
801 // A non-const method call may mutate the object: stays non-const.
802 EXPECT_THAT(apply(R"cpp(
803 struct S { void mutate(); };
804 void f(S s) { [[s.mutate(); s.mutate();]] }
805 )cpp"),
806 HasSubstr("void extracted(S &s)"));
807 // A const method call cannot mutate the object: becomes const.
808 EXPECT_THAT(apply(R"cpp(
809 struct S { void inspect() const; };
810 void f(S s) { [[s.inspect(); s.inspect();]] }
811 )cpp"),
812 HasSubstr("void extracted(const S &s)"));
813 // Passed to a parameter taking a non-const reference: stays non-const,
814 // since the callee could mutate it through that reference.
815 EXPECT_THAT(apply(R"cpp(
816 void mayMutate(int &);
817 void f(int x) { [[mayMutate(x); mayMutate(x);]] }
818 )cpp"),
819 HasSubstr("void extracted(int &x)"));
820 // Passed to a parameter taking a const reference or by value: becomes
821 // an unmutated scalar, so by value.
822 EXPECT_THAT(apply(R"cpp(
823 void readOnly(const int &);
824 void f(int x) { [[readOnly(x); readOnly(x);]] }
825 )cpp"),
826 HasSubstr("void extracted(int x)"));
827 EXPECT_THAT(apply(R"cpp(
828 void byValue(int);
829 void f(int x) { [[byValue(x); byValue(x);]] }
830 )cpp"),
831 HasSubstr("void extracted(int x)"));
832 // A scalar parameter that's already declared const keeps that
833 // qualifier when passed by value: It might be relevant for overload
834 // resolution.
835 EXPECT_THAT(
836 apply("void use(int); void f(const int x) { [[use(x); use(x);]] }"),
837 HasSubstr("void extracted(const int x)"));
838 // A non-scalar parameter that's already declared const keeps that
839 // qualifier, since it's still passed by reference.
840 EXPECT_THAT(apply(R"cpp(
841 struct S {};
842 void use(const S &);
843 void f(const S s) { [[use(s); use(s);]] }
844 )cpp"),
845 HasSubstr("void extracted(const S &s)"));
846}
847
848TEST_F(ExtractFunctionTest, ConstParametersReferenceAliasing) {
849 Context = File;
850 // A non-const reference bound to a captured variable conservatively
851 // mutates that variable too, without checking whether the reference
852 // itself is ever actually mutated -- even when the binding and a later
853 // mutation of the reference are two separate root statements of the same
854 // zone. Failing to notice this would incorrectly mark `x` const, which
855 // wouldn't compile (`int &r` can't bind to a `const int`).
856 EXPECT_THAT(apply(R"cpp(
857 void f(int x) {
858 [[int &r = x;
859 r = 2;]]
860 }
861 )cpp"),
862 HasSubstr("void extracted(int &x)"));
863}
864
865TEST_F(ExtractFunctionTest, ConstParametersConservativeAliasing) {
866 Context = File;
867 // Taking the address of a captured variable conservatively mutates it,
868 // regardless of what's later done with the pointer.
869 EXPECT_THAT(apply("void f(int x) { [[int *p = &x;]] }"),
870 HasSubstr("void extracted(int &x)"));
871 // Explicit cast to a non-const reference type: stays non-const.
872 EXPECT_THAT(apply("void f(int x) { [[static_cast<int &>(x) = 1;]] }"),
873 HasSubstr("void extracted(int &x)"));
874 // Captured by reference in a lambda: stays non-const, without checking
875 // whether the lambda actually mutates it.
876 EXPECT_THAT(apply("void f(int x) { [[auto l = [&x]() { int y = x; };]] }"),
877 HasSubstr("int &x"));
878 // Captured by value in a lambda: doesn't alias x, so it's unmutated and
879 // (being a scalar) passed by value.
880 EXPECT_THAT(apply("void f(int x) { [[auto l = [x]() { int y = x; };]] }"),
881 HasSubstr("void extracted(int x)"));
882 // Returning a captured variable is conservatively treated as a possible
883 // mutation, regardless of whether the return is actually by value (safe)
884 // or by non-const reference (not safe) -- telling these apart isn't
885 // worth the complexity here.
886 EXPECT_THAT(apply("int f(int x) { [[return x;]] }"), HasSubstr("&x"));
887 // Array-typed captures are never made const.
888 EXPECT_THAT(apply("void f() { int arr[5]; [[arr[0] = 1;]] }"),
889 Not(HasSubstr("const")));
890}
891
892TEST_F(ExtractFunctionTest, ConstParametersMemberCallArguments) {
893 Context = File;
894 // A non-const-ref argument to a member call (as opposed to the implicit
895 // object, already covered by ConstParameters) stays non-const. The
896 // method itself is const so it doesn't also mark `s` mutated, isolating
897 // the argument check from the object check.
898 EXPECT_THAT(apply(R"cpp(
899 struct S { void mayMutate(int &) const; };
900 void f(S s, int x) { [[s.mayMutate(x); s.mayMutate(x);]] }
901 )cpp"),
902 HasSubstr("extracted(const S &s, int &x)"));
903 // Same, but through an overloaded operator: the implicit object is
904 // args[0], so the real parameter (checked against args[1:]) must be
905 // found at the right offset.
906 EXPECT_THAT(apply(R"cpp(
907 struct S { void operator()(int &) const; };
908 void f(S s, int x) { [[s(x); s(x);]] }
909 )cpp"),
910 HasSubstr("extracted(const S &s, int &x)"));
911 // A free (non-member) operator overload has no implicit object, so all
912 // operands align directly with the callee's parameters.
913 EXPECT_THAT(apply(R"cpp(
914 struct S {};
915 void operator+(const S &, int &);
916 void f(S s, int x) { [[s + x; s + x;]] }
917 )cpp"),
918 HasSubstr("extracted(const S &s, int &x)"));
919}
920
921TEST_F(ExtractFunctionTest, ConstParametersPointerIndirection) {
922 Context = File;
923 // Mutating a member/element through a pointer only mutates the pointee,
924 // never the pointer's own binding, so the pointer itself is unmutated --
925 // unlike the same access through a value or reference (already covered
926 // by ConstParameters' `ptr` case, which is mutated directly instead).
927 // Being an unmutated scalar, it's then passed by value.
928 EXPECT_THAT(apply(R"cpp(
929 struct S { int x; };
930 void f(S *ptr) { [[ptr->x = 1;]] }
931 )cpp"),
932 HasSubstr("extracted(S *ptr)"));
933 EXPECT_THAT(apply("void f(int *p) { [[p[0] = 1;]] }"),
934 HasSubstr("extracted(int *p)"));
935 // Same for a plain dereference.
936 EXPECT_THAT(apply("void f(int *p) { [[*p = 1;]] }"),
937 HasSubstr("extracted(int *p)"));
938}
939
940TEST_F(ExtractFunctionTest, ConstParametersConditionalReferenceBinding) {
941 Context = File;
942 // A reference bound to a conditional expression could alias either
943 // branch at runtime, so both must be marked non-const -- getting only
944 // one (or neither) would let the other stay const while still being
945 // reachable through the reference, producing code that doesn't compile.
946 EXPECT_THAT(apply(R"cpp(
947 void f(bool cond, int c, int d) {
948 [[int &a = cond ? c : d;
949 a = 5;]]
950 }
951 )cpp"),
952 HasSubstr("extracted(bool cond, int &c, int &d)"));
953}
954
955TEST_F(ExtractFunctionTest, ConstParametersConditionalMutatingAccess) {
956 Context = File;
957 // Same as above, but no named LHS.
958 EXPECT_THAT(apply(R"cpp(
959 struct S { int n; };
960 void f(bool cond, S s1, S s2) {
961 [[(cond ? s1 : s2).n = 0;]]
962 }
963 )cpp"),
964 HasSubstr("extracted(bool cond, S &s1, S &s2)"));
965}
966
967TEST_F(ExtractFunctionTest, ConstParametersStaticOperatorCall) {
968 Context = File;
969 // A static operator() (or operator[], since C++23) has no implicit
970 // object at all, so calling it through `s(...)` syntax doesn't touch
971 // `s` regardless of the operator's own constness.
972 ExtraArgs.push_back("-std=c++23");
973 EXPECT_THAT(apply(R"cpp(
974 struct S { static void operator()(int); };
975 void f(S s, int x) { [[s(x); s(x);]] }
976 )cpp"),
977 HasSubstr("extracted(const S &s, int x)"));
978}
979
980TEST_F(ExtractFunctionTest, ConstParametersFunctionPointer) {
981 Context = File;
982 // A function pointer's declarator syntax puts the name inside the
983 // parens around the "*", not after the whole type: printType()'s
984 // placeholder must be threaded through for this to render as
985 // `void (*F)(int)` rather than the uncompilable `void (*)(int) F`.
986 EXPECT_THAT(apply("void f(void (*F)(int)) { [[F(1);]] }"),
987 HasSubstr("extracted(void (*F)(int))"));
988}
989
990TEST_F(ExtractFunctionTest, ConstParametersFunctionReference) {
991 Context = File;
992 // A (non-pointer) function type can't be cv-qualified at all, unlike
993 // every other non-scalar, non-array type this conservative-const
994 // logic otherwise applies to: adding const here would produce
995 // unparseable output (`void (const &F)(int)`).
996 EXPECT_THAT(apply("void f(void (&F)(int)) { [[F(0);]] }"),
997 HasSubstr("extracted(void (&F)(int))"));
998}
999
1000TEST_F(ExtractFunctionTest, ConstParametersScalarsByValue) {
1001 Context = File;
1002 // An unmutated pointer is a scalar too: passed by value.
1003 EXPECT_THAT(apply("void use(int *); void f(int *p) { [[use(p); use(p);]] }"),
1004 HasSubstr("extracted(int *p)"));
1005 // An unmutated enum: passed by value.
1006 EXPECT_THAT(apply(R"cpp(
1007 enum E { A, B };
1008 void use(E);
1009 void f(E e) { [[use(e); use(e);]] }
1010 )cpp"),
1011 HasSubstr("extracted(E e)"));
1012 // A class type, even one that's small and trivially copyable, is never
1013 // passed by value: that's deliberately out of scope for now.
1014 EXPECT_THAT(apply(R"cpp(
1015 struct Point { int x, y; };
1016 void use(Point);
1017 void f(Point p) { [[use(p);]] }
1018 )cpp"),
1019 HasSubstr("extracted(const Point &p)"));
1020 // An unmutated array is not a scalar (even though its element type is):
1021 // stays a non-const reference, per the existing array carve-out.
1022 EXPECT_THAT(apply("void f() { int arr[5]; [[int x = arr[0];]] }"),
1023 HasSubstr("extracted(int (&arr)[5])"));
1024}
1025
1026// Variables of reference type, const or non-const, must stay references,
1027// otherwise they'd stop tracking their target.
1028TEST_F(ExtractFunctionTest, ReferenceToScalar) {
1029 Context = File;
1030 EXPECT_THAT(apply(R"cpp(
1031 void bar(int) {}
1032 void foo() {
1033 int A = 0;
1034 int &B = A;
1035 [[
1036 A = 1;
1037 bar(B);
1038 ]]
1039 })cpp"),
1040 HasSubstr("extracted(int &A, const int &B)"));
1041 EXPECT_THAT(apply(R"cpp(
1042 void bar(int) {}
1043 void foo() {
1044 int A = 0;
1045 const int &B = A;
1046 [[
1047 A = 1;
1048 bar(B);
1049 ]]
1050 })cpp"),
1051 HasSubstr("extracted(int &A, const int &B)"));
1052}
1053
1054TEST_F(ExtractFunctionTest, VolatileScalar) {
1055 Context = File;
1056 EXPECT_THAT(apply(R"cpp(
1057 void bar(const volatile int &, int) {}
1058 void foo() {
1059 volatile int V = 0;
1060 [[
1061 bar(V, 0);
1062 ]]
1063 })cpp"),
1064 HasSubstr("extracted(const volatile int &V)"));
1065}
1066
1067TEST_F(ExtractFunctionTest, CFileAllowUnmodifiedScalar) {
1068 FileName = "a.c";
1069 Context = File;
1070 EXPECT_THAT(apply(R"cpp(
1071 int i;
1072 void foo() {
1073 int j = 0;
1074 [[i = j;]]
1075 })cpp"),
1076 HasSubstr("extracted(int j)"));
1077}
1078
1079TEST_F(ExtractFunctionTest, CFileModifiedScalarBecomesPointer) {
1080 // C has no references: a mutated capture becomes a real pointer
1081 // parameter instead, with the call site taking its address and the
1082 // body dereferencing it.
1083 FileName = "a.c";
1084 Context = File;
1085 EXPECT_THAT(apply(R"cpp(
1086 void foo() {
1087 int j;
1088 [[j = 0;]]
1089 })cpp"),
1090 AllOf(HasSubstr("extracted(int *j)"), HasSubstr("(*j) = 0;"),
1091 HasSubstr("extracted(&j)")));
1092}
1093
1094TEST_F(ExtractFunctionTest, CFileUnmodifiedStructBecomesConstPointer) {
1095 // Same, but for an unmutated non-scalar capture: the parameter becomes
1096 // a pointer to const, and every member access on it is rewritten too.
1097 FileName = "a.c";
1098 Context = File;
1099 EXPECT_THAT(apply(R"cpp(
1100 struct pair { int v1; int v2; };
1101 int i;
1102 void foo() {
1103 struct pair p;
1104 p.v1 = 0;
1105 [[i = p.v1;]]
1106 })cpp"),
1107 AllOf(HasSubstr("extracted(const struct pair *p)"),
1108 HasSubstr("i = p->v1;"), HasSubstr("extracted(&p)")));
1109}
1110
1111TEST_F(ExtractFunctionTest, CFileStructMixedUses) {
1112 // The same capture can appear both as a member-access base (rewritten
1113 // to "->") and as a plain use (wrapped in "(*...)") within a single
1114 // extraction; each occurrence is rewritten independently.
1115 FileName = "a.c";
1116 Context = File;
1117 EXPECT_THAT(apply(R"cpp(
1118 struct pair { int v1; int v2; };
1119 void use(struct pair);
1120 int i;
1121 void foo() {
1122 struct pair p;
1123 [[use(p); i = p.v1;]]
1124 })cpp"),
1125 AllOf(HasSubstr("use((*p));"), HasSubstr("i = p->v1;")));
1126}
1127
1128TEST_F(ExtractFunctionTest, CFileRejectMacroDot) {
1129 // The identifier itself need not be a macro expansion for the
1130 // member-access ".", immediately following it, to be one -- that dot
1131 // is a separate token with its own location, which also needs
1132 // checking before relying on it to splice in "->".
1133 FileName = "a.c";
1134 Context = File;
1135 EXPECT_EQ(apply(R"cpp(
1136 #define DOT .
1137 struct pair { int v1; int v2; };
1138 void foo() {
1139 struct pair p;
1140 [[p DOT v1 = 1;]]
1141 })cpp"),
1142 "fail: Too complex to extract.");
1143}
1144
1145TEST_F(ExtractFunctionTest, CFileModifiedArrayStaysPlainPointer) {
1146 // Unlike other non-scalar types, an array decays to a pointer on its
1147 // own wherever it's used, so it needs neither an address-of at the
1148 // call site nor a dereference-rewrite of its uses in the body. The
1149 // parameter's own type must be decayed too, though: leaving it as an
1150 // array type would print as the uncompilable "int[5] arr" (there's no
1151 // special-cased array declarator syntax, unlike C++'s reference case).
1152 FileName = "a.c";
1153 Context = File;
1154 EXPECT_THAT(apply(R"cpp(
1155 void foo() {
1156 int arr[5];
1157 [[arr[0] = 1;]]
1158 })cpp"),
1159 AllOf(HasSubstr("extracted(int *arr)"), HasSubstr("arr[0] = 1;"),
1160 HasSubstr("extracted(arr)"), Not(HasSubstr("&arr"))));
1161}
1162
1163TEST_F(ExtractFunctionTest, CFileStaticFunctionStaysStatic) {
1164 // A free function's own `static` (internal linkage) must carry over to
1165 // an extracted sibling, or that sibling would default to external
1166 // linkage instead.
1167 FileName = "a.c";
1168 Context = File;
1169 EXPECT_THAT(apply(R"cpp(
1170 static void foo() {
1171 int j = 0;
1172 [[int k = j;]]
1173 })cpp"),
1174 HasSubstr("static void extracted"));
1175}
1176
1177TEST_F(ExtractFunctionTest, CFileStaticForwardDeclaredFunctionStaysStatic) {
1178 // Same as above, but the definition itself omits `static` (legal in C:
1179 // once a prior declaration gives the function internal linkage, a
1180 // later one doesn't need to repeat it, and still has it). Checking
1181 // only the current declaration's storage class would miss this.
1182 FileName = "a.c";
1183 Context = File;
1184 EXPECT_THAT(apply(R"cpp(
1185 static void foo();
1186 void foo() {
1187 int j = 0;
1188 [[int k = j;]]
1189 })cpp"),
1190 HasSubstr("static void extracted"));
1191}
1192
1193TEST_F(ExtractFunctionTest, CFileRejectArraySizeof) {
1194 // Decaying the array to a pointer parameter would silently change the
1195 // meaning of a `sizeof` on it (pointer size instead of array size), so
1196 // this is refused rather than risk miscompiling it.
1197 FileName = "a.c";
1198 Context = File;
1199 EXPECT_EQ(apply(R"cpp(
1200 void foo() {
1201 int arr[5];
1202 [[int n = sizeof(arr);]]
1203 })cpp"),
1204 "fail: Too complex to extract.");
1205}
1206
1207TEST_F(ExtractFunctionTest, CFileRejectArrayAlignof) {
1208 // Same hazard as sizeof, and the same UnaryExprOrTypeTraitExpr AST
1209 // node: alignof(int) and alignof(int *) aren't guaranteed to match
1210 // (and commonly don't, e.g. 4 vs 8 on a typical 64-bit target).
1211 FileName = "a.c";
1212 Context = File;
1213 EXPECT_EQ(apply(R"cpp(
1214 void foo() {
1215 int arr[5];
1216 [[int n = __alignof(arr);]]
1217 })cpp"),
1218 "fail: Too complex to extract.");
1219}
1220
1221TEST_F(ExtractFunctionTest, CFileRejectArrayTypeof) {
1222 // Same hazard again, but via a completely different AST node
1223 // (TypeOfExprType, reached through the VarDecl's TypeLoc, not through
1224 // any Stmt a plain expression visitor would see): typeof(arr) would
1225 // resolve to the decayed pointer type instead of the array type.
1226 FileName = "a.c";
1227 Context = File;
1228 EXPECT_EQ(apply(R"cpp(
1229 void foo() {
1230 int arr[5];
1231 [[__typeof__(arr) copy;]]
1232 })cpp"),
1233 "fail: Too complex to extract.");
1234}
1235
1236TEST_F(ExtractFunctionTest, CFileAllowSingleDimensionVariableLengthArray) {
1237 // A VLA's own outermost dimension is never a problem: once decayed,
1238 // it no longer appears in the parameter type at all (`int A[N]`
1239 // decays to plain `int *A`, not something that needs to mention `N`).
1240 FileName = "a.c";
1241 Context = File;
1242 EXPECT_THAT(apply(R"cpp(
1243 void foo(int N) {
1244 int A[N];
1245 [[A[0] = 1;]]
1246 })cpp"),
1247 HasSubstr("extracted(int *A)"));
1248}
1249
1250TEST_F(ExtractFunctionTest, CFileRejectMultiDimensionVariableLengthArray) {
1251 // Unlike the single-dimension case, an inner dimension that's still
1252 // variable-length after decaying the outer one (`int A[2][N]` decays
1253 // to `int (*A)[N]`) is a real hazard: `N` is an arbitrary expression,
1254 // typically another variable's name, that isn't necessarily (and
1255 // here isn't at all) itself a parameter of the extracted function,
1256 // and even printing it would reference a name meaningless in the new
1257 // scope.
1258 FileName = "a.c";
1259 Context = File;
1260 EXPECT_EQ(apply(R"cpp(
1261 void foo(int N) {
1262 int A[2][N];
1263 [[A[0][0] = 1;]]
1264 })cpp"),
1265 "fail: Too complex to extract.");
1266}
1267
1268TEST_F(ExtractFunctionTest, CXXRejectVariableLengthArray) {
1269 // Clang also supports VLAs in C++ as a GNU extension. An unmutated
1270 // array capture is passed by reference there (see
1271 // ConstParametersScalarsByValue's plain, fixed-size array case,
1272 // which that's still fine for) rather than decaying it to a pointer
1273 // the way C does -- but a reference's declared type spells out every
1274 // dimension's bound, including the outermost one:
1275 // `void extracted(int (&A)[2][N])` looks plausible, and even
1276 // compiles, but a call to it doesn't -- `N` in the parameter list
1277 // isn't the same `N` as at the call site, and a reference-to-array
1278 // parameter can't be bound by just matching the spelling.
1279 Context = File;
1280 EXPECT_EQ(apply(R"cpp(
1281 void foo(int N) {
1282 int A[2][N];
1283 [[A[0][0] = 1;]]
1284 })cpp"),
1285 "fail: Too complex to extract.");
1286}
1287
1288TEST_F(ExtractFunctionTest, CXXRejectSingleDimensionVariableLengthArray) {
1289 // Unlike in C (where a single-dimension VLA decays to a pointer that
1290 // no longer mentions the bound at all, see
1291 // CFileAllowSingleDimensionVariableLengthArray), a C++ reference's
1292 // declared type keeps the bound regardless of dimension count, so
1293 // even this simpler case has the same hazard as the 2D one above.
1294 Context = File;
1295 EXPECT_EQ(apply(R"cpp(
1296 void foo(int N) {
1297 int A[N];
1298 [[A[0] = 1;]]
1299 })cpp"),
1300 "fail: Too complex to extract.");
1301}
1302
1303TEST_F(ExtractFunctionTest, CXXRejectPointerToVariableLengthArray) {
1304 // A pointer to a VLA (`int (*)[N]`) isn't an array type itself, so
1305 // the array-specific VLA checks above don't apply to it -- but it
1306 // has the exact same hazard: its pointee's bound is baked into the
1307 // type regardless of how the pointer itself ends up passed (by
1308 // value, by reference, or, in C, by a further pointer indirection),
1309 // so it can't be printed without mentioning a name that isn't in
1310 // scope.
1311 Context = File;
1312 EXPECT_EQ(apply(R"cpp(
1313 void foo(int N) {
1314 int (*A)[N];
1315 [[(*A)[0] = 1;]]
1316 })cpp"),
1317 "fail: Too complex to extract.");
1318}
1319
1320TEST_F(ExtractFunctionTest, CFileRejectMutatedPointerToVariableLengthArray) {
1321 // Same hazard as above, but in C, and with the pointer additionally
1322 // mutated (so it would otherwise go through the C pointer-adapter
1323 // path instead of being passed by value): the bound is baked into
1324 // the pointee type regardless, so `int (**A)[N]` is just as broken
1325 // as the unmutated `int (*A)[N]` would have been.
1326 FileName = "a.c";
1327 Context = File;
1328 EXPECT_EQ(apply(R"cpp(
1329 void foo(int N) {
1330 int (*A)[N];
1331 A = 0;
1332 [[(*A)[0] = 1;]]
1333 })cpp"),
1334 "fail: Too complex to extract.");
1335}
1336
1337TEST_F(ExtractFunctionTest, RejectPureForwardingZeroArgCall) {
1338 Context = File;
1339 // No abstraction gained: the extracted function would just rename the
1340 // call, with no customizable behavior at all.
1341 EXPECT_EQ(apply("void foo(); void f() { [[foo();]] }"), "unavailable");
1342}
1343
1344TEST_F(ExtractFunctionTest, RejectPureForwardingSingleArgCall) {
1345 Context = File;
1346 // x is forwarded to foo unchanged: still just a rename.
1347 EXPECT_EQ(apply("void foo(int); void f(int x) { [[foo(x);]] }"),
1348 "unavailable");
1349}
1350
1351TEST_F(ExtractFunctionTest, OfferDespitePureForwarding_LiteralArg) {
1352 Context = File;
1353 // Hardcoding 100 is a real specialization, unlike a plain rename.
1354 EXPECT_THAT(apply("void foo(int); void f() { [[foo(100);]] }"),
1355 HasSubstr("extracted"));
1356}
1357
1358TEST_F(ExtractFunctionTest, OfferDespitePureForwarding_GlobalArg) {
1359 Context = File;
1360 // Hardcoding which already-visible global to use is likewise a real
1361 // specialization: unlike a captured local, globalVar doesn't need to
1362 // become a parameter at all.
1363 EXPECT_THAT(apply(R"cpp(
1364 int globalVar;
1365 void foo(int);
1366 void f() { [[foo(globalVar);]] }
1367 )cpp"),
1368 HasSubstr("extracted"));
1369}
1370
1371TEST_F(ExtractFunctionTest, OfferDespitePureForwarding_ComputedArg) {
1372 Context = File;
1373 // foo(x + y) needs 2 parameters for 1 argument slot: real computation,
1374 // not just forwarding x and y unchanged.
1375 EXPECT_THAT(apply("void foo(int); void f(int x, int y) { [[foo(x + y);]] }"),
1376 HasSubstr("extracted"));
1377}
1378
1379TEST_F(ExtractFunctionTest, RejectPureForwardingMethodCall) {
1380 Context = File;
1381 // Both the receiver and the argument are forwarded unchanged.
1382 EXPECT_EQ(apply(R"cpp(
1383 struct S { void method(int); };
1384 void f(S s, int x) { [[s.method(x);]] }
1385 )cpp"),
1386 "unavailable");
1387}
1388
1389TEST_F(ExtractFunctionTest, OfferDespitePureForwarding_GlobalReceiver) {
1390 Context = File;
1391 // Hardcoding which already-visible object to call the method on is a
1392 // real specialization, same as a global argument.
1393 EXPECT_THAT(apply(R"cpp(
1394 struct S { void method(); };
1395 S globalS;
1396 void f() { [[globalS.method();]] }
1397 )cpp"),
1398 HasSubstr("extracted"));
1399}
1400
1401TEST_F(ExtractFunctionTest, RejectPureForwardingOperatorCall) {
1402 Context = File;
1403 // Both operands of a + b are forwarded unchanged; the operator function
1404 // itself (never customizable) is correctly excluded from the check.
1405 EXPECT_EQ(apply(R"cpp(
1406 struct S {};
1407 void operator+(const S &, int);
1408 void f(S s, int x) { [[s + x;]] }
1409 )cpp"),
1410 "unavailable");
1411}
1412
1413TEST_F(ExtractFunctionTest, RejectPureForwardingFunctionPointerCall) {
1414 Context = File;
1415 // Unlike an ordinary function name, a function-pointer variable callee
1416 // does need to become a parameter -- but it's still forwarded
1417 // unchanged here, same as x.
1418 EXPECT_EQ(apply("void f(void (*fp)(int), int x) { [[fp(x);]] }"),
1419 "unavailable");
1420}
1421
1422TEST_F(ExtractFunctionTest, OfferDespitePureForwarding_ComputedCallee) {
1423 Context = File;
1424 // Which function pointer gets called is itself computed here, not a
1425 // bare forwarded reference: real logic, not just forwarding.
1426 EXPECT_THAT(
1427 apply("void f(void (*fp[2])(int), int i, int x) { [[fp[i](x);]] }"),
1428 HasSubstr("extracted"));
1429}
1430
1431TEST_F(ExtractFunctionTest, RejectPureForwardingReturnStmt) {
1432 Context = File;
1433 // A call wrapped in a ReturnStmt is just as much a pure rename as a
1434 // bare call statement.
1435 EXPECT_EQ(apply("int foo(int); int f(int x) { [[return foo(x);]] }"),
1436 "unavailable");
1437}
1438
1439TEST_F(ExtractFunctionTest, RejectPureForwardingReturnStmtImplicitConversion) {
1440 Context = File;
1441 // The implicit int-to-long conversion doesn't change the verdict: the
1442 // extracted function would perform the exact same conversion
1443 // internally, so this is still just a rename.
1444 EXPECT_EQ(apply("int foo(int); long f(int x) { [[return foo(x);]] }"),
1445 "unavailable");
1446}
1447
1448TEST_F(ExtractFunctionTest, OfferDespitePureForwarding_ReturnStmtComputedArg) {
1449 Context = File;
1450 // Same as the non-return case: a computed argument is real abstraction.
1451 EXPECT_THAT(
1452 apply("int foo(int); int f(int x, int y) { [[return foo(x + y);]] }"),
1453 HasSubstr("extracted"));
1454}
1455
1456TEST_F(ExtractFunctionTest, RejectBareReturnStmt) {
1457 Context = File;
1458 // A value-less return has nothing to abstract over at all: extracting
1459 // it would just wrap it in a function that does the exact same thing.
1460 EXPECT_EQ(apply("void f() { [[return;]] }"), "unavailable");
1461}
1462
1463TEST_F(ExtractFunctionTest, OfferDespitePureForwarding_PointerToMemberCallee) {
1464 Context = File;
1465 // Unlike an ordinary method call, a pointer-to-member call has no
1466 // statically known method (getMethodDecl() is null): which member is
1467 // invoked is itself computed here (table[I]), not inherent to what the
1468 // wrapper does, so it must be checked like any other operand.
1469 EXPECT_THAT(apply(R"cpp(
1470 struct St {};
1471 void f(St S, void (St::*table[2])(int), int I, int A) {
1472 [[(S.*table[I])(A);]]
1473 })cpp"),
1474 HasSubstr("extracted"));
1475}
1476
1477TEST_F(ExtractFunctionTest, RejectPureForwardingImplicitThis) {
1478 Context = File;
1479 // An implicit `this` receiver always refers to the same object -- the
1480 // extracted function becomes a sibling member itself, so `this` stays
1481 // implicit there too -- same as an ordinary function name, it must not
1482 // disqualify suppression just for not being a "bare" reference.
1483 EXPECT_EQ(apply(R"cpp(
1484 struct S {
1485 void foo(int);
1486 void bar(int A) {
1487 [[foo(A);]]
1488 }
1489 };)cpp"),
1490 "unavailable");
1491}
1492
1493TEST_F(ExtractFunctionTest, RejectPureForwardingDefaultArg) {
1494 Context = File;
1495 // An argument filled in from the callee's own default (as opposed to
1496 // one actually written at this call site) isn't something to hardcode
1497 // a judgement about: it must not disqualify suppression.
1498 EXPECT_EQ(apply(R"cpp(
1499 void foo(int A, int B = 0);
1500 void bar(int A) {
1501 [[foo(A);]]
1502 })cpp"),
1503 "unavailable");
1504}
1505
1506TEST_F(ExtractFunctionTest, RejectPureForwardingParenthesizedReturn) {
1507 Context = File;
1508 // Parentheses around the returned call must be looked through, same as
1509 // an implicit conversion.
1510 EXPECT_EQ(apply(R"cpp(
1511 int foo(int);
1512 int bar(int A) {
1513 [[return (foo(A));]]
1514 })cpp"),
1515 "unavailable");
1516}
1517
1518TEST_F(ExtractFunctionTest, OfferDespitePureForwarding_RepeatedArg) {
1519 Context = File;
1520 // Using the same captured local for more than one argument constrains a
1521 // relationship (both slots must hold the same value) that a plain
1522 // rename wouldn't: the same kind of real specialization as hardcoding
1523 // a literal, even though each individual operand is bare.
1524 EXPECT_THAT(apply(R"cpp(
1525 void foo(int A, int B);
1526 void bar(int A, int B) {
1527 [[foo(A, A);]]
1528 })cpp"),
1529 HasSubstr("extracted"));
1530}
1531
1532TEST_F(ExtractFunctionTest, OfferDespitePureForwarding_ReorderedArgs) {
1533 Context = File;
1534 // createParameters orders the extracted function's parameters by each
1535 // Decl's declaration order in bar (A, then B), not by how they're used
1536 // here -- so despite every argument being an independently bare,
1537 // distinct local, the extracted function ends up permuting its own
1538 // parameters internally (extracted(int A, int B) { foo(B, A); }): not
1539 // a transparent pass-through, since its signature alone no longer
1540 // predicts its behavior the way a plain rename's would.
1541 EXPECT_THAT(apply(R"cpp(
1542 void foo(int A, int B);
1543 void bar(int A, int B) {
1544 [[foo(B, A);]]
1545 })cpp"),
1546 HasSubstr("extracted"));
1547}
1548
1549TEST_F(ExtractFunctionTest, RejectPureForwardingPreservedArgOrder) {
1550 Context = File;
1551 // Same distinct locals as the reordered case above, but passed in the
1552 // same order they're declared in bar: no permutation is hidden in the
1553 // extracted function, so this is still just a rename.
1554 EXPECT_EQ(apply(R"cpp(
1555 void foo(int A, int B);
1556 void bar(int A, int B) {
1557 [[foo(A, B);]]
1558 })cpp"),
1559 "unavailable");
1560}
1561
1562TEST_F(ExtractFunctionTest, RejectPureForwardingFunctionScopeStatic) {
1563 Context = File;
1564 // A function-scope static variable's name, unlike a namespace-scope
1565 // global's, isn't visible wherever the extracted function ends up: it
1566 // does need forwarding, same as an ordinary local variable, so this is
1567 // still just a rename.
1568 EXPECT_EQ(apply(R"cpp(
1569 void foo(int);
1570 void bar() {
1571 static int A;
1572 [[foo(A);]]
1573 })cpp"),
1574 "unavailable");
1575}
1576
1577TEST_F(ExtractFunctionTest, RejectPureForwardingFunctionScopeThreadLocal) {
1578 Context = File;
1579 // Same as the static case: thread_local doesn't change the variable's
1580 // name visibility, only its storage.
1581 EXPECT_EQ(apply(R"cpp(
1582 void foo(int);
1583 void bar() {
1584 thread_local int A;
1585 [[foo(A);]]
1586 })cpp"),
1587 "unavailable");
1588}
1589
1590TEST_F(ExtractFunctionTest, OfferDespitePureForwarding_NamespaceScopeStatic) {
1591 Context = File;
1592 // Unlike a function-scope static, a namespace-scope one (internal
1593 // linkage notwithstanding) is visible wherever the extracted function
1594 // ends up, same as an ordinary global: hardcoding it is a real
1595 // specialization.
1596 EXPECT_THAT(apply(R"cpp(
1597 static int A;
1598 void foo(int);
1599 void bar() { [[foo(A);]] }
1600 )cpp"),
1601 HasSubstr("extracted"));
1602}
1603
1604TEST_F(ExtractFunctionTest, RejectPureForwardingStructuredBinding) {
1605 Context = File;
1606 // A structured-binding name is declared by a BindingDecl, not a
1607 // VarDecl, but is just as function-local and needs forwarding the
1608 // same way.
1609 ExtraArgs.push_back("-std=c++17");
1610 EXPECT_EQ(apply(R"cpp(
1611 void foo(int);
1612 struct Pair { int A, B; };
1613 Pair make();
1614 void bar() {
1615 auto [A, B] = make();
1616 [[foo(A);]]
1617 })cpp"),
1618 "unavailable");
1619}
1620
1621} // namespace
1622} // namespace clangd
1623} // namespace clang
#define TWEAK_TEST(TweakID)
A context is an immutable container for per-request data that must be propagated through layers that ...
Definition Context.h:69
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//