@@ -3,18 +3,20 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
33use clippy_utils:: source:: { snippet, walk_span_to_context} ;
44use clippy_utils:: sugg:: Sugg ;
55use clippy_utils:: ty:: { is_type_diagnostic_item, needs_ordered_drop} ;
6- use clippy_utils:: visitors:: any_temporaries_need_ordered_drop;
6+ use clippy_utils:: visitors:: { any_temporaries_need_ordered_drop, for_each_expr } ;
77use clippy_utils:: { higher, is_expn_of, is_trait_method} ;
88use if_chain:: if_chain;
99use rustc_ast:: ast:: LitKind ;
1010use rustc_errors:: Applicability ;
1111use rustc_hir:: def:: { DefKind , Res } ;
1212use rustc_hir:: LangItem :: { self , OptionNone , OptionSome , PollPending , PollReady , ResultErr , ResultOk } ;
13- use rustc_hir:: { Arm , Expr , ExprKind , Node , Pat , PatKind , QPath , UnOp } ;
13+ use rustc_hir:: { Arm , Expr , ExprKind , Guard , Node , Pat , PatKind , QPath , UnOp } ;
1414use rustc_lint:: LateContext ;
1515use rustc_middle:: ty:: subst:: GenericArgKind ;
1616use rustc_middle:: ty:: { self , Ty } ;
1717use rustc_span:: { sym, Symbol } ;
18+ use std:: fmt:: Write ;
19+ use std:: ops:: ControlFlow ;
1820
1921pub ( super ) fn check < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
2022 if let Some ( higher:: WhileLet { let_pat, let_expr, .. } ) = higher:: WhileLet :: hir ( expr) {
@@ -199,36 +201,61 @@ fn find_sugg_for_if_let<'tcx>(
199201}
200202
201203pub ( super ) fn check_match < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > , op : & Expr < ' _ > , arms : & [ Arm < ' _ > ] ) {
202- if let [ arm1, arm2] = arms
203- && arm1. guard . is_none ( )
204- && arm2. guard . is_none ( )
205- {
206- let node_pair = ( & arm1. pat . kind , & arm2. pat . kind ) ;
204+ if arms. len ( ) == 2 {
205+ let node_pair = ( & arms[ 0 ] . pat . kind , & arms[ 1 ] . pat . kind ) ;
207206
208- if let Some ( good_method) = found_good_method ( cx, arms, node_pair) {
207+ if let Some ( ( good_method, maybe_guard ) ) = found_good_method ( cx, arms, node_pair) {
209208 let span = is_expn_of ( expr. span , "matches" ) . unwrap_or ( expr. span . to ( op. span ) ) ;
210209 let result_expr = match & op. kind {
211210 ExprKind :: AddrOf ( _, _, borrowed) => borrowed,
212211 _ => op,
213212 } ;
213+ let mut sugg = format ! ( "{}.{good_method}" , snippet( cx, result_expr. span, "_" ) ) ;
214+
215+ if let Some ( guard) = maybe_guard {
216+ let Guard :: If ( guard) = * guard else { return } ; // `...is_none() && let ...` is a syntax error
217+
218+ // wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying!
219+ // `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs,
220+ // counter to the intuition that it should be `Guard::IfLet`, so we need another check
221+ // to see that there aren't any let chains anywhere in the guard, as that would break
222+ // if we suggest `t.is_none() && (let X = y && z)` for:
223+ // `match t { None if let X = y && z => true, _ => false }`
224+ let has_nested_let_chain = for_each_expr ( guard, |expr| {
225+ if matches ! ( expr. kind, ExprKind :: Let ( ..) ) {
226+ ControlFlow :: Break ( ( ) )
227+ } else {
228+ ControlFlow :: Continue ( ( ) )
229+ }
230+ } )
231+ . is_some ( ) ;
232+
233+ if has_nested_let_chain {
234+ return ;
235+ }
236+
237+ let guard = Sugg :: hir ( cx, guard, ".." ) ;
238+ let _ = write ! ( sugg, " && {}" , guard. maybe_par( ) ) ;
239+ }
240+
214241 span_lint_and_sugg (
215242 cx,
216243 REDUNDANT_PATTERN_MATCHING ,
217244 span,
218245 & format ! ( "redundant pattern matching, consider using `{good_method}`" ) ,
219246 "try" ,
220- format ! ( "{}.{good_method}" , snippet ( cx , result_expr . span , "_" ) ) ,
247+ sugg ,
221248 Applicability :: MachineApplicable ,
222249 ) ;
223250 }
224251 }
225252}
226253
227- fn found_good_method < ' a > (
254+ fn found_good_method < ' tcx > (
228255 cx : & LateContext < ' _ > ,
229- arms : & [ Arm < ' _ > ] ,
256+ arms : & ' tcx [ Arm < ' tcx > ] ,
230257 node : ( & PatKind < ' _ > , & PatKind < ' _ > ) ,
231- ) -> Option < & ' a str > {
258+ ) -> Option < ( & ' static str , Option < & ' tcx Guard < ' tcx > > ) > {
232259 match node {
233260 (
234261 PatKind :: TupleStruct ( ref path_left, patterns_left, _) ,
@@ -314,7 +341,11 @@ fn get_ident(path: &QPath<'_>) -> Option<rustc_span::symbol::Ident> {
314341 }
315342}
316343
317- fn get_good_method < ' a > ( cx : & LateContext < ' _ > , arms : & [ Arm < ' _ > ] , path_left : & QPath < ' _ > ) -> Option < & ' a str > {
344+ fn get_good_method < ' tcx > (
345+ cx : & LateContext < ' _ > ,
346+ arms : & ' tcx [ Arm < ' tcx > ] ,
347+ path_left : & QPath < ' _ > ,
348+ ) -> Option < ( & ' static str , Option < & ' tcx Guard < ' tcx > > ) > {
318349 if let Some ( name) = get_ident ( path_left) {
319350 return match name. as_str ( ) {
320351 "Ok" => {
@@ -380,16 +411,16 @@ fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expecte
380411}
381412
382413#[ expect( clippy:: too_many_arguments) ]
383- fn find_good_method_for_match < ' a > (
414+ fn find_good_method_for_match < ' a , ' tcx > (
384415 cx : & LateContext < ' _ > ,
385- arms : & [ Arm < ' _ > ] ,
416+ arms : & ' tcx [ Arm < ' tcx > ] ,
386417 path_left : & QPath < ' _ > ,
387418 path_right : & QPath < ' _ > ,
388419 expected_item_left : Item ,
389420 expected_item_right : Item ,
390421 should_be_left : & ' a str ,
391422 should_be_right : & ' a str ,
392- ) -> Option < & ' a str > {
423+ ) -> Option < ( & ' a str , Option < & ' tcx Guard < ' tcx > > ) > {
393424 let first_pat = arms[ 0 ] . pat ;
394425 let second_pat = arms[ 1 ] . pat ;
395426
@@ -407,22 +438,22 @@ fn find_good_method_for_match<'a>(
407438
408439 match body_node_pair {
409440 ( ExprKind :: Lit ( lit_left) , ExprKind :: Lit ( lit_right) ) => match ( & lit_left. node , & lit_right. node ) {
410- ( LitKind :: Bool ( true ) , LitKind :: Bool ( false ) ) => Some ( should_be_left) ,
411- ( LitKind :: Bool ( false ) , LitKind :: Bool ( true ) ) => Some ( should_be_right) ,
441+ ( LitKind :: Bool ( true ) , LitKind :: Bool ( false ) ) => Some ( ( should_be_left, arms [ 0 ] . guard . as_ref ( ) ) ) ,
442+ ( LitKind :: Bool ( false ) , LitKind :: Bool ( true ) ) => Some ( ( should_be_right, arms [ 1 ] . guard . as_ref ( ) ) ) ,
412443 _ => None ,
413444 } ,
414445 _ => None ,
415446 }
416447}
417448
418- fn find_good_method_for_matches_macro < ' a > (
449+ fn find_good_method_for_matches_macro < ' a , ' tcx > (
419450 cx : & LateContext < ' _ > ,
420- arms : & [ Arm < ' _ > ] ,
451+ arms : & ' tcx [ Arm < ' tcx > ] ,
421452 path_left : & QPath < ' _ > ,
422453 expected_item_left : Item ,
423454 should_be_left : & ' a str ,
424455 should_be_right : & ' a str ,
425- ) -> Option < & ' a str > {
456+ ) -> Option < ( & ' a str , Option < & ' tcx Guard < ' tcx > > ) > {
426457 let first_pat = arms[ 0 ] . pat ;
427458
428459 let body_node_pair = if is_pat_variant ( cx, first_pat, path_left, expected_item_left) {
@@ -433,8 +464,8 @@ fn find_good_method_for_matches_macro<'a>(
433464
434465 match body_node_pair {
435466 ( ExprKind :: Lit ( lit_left) , ExprKind :: Lit ( lit_right) ) => match ( & lit_left. node , & lit_right. node ) {
436- ( LitKind :: Bool ( true ) , LitKind :: Bool ( false ) ) => Some ( should_be_left) ,
437- ( LitKind :: Bool ( false ) , LitKind :: Bool ( true ) ) => Some ( should_be_right) ,
467+ ( LitKind :: Bool ( true ) , LitKind :: Bool ( false ) ) => Some ( ( should_be_left, arms [ 0 ] . guard . as_ref ( ) ) ) ,
468+ ( LitKind :: Bool ( false ) , LitKind :: Bool ( true ) ) => Some ( ( should_be_right, arms [ 1 ] . guard . as_ref ( ) ) ) ,
438469 _ => None ,
439470 } ,
440471 _ => None ,
0 commit comments