1
+ use std:: ops:: ControlFlow ;
2
+
1
3
use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
4
use clippy_utils:: macros:: HirNode ;
3
- use clippy_utils:: source:: { indent_of, snippet, snippet_block_with_context, snippet_with_context} ;
5
+ use clippy_utils:: source:: {
6
+ RelativeIndent , indent_of, reindent_multiline_relative, snippet, snippet_block_with_context, snippet_with_context,
7
+ } ;
4
8
use clippy_utils:: { is_refutable, peel_blocks} ;
9
+ use rustc_data_structures:: fx:: FxHashSet ;
5
10
use rustc_errors:: Applicability ;
6
- use rustc_hir:: { Arm , Expr , ExprKind , Node , PatKind , StmtKind } ;
11
+ use rustc_hir:: def:: Res ;
12
+ use rustc_hir:: intravisit:: { Visitor , walk_block, walk_expr, walk_path, walk_stmt} ;
13
+ use rustc_hir:: { Arm , Block , Expr , ExprKind , HirId , Node , PatKind , Path , Stmt , StmtKind } ;
7
14
use rustc_lint:: LateContext ;
8
- use rustc_span:: Span ;
15
+ use rustc_span:: { Span , Symbol } ;
9
16
10
17
use super :: MATCH_SINGLE_BINDING ;
11
18
@@ -50,10 +57,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
50
57
cx,
51
58
( ex, expr) ,
52
59
( bind_names, matched_vars) ,
53
- & snippet_body,
60
+ snippet_body,
54
61
& mut app,
55
62
Some ( span) ,
56
63
true ,
64
+ is_var_binding_used_later ( cx, expr, & arms[ 0 ] ) ,
57
65
) ;
58
66
59
67
span_lint_and_sugg (
@@ -83,10 +91,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
83
91
cx,
84
92
( ex, expr) ,
85
93
( bind_names, matched_vars) ,
86
- & snippet_body,
94
+ snippet_body,
87
95
& mut app,
88
96
None ,
89
97
true ,
98
+ is_var_binding_used_later ( cx, expr, & arms[ 0 ] ) ,
90
99
) ;
91
100
( expr. span , sugg)
92
101
} ,
@@ -108,10 +117,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
108
117
cx,
109
118
( ex, expr) ,
110
119
( bind_names, matched_vars) ,
111
- & snippet_body,
120
+ snippet_body,
112
121
& mut app,
113
122
None ,
114
123
false ,
124
+ true ,
115
125
) ;
116
126
117
127
span_lint_and_sugg (
@@ -139,6 +149,125 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
139
149
}
140
150
}
141
151
152
+ struct VarBindingVisitor < ' a , ' tcx > {
153
+ cx : & ' a LateContext < ' tcx > ,
154
+ identifiers : FxHashSet < Symbol > ,
155
+ }
156
+
157
+ impl < ' tcx > Visitor < ' tcx > for VarBindingVisitor < ' _ , ' tcx > {
158
+ type Result = ControlFlow < ( ) > ;
159
+
160
+ fn visit_path ( & mut self , path : & Path < ' tcx > , _: HirId ) -> Self :: Result {
161
+ if let Res :: Local ( _) = path. res
162
+ && let [ segment] = path. segments
163
+ && self . identifiers . contains ( & segment. ident . name )
164
+ {
165
+ return ControlFlow :: Break ( ( ) ) ;
166
+ }
167
+
168
+ walk_path ( self , path)
169
+ }
170
+
171
+ fn visit_block ( & mut self , block : & ' tcx Block < ' tcx > ) -> Self :: Result {
172
+ let before = self . identifiers . clone ( ) ;
173
+ walk_block ( self , block) ?;
174
+ self . identifiers = before;
175
+ ControlFlow :: Continue ( ( ) )
176
+ }
177
+
178
+ fn visit_stmt ( & mut self , stmt : & ' tcx Stmt < ' tcx > ) -> Self :: Result {
179
+ if let StmtKind :: Let ( let_stmt) = stmt. kind {
180
+ if let Some ( init) = let_stmt. init {
181
+ self . visit_expr ( init) ?;
182
+ }
183
+
184
+ let_stmt. pat . each_binding ( |_, _, _, ident| {
185
+ self . identifiers . remove ( & ident. name ) ;
186
+ } ) ;
187
+ }
188
+ walk_stmt ( self , stmt)
189
+ }
190
+
191
+ fn visit_expr ( & mut self , expr : & ' tcx Expr < ' tcx > ) -> Self :: Result {
192
+ match expr. kind {
193
+ ExprKind :: If (
194
+ Expr {
195
+ kind : ExprKind :: Let ( let_expr) ,
196
+ ..
197
+ } ,
198
+ then,
199
+ else_,
200
+ ) => {
201
+ self . visit_expr ( let_expr. init ) ?;
202
+ let before = self . identifiers . clone ( ) ;
203
+ let_expr. pat . each_binding ( |_, _, _, ident| {
204
+ self . identifiers . remove ( & ident. name ) ;
205
+ } ) ;
206
+
207
+ self . visit_expr ( then) ?;
208
+ self . identifiers = before;
209
+ if let Some ( else_) = else_ {
210
+ self . visit_expr ( else_) ?;
211
+ }
212
+ ControlFlow :: Continue ( ( ) )
213
+ } ,
214
+ ExprKind :: Closure ( closure) => {
215
+ let body = self . cx . tcx . hir_body ( closure. body ) ;
216
+ let before = self . identifiers . clone ( ) ;
217
+ for param in body. params {
218
+ param. pat . each_binding ( |_, _, _, ident| {
219
+ self . identifiers . remove ( & ident. name ) ;
220
+ } ) ;
221
+ }
222
+ self . visit_expr ( & body. value ) ?;
223
+ self . identifiers = before;
224
+ ControlFlow :: Continue ( ( ) )
225
+ } ,
226
+ ExprKind :: Match ( expr, arms, _) => {
227
+ self . visit_expr ( expr) ?;
228
+ for arm in arms {
229
+ let before = self . identifiers . clone ( ) ;
230
+ arm. pat . each_binding ( |_, _, _, ident| {
231
+ self . identifiers . remove ( & ident. name ) ;
232
+ } ) ;
233
+ if let Some ( guard) = arm. guard {
234
+ self . visit_expr ( guard) ?;
235
+ }
236
+ self . visit_expr ( arm. body ) ?;
237
+ self . identifiers = before;
238
+ }
239
+ ControlFlow :: Continue ( ( ) )
240
+ } ,
241
+ _ => walk_expr ( self , expr) ,
242
+ }
243
+ }
244
+ }
245
+
246
+ fn is_var_binding_used_later ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , arm : & Arm < ' _ > ) -> bool {
247
+ let Node :: Stmt ( stmt) = cx. tcx . parent_hir_node ( expr. hir_id ) else {
248
+ return false ;
249
+ } ;
250
+ let Node :: Block ( block) = cx. tcx . parent_hir_node ( stmt. hir_id ) else {
251
+ return false ;
252
+ } ;
253
+
254
+ let mut identifiers = FxHashSet :: default ( ) ;
255
+ arm. pat . each_binding ( |_, _, _, ident| {
256
+ identifiers. insert ( ident. name ) ;
257
+ } ) ;
258
+
259
+ let mut visitor = VarBindingVisitor { cx, identifiers } ;
260
+ block
261
+ . stmts
262
+ . iter ( )
263
+ . skip_while ( |s| s. hir_id != stmt. hir_id )
264
+ . skip ( 1 )
265
+ . any ( |stmt| matches ! ( visitor. visit_stmt( stmt) , ControlFlow :: Break ( ( ) ) ) )
266
+ || block
267
+ . expr
268
+ . is_some_and ( |expr| matches ! ( visitor. visit_expr( expr) , ControlFlow :: Break ( ( ) ) ) )
269
+ }
270
+
142
271
/// Returns true if the `ex` match expression is in a local (`let`) or assign expression
143
272
fn opt_parent_assign_span < ' a > ( cx : & LateContext < ' a > , ex : & Expr < ' a > ) -> Option < AssignmentExpr > {
144
273
if let Node :: Expr ( parent_arm_expr) = cx. tcx . parent_hir_node ( ex. hir_id ) {
@@ -161,47 +290,57 @@ fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<Ass
161
290
None
162
291
}
163
292
164
- fn expr_parent_requires_curlies < ' a > ( cx : & LateContext < ' a > , match_expr : & Expr < ' a > ) -> bool {
293
+ fn expr_in_nested_block ( cx : & LateContext < ' _ > , match_expr : & Expr < ' _ > ) -> bool {
294
+ if let Node :: Block ( block) = cx. tcx . parent_hir_node ( match_expr. hir_id ) {
295
+ return block
296
+ . expr
297
+ . map_or_else ( || matches ! ( block. stmts, [ _] ) , |_| block. stmts . is_empty ( ) ) ;
298
+ }
299
+ false
300
+ }
301
+
302
+ fn expr_must_have_curlies ( cx : & LateContext < ' _ > , match_expr : & Expr < ' _ > ) -> bool {
165
303
let parent = cx. tcx . parent_hir_node ( match_expr. hir_id ) ;
166
- matches ! (
167
- parent,
168
- Node :: Expr ( Expr {
169
- kind: ExprKind :: Closure { .. } ,
170
- ..
171
- } ) | Node :: AnonConst ( ..)
304
+ if let Node :: Expr ( Expr {
305
+ kind : ExprKind :: Closure { .. } ,
306
+ ..
307
+ } )
308
+ | Node :: AnonConst ( ..) = parent
309
+ {
310
+ return true ;
311
+ }
312
+
313
+ if let Node :: Arm ( arm) = & cx. tcx . parent_hir_node ( match_expr. hir_id )
314
+ && let ExprKind :: Match ( ..) = arm. body . kind
315
+ {
316
+ return true ;
317
+ }
318
+
319
+ false
320
+ }
321
+
322
+ fn reindent_snippet_if_in_block ( snippet_body : & str , has_assignment : bool ) -> String {
323
+ if has_assignment || !snippet_body. starts_with ( '{' ) {
324
+ return reindent_multiline_relative ( snippet_body, true , RelativeIndent :: Add ( 0 ) ) ;
325
+ }
326
+
327
+ reindent_multiline_relative (
328
+ snippet_body. trim_start_matches ( '{' ) . trim_end_matches ( '}' ) . trim ( ) ,
329
+ false ,
330
+ RelativeIndent :: Sub ( 4 ) ,
172
331
)
173
332
}
174
333
175
334
fn sugg_with_curlies < ' a > (
176
335
cx : & LateContext < ' a > ,
177
336
( ex, match_expr) : ( & Expr < ' a > , & Expr < ' a > ) ,
178
337
( bind_names, matched_vars) : ( Span , Span ) ,
179
- snippet_body : & str ,
338
+ mut snippet_body : String ,
180
339
applicability : & mut Applicability ,
181
340
assignment : Option < Span > ,
182
341
needs_var_binding : bool ,
342
+ is_var_binding_used_later : bool ,
183
343
) -> String {
184
- let mut indent = " " . repeat ( indent_of ( cx, ex. span ) . unwrap_or ( 0 ) ) ;
185
-
186
- let ( mut cbrace_start, mut cbrace_end) = ( String :: new ( ) , String :: new ( ) ) ;
187
- if expr_parent_requires_curlies ( cx, match_expr) {
188
- cbrace_end = format ! ( "\n {indent}}}" ) ;
189
- // Fix body indent due to the closure
190
- indent = " " . repeat ( indent_of ( cx, bind_names) . unwrap_or ( 0 ) ) ;
191
- cbrace_start = format ! ( "{{\n {indent}" ) ;
192
- }
193
-
194
- // If the parent is already an arm, and the body is another match statement,
195
- // we need curly braces around suggestion
196
- if let Node :: Arm ( arm) = & cx. tcx . parent_hir_node ( match_expr. hir_id )
197
- && let ExprKind :: Match ( ..) = arm. body . kind
198
- {
199
- cbrace_end = format ! ( "\n {indent}}}" ) ;
200
- // Fix body indent due to the match
201
- indent = " " . repeat ( indent_of ( cx, bind_names) . unwrap_or ( 0 ) ) ;
202
- cbrace_start = format ! ( "{{\n {indent}" ) ;
203
- }
204
-
205
344
let assignment_str = assignment. map_or_else ( String :: new, |span| {
206
345
let mut s = snippet ( cx, span, ".." ) . to_string ( ) ;
207
346
s. push_str ( " = " ) ;
@@ -221,5 +360,17 @@ fn sugg_with_curlies<'a>(
221
360
. to_string ( )
222
361
} ;
223
362
363
+ let mut indent = " " . repeat ( indent_of ( cx, ex. span ) . unwrap_or ( 0 ) ) ;
364
+ let ( mut cbrace_start, mut cbrace_end) = ( String :: new ( ) , String :: new ( ) ) ;
365
+ if !expr_in_nested_block ( cx, match_expr)
366
+ && ( ( needs_var_binding && is_var_binding_used_later) || expr_must_have_curlies ( cx, match_expr) )
367
+ {
368
+ cbrace_end = format ! ( "\n {indent}}}" ) ;
369
+ // Fix body indent due to the closure
370
+ indent = " " . repeat ( indent_of ( cx, bind_names) . unwrap_or ( 0 ) ) ;
371
+ cbrace_start = format ! ( "{{\n {indent}" ) ;
372
+ snippet_body = reindent_snippet_if_in_block ( & snippet_body, !assignment_str. is_empty ( ) ) ;
373
+ }
374
+
224
375
format ! ( "{cbrace_start}{scrutinee};\n {indent}{assignment_str}{snippet_body}{cbrace_end}" )
225
376
}
0 commit comments