@@ -20,30 +20,47 @@ AST_MATCHER(LambdaExpr, hasDefaultCapture) {
20
20
return Node.getCaptureDefault () != LCD_None;
21
21
}
22
22
23
- std::string getExplicitCaptureText (const LambdaCapture &Capture,
23
+ // Find the source range of the default capture token (= or &)
24
+ SourceRange getDefaultCaptureRange (const LambdaExpr *Lambda,
24
25
const SourceManager &SM,
25
26
const LangOptions &LangOpts) {
26
- if (!Capture.isExplicit () || !Capture.getLocation ().isValid ()) {
27
- return " " ;
28
- }
27
+ SourceLocation DefaultLoc = Lambda->getCaptureDefaultLoc ();
28
+ if (DefaultLoc.isInvalid ())
29
+ return SourceRange ();
30
+
31
+ // The default capture is a single token
32
+ SourceLocation EndLoc =
33
+ Lexer::getLocForEndOfToken (DefaultLoc, 0 , SM, LangOpts);
34
+ return SourceRange (DefaultLoc, EndLoc);
35
+ }
29
36
30
- // For explicit captures, extract the actual source text to preserve
31
- // original formatting, spacing, and comments
32
- SourceLocation CaptureBegin = Capture.getLocation ();
37
+ // Find where to insert implicit captures
38
+ SourceLocation getImplicitCaptureInsertionLoc (const LambdaExpr *Lambda,
39
+ const SourceManager &SM,
40
+ const LangOptions &LangOpts) {
41
+ // If there are explicit captures, insert after the last one
42
+ if (Lambda->explicit_capture_begin () != Lambda->explicit_capture_end ()) {
43
+ // Find the location after the last explicit capture
44
+ const auto *LastExplicit = Lambda->explicit_capture_end () - 1 ;
45
+ SourceLocation LastLoc = LastExplicit->getLocation ();
46
+ if (LastLoc.isValid ()) {
47
+ return Lexer::getLocForEndOfToken (LastLoc, 0 , SM, LangOpts);
48
+ }
49
+ }
33
50
34
- // Find the end of this capture by looking for the next comma or closing
35
- // bracket This is a simplified approach - a more robust implementation would
36
- // parse tokens
37
- SourceLocation CaptureEnd =
38
- Lexer::getLocForEndOfToken (CaptureBegin, 0 , SM, LangOpts);
51
+ // If no explicit captures, insert after the default capture
52
+ SourceLocation DefaultLoc = Lambda-> getCaptureDefaultLoc ();
53
+ if (DefaultLoc. isValid ()) {
54
+ return Lexer::getLocForEndOfToken (DefaultLoc, 0 , SM, LangOpts);
55
+ }
39
56
40
- // For now, we'll still reconstruct to ensure correctness
41
- // but this framework allows for future enhancement to preserve exact source
42
- // text
43
- return " " ;
57
+ // Fallback: insert at the beginning of the capture list
58
+ return Lambda->getIntroducerRange ().getBegin ().getLocWithOffset (1 );
44
59
}
45
60
46
- std::string getCaptureString (const LambdaCapture &Capture) {
61
+ // Generate the text for an implicit capture
62
+ std::optional<std::string>
63
+ generateImplicitCaptureText (const LambdaCapture &Capture) {
47
64
if (Capture.capturesThis ()) {
48
65
return Capture.getCaptureKind () == LCK_StarThis ? " *this" : " this" ;
49
66
}
@@ -57,38 +74,15 @@ std::string getCaptureString(const LambdaCapture &Capture) {
57
74
return Result;
58
75
}
59
76
60
- // Handle VLA captures - these are rare but possible
61
- return " /* VLA capture */" ;
62
- }
63
-
64
- std::string buildExplicitCaptureList (const LambdaExpr *Lambda,
65
- const SourceManager &SM,
66
- const LangOptions &LangOpts) {
67
- std::vector<std::string> CaptureStrings;
68
-
69
- // Add explicit captures first (preserve their order and syntax)
70
- for (const auto &Capture : Lambda->explicit_captures ()) {
71
- // Try to get the original source text first
72
- std::string ExplicitText = getExplicitCaptureText (Capture, SM, LangOpts);
73
- if (!ExplicitText.empty ()) {
74
- CaptureStrings.push_back (ExplicitText);
75
- } else {
76
- // Fall back to reconstructed text
77
- CaptureStrings.push_back (getCaptureString (Capture));
78
- }
79
- }
80
-
81
- // Add implicit captures (convert to explicit syntax)
82
- for (const auto &Capture : Lambda->implicit_captures ()) {
83
- CaptureStrings.push_back (getCaptureString (Capture));
84
- }
85
-
86
- return " [" + llvm::join (CaptureStrings, " , " ) + " ]" ;
77
+ // TODO: handle VLAs and other weird captures
78
+ return std::nullopt ;
87
79
}
88
80
89
- SourceRange getCaptureListRange (const LambdaExpr *Lambda) {
90
- SourceRange IntroducerRange = Lambda->getIntroducerRange ();
91
- return IntroducerRange;
81
+ // Check if we need a comma before inserting captures
82
+ bool needsCommaBefore (const LambdaExpr *Lambda, SourceLocation InsertLoc,
83
+ const SourceManager &SM, const LangOptions &LangOpts) {
84
+ // If there are explicit captures, we need a comma
85
+ return Lambda->explicit_capture_begin () != Lambda->explicit_capture_end ();
92
86
}
93
87
94
88
} // namespace
@@ -102,24 +96,72 @@ void AvoidDefaultLambdaCaptureCheck::check(
102
96
const auto *Lambda = Result.Nodes .getNodeAs <LambdaExpr>(" lambda" );
103
97
assert (Lambda);
104
98
99
+ const SourceManager &SM = *Result.SourceManager ;
100
+ const LangOptions &LangOpts = Result.Context ->getLangOpts ();
101
+
105
102
const SourceLocation DefaultCaptureLoc = Lambda->getCaptureDefaultLoc ();
106
103
if (DefaultCaptureLoc.isInvalid ())
107
104
return ;
108
105
109
- // Build the replacement capture list
110
- std::string NewCaptureList = buildExplicitCaptureList (
111
- Lambda, *Result.SourceManager , Result.Context ->getLangOpts ());
112
-
113
- // Get the range of the entire capture list [...]
114
- SourceRange CaptureListRange = getCaptureListRange (Lambda);
115
-
116
106
auto Diag = diag (DefaultCaptureLoc,
117
107
" lambda default captures are discouraged; "
118
108
" prefer to capture specific variables explicitly" );
119
109
120
- // Only provide fixit if we can determine a valid replacement
121
- if (CaptureListRange.isValid () && !NewCaptureList.empty ()) {
122
- Diag << FixItHint::CreateReplacement (CaptureListRange, NewCaptureList);
110
+ // Get the range of the default capture token to remove
111
+ SourceRange DefaultRange = getDefaultCaptureRange (Lambda, SM, LangOpts);
112
+ if (!DefaultRange.isValid ())
113
+ return ;
114
+
115
+ // Collect all implicit captures that need to be made explicit
116
+ std::vector<std::string> ImplicitCaptureTexts;
117
+ for (const auto &Capture : Lambda->implicit_captures ()) {
118
+ if (const auto CaptureText = generateImplicitCaptureText (Capture)) {
119
+ ImplicitCaptureTexts.push_back (CaptureText.value ());
120
+ }
121
+ }
122
+
123
+ // If there are no implicit captures, just remove the default capture
124
+ if (ImplicitCaptureTexts.empty ()) {
125
+ // Also remove any trailing comma if it exists
126
+ SourceLocation AfterDefault = DefaultRange.getEnd ();
127
+ SourceLocation CommaLoc = Lexer::findLocationAfterToken (
128
+ AfterDefault, tok::comma, SM, LangOpts, false );
129
+
130
+ if (CommaLoc.isValid ()) {
131
+ // Remove default capture and the comma
132
+ SourceRange RemovalRange (DefaultRange.getBegin (), CommaLoc);
133
+ Diag << FixItHint::CreateRemoval (RemovalRange);
134
+ } else {
135
+ // Just remove the default capture
136
+ Diag << FixItHint::CreateRemoval (DefaultRange);
137
+ }
138
+ return ;
139
+ }
140
+
141
+ // Find where to insert the implicit captures
142
+ SourceLocation InsertLoc =
143
+ getImplicitCaptureInsertionLoc (Lambda, SM, LangOpts);
144
+ if (!InsertLoc.isValid ())
145
+ return ;
146
+
147
+ // Apply the transformations:
148
+ // 1. Remove the default capture
149
+ Diag << FixItHint::CreateRemoval (DefaultRange);
150
+
151
+ // 2. Insert the explicit captures if any
152
+ if (!ImplicitCaptureTexts.empty ()) {
153
+ // Build the insertion text for implicit captures
154
+ std::string InsertionText;
155
+ bool NeedsComma = needsCommaBefore (Lambda, InsertLoc, SM, LangOpts);
156
+
157
+ for (size_t I = 0 ; I < ImplicitCaptureTexts.size (); ++I) {
158
+ if (NeedsComma || I > 0 ) {
159
+ InsertionText += " , " ;
160
+ }
161
+ InsertionText += ImplicitCaptureTexts[I];
162
+ }
163
+
164
+ Diag << FixItHint::CreateInsertion (InsertLoc, InsertionText);
123
165
}
124
166
}
125
167
0 commit comments