@@ -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) {
@@ -202,30 +204,58 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op
202204 if arms. len ( ) == 2 {
203205 let node_pair = ( & arms[ 0 ] . pat . kind , & arms[ 1 ] . pat . kind ) ;
204206
205- 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) {
206208 let span = is_expn_of ( expr. span , "matches" ) . unwrap_or ( expr. span . to ( op. span ) ) ;
207209 let result_expr = match & op. kind {
208210 ExprKind :: AddrOf ( _, _, borrowed) => borrowed,
209211 _ => op,
210212 } ;
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+
211241 span_lint_and_sugg (
212242 cx,
213243 REDUNDANT_PATTERN_MATCHING ,
214244 span,
215245 & format ! ( "redundant pattern matching, consider using `{good_method}`" ) ,
216246 "try" ,
217- format ! ( "{}.{good_method}" , snippet ( cx , result_expr . span , "_" ) ) ,
247+ sugg ,
218248 Applicability :: MachineApplicable ,
219249 ) ;
220250 }
221251 }
222252}
223253
224- fn found_good_method < ' a > (
254+ fn found_good_method < ' tcx > (
225255 cx : & LateContext < ' _ > ,
226- arms : & [ Arm < ' _ > ] ,
256+ arms : & ' tcx [ Arm < ' tcx > ] ,
227257 node : ( & PatKind < ' _ > , & PatKind < ' _ > ) ,
228- ) -> Option < & ' a str > {
258+ ) -> Option < ( & ' static str , Option < & ' tcx Guard < ' tcx > > ) > {
229259 match node {
230260 (
231261 PatKind :: TupleStruct ( ref path_left, patterns_left, _) ,
@@ -311,7 +341,11 @@ fn get_ident(path: &QPath<'_>) -> Option<rustc_span::symbol::Ident> {
311341 }
312342}
313343
314- 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 > > ) > {
315349 if let Some ( name) = get_ident ( path_left) {
316350 return match name. as_str ( ) {
317351 "Ok" => {
@@ -377,16 +411,16 @@ fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expecte
377411}
378412
379413#[ expect( clippy:: too_many_arguments) ]
380- fn find_good_method_for_match < ' a > (
414+ fn find_good_method_for_match < ' a , ' tcx > (
381415 cx : & LateContext < ' _ > ,
382- arms : & [ Arm < ' _ > ] ,
416+ arms : & ' tcx [ Arm < ' tcx > ] ,
383417 path_left : & QPath < ' _ > ,
384418 path_right : & QPath < ' _ > ,
385419 expected_item_left : Item ,
386420 expected_item_right : Item ,
387421 should_be_left : & ' a str ,
388422 should_be_right : & ' a str ,
389- ) -> Option < & ' a str > {
423+ ) -> Option < ( & ' a str , Option < & ' tcx Guard < ' tcx > > ) > {
390424 let first_pat = arms[ 0 ] . pat ;
391425 let second_pat = arms[ 1 ] . pat ;
392426
@@ -404,22 +438,22 @@ fn find_good_method_for_match<'a>(
404438
405439 match body_node_pair {
406440 ( ExprKind :: Lit ( lit_left) , ExprKind :: Lit ( lit_right) ) => match ( & lit_left. node , & lit_right. node ) {
407- ( LitKind :: Bool ( true ) , LitKind :: Bool ( false ) ) => Some ( should_be_left) ,
408- ( 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 ( ) ) ) ,
409443 _ => None ,
410444 } ,
411445 _ => None ,
412446 }
413447}
414448
415- fn find_good_method_for_matches_macro < ' a > (
449+ fn find_good_method_for_matches_macro < ' a , ' tcx > (
416450 cx : & LateContext < ' _ > ,
417- arms : & [ Arm < ' _ > ] ,
451+ arms : & ' tcx [ Arm < ' tcx > ] ,
418452 path_left : & QPath < ' _ > ,
419453 expected_item_left : Item ,
420454 should_be_left : & ' a str ,
421455 should_be_right : & ' a str ,
422- ) -> Option < & ' a str > {
456+ ) -> Option < ( & ' a str , Option < & ' tcx Guard < ' tcx > > ) > {
423457 let first_pat = arms[ 0 ] . pat ;
424458
425459 let body_node_pair = if is_pat_variant ( cx, first_pat, path_left, expected_item_left) {
@@ -430,8 +464,8 @@ fn find_good_method_for_matches_macro<'a>(
430464
431465 match body_node_pair {
432466 ( ExprKind :: Lit ( lit_left) , ExprKind :: Lit ( lit_right) ) => match ( & lit_left. node , & lit_right. node ) {
433- ( LitKind :: Bool ( true ) , LitKind :: Bool ( false ) ) => Some ( should_be_left) ,
434- ( 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 ( ) ) ) ,
435469 _ => None ,
436470 } ,
437471 _ => None ,
0 commit comments