@@ -11,18 +11,21 @@ use clippy_utils::{
1111 pat_and_expr_can_be_question_mark, path_res, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
1212 span_contains_cfg, span_contains_comment,
1313} ;
14+ use itertools:: Itertools ;
1415use rustc_errors:: Applicability ;
1516use rustc_hir:: LangItem :: { self , OptionNone , OptionSome , ResultErr , ResultOk } ;
1617use rustc_hir:: def:: Res ;
18+ use rustc_hir:: intravisit:: FnKind ;
1719use rustc_hir:: {
18- Arm , BindingMode , Block , Body , ByRef , Expr , ExprKind , FnRetTy , HirId , LetStmt , MatchSource , Mutability , Node , Pat ,
19- PatKind , PathSegment , QPath , Stmt , StmtKind ,
20+ Arm , BindingMode , Block , Body , ByRef , Expr , ExprKind , FnDecl , FnRetTy , HirId , LetStmt , MatchSource , Mutability ,
21+ Node , Pat , PatKind , PathSegment , QPath , Stmt , StmtKind ,
2022} ;
2123use rustc_lint:: { LateContext , LateLintPass } ;
2224use rustc_middle:: ty:: { self , Ty } ;
2325use rustc_session:: impl_lint_pass;
24- use rustc_span:: sym ;
26+ use rustc_span:: def_id :: LocalDefId ;
2527use rustc_span:: symbol:: Symbol ;
28+ use rustc_span:: { Span , sym} ;
2629
2730declare_clippy_lint ! {
2831 /// ### What it does
@@ -456,6 +459,61 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr:
456459 }
457460}
458461
462+ fn check_if_let_some_as_return_val < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
463+ if let Some ( higher:: IfLet {
464+ let_pat,
465+ let_expr,
466+ if_then,
467+ if_else,
468+ ..
469+ } ) = higher:: IfLet :: hir ( cx, expr)
470+ && let PatKind :: TupleStruct ( ref path1, [ field] , ddpos) = let_pat. kind
471+ && ddpos. as_opt_usize ( ) . is_none ( )
472+ && let PatKind :: Binding ( BindingMode ( by_ref, _) , _, ident, None ) = field. kind
473+ && let caller_ty = cx. typeck_results ( ) . expr_ty ( let_expr)
474+ && let if_block = IfBlockType :: IfLet (
475+ cx. qpath_res ( path1, let_pat. hir_id ) ,
476+ caller_ty,
477+ ident. name ,
478+ let_expr,
479+ if_then,
480+ if_else,
481+ )
482+ && let ExprKind :: Block ( if_then_block, _) = if_then. kind
483+ // Don't consider case where if-then branch has only one statement/expression
484+ && ( if_then_block. stmts . len ( ) >= 2 || if_then_block. stmts . len ( ) == 1 && if_then_block. expr . is_some ( ) )
485+ && ( is_early_return ( sym:: Option , cx, & if_block) )
486+ && if_else
487+ . map ( |e| eq_expr_value ( cx, let_expr, peel_blocks ( e) ) )
488+ . filter ( |e| * e)
489+ . is_none ( )
490+ {
491+ let mut applicability = Applicability :: MachineApplicable ;
492+ let receiver_str = snippet_with_applicability ( cx, let_expr. span , ".." , & mut applicability) ;
493+ let method_call_str = match by_ref {
494+ ByRef :: Yes ( Mutability :: Mut ) => ".as_mut()" ,
495+ ByRef :: Yes ( Mutability :: Not ) => ".as_ref()" ,
496+ ByRef :: No => "" ,
497+ } ;
498+
499+ let body = snippet_with_applicability ( cx, if_then. span , ".." , & mut applicability) ;
500+ let mut body_lines = body. lines ( ) . map ( |line| & line[ line. len ( ) . min ( 4 ) ..] ) ;
501+ let ( _, _) = ( body_lines. next ( ) , body_lines. next_back ( ) ) ;
502+ let body_str = body_lines. join ( "\n " ) ;
503+
504+ let sugg = format ! ( "let {ident} = {receiver_str}{method_call_str}?;\n {body_str}" , ) ;
505+ span_lint_and_sugg (
506+ cx,
507+ QUESTION_MARK ,
508+ expr. span ,
509+ "this block may be rewritten with the `?` operator" ,
510+ "replace it with" ,
511+ sugg,
512+ applicability,
513+ ) ;
514+ }
515+ }
516+
459517impl QuestionMark {
460518 fn inside_try_block ( & self ) -> bool {
461519 self . try_block_depth_stack . last ( ) > Some ( & 0 )
@@ -531,6 +589,37 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
531589 self . try_block_depth_stack . push ( 0 ) ;
532590 }
533591
592+ // Defining check_fn instead of using check_body to avoid accidentally triggering on
593+ // const expressions, not sure if this is necessary
594+ fn check_fn (
595+ & mut self ,
596+ cx : & LateContext < ' tcx > ,
597+ _: FnKind < ' tcx > ,
598+ _: & ' tcx FnDecl < ' tcx > ,
599+ body : & ' tcx Body < ' tcx > ,
600+ _: Span ,
601+ _: LocalDefId ,
602+ ) {
603+ let return_expr = match body. value . kind {
604+ ExprKind :: Block ( block, _) => block. expr . or_else ( || {
605+ block. stmts . iter ( ) . last ( ) . and_then ( |stmt| {
606+ if let StmtKind :: Semi ( expr) = stmt. kind
607+ && let ExprKind :: Ret ( Some ( ret) ) = expr. kind
608+ {
609+ Some ( ret)
610+ } else {
611+ None
612+ }
613+ } )
614+ } ) ,
615+ _ => None ,
616+ } ;
617+
618+ if let Some ( expr) = return_expr {
619+ check_if_let_some_as_return_val ( cx, expr) ;
620+ }
621+ }
622+
534623 fn check_body_post ( & mut self , _: & LateContext < ' tcx > , _: & Body < ' tcx > ) {
535624 self . try_block_depth_stack . pop ( ) ;
536625 }
0 commit comments