@@ -6,17 +6,18 @@ use clippy_utils::source::snippet_with_context;
66use clippy_utils:: ty:: is_type_diagnostic_item;
77use clippy_utils:: visitors:: { Descend , Visitable } ;
88use if_chain:: if_chain;
9- use rustc_data_structures:: fx:: FxHashSet ;
9+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
1010use rustc_errors:: Applicability ;
1111use rustc_hir:: intravisit:: { walk_expr, Visitor } ;
1212use rustc_hir:: { Expr , ExprKind , HirId , ItemId , Local , MatchSource , Pat , PatKind , QPath , Stmt , StmtKind , Ty } ;
1313use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
1414use rustc_middle:: lint:: in_external_macro;
1515use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
16- use rustc_span:: symbol:: sym;
16+ use rustc_span:: symbol:: { sym, Symbol } ;
1717use rustc_span:: Span ;
1818use serde:: Deserialize ;
1919use std:: ops:: ControlFlow ;
20+ use std:: slice;
2021
2122declare_clippy_lint ! {
2223 /// ### What it does
@@ -81,11 +82,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
8182 {
8283 match if_let_or_match {
8384 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) ;
8586 if let Some ( if_else) = if_else;
8687 if expr_diverges( cx, if_else) ;
8788 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) ;
8990 }
9091 } ,
9192 IfLetOrMatch :: Match ( match_expr, arms, source) => {
@@ -118,11 +119,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
118119 return ;
119120 }
120121 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+ } ;
124125
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 ) ;
126127 } ,
127128 }
128129 } ;
@@ -135,7 +136,7 @@ fn emit_manual_let_else(
135136 cx : & LateContext < ' _ > ,
136137 span : Span ,
137138 expr : & Expr < ' _ > ,
138- local : & Pat < ' _ > ,
139+ ident_map : & FxHashMap < Symbol , & Pat < ' _ > > ,
139140 pat : & Pat < ' _ > ,
140141 else_body : & Expr < ' _ > ,
141142) {
@@ -159,62 +160,99 @@ fn emit_manual_let_else(
159160 } else {
160161 format ! ( "{{ {sn_else} }}" )
161162 } ;
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 ) ;
163164 let sugg = format ! ( "let {sn_bl} = {sn_expr} else {else_bl};" ) ;
164165 diag. span_suggestion ( span, "consider writing" , sugg, app) ;
165166 } ,
166167 ) ;
167168}
168169
169- // replaces the locals in the pattern
170+ /// Replaces the locals in the pattern
171+ ///
172+ /// For this example:
173+ ///
174+ /// ```ignore
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+ /// ```ignore
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+ /// ```ignore
188+ /// Bar { Some(a), b_i: FooBar { b, c } }
189+ /// ```
170190fn replace_in_pattern (
171191 cx : & LateContext < ' _ > ,
172192 span : Span ,
173- local : & Pat < ' _ > ,
193+ ident_map : & FxHashMap < Symbol , & Pat < ' _ > > ,
174194 pat : & Pat < ' _ > ,
175195 app : & mut Applicability ,
176196 top_level : bool ,
177197) -> 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+ }
186208
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+ }
201228 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+ }
218256 }
219257 let ( sn_pat, _) = snippet_with_context ( cx, pat. span , span. ctxt ( ) , "" , app) ;
220258 sn_pat. into_owned ( )
@@ -358,37 +396,74 @@ fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: boo
358396 !has_disallowed
359397}
360398
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+ /// ```ignore
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+ /// ```ignore
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
418+ /// like `[foo, bar]`. Then we turn that into `FxHashMap [(foo) -> (foo_o), (bar) -> (bar_o)]` which
419+ /// we return.
420+ fn expr_simple_identity_map < ' a , ' hir > (
421+ local_pat : & ' a Pat < ' hir > ,
422+ let_pat : & ' _ Pat < ' hir > ,
423+ expr : & ' _ Expr < ' hir > ,
424+ ) -> Option < FxHashMap < Symbol , & ' a Pat < ' hir > > > {
365425 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 ,
426+ let ( sub_pats, paths) = match ( local_pat. kind , peeled. kind ) {
427+ ( PatKind :: Tuple ( pats, _) , ExprKind :: Tup ( exprs) ) | ( PatKind :: Slice ( pats, ..) , ExprKind :: Array ( exprs) ) => {
428+ ( pats, exprs)
429+ } ,
430+ ( _, ExprKind :: Path ( _) ) => ( slice:: from_ref ( local_pat) , slice:: from_ref ( peeled) ) ,
431+ _ => return None ,
370432 } ;
433+
434+ // There is some length mismatch, which indicates usage of .. in the patterns above e.g.:
435+ // let (a, ..) = if let [a, b, _c] = ex { (a, b) } else { ... };
436+ // We bail in these cases as they should be rare.
437+ if paths. len ( ) != sub_pats. len ( ) {
438+ return None ;
439+ }
440+
371441 let mut pat_bindings = FxHashSet :: default ( ) ;
372- pat . each_binding_or_first ( & mut |_ann, _hir_id, _sp, ident| {
442+ let_pat . each_binding_or_first ( & mut |_ann, _hir_id, _sp, ident| {
373443 pat_bindings. insert ( ident) ;
374444 } ) ;
375445 if pat_bindings. len ( ) < paths. len ( ) {
376- return false ;
446+ // This rebinds some bindings from the outer scope, or it repeats some copy-able bindings multiple
447+ // times. We don't support these cases so we bail here. E.g.:
448+ // let foo = 0;
449+ // let (new_foo, bar, bar_copied) = if let Some(bar) = Some(0) { (foo, bar, bar) } else { .. };
450+ return None ;
377451 }
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 ;
452+ let mut ident_map = FxHashMap :: default ( ) ;
453+ for ( sub_pat, path) in sub_pats. iter ( ) . zip ( paths. iter ( ) ) {
454+ if let ExprKind :: Path ( QPath :: Resolved ( _ty, path) ) = path. kind &&
455+ let [ path_seg] = path. segments
456+ {
457+ let ident = path_seg. ident ;
458+ if !pat_bindings. remove ( & ident) {
459+ return None ;
388460 }
461+ ident_map. insert ( ident. name , sub_pat) ;
462+ } else {
463+ return None ;
389464 }
390465 }
391- true
466+ Some ( ident_map )
392467}
393468
394469#[ derive( Clone , Copy , Debug , PartialEq , Eq , Hash , Deserialize ) ]
0 commit comments