@@ -6,17 +6,18 @@ use clippy_utils::source::snippet_with_context;
6
6
use clippy_utils:: ty:: is_type_diagnostic_item;
7
7
use clippy_utils:: visitors:: { Descend , Visitable } ;
8
8
use if_chain:: if_chain;
9
- use rustc_data_structures:: fx:: FxHashSet ;
9
+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
10
10
use rustc_errors:: Applicability ;
11
11
use rustc_hir:: intravisit:: { walk_expr, Visitor } ;
12
12
use rustc_hir:: { Expr , ExprKind , HirId , ItemId , Local , MatchSource , Pat , PatKind , QPath , Stmt , StmtKind , Ty } ;
13
13
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
14
14
use rustc_middle:: lint:: in_external_macro;
15
15
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
16
- use rustc_span:: symbol:: sym;
16
+ use rustc_span:: symbol:: { sym, Symbol } ;
17
17
use rustc_span:: Span ;
18
18
use serde:: Deserialize ;
19
19
use std:: ops:: ControlFlow ;
20
+ use std:: slice;
20
21
21
22
declare_clippy_lint ! {
22
23
/// ### What it does
@@ -81,11 +82,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
81
82
{
82
83
match if_let_or_match {
83
84
IfLetOrMatch :: IfLet ( if_let_expr, let_pat, if_then, if_else) => if_chain ! {
84
- if expr_is_simple_identity ( let_pat, if_then) ;
85
+ if let Some ( ident_map ) = expr_simple_identity_map ( local . pat , let_pat, if_then) ;
85
86
if let Some ( if_else) = if_else;
86
87
if expr_diverges( cx, if_else) ;
87
88
then {
88
- emit_manual_let_else( cx, stmt. span, if_let_expr, local . pat , let_pat, if_else) ;
89
+ emit_manual_let_else( cx, stmt. span, if_let_expr, & ident_map , let_pat, if_else) ;
89
90
}
90
91
} ,
91
92
IfLetOrMatch :: Match ( match_expr, arms, source) => {
@@ -118,11 +119,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
118
119
return ;
119
120
}
120
121
let pat_arm = & arms[ 1 - idx] ;
121
- if ! expr_is_simple_identity ( pat_arm. pat , pat_arm. body ) {
122
- return ;
123
- }
122
+ let Some ( ident_map ) = expr_simple_identity_map ( local . pat , pat_arm. pat , pat_arm. body ) else {
123
+ return
124
+ } ;
124
125
125
- emit_manual_let_else ( cx, stmt. span , match_expr, local . pat , pat_arm. pat , diverging_arm. body ) ;
126
+ emit_manual_let_else ( cx, stmt. span , match_expr, & ident_map , pat_arm. pat , diverging_arm. body ) ;
126
127
} ,
127
128
}
128
129
} ;
@@ -135,7 +136,7 @@ fn emit_manual_let_else(
135
136
cx : & LateContext < ' _ > ,
136
137
span : Span ,
137
138
expr : & Expr < ' _ > ,
138
- local : & Pat < ' _ > ,
139
+ ident_map : & FxHashMap < Symbol , & Pat < ' _ > > ,
139
140
pat : & Pat < ' _ > ,
140
141
else_body : & Expr < ' _ > ,
141
142
) {
@@ -159,62 +160,99 @@ fn emit_manual_let_else(
159
160
} else {
160
161
format ! ( "{{ {sn_else} }}" )
161
162
} ;
162
- let sn_bl = replace_in_pattern ( cx, span, local , pat, & mut app, true ) ;
163
+ let sn_bl = replace_in_pattern ( cx, span, ident_map , pat, & mut app, true ) ;
163
164
let sugg = format ! ( "let {sn_bl} = {sn_expr} else {else_bl};" ) ;
164
165
diag. span_suggestion ( span, "consider writing" , sugg, app) ;
165
166
} ,
166
167
) ;
167
168
}
168
169
169
- // replaces the locals in the pattern
170
+ /// Replaces the locals in the pattern
171
+ ///
172
+ /// For this example:
173
+ ///
174
+ /// ```
175
+ /// let (a, FooBar { b, c }) = if let Bar { Some(a_i), b_i } = ex { (a_i, b_i) } else { return };
176
+ /// ```
177
+ ///
178
+ /// We have:
179
+ ///
180
+ /// ```
181
+ /// pat: Bar { Some(a_i), b_i }
182
+ /// ident_map: (a_i) -> (a), (b_i) -> (FooBar { b, c })
183
+ /// ```
184
+ ///
185
+ /// We return:
186
+ ///
187
+ /// ```
188
+ /// Bar { Some(a), b_i: FooBar { b, c } }
189
+ /// ```
170
190
fn replace_in_pattern (
171
191
cx : & LateContext < ' _ > ,
172
192
span : Span ,
173
- local : & Pat < ' _ > ,
193
+ ident_map : & FxHashMap < Symbol , & Pat < ' _ > > ,
174
194
pat : & Pat < ' _ > ,
175
195
app : & mut Applicability ,
176
196
top_level : bool ,
177
197
) -> String {
178
- let mut bindings_count = 0 ;
179
- pat. each_binding_or_first ( & mut |_, _, _, _| bindings_count += 1 ) ;
180
- // If the pattern creates multiple bindings, exit early,
181
- // as otherwise we might paste the pattern to the positions of multiple bindings.
182
- if bindings_count > 1 {
183
- let ( sn_pat, _) = snippet_with_context ( cx, pat. span , span. ctxt ( ) , "" , app) ;
184
- return sn_pat. into_owned ( ) ;
185
- }
198
+ // We put a labeled block here so that we can implement the fallback in this function.
199
+ // As the function has multiple call sites, implementing the fallback via an Option<T>
200
+ // return type and unwrap_or_else would cause repetition. Similarly, the function also
201
+ // invokes the fall back multiple times.
202
+ ' a: {
203
+ // If the ident map is empty, there is no replacement to do.
204
+ // The code following this if assumes a non-empty ident_map.
205
+ if ident_map. is_empty ( ) {
206
+ break ' a;
207
+ }
186
208
187
- match pat. kind {
188
- PatKind :: Binding ( ..) => {
189
- let ( sn_bdg, _) = snippet_with_context ( cx, local. span , span. ctxt ( ) , "" , app) ;
190
- return sn_bdg. to_string ( ) ;
191
- } ,
192
- PatKind :: Or ( pats) => {
193
- let patterns = pats
194
- . iter ( )
195
- . map ( |pat| replace_in_pattern ( cx, span, local, pat, app, false ) )
196
- . collect :: < Vec < _ > > ( ) ;
197
- let or_pat = patterns. join ( " | " ) ;
198
- if top_level {
199
- return format ! ( "({or_pat})" ) ;
200
- } else {
209
+ match pat. kind {
210
+ PatKind :: Binding ( _ann, _id, binding_name, opt_subpt) => {
211
+ let Some ( pat_to_put) = ident_map. get ( & binding_name. name ) else { break ' a } ;
212
+ let ( sn_ptp, _) = snippet_with_context ( cx, pat_to_put. span , span. ctxt ( ) , "" , app) ;
213
+ if let Some ( subpt) = opt_subpt {
214
+ let subpt = replace_in_pattern ( cx, span, ident_map, subpt, app, false ) ;
215
+ return format ! ( "{sn_ptp} @ {subpt}" ) ;
216
+ }
217
+ return sn_ptp. to_string ( ) ;
218
+ } ,
219
+ PatKind :: Or ( pats) => {
220
+ let patterns = pats
221
+ . iter ( )
222
+ . map ( |pat| replace_in_pattern ( cx, span, ident_map, pat, app, false ) )
223
+ . collect :: < Vec < _ > > ( ) ;
224
+ let or_pat = patterns. join ( " | " ) ;
225
+ if top_level {
226
+ return format ! ( "({or_pat})" ) ;
227
+ }
201
228
return or_pat;
202
- }
203
- } ,
204
- // Replace the variable name iff `TupleStruct` has one argument like `Variant(v)`.
205
- PatKind :: TupleStruct ( ref w, args, dot_dot_pos) => {
206
- let mut args = args
207
- . iter ( )
208
- . map ( |pat| replace_in_pattern ( cx, span, local, pat, app, false ) )
209
- . collect :: < Vec < _ > > ( ) ;
210
- if let Some ( pos) = dot_dot_pos. as_opt_usize ( ) {
211
- args. insert ( pos, ".." . to_owned ( ) ) ;
212
- }
213
- let args = args. join ( ", " ) ;
214
- let sn_wrapper = cx. sess ( ) . source_map ( ) . span_to_snippet ( w. span ( ) ) . unwrap_or_default ( ) ;
215
- return format ! ( "{sn_wrapper}({args})" ) ;
216
- } ,
217
- _ => { } ,
229
+ } ,
230
+ // Replace the variable name iff `TupleStruct` has one argument like `Variant(v)`.
231
+ PatKind :: TupleStruct ( ref w, args, dot_dot_pos) => {
232
+ let mut args = args
233
+ . iter ( )
234
+ . map ( |pat| replace_in_pattern ( cx, span, ident_map, pat, app, false ) )
235
+ . collect :: < Vec < _ > > ( ) ;
236
+ if let Some ( pos) = dot_dot_pos. as_opt_usize ( ) {
237
+ args. insert ( pos, ".." . to_owned ( ) ) ;
238
+ }
239
+ let args = args. join ( ", " ) ;
240
+ let sn_wrapper = cx. sess ( ) . source_map ( ) . span_to_snippet ( w. span ( ) ) . unwrap_or_default ( ) ;
241
+ return format ! ( "{sn_wrapper}({args})" ) ;
242
+ } ,
243
+ PatKind :: Tuple ( args, dot_dot_pos) => {
244
+ let mut args = args
245
+ . iter ( )
246
+ . map ( |pat| replace_in_pattern ( cx, span, ident_map, pat, app, false ) )
247
+ . collect :: < Vec < _ > > ( ) ;
248
+ if let Some ( pos) = dot_dot_pos. as_opt_usize ( ) {
249
+ args. insert ( pos, ".." . to_owned ( ) ) ;
250
+ }
251
+ let args = args. join ( ", " ) ;
252
+ return format ! ( "({args})" ) ;
253
+ } ,
254
+ _ => { } ,
255
+ }
218
256
}
219
257
let ( sn_pat, _) = snippet_with_context ( cx, pat. span , span. ctxt ( ) , "" , app) ;
220
258
sn_pat. into_owned ( )
@@ -358,37 +396,73 @@ fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: boo
358
396
!has_disallowed
359
397
}
360
398
361
- /// Checks if the passed block is a simple identity referring to bindings created by the pattern
362
- fn expr_is_simple_identity ( pat : & ' _ Pat < ' _ > , expr : & ' _ Expr < ' _ > ) -> bool {
363
- // We support patterns with multiple bindings and tuples, like:
364
- // let ... = if let (Some(foo), bar) = g() { (foo, bar) } else { ... }
399
+ /// Checks if the passed block is a simple identity referring to bindings created by the pattern,
400
+ /// and if yes, returns a mapping between the relevant sub-pattern and the identifier it corresponds
401
+ /// to.
402
+ ///
403
+ /// We support patterns with multiple bindings and tuples, e.g.:
404
+ ///
405
+ /// ```
406
+ /// let (foo_o, bar_o) = if let (Some(foo), bar) = g() { (foo, bar) } else { ... }
407
+ /// ```
408
+ ///
409
+ /// The expected params would be:
410
+ ///
411
+ /// ```
412
+ /// local_pat: (foo_o, bar_o)
413
+ /// let_pat: (Some(foo), bar)
414
+ /// expr: (foo, bar)
415
+ /// ```
416
+ ///
417
+ /// We build internal `sub_pats` so that it looks like `[foo_o, bar_o]` and `paths` so that it looks like
418
+ /// `[foo, bar]`. Then we turn that into `FxHashMap [(foo) -> (foo_o), (bar) -> (bar_o)]` which we return.
419
+ fn expr_simple_identity_map < ' a , ' hir > (
420
+ local_pat : & ' a Pat < ' hir > ,
421
+ let_pat : & ' _ Pat < ' hir > ,
422
+ expr : & ' _ Expr < ' hir > ,
423
+ ) -> Option < FxHashMap < Symbol , & ' a Pat < ' hir > > > {
365
424
let peeled = peel_blocks ( expr) ;
366
- let paths = match peeled. kind {
367
- ExprKind :: Tup ( exprs) | ExprKind :: Array ( exprs) => exprs,
368
- ExprKind :: Path ( _) => std:: slice:: from_ref ( peeled) ,
369
- _ => return false ,
425
+ let ( sub_pats, paths) = match ( local_pat. kind , peeled. kind ) {
426
+ ( PatKind :: Tuple ( pats, _) , ExprKind :: Tup ( exprs) ) | ( PatKind :: Slice ( pats, ..) , ExprKind :: Array ( exprs) ) => {
427
+ ( pats, exprs)
428
+ } ,
429
+ ( _, ExprKind :: Path ( _) ) => ( slice:: from_ref ( local_pat) , slice:: from_ref ( peeled) ) ,
430
+ _ => return None ,
370
431
} ;
432
+
433
+ // There is some length mismatch, which indicates usage of .. in the patterns above e.g.:
434
+ // let (a, ..) = if let [a, b, _c] = ex { (a, b) } else { ... };
435
+ // We bail in these cases as they should be rare.
436
+ if paths. len ( ) != sub_pats. len ( ) {
437
+ return None ;
438
+ }
439
+
371
440
let mut pat_bindings = FxHashSet :: default ( ) ;
372
- pat . each_binding_or_first ( & mut |_ann, _hir_id, _sp, ident| {
441
+ let_pat . each_binding_or_first ( & mut |_ann, _hir_id, _sp, ident| {
373
442
pat_bindings. insert ( ident) ;
374
443
} ) ;
375
444
if pat_bindings. len ( ) < paths. len ( ) {
376
- return false ;
445
+ // This rebinds some bindings from the outer scope, or it repeats some copy-able bindings multiple
446
+ // times. We don't support these cases so we bail here. E.g.:
447
+ // let foo = 0;
448
+ // let (new_foo, bar, bar_copied) = if let Some(bar) = Some(0) { (foo, bar, bar) } else { .. };
449
+ return None ;
377
450
}
378
- for path in paths {
379
- if_chain ! {
380
- if let ExprKind :: Path ( QPath :: Resolved ( _ty, path) ) = path. kind;
381
- if let [ path_seg] = path. segments;
382
- then {
383
- if !pat_bindings. remove( & path_seg. ident) {
384
- return false ;
385
- }
386
- } else {
387
- return false ;
451
+ let mut ident_map = FxHashMap :: default ( ) ;
452
+ for ( sub_pat, path) in sub_pats. iter ( ) . zip ( paths. iter ( ) ) {
453
+ if let ExprKind :: Path ( QPath :: Resolved ( _ty, path) ) = path. kind &&
454
+ let [ path_seg] = path. segments
455
+ {
456
+ let ident = path_seg. ident ;
457
+ if !pat_bindings. remove ( & ident) {
458
+ return None ;
388
459
}
460
+ ident_map. insert ( ident. name , sub_pat) ;
461
+ } else {
462
+ return None ;
389
463
}
390
464
}
391
- true
465
+ Some ( ident_map )
392
466
}
393
467
394
468
#[ derive( Clone , Copy , Debug , PartialEq , Eq , Hash , Deserialize ) ]
0 commit comments