@@ -2,10 +2,10 @@ use crate::manual_let_else::MANUAL_LET_ELSE;
22use crate :: question_mark_used:: QUESTION_MARK_USED ;
33use clippy_config:: Conf ;
44use clippy_config:: types:: MatchLintBehaviour ;
5- use clippy_utils:: diagnostics:: span_lint_and_sugg;
5+ use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then } ;
66use clippy_utils:: msrvs:: { self , Msrv } ;
77use clippy_utils:: res:: { MaybeDef , MaybeQPath , MaybeResPath } ;
8- use clippy_utils:: source:: snippet_with_applicability;
8+ use clippy_utils:: source:: { reindent_multiline , snippet_indent , snippet_with_applicability, snippet_with_context } ;
99use clippy_utils:: sugg:: Sugg ;
1010use clippy_utils:: ty:: { implements_trait, is_copy} ;
1111use clippy_utils:: usage:: local_used_after_expr;
@@ -17,13 +17,15 @@ use clippy_utils::{
1717use rustc_errors:: Applicability ;
1818use rustc_hir:: LangItem :: { self , OptionNone , OptionSome , ResultErr , ResultOk } ;
1919use rustc_hir:: def:: Res ;
20+ use rustc_hir:: def_id:: LocalDefId ;
2021use rustc_hir:: {
21- Arm , BindingMode , Block , Body , ByRef , Expr , ExprKind , FnRetTy , HirId , LetStmt , MatchSource , Mutability , Node , Pat ,
22- PatKind , PathSegment , QPath , Stmt , StmtKind ,
22+ Arm , BindingMode , Block , Body , ByRef , Expr , ExprKind , FnDecl , FnRetTy , HirId , LetStmt , MatchSource , Mutability ,
23+ Node , Pat , PatKind , PathSegment , QPath , Stmt , StmtKind , intravisit ,
2324} ;
24- use rustc_lint:: { LateContext , LateLintPass } ;
25+ use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
2526use rustc_middle:: ty:: { self , Ty } ;
2627use rustc_session:: impl_lint_pass;
28+ use rustc_span:: Span ;
2729use rustc_span:: symbol:: Symbol ;
2830
2931declare_clippy_lint ! {
@@ -524,6 +526,63 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr:
524526 }
525527}
526528
529+ fn check_if_let_some_as_return_val < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
530+ if !expr. span . in_external_macro ( cx. sess ( ) . source_map ( ) )
531+ && let Some ( higher:: IfLet {
532+ let_pat,
533+ let_expr,
534+ if_then,
535+ if_else,
536+ ..
537+ } ) = higher:: IfLet :: hir ( cx, expr)
538+ && let PatKind :: TupleStruct ( ref path1, [ field] , ddpos) = let_pat. kind
539+ && ddpos. as_opt_usize ( ) . is_none ( )
540+ && let PatKind :: Binding ( BindingMode ( by_ref, _) , _, ident, None ) = field. kind
541+ && let caller_ty = cx. typeck_results ( ) . expr_ty ( let_expr)
542+ && let if_block = IfBlockType :: IfLet (
543+ cx. qpath_res ( path1, let_pat. hir_id ) ,
544+ caller_ty,
545+ ident. name ,
546+ let_expr,
547+ if_then,
548+ if_else,
549+ )
550+ && let ExprKind :: Block ( if_then_block, _) = if_then. kind
551+ // Don't consider case where if-then branch has only one statement/expression
552+ && ( if_then_block. stmts . len ( ) >= 2 || if_then_block. stmts . len ( ) == 1 && if_then_block. expr . is_some ( ) )
553+ && is_early_return ( sym:: Option , cx, & if_block)
554+ {
555+ let mut applicability = Applicability :: MachineApplicable ;
556+ let receiver_str = snippet_with_context ( cx, let_expr. span , expr. span . ctxt ( ) , ".." , & mut applicability) . 0 ;
557+ let method_call_str = match by_ref {
558+ ByRef :: Yes ( Mutability :: Mut ) => ".as_mut()" ,
559+ ByRef :: Yes ( Mutability :: Not ) => ".as_ref()" ,
560+ ByRef :: No => "" ,
561+ } ;
562+ let indent = snippet_indent ( cx, if_then. span ) . unwrap_or_default ( ) ;
563+ let body_str = {
564+ let snippet = snippet_with_applicability ( cx, if_then. span , ".." , & mut applicability) ;
565+ let Some ( snippet) = snippet. strip_prefix ( '{' ) . and_then ( |s| s. strip_suffix ( '}' ) ) else {
566+ return ;
567+ } ;
568+ // There probably was a newline before the `}`, and it adds an extra line to the suggestion
569+ // -- remove it
570+ let snippet = snippet. trim_end ( ) ;
571+ reindent_multiline ( snippet, true , Some ( indent. len ( ) ) )
572+ } ;
573+ let sugg = format ! ( "let {ident} = {receiver_str}{method_call_str}?;{body_str}" ) ;
574+ span_lint_and_then (
575+ cx,
576+ QUESTION_MARK ,
577+ expr. span ,
578+ "this block may be rewritten with the `?` operator" ,
579+ |diag| {
580+ diag. span_suggestion_verbose ( expr. span , "replace it with" , sugg, applicability) ;
581+ } ,
582+ ) ;
583+ }
584+ }
585+
527586impl QuestionMark {
528587 fn inside_try_block ( & self ) -> bool {
529588 self . try_block_depth_stack . last ( ) > Some ( & 0 )
@@ -603,6 +662,42 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
603662 self . try_block_depth_stack . push ( 0 ) ;
604663 }
605664
665+ // Defining `check_fn` instead of using `check_body` to avoid accidentally triggering on
666+ // const expressions
667+ fn check_fn (
668+ & mut self ,
669+ cx : & LateContext < ' tcx > ,
670+ _: intravisit:: FnKind < ' tcx > ,
671+ _: & ' tcx FnDecl < ' tcx > ,
672+ body : & ' tcx Body < ' tcx > ,
673+ _: Span ,
674+ local_def_id : LocalDefId ,
675+ ) {
676+ if !is_in_const_context ( cx)
677+ && is_lint_allowed ( cx, QUESTION_MARK_USED , HirId :: make_owner ( local_def_id) )
678+ && self . msrv . meets ( cx, msrvs:: QUESTION_MARK_OPERATOR )
679+ && let Some ( return_expr) = match body. value . kind {
680+ #[ expect(
681+ clippy:: unnecessary_lazy_evaluations,
682+ reason = "there are a bunch of if-let's, why should we evaluate them eagerly? Arguably an FP"
683+ ) ]
684+ ExprKind :: Block ( block, _) => block. expr . or_else ( || {
685+ if let [ .., stmt] = block. stmts
686+ && let StmtKind :: Semi ( expr) = stmt. kind
687+ && let ExprKind :: Ret ( Some ( ret) ) = expr. kind
688+ {
689+ Some ( ret)
690+ } else {
691+ None
692+ }
693+ } ) ,
694+ _ => None ,
695+ }
696+ {
697+ check_if_let_some_as_return_val ( cx, return_expr) ;
698+ }
699+ }
700+
606701 fn check_body_post ( & mut self , _: & LateContext < ' tcx > , _: & Body < ' tcx > ) {
607702 self . try_block_depth_stack . pop ( ) ;
608703 }
0 commit comments