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} ;
4
- use clippy_utils:: { is_refutable, peel_blocks} ;
5
+ use clippy_utils:: source:: { indent_of, reindent_multiline, snippet, snippet_block_with_context, snippet_with_context} ;
6
+ use clippy_utils:: { is_expr_identity_of_pat, is_refutable, peel_blocks} ;
7
+ use rustc_data_structures:: fx:: FxHashSet ;
5
8
use rustc_errors:: Applicability ;
6
- use rustc_hir:: { Arm , Expr , ExprKind , Node , PatKind , StmtKind } ;
9
+ use rustc_hir:: def:: Res ;
10
+ use rustc_hir:: intravisit:: { Visitor , walk_block, walk_expr, walk_path, walk_stmt} ;
11
+ use rustc_hir:: { Arm , Block , Expr , ExprKind , HirId , Node , PatKind , Path , Stmt , StmtKind } ;
7
12
use rustc_lint:: LateContext ;
8
- use rustc_span:: Span ;
13
+ use rustc_span:: { Span , Symbol } ;
9
14
10
15
use super :: MATCH_SINGLE_BINDING ;
11
16
@@ -50,10 +55,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
50
55
cx,
51
56
( ex, expr) ,
52
57
( bind_names, matched_vars) ,
53
- & snippet_body,
58
+ snippet_body,
54
59
& mut app,
55
60
Some ( span) ,
56
61
true ,
62
+ is_var_binding_used_later ( cx, expr, & arms[ 0 ] ) ,
57
63
) ;
58
64
59
65
span_lint_and_sugg (
@@ -78,15 +84,28 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
78
84
snippet_with_context( cx, pat_span, ctxt, ".." , & mut app) . 0
79
85
) ,
80
86
) ,
87
+ None if is_expr_identity_of_pat ( cx, arms[ 0 ] . pat , ex, false ) => {
88
+ span_lint_and_sugg (
89
+ cx,
90
+ MATCH_SINGLE_BINDING ,
91
+ expr. span ,
92
+ "this match could be replaced by its body itself" ,
93
+ "consider using the match body instead" ,
94
+ snippet_body,
95
+ Applicability :: MachineApplicable ,
96
+ ) ;
97
+ return ;
98
+ } ,
81
99
None => {
82
100
let sugg = sugg_with_curlies (
83
101
cx,
84
102
( ex, expr) ,
85
103
( bind_names, matched_vars) ,
86
- & snippet_body,
104
+ snippet_body,
87
105
& mut app,
88
106
None ,
89
107
true ,
108
+ is_var_binding_used_later ( cx, expr, & arms[ 0 ] ) ,
90
109
) ;
91
110
( expr. span , sugg)
92
111
} ,
@@ -108,10 +127,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
108
127
cx,
109
128
( ex, expr) ,
110
129
( bind_names, matched_vars) ,
111
- & snippet_body,
130
+ snippet_body,
112
131
& mut app,
113
132
None ,
114
133
false ,
134
+ true ,
115
135
) ;
116
136
117
137
span_lint_and_sugg (
@@ -139,6 +159,125 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
139
159
}
140
160
}
141
161
162
+ struct VarBindingVisitor < ' a , ' tcx > {
163
+ cx : & ' a LateContext < ' tcx > ,
164
+ identifiers : FxHashSet < Symbol > ,
165
+ }
166
+
167
+ impl < ' tcx > Visitor < ' tcx > for VarBindingVisitor < ' _ , ' tcx > {
168
+ type Result = ControlFlow < ( ) > ;
169
+
170
+ fn visit_path ( & mut self , path : & Path < ' tcx > , _: HirId ) -> Self :: Result {
171
+ if let Res :: Local ( _) = path. res
172
+ && let [ segment] = path. segments
173
+ && self . identifiers . contains ( & segment. ident . name )
174
+ {
175
+ return ControlFlow :: Break ( ( ) ) ;
176
+ }
177
+
178
+ walk_path ( self , path)
179
+ }
180
+
181
+ fn visit_block ( & mut self , block : & ' tcx Block < ' tcx > ) -> Self :: Result {
182
+ let before = self . identifiers . clone ( ) ;
183
+ walk_block ( self , block) ?;
184
+ self . identifiers = before;
185
+ ControlFlow :: Continue ( ( ) )
186
+ }
187
+
188
+ fn visit_stmt ( & mut self , stmt : & ' tcx Stmt < ' tcx > ) -> Self :: Result {
189
+ if let StmtKind :: Let ( let_stmt) = stmt. kind {
190
+ if let Some ( init) = let_stmt. init {
191
+ self . visit_expr ( init) ?;
192
+ }
193
+
194
+ let_stmt. pat . each_binding ( |_, _, _, ident| {
195
+ self . identifiers . remove ( & ident. name ) ;
196
+ } ) ;
197
+ }
198
+ walk_stmt ( self , stmt)
199
+ }
200
+
201
+ fn visit_expr ( & mut self , expr : & ' tcx Expr < ' tcx > ) -> Self :: Result {
202
+ match expr. kind {
203
+ ExprKind :: If (
204
+ Expr {
205
+ kind : ExprKind :: Let ( let_expr) ,
206
+ ..
207
+ } ,
208
+ then,
209
+ else_,
210
+ ) => {
211
+ self . visit_expr ( let_expr. init ) ?;
212
+ let before = self . identifiers . clone ( ) ;
213
+ let_expr. pat . each_binding ( |_, _, _, ident| {
214
+ self . identifiers . remove ( & ident. name ) ;
215
+ } ) ;
216
+
217
+ self . visit_expr ( then) ?;
218
+ self . identifiers = before;
219
+ if let Some ( else_) = else_ {
220
+ self . visit_expr ( else_) ?;
221
+ }
222
+ ControlFlow :: Continue ( ( ) )
223
+ } ,
224
+ ExprKind :: Closure ( closure) => {
225
+ let body = self . cx . tcx . hir_body ( closure. body ) ;
226
+ let before = self . identifiers . clone ( ) ;
227
+ for param in body. params {
228
+ param. pat . each_binding ( |_, _, _, ident| {
229
+ self . identifiers . remove ( & ident. name ) ;
230
+ } ) ;
231
+ }
232
+ self . visit_expr ( body. value ) ?;
233
+ self . identifiers = before;
234
+ ControlFlow :: Continue ( ( ) )
235
+ } ,
236
+ ExprKind :: Match ( expr, arms, _) => {
237
+ self . visit_expr ( expr) ?;
238
+ for arm in arms {
239
+ let before = self . identifiers . clone ( ) ;
240
+ arm. pat . each_binding ( |_, _, _, ident| {
241
+ self . identifiers . remove ( & ident. name ) ;
242
+ } ) ;
243
+ if let Some ( guard) = arm. guard {
244
+ self . visit_expr ( guard) ?;
245
+ }
246
+ self . visit_expr ( arm. body ) ?;
247
+ self . identifiers = before;
248
+ }
249
+ ControlFlow :: Continue ( ( ) )
250
+ } ,
251
+ _ => walk_expr ( self , expr) ,
252
+ }
253
+ }
254
+ }
255
+
256
+ fn is_var_binding_used_later ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , arm : & Arm < ' _ > ) -> bool {
257
+ let Node :: Stmt ( stmt) = cx. tcx . parent_hir_node ( expr. hir_id ) else {
258
+ return false ;
259
+ } ;
260
+ let Node :: Block ( block) = cx. tcx . parent_hir_node ( stmt. hir_id ) else {
261
+ return false ;
262
+ } ;
263
+
264
+ let mut identifiers = FxHashSet :: default ( ) ;
265
+ arm. pat . each_binding ( |_, _, _, ident| {
266
+ identifiers. insert ( ident. name ) ;
267
+ } ) ;
268
+
269
+ let mut visitor = VarBindingVisitor { cx, identifiers } ;
270
+ block
271
+ . stmts
272
+ . iter ( )
273
+ . skip_while ( |s| s. hir_id != stmt. hir_id )
274
+ . skip ( 1 )
275
+ . any ( |stmt| matches ! ( visitor. visit_stmt( stmt) , ControlFlow :: Break ( ( ) ) ) )
276
+ || block
277
+ . expr
278
+ . is_some_and ( |expr| matches ! ( visitor. visit_expr( expr) , ControlFlow :: Break ( ( ) ) ) )
279
+ }
280
+
142
281
/// Returns true if the `ex` match expression is in a local (`let`) or assign expression
143
282
fn opt_parent_assign_span < ' a > ( cx : & LateContext < ' a > , ex : & Expr < ' a > ) -> Option < AssignmentExpr > {
144
283
if let Node :: Expr ( parent_arm_expr) = cx. tcx . parent_hir_node ( ex. hir_id ) {
@@ -161,47 +300,66 @@ fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<Ass
161
300
None
162
301
}
163
302
164
- fn expr_parent_requires_curlies < ' a > ( cx : & LateContext < ' a > , match_expr : & Expr < ' a > ) -> bool {
303
+ fn expr_in_nested_block ( cx : & LateContext < ' _ > , match_expr : & Expr < ' _ > ) -> bool {
304
+ if let Node :: Block ( block) = cx. tcx . parent_hir_node ( match_expr. hir_id ) {
305
+ return block
306
+ . expr
307
+ . map_or_else ( || matches ! ( block. stmts, [ _] ) , |_| block. stmts . is_empty ( ) ) ;
308
+ }
309
+ false
310
+ }
311
+
312
+ fn expr_must_have_curlies ( cx : & LateContext < ' _ > , match_expr : & Expr < ' _ > ) -> bool {
165
313
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 ( ..)
314
+ if let Node :: Expr ( Expr {
315
+ kind : ExprKind :: Closure ( ..) | ExprKind :: Binary ( ..) ,
316
+ ..
317
+ } )
318
+ | Node :: AnonConst ( ..) = parent
319
+ {
320
+ return true ;
321
+ }
322
+
323
+ if let Node :: Arm ( arm) = & cx. tcx . parent_hir_node ( match_expr. hir_id )
324
+ && let ExprKind :: Match ( ..) = arm. body . kind
325
+ {
326
+ return true ;
327
+ }
328
+
329
+ false
330
+ }
331
+
332
+ fn indent_of_nth_line ( snippet : & str , nth : usize ) -> Option < usize > {
333
+ snippet
334
+ . lines ( )
335
+ . nth ( nth)
336
+ . and_then ( |s| s. find ( |c : char | !c. is_whitespace ( ) ) )
337
+ }
338
+
339
+ fn reindent_snippet_if_in_block ( snippet_body : & str , has_assignment : bool ) -> String {
340
+ if has_assignment || !snippet_body. starts_with ( '{' ) {
341
+ return reindent_multiline ( snippet_body, true , indent_of_nth_line ( snippet_body, 1 ) ) ;
342
+ }
343
+
344
+ let snippet_body = snippet_body. trim_start_matches ( '{' ) . trim_end_matches ( '}' ) . trim ( ) ;
345
+ reindent_multiline (
346
+ snippet_body,
347
+ false ,
348
+ indent_of_nth_line ( snippet_body, 0 ) . map ( |indent| indent. saturating_sub ( 4 ) ) ,
172
349
)
173
350
}
174
351
352
+ #[ expect( clippy:: too_many_arguments) ]
175
353
fn sugg_with_curlies < ' a > (
176
354
cx : & LateContext < ' a > ,
177
355
( ex, match_expr) : ( & Expr < ' a > , & Expr < ' a > ) ,
178
356
( bind_names, matched_vars) : ( Span , Span ) ,
179
- snippet_body : & str ,
357
+ mut snippet_body : String ,
180
358
applicability : & mut Applicability ,
181
359
assignment : Option < Span > ,
182
360
needs_var_binding : bool ,
361
+ is_var_binding_used_later : bool ,
183
362
) -> 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
363
let assignment_str = assignment. map_or_else ( String :: new, |span| {
206
364
let mut s = snippet ( cx, span, ".." ) . to_string ( ) ;
207
365
s. push_str ( " = " ) ;
@@ -221,5 +379,17 @@ fn sugg_with_curlies<'a>(
221
379
. to_string ( )
222
380
} ;
223
381
382
+ let mut indent = " " . repeat ( indent_of ( cx, ex. span ) . unwrap_or ( 0 ) ) ;
383
+ let ( mut cbrace_start, mut cbrace_end) = ( String :: new ( ) , String :: new ( ) ) ;
384
+ if !expr_in_nested_block ( cx, match_expr)
385
+ && ( ( needs_var_binding && is_var_binding_used_later) || expr_must_have_curlies ( cx, match_expr) )
386
+ {
387
+ cbrace_end = format ! ( "\n {indent}}}" ) ;
388
+ // Fix body indent due to the closure
389
+ indent = " " . repeat ( indent_of ( cx, bind_names) . unwrap_or ( 0 ) ) ;
390
+ cbrace_start = format ! ( "{{\n {indent}" ) ;
391
+ snippet_body = reindent_snippet_if_in_block ( & snippet_body, !assignment_str. is_empty ( ) ) ;
392
+ }
393
+
224
394
format ! ( "{cbrace_start}{scrutinee};\n {indent}{assignment_str}{snippet_body}{cbrace_end}" )
225
395
}
0 commit comments