1+ use crate :: manual_let_else:: { MatchLintBehaviour , MANUAL_LET_ELSE } ;
12use clippy_utils:: diagnostics:: span_lint_and_sugg;
3+ use clippy_utils:: msrvs:: Msrv ;
24use clippy_utils:: source:: snippet_with_applicability;
35use clippy_utils:: ty:: is_type_diagnostic_item;
46use clippy_utils:: {
5- eq_expr_value, get_parent_node, in_constant, is_else_clause, is_res_lang_ctor, path_to_local , path_to_local_id ,
6- peel_blocks, peel_blocks_with_stmt,
7+ eq_expr_value, get_parent_node, in_constant, is_else_clause, is_res_lang_ctor, pat_and_expr_can_be_question_mark ,
8+ path_to_local , path_to_local_id , peel_blocks, peel_blocks_with_stmt,
79} ;
810use clippy_utils:: { higher, is_path_lang_item} ;
911use if_chain:: if_chain;
1012use rustc_errors:: Applicability ;
1113use rustc_hir:: def:: Res ;
1214use rustc_hir:: LangItem :: { self , OptionNone , OptionSome , ResultErr , ResultOk } ;
13- use rustc_hir:: { BindingAnnotation , ByRef , Expr , ExprKind , Node , PatKind , PathSegment , QPath } ;
15+ use rustc_hir:: {
16+ BindingAnnotation , Block , ByRef , Expr , ExprKind , Local , Node , PatKind , PathSegment , QPath , Stmt , StmtKind ,
17+ } ;
1418use rustc_lint:: { LateContext , LateLintPass } ;
1519use rustc_middle:: ty:: Ty ;
1620use rustc_session:: declare_tool_lint;
@@ -42,16 +46,29 @@ declare_clippy_lint! {
4246 "checks for expressions that could be replaced by the question mark operator"
4347}
4448
45- #[ derive( Default ) ]
4649pub struct QuestionMark {
50+ pub ( crate ) msrv : Msrv ,
51+ pub ( crate ) matches_behaviour : MatchLintBehaviour ,
4752 /// Keeps track of how many try blocks we are in at any point during linting.
4853 /// This allows us to answer the question "are we inside of a try block"
4954 /// very quickly, without having to walk up the parent chain, by simply checking
5055 /// if it is greater than zero.
5156 /// As for why we need this in the first place: <https://github.com/rust-lang/rust-clippy/issues/8628>
5257 try_block_depth_stack : Vec < u32 > ,
5358}
54- impl_lint_pass ! ( QuestionMark => [ QUESTION_MARK ] ) ;
59+
60+ impl_lint_pass ! ( QuestionMark => [ QUESTION_MARK , MANUAL_LET_ELSE ] ) ;
61+
62+ impl QuestionMark {
63+ #[ must_use]
64+ pub fn new ( msrv : Msrv , matches_behaviour : MatchLintBehaviour ) -> Self {
65+ Self {
66+ msrv,
67+ matches_behaviour,
68+ try_block_depth_stack : Vec :: new ( ) ,
69+ }
70+ }
71+ }
5572
5673enum IfBlockType < ' hir > {
5774 /// An `if x.is_xxx() { a } else { b } ` expression.
@@ -78,6 +95,29 @@ enum IfBlockType<'hir> {
7895 ) ,
7996}
8097
98+ fn check_let_some_else_return_none ( cx : & LateContext < ' _ > , stmt : & Stmt < ' _ > ) {
99+ if let StmtKind :: Local ( Local { pat, init : Some ( init_expr) , els : Some ( els) , .. } ) = stmt. kind &&
100+ let Block { stmts : & [ ] , expr : Some ( els) , .. } = els &&
101+ let Some ( inner_pat) = pat_and_expr_can_be_question_mark ( cx, pat, els)
102+ {
103+ let mut applicability = Applicability :: MaybeIncorrect ;
104+ let init_expr_str = snippet_with_applicability ( cx, init_expr. span , ".." , & mut applicability) ;
105+ let receiver_str = snippet_with_applicability ( cx, inner_pat. span , ".." , & mut applicability) ;
106+ let sugg = format ! (
107+ "let {receiver_str} = {init_expr_str}?;" ,
108+ ) ;
109+ span_lint_and_sugg (
110+ cx,
111+ QUESTION_MARK ,
112+ stmt. span ,
113+ "this `let...else` may be rewritten with the `?` operator" ,
114+ "replace it with" ,
115+ sugg,
116+ applicability,
117+ ) ;
118+ }
119+ }
120+
81121fn is_early_return ( smbl : Symbol , cx : & LateContext < ' _ > , if_block : & IfBlockType < ' _ > ) -> bool {
82122 match * if_block {
83123 IfBlockType :: IfIs ( caller, caller_ty, call_sym, if_then, _) => {
@@ -259,6 +299,12 @@ fn is_try_block(cx: &LateContext<'_>, bl: &rustc_hir::Block<'_>) -> bool {
259299}
260300
261301impl < ' tcx > LateLintPass < ' tcx > for QuestionMark {
302+ fn check_stmt ( & mut self , cx : & LateContext < ' tcx > , stmt : & ' tcx Stmt < ' _ > ) {
303+ if !in_constant ( cx, stmt. hir_id ) {
304+ check_let_some_else_return_none ( cx, stmt) ;
305+ }
306+ self . check_manual_let_else ( cx, stmt) ;
307+ }
262308 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
263309 if !in_constant ( cx, expr. hir_id ) {
264310 self . check_is_none_or_err_and_early_return ( cx, expr) ;
@@ -291,4 +337,5 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
291337 . expect ( "blocks are always part of bodies and must have a depth" ) -= 1 ;
292338 }
293339 }
340+ extract_msrv_attr ! ( LateContext ) ;
294341}
0 commit comments