1- use crate :: manual_let_else:: pat_and_expr_can_be_question_mark ;
1+ use crate :: manual_let_else:: { MatchLintBehaviour , MANUAL_LET_ELSE } ;
22use clippy_utils:: diagnostics:: span_lint_and_sugg;
3+ use clippy_utils:: msrvs:: Msrv ;
34use clippy_utils:: source:: snippet_with_applicability;
45use clippy_utils:: ty:: is_type_diagnostic_item;
56use clippy_utils:: {
6- eq_expr_value, get_parent_node, in_constant, is_else_clause, is_res_lang_ctor , path_to_local , path_to_local_id ,
7- peel_blocks, peel_blocks_with_stmt,
7+ eq_expr_value, get_parent_node, in_constant, is_else_clause, is_refutable , is_res_lang_ctor , path_to_local ,
8+ path_to_local_id , peel_blocks, peel_blocks_with_stmt,
89} ;
910use clippy_utils:: { higher, is_path_lang_item} ;
1011use if_chain:: if_chain;
1112use rustc_errors:: Applicability ;
1213use rustc_hir:: def:: Res ;
1314use rustc_hir:: LangItem :: { self , OptionNone , OptionSome , ResultErr , ResultOk } ;
1415use rustc_hir:: {
15- BindingAnnotation , Block , ByRef , Expr , ExprKind , Local , Node , PatKind , PathSegment , QPath , Stmt , StmtKind ,
16+ BindingAnnotation , Block , ByRef , Expr , ExprKind , Local , Node , Pat , PatKind , PathSegment , QPath , Stmt , StmtKind ,
1617} ;
1718use rustc_lint:: { LateContext , LateLintPass } ;
1819use rustc_middle:: ty:: Ty ;
@@ -45,16 +46,29 @@ declare_clippy_lint! {
4546 "checks for expressions that could be replaced by the question mark operator"
4647}
4748
48- #[ derive( Default ) ]
4949pub struct QuestionMark {
50+ pub ( crate ) msrv : Msrv ,
51+ pub ( crate ) matches_behaviour : MatchLintBehaviour ,
5052 /// Keeps track of how many try blocks we are in at any point during linting.
5153 /// This allows us to answer the question "are we inside of a try block"
5254 /// very quickly, without having to walk up the parent chain, by simply checking
5355 /// if it is greater than zero.
5456 /// As for why we need this in the first place: <https://github.com/rust-lang/rust-clippy/issues/8628>
5557 try_block_depth_stack : Vec < u32 > ,
5658}
57- 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+ }
5872
5973enum IfBlockType < ' hir > {
6074 /// An `if x.is_xxx() { a } else { b } ` expression.
@@ -81,6 +95,50 @@ enum IfBlockType<'hir> {
8195 ) ,
8296}
8397
98+ /// Returns whether the given let pattern and else body can be turned into a question mark
99+ ///
100+ /// For this example:
101+ /// ```ignore
102+ /// let FooBar { a, b } = if let Some(a) = ex { a } else { return None };
103+ /// ```
104+ /// We get as parameters:
105+ /// ```ignore
106+ /// pat: Some(a)
107+ /// else_body: return None
108+ /// ```
109+
110+ /// And for this example:
111+ /// ```ignore
112+ /// let Some(FooBar { a, b }) = ex else { return None };
113+ /// ```
114+ /// We get as parameters:
115+ /// ```ignore
116+ /// pat: Some(FooBar { a, b })
117+ /// else_body: return None
118+ /// ```
119+
120+ /// We output `Some(a)` in the first instance, and `Some(FooBar { a, b })` in the second, because
121+ /// the question mark operator is applicable here. Callers have to check whether we are in a
122+ /// constant or not.
123+ pub ( crate ) fn pat_and_expr_can_be_question_mark < ' a , ' hir > (
124+ cx : & LateContext < ' _ > ,
125+ pat : & ' a Pat < ' hir > ,
126+ else_body : & Expr < ' _ > ,
127+ ) -> Option < & ' a Pat < ' hir > > {
128+ if let PatKind :: TupleStruct ( pat_path, [ inner_pat] , _) = pat. kind &&
129+ is_res_lang_ctor ( cx, cx. qpath_res ( & pat_path, pat. hir_id ) , OptionSome ) &&
130+ !is_refutable ( cx, inner_pat) &&
131+ let else_body = peel_blocks ( else_body) &&
132+ let ExprKind :: Ret ( Some ( ret_val) ) = else_body. kind &&
133+ let ExprKind :: Path ( ret_path) = ret_val. kind &&
134+ is_res_lang_ctor ( cx, cx. qpath_res ( & ret_path, ret_val. hir_id ) , OptionNone )
135+ {
136+ Some ( inner_pat)
137+ } else {
138+ None
139+ }
140+ }
141+
84142fn check_let_some_else_return_none ( cx : & LateContext < ' _ > , stmt : & Stmt < ' _ > ) {
85143 if let StmtKind :: Local ( Local { pat, init : Some ( init_expr) , els : Some ( els) , .. } ) = stmt. kind &&
86144 let Block { stmts : & [ ] , expr : Some ( els) , .. } = els &&
@@ -289,6 +347,7 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
289347 if !in_constant ( cx, stmt. hir_id ) {
290348 check_let_some_else_return_none ( cx, stmt) ;
291349 }
350+ self . check_manual_let_else ( cx, stmt) ;
292351 }
293352 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
294353 if !in_constant ( cx, expr. hir_id ) {
@@ -322,4 +381,5 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
322381 . expect ( "blocks are always part of bodies and must have a depth" ) -= 1 ;
323382 }
324383 }
384+ extract_msrv_attr ! ( LateContext ) ;
325385}
0 commit comments