@@ -2,9 +2,9 @@ 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 } ;
7- use clippy_utils:: source:: snippet_with_applicability;
7+ use clippy_utils:: source:: { reindent_multiline , snippet_indent , snippet_with_applicability, snippet_with_context } ;
88use clippy_utils:: sugg:: Sugg ;
99use clippy_utils:: ty:: { implements_trait, is_copy, is_type_diagnostic_item} ;
1010use clippy_utils:: usage:: local_used_after_expr;
@@ -16,13 +16,15 @@ use clippy_utils::{
1616use rustc_errors:: Applicability ;
1717use rustc_hir:: LangItem :: { self , OptionNone , OptionSome , ResultErr , ResultOk } ;
1818use rustc_hir:: def:: Res ;
19+ use rustc_hir:: def_id:: LocalDefId ;
1920use rustc_hir:: {
20- Arm , BindingMode , Block , Body , ByRef , Expr , ExprKind , FnRetTy , HirId , LetStmt , MatchSource , Mutability , Node , Pat ,
21- PatKind , PathSegment , QPath , Stmt , StmtKind ,
21+ Arm , BindingMode , Block , Body , ByRef , Expr , ExprKind , FnDecl , FnRetTy , HirId , LetStmt , MatchSource , Mutability ,
22+ Node , Pat , PatKind , PathSegment , QPath , Stmt , StmtKind , intravisit ,
2223} ;
23- use rustc_lint:: { LateContext , LateLintPass } ;
24+ use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
2425use rustc_middle:: ty:: { self , Ty } ;
2526use rustc_session:: impl_lint_pass;
27+ use rustc_span:: Span ;
2628use rustc_span:: symbol:: Symbol ;
2729
2830declare_clippy_lint ! {
@@ -515,6 +517,63 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr:
515517 }
516518}
517519
520+ fn check_if_let_some_as_return_val < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
521+ if !expr. span . in_external_macro ( cx. sess ( ) . source_map ( ) )
522+ && let Some ( higher:: IfLet {
523+ let_pat,
524+ let_expr,
525+ if_then,
526+ if_else,
527+ ..
528+ } ) = higher:: IfLet :: hir ( cx, expr)
529+ && let PatKind :: TupleStruct ( ref path1, [ field] , ddpos) = let_pat. kind
530+ && ddpos. as_opt_usize ( ) . is_none ( )
531+ && let PatKind :: Binding ( BindingMode ( by_ref, _) , _, ident, None ) = field. kind
532+ && let caller_ty = cx. typeck_results ( ) . expr_ty ( let_expr)
533+ && let if_block = IfBlockType :: IfLet (
534+ cx. qpath_res ( path1, let_pat. hir_id ) ,
535+ caller_ty,
536+ ident. name ,
537+ let_expr,
538+ if_then,
539+ if_else,
540+ )
541+ && let ExprKind :: Block ( if_then_block, _) = if_then. kind
542+ // Don't consider case where if-then branch has only one statement/expression
543+ && ( if_then_block. stmts . len ( ) >= 2 || if_then_block. stmts . len ( ) == 1 && if_then_block. expr . is_some ( ) )
544+ && is_early_return ( sym:: Option , cx, & if_block)
545+ {
546+ let mut applicability = Applicability :: MachineApplicable ;
547+ let receiver_str = snippet_with_context ( cx, let_expr. span , expr. span . ctxt ( ) , ".." , & mut applicability) . 0 ;
548+ let method_call_str = match by_ref {
549+ ByRef :: Yes ( Mutability :: Mut ) => ".as_mut()" ,
550+ ByRef :: Yes ( Mutability :: Not ) => ".as_ref()" ,
551+ ByRef :: No => "" ,
552+ } ;
553+ let indent = snippet_indent ( cx, if_then. span ) . unwrap_or_default ( ) ;
554+ let body_str = {
555+ let snippet = snippet_with_applicability ( cx, if_then. span , ".." , & mut applicability) ;
556+ let Some ( snippet) = snippet. strip_prefix ( '{' ) . and_then ( |s| s. strip_suffix ( '}' ) ) else {
557+ return ;
558+ } ;
559+ // There probably was a newline before the `}`, and it adds an extra line to the suggestion
560+ // -- remove it
561+ let snippet = snippet. trim_end ( ) ;
562+ reindent_multiline ( snippet, true , Some ( indent. len ( ) ) )
563+ } ;
564+ let sugg = format ! ( "let {ident} = {receiver_str}{method_call_str}?;{body_str}" ) ;
565+ span_lint_and_then (
566+ cx,
567+ QUESTION_MARK ,
568+ expr. span ,
569+ "this block may be rewritten with the `?` operator" ,
570+ |diag| {
571+ diag. span_suggestion_verbose ( expr. span , "replace it with" , sugg, applicability) ;
572+ } ,
573+ ) ;
574+ }
575+ }
576+
518577impl QuestionMark {
519578 fn inside_try_block ( & self ) -> bool {
520579 self . try_block_depth_stack . last ( ) > Some ( & 0 )
@@ -594,6 +653,42 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
594653 self . try_block_depth_stack . push ( 0 ) ;
595654 }
596655
656+ // Defining `check_fn` instead of using `check_body` to avoid accidentally triggering on
657+ // const expressions
658+ fn check_fn (
659+ & mut self ,
660+ cx : & LateContext < ' tcx > ,
661+ _: intravisit:: FnKind < ' tcx > ,
662+ _: & ' tcx FnDecl < ' tcx > ,
663+ body : & ' tcx Body < ' tcx > ,
664+ _: Span ,
665+ local_def_id : LocalDefId ,
666+ ) {
667+ if !is_in_const_context ( cx)
668+ && is_lint_allowed ( cx, QUESTION_MARK_USED , HirId :: make_owner ( local_def_id) )
669+ && self . msrv . meets ( cx, msrvs:: QUESTION_MARK_OPERATOR )
670+ && let Some ( return_expr) = match body. value . kind {
671+ #[ expect(
672+ clippy:: unnecessary_lazy_evaluations,
673+ reason = "there are a bunch of if-let's, why should we evaluate them eagerly? Arguably an FP"
674+ ) ]
675+ ExprKind :: Block ( block, _) => block. expr . or_else ( || {
676+ if let [ .., stmt] = block. stmts
677+ && let StmtKind :: Semi ( expr) = stmt. kind
678+ && let ExprKind :: Ret ( Some ( ret) ) = expr. kind
679+ {
680+ Some ( ret)
681+ } else {
682+ None
683+ }
684+ } ) ,
685+ _ => None ,
686+ }
687+ {
688+ check_if_let_some_as_return_val ( cx, return_expr) ;
689+ }
690+ }
691+
597692 fn check_body_post ( & mut self , _: & LateContext < ' tcx > , _: & Body < ' tcx > ) {
598693 self . try_block_depth_stack . pop ( ) ;
599694 }
0 commit comments