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);]] } }
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).
786 EXPECT_THAT(apply(R"cpp(
787 void use(int);
788 void f(int x) { [[use(x);]] }
789 )cpp"),
790 HasSubstr("void extracted(int x)"));
791 // Direct assignment: stays non-const.
792 EXPECT_THAT(apply("void f(int x) { [[x = 1;]] }"),
793 HasSubstr("void extracted(int &x)"));
794 // Compound assignment: stays non-const.
795 EXPECT_THAT(apply("void f(int x) { [[x += 1;]] }"),
796 HasSubstr("void extracted(int &x)"));
797 // Increment/decrement: stays non-const.
798 EXPECT_THAT(apply("void f(int x) { [[++x;]] }"),
799 HasSubstr("void extracted(int &x)"));
800 // A non-const method call may mutate the object: stays non-const.
801 EXPECT_THAT(apply(R"cpp(
802 struct S { void mutate(); };
803 void f(S s) { [[s.mutate();]] }
804 )cpp"),
805 HasSubstr("void extracted(S &s)"));
806 // A const method call cannot mutate the object: becomes const.
807 EXPECT_THAT(apply(R"cpp(
808 struct S { void inspect() const; };
809 void f(S s) { [[s.inspect();]] }
810 )cpp"),
811 HasSubstr("void extracted(const S &s)"));
812 // Passed to a parameter taking a non-const reference: stays non-const,
813 // since the callee could mutate it through that reference.
814 EXPECT_THAT(apply(R"cpp(
815 void mayMutate(int &);
816 void f(int x) { [[mayMutate(x);]] }
817 )cpp"),
818 HasSubstr("void extracted(int &x)"));
819 // Passed to a parameter taking a const reference or by value: becomes
820 // an unmutated scalar, so by value.
821 EXPECT_THAT(apply(R"cpp(
822 void readOnly(const int &);
823 void f(int x) { [[readOnly(x);]] }
824 )cpp"),
825 HasSubstr("void extracted(int x)"));
826 EXPECT_THAT(apply(R"cpp(
827 void byValue(int);
828 void f(int x) { [[byValue(x);]] }
829 )cpp"),
830 HasSubstr("void extracted(int x)"));
831 // A scalar parameter that's already declared const keeps that
832 // qualifier when passed by value: It might be relevant for overload
833 // resolution.
834 EXPECT_THAT(apply("void use(int); void f(const int x) { [[use(x);]] }"),
835 HasSubstr("void extracted(const int x)"));
836 // A non-scalar parameter that's already declared const keeps that
837 // qualifier, since it's still passed by reference.
838 EXPECT_THAT(apply(R"cpp(
839 struct S {};
840 void use(const S &);
841 void f(const S s) { [[use(s);]] }
842 )cpp"),
843 HasSubstr("void extracted(const S &s)"));
844}
845
846TEST_F(ExtractFunctionTest, ConstParametersReferenceAliasing) {
847 Context = File;
848 // A non-const reference bound to a captured variable conservatively
849 // mutates that variable too, without checking whether the reference
850 // itself is ever actually mutated -- even when the binding and a later
851 // mutation of the reference are two separate root statements of the same
852 // zone. Failing to notice this would incorrectly mark `x` const, which
853 // wouldn't compile (`int &r` can't bind to a `const int`).
854 EXPECT_THAT(apply(R"cpp(
855 void f(int x) {
856 [[int &r = x;
857 r = 2;]]
858 }
859 )cpp"),
860 HasSubstr("void extracted(int &x)"));
861}
862
863TEST_F(ExtractFunctionTest, ConstParametersConservativeAliasing) {
864 Context = File;
865 // Taking the address of a captured variable conservatively mutates it,
866 // regardless of what's later done with the pointer.
867 EXPECT_THAT(apply("void f(int x) { [[int *p = &x;]] }"),
868 HasSubstr("void extracted(int &x)"));
869 // Explicit cast to a non-const reference type: stays non-const.
870 EXPECT_THAT(apply("void f(int x) { [[static_cast<int &>(x) = 1;]] }"),
871 HasSubstr("void extracted(int &x)"));
872 // Captured by reference in a lambda: stays non-const, without checking
873 // whether the lambda actually mutates it.
874 EXPECT_THAT(apply("void f(int x) { [[auto l = [&x]() { int y = x; };]] }"),
875 HasSubstr("int &x"));
876 // Captured by value in a lambda: doesn't alias x, so it's unmutated and
877 // (being a scalar) passed by value.
878 EXPECT_THAT(apply("void f(int x) { [[auto l = [x]() { int y = x; };]] }"),
879 HasSubstr("void extracted(int x)"));
880 // Returning a captured variable is conservatively treated as a possible
881 // mutation, regardless of whether the return is actually by value (safe)
882 // or by non-const reference (not safe) -- telling these apart isn't
883 // worth the complexity here.
884 EXPECT_THAT(apply("int f(int x) { [[return x;]] }"), HasSubstr("&x"));
885 // Array-typed captures are never made const.
886 EXPECT_THAT(apply("void f() { int arr[5]; [[arr[0] = 1;]] }"),
887 Not(HasSubstr("const")));
888}
889
890TEST_F(ExtractFunctionTest, ConstParametersMemberCallArguments) {
891 Context = File;
892 // A non-const-ref argument to a member call (as opposed to the implicit
893 // object, already covered by ConstParameters) stays non-const. The
894 // method itself is const so it doesn't also mark `s` mutated, isolating
895 // the argument check from the object check.
896 EXPECT_THAT(apply(R"cpp(
897 struct S { void mayMutate(int &) const; };
898 void f(S s, int x) { [[s.mayMutate(x);]] }
899 )cpp"),
900 HasSubstr("extracted(const S &s, int &x)"));
901 // Same, but through an overloaded operator: the implicit object is
902 // args[0], so the real parameter (checked against args[1:]) must be
903 // found at the right offset.
904 EXPECT_THAT(apply(R"cpp(
905 struct S { void operator()(int &) const; };
906 void f(S s, int x) { [[s(x);]] }
907 )cpp"),
908 HasSubstr("extracted(const S &s, int &x)"));
909 // A free (non-member) operator overload has no implicit object, so all
910 // operands align directly with the callee's parameters.
911 EXPECT_THAT(apply(R"cpp(
912 struct S {};
913 void operator+(const S &, int &);
914 void f(S s, int x) { [[s + x;]] }
915 )cpp"),
916 HasSubstr("extracted(const S &s, int &x)"));
917}
918
919TEST_F(ExtractFunctionTest, ConstParametersPointerIndirection) {
920 Context = File;
921 // Mutating a member/element through a pointer only mutates the pointee,
922 // never the pointer's own binding, so the pointer itself is unmutated --
923 // unlike the same access through a value or reference (already covered
924 // by ConstParameters' `ptr` case, which is mutated directly instead).
925 // Being an unmutated scalar, it's then passed by value.
926 EXPECT_THAT(apply(R"cpp(
927 struct S { int x; };
928 void f(S *ptr) { [[ptr->x = 1;]] }
929 )cpp"),
930 HasSubstr("extracted(S * ptr)"));
931 EXPECT_THAT(apply("void f(int *p) { [[p[0] = 1;]] }"),
932 HasSubstr("extracted(int * p)"));
933 // Same for a plain dereference.
934 EXPECT_THAT(apply("void f(int *p) { [[*p = 1;]] }"),
935 HasSubstr("extracted(int * p)"));
936}
937
938TEST_F(ExtractFunctionTest, ConstParametersConditionalReferenceBinding) {
939 Context = File;
940 // A reference bound to a conditional expression could alias either
941 // branch at runtime, so both must be marked non-const -- getting only
942 // one (or neither) would let the other stay const while still being
943 // reachable through the reference, producing code that doesn't compile.
944 EXPECT_THAT(apply(R"cpp(
945 void f(bool cond, int c, int d) {
946 [[int &a = cond ? c : d;
947 a = 5;]]
948 }
949 )cpp"),
950 HasSubstr("extracted(bool cond, int &c, int &d)"));
951}
952
953TEST_F(ExtractFunctionTest, ConstParametersConditionalMutatingAccess) {
954 Context = File;
955 // Same as above, but no named LHS.
956 EXPECT_THAT(apply(R"cpp(
957 struct S { int n; };
958 void f(bool cond, S s1, S s2) {
959 [[(cond ? s1 : s2).n = 0;]]
960 }
961 )cpp"),
962 HasSubstr("extracted(bool cond, S &s1, S &s2)"));
963}
964
965TEST_F(ExtractFunctionTest, ConstParametersStaticOperatorCall) {
966 Context = File;
967 // A static operator() (or operator[], since C++23) has no implicit
968 // object at all, so calling it through `s(...)` syntax doesn't touch
969 // `s` regardless of the operator's own constness.
970 ExtraArgs.push_back("-std=c++23");
971 EXPECT_THAT(apply(R"cpp(
972 struct S { static void operator()(int); };
973 void f(S s, int x) { [[s(x);]] }
974 )cpp"),
975 HasSubstr("extracted(const S &s, int x)"));
976}
977
978TEST_F(ExtractFunctionTest, ConstParametersScalarsByValue) {
979 Context = File;
980 // An unmutated pointer is a scalar too: passed by value.
981 EXPECT_THAT(apply("void use(int *); void f(int *p) { [[use(p);]] }"),
982 HasSubstr("extracted(int * p)"));
983 // An unmutated enum: passed by value.
984 EXPECT_THAT(apply(R"cpp(
985 enum E { A, B };
986 void use(E);
987 void f(E e) { [[use(e);]] }
988 )cpp"),
989 HasSubstr("extracted(E e)"));
990 // A class type, even one that's small and trivially copyable, is never
991 // passed by value: that's deliberately out of scope for now.
992 EXPECT_THAT(apply(R"cpp(
993 struct Point { int x, y; };
994 void use(Point);
995 void f(Point p) { [[use(p);]] }
996 )cpp"),
997 HasSubstr("extracted(const Point &p)"));
998 // An unmutated array is not a scalar (even though its element type is):
999 // stays a non-const reference, per the existing array carve-out.
1000 EXPECT_THAT(apply("void f() { int arr[5]; [[int x = arr[0];]] }"),
1001 HasSubstr("extracted(int[5] &arr)"));
1002}
1003
1004// Variables of reference type, const or non-const, must stay references,
1005// otherwise they'd stop tracking their target.
1006TEST_F(ExtractFunctionTest, ReferenceToScalar) {
1007 Context = File;
1008 EXPECT_THAT(apply(R"cpp(
1009 void bar(int) {}
1010 void foo() {
1011 int A = 0;
1012 int &B = A;
1013 [[
1014 A = 1;
1015 bar(B);
1016 ]]
1017 })cpp"),
1018 HasSubstr("extracted(int &A, const int &B)"));
1019 EXPECT_THAT(apply(R"cpp(
1020 void bar(int) {}
1021 void foo() {
1022 int A = 0;
1023 const int &B = A;
1024 [[
1025 A = 1;
1026 bar(B);
1027 ]]
1028 })cpp"),
1029 HasSubstr("extracted(int &A, const int &B)"));
1030}
1031
1032TEST_F(ExtractFunctionTest, VolatileScalar) {
1033 Context = File;
1034 EXPECT_THAT(apply(R"cpp(
1035 void bar(const volatile int &, int) {}
1036 void foo() {
1037 volatile int V = 0;
1038 [[
1039 bar(V, 0);
1040 ]]
1041 })cpp"),
1042 HasSubstr("extracted(const volatile int &V)"));
1043}
1044
1045TEST_F(ExtractFunctionTest, CFileAllowUnmodifiedScalar) {
1046 FileName = "a.c";
1047 Context = File;
1048 EXPECT_THAT(apply(R"cpp(
1049 int i;
1050 void foo() {
1051 int j = 0;
1052 [[i = j;]]
1053 })cpp"),
1054 HasSubstr("extracted(int j)"));
1055}
1056
1057TEST_F(ExtractFunctionTest, CFileModifiedScalarBecomesPointer) {
1058 // C has no references: a mutated capture becomes a real pointer
1059 // parameter instead, with the call site taking its address and the
1060 // body dereferencing it.
1061 FileName = "a.c";
1062 Context = File;
1063 EXPECT_THAT(apply(R"cpp(
1064 void foo() {
1065 int j;
1066 [[j = 0;]]
1067 })cpp"),
1068 AllOf(HasSubstr("extracted(int * j)"), HasSubstr("(*j) = 0;"),
1069 HasSubstr("extracted(&j)")));
1070}
1071
1072TEST_F(ExtractFunctionTest, CFileUnmodifiedStructBecomesConstPointer) {
1073 // Same, but for an unmutated non-scalar capture: the parameter becomes
1074 // a pointer to const, and every member access on it is rewritten too.
1075 FileName = "a.c";
1076 Context = File;
1077 EXPECT_THAT(apply(R"cpp(
1078 struct pair { int v1; int v2; };
1079 int i;
1080 void foo() {
1081 struct pair p;
1082 p.v1 = 0;
1083 [[i = p.v1;]]
1084 })cpp"),
1085 AllOf(HasSubstr("extracted(const struct pair * p)"),
1086 HasSubstr("i = p->v1;"), HasSubstr("extracted(&p)")));
1087}
1088
1089TEST_F(ExtractFunctionTest, CFileStructMixedUses) {
1090 // The same capture can appear both as a member-access base (rewritten
1091 // to "->") and as a plain use (wrapped in "(*...)") within a single
1092 // extraction; each occurrence is rewritten independently.
1093 FileName = "a.c";
1094 Context = File;
1095 EXPECT_THAT(apply(R"cpp(
1096 struct pair { int v1; int v2; };
1097 void use(struct pair);
1098 int i;
1099 void foo() {
1100 struct pair p;
1101 [[use(p); i = p.v1;]]
1102 })cpp"),
1103 AllOf(HasSubstr("use((*p));"), HasSubstr("i = p->v1;")));
1104}
1105
1106TEST_F(ExtractFunctionTest, CFileRejectMacroDot) {
1107 // The identifier itself need not be a macro expansion for the
1108 // member-access ".", immediately following it, to be one -- that dot
1109 // is a separate token with its own location, which also needs
1110 // checking before relying on it to splice in "->".
1111 FileName = "a.c";
1112 Context = File;
1113 EXPECT_EQ(apply(R"cpp(
1114 #define DOT .
1115 struct pair { int v1; int v2; };
1116 void foo() {
1117 struct pair p;
1118 [[p DOT v1 = 1;]]
1119 })cpp"),
1120 "fail: Too complex to extract.");
1121}
1122
1123TEST_F(ExtractFunctionTest, CFileModifiedArrayStaysPlainPointer) {
1124 // Unlike other non-scalar types, an array decays to a pointer on its
1125 // own wherever it's used, so it needs neither an address-of at the
1126 // call site nor a dereference-rewrite of its uses in the body. The
1127 // parameter's own type must be decayed too, though: leaving it as an
1128 // array type would print as the uncompilable "int[5] arr" (there's no
1129 // special-cased array declarator syntax, unlike C++'s reference case).
1130 FileName = "a.c";
1131 Context = File;
1132 EXPECT_THAT(apply(R"cpp(
1133 void foo() {
1134 int arr[5];
1135 [[arr[0] = 1;]]
1136 })cpp"),
1137 AllOf(HasSubstr("extracted(int * arr)"), HasSubstr("arr[0] = 1;"),
1138 HasSubstr("extracted(arr)"), Not(HasSubstr("&arr"))));
1139}
1140
1141TEST_F(ExtractFunctionTest, CFileStaticFunctionStaysStatic) {
1142 // A free function's own `static` (internal linkage) must carry over to
1143 // an extracted sibling, or that sibling would default to external
1144 // linkage instead.
1145 FileName = "a.c";
1146 Context = File;
1147 EXPECT_THAT(apply(R"cpp(
1148 static void foo() {
1149 int j = 0;
1150 [[int k = j;]]
1151 })cpp"),
1152 HasSubstr("static void extracted"));
1153}
1154
1155TEST_F(ExtractFunctionTest, CFileStaticForwardDeclaredFunctionStaysStatic) {
1156 // Same as above, but the definition itself omits `static` (legal in C:
1157 // once a prior declaration gives the function internal linkage, a
1158 // later one doesn't need to repeat it, and still has it). Checking
1159 // only the current declaration's storage class would miss this.
1160 FileName = "a.c";
1161 Context = File;
1162 EXPECT_THAT(apply(R"cpp(
1163 static void foo();
1164 void foo() {
1165 int j = 0;
1166 [[int k = j;]]
1167 })cpp"),
1168 HasSubstr("static void extracted"));
1169}
1170
1171TEST_F(ExtractFunctionTest, CFileRejectArraySizeof) {
1172 // Decaying the array to a pointer parameter would silently change the
1173 // meaning of a `sizeof` on it (pointer size instead of array size), so
1174 // this is refused rather than risk miscompiling it.
1175 FileName = "a.c";
1176 Context = File;
1177 EXPECT_EQ(apply(R"cpp(
1178 void foo() {
1179 int arr[5];
1180 [[int n = sizeof(arr);]]
1181 })cpp"),
1182 "fail: Too complex to extract.");
1183}
1184
1185TEST_F(ExtractFunctionTest, CFileRejectArrayAlignof) {
1186 // Same hazard as sizeof, and the same UnaryExprOrTypeTraitExpr AST
1187 // node: alignof(int) and alignof(int *) aren't guaranteed to match
1188 // (and commonly don't, e.g. 4 vs 8 on a typical 64-bit target).
1189 FileName = "a.c";
1190 Context = File;
1191 EXPECT_EQ(apply(R"cpp(
1192 void foo() {
1193 int arr[5];
1194 [[int n = __alignof(arr);]]
1195 })cpp"),
1196 "fail: Too complex to extract.");
1197}
1198
1199TEST_F(ExtractFunctionTest, CFileRejectArrayTypeof) {
1200 // Same hazard again, but via a completely different AST node
1201 // (TypeOfExprType, reached through the VarDecl's TypeLoc, not through
1202 // any Stmt a plain expression visitor would see): typeof(arr) would
1203 // resolve to the decayed pointer type instead of the array type.
1204 FileName = "a.c";
1205 Context = File;
1206 EXPECT_EQ(apply(R"cpp(
1207 void foo() {
1208 int arr[5];
1209 [[__typeof__(arr) copy;]]
1210 })cpp"),
1211 "fail: Too complex to extract.");
1212}
1213
1214} // namespace
1215} // namespace clangd
1216} // 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++ -*-===//