1
- use crate :: manual_let_else:: pat_and_expr_can_be_question_mark ;
1
+ use crate :: manual_let_else:: { MatchLintBehaviour , MANUAL_LET_ELSE } ;
2
2
use clippy_utils:: diagnostics:: span_lint_and_sugg;
3
+ use clippy_utils:: msrvs:: Msrv ;
3
4
use clippy_utils:: source:: snippet_with_applicability;
4
5
use clippy_utils:: ty:: is_type_diagnostic_item;
5
6
use 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,
8
9
} ;
9
10
use clippy_utils:: { higher, is_path_lang_item} ;
10
11
use if_chain:: if_chain;
11
12
use rustc_errors:: Applicability ;
12
13
use rustc_hir:: def:: Res ;
13
14
use rustc_hir:: LangItem :: { self , OptionNone , OptionSome , ResultErr , ResultOk } ;
14
15
use 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 ,
16
17
} ;
17
18
use rustc_lint:: { LateContext , LateLintPass } ;
18
19
use rustc_middle:: ty:: Ty ;
@@ -45,16 +46,29 @@ declare_clippy_lint! {
45
46
"checks for expressions that could be replaced by the question mark operator"
46
47
}
47
48
48
- #[ derive( Default ) ]
49
49
pub struct QuestionMark {
50
+ pub ( crate ) msrv : Msrv ,
51
+ pub ( crate ) matches_behaviour : MatchLintBehaviour ,
50
52
/// Keeps track of how many try blocks we are in at any point during linting.
51
53
/// This allows us to answer the question "are we inside of a try block"
52
54
/// very quickly, without having to walk up the parent chain, by simply checking
53
55
/// if it is greater than zero.
54
56
/// As for why we need this in the first place: <https://github.com/rust-lang/rust-clippy/issues/8628>
55
57
try_block_depth_stack : Vec < u32 > ,
56
58
}
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
+ }
58
72
59
73
enum IfBlockType < ' hir > {
60
74
/// An `if x.is_xxx() { a } else { b } ` expression.
@@ -81,6 +95,50 @@ enum IfBlockType<'hir> {
81
95
) ,
82
96
}
83
97
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
+
84
142
fn check_let_some_else_return_none ( cx : & LateContext < ' _ > , stmt : & Stmt < ' _ > ) {
85
143
if let StmtKind :: Local ( Local { pat, init : Some ( init_expr) , els : Some ( els) , .. } ) = stmt. kind &&
86
144
let Block { stmts : & [ ] , expr : Some ( els) , .. } = els &&
@@ -289,6 +347,7 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
289
347
if !in_constant ( cx, stmt. hir_id ) {
290
348
check_let_some_else_return_none ( cx, stmt) ;
291
349
}
350
+ self . check_manual_let_else ( cx, stmt) ;
292
351
}
293
352
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
294
353
if !in_constant ( cx, expr. hir_id ) {
@@ -322,4 +381,5 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
322
381
. expect ( "blocks are always part of bodies and must have a depth" ) -= 1 ;
323
382
}
324
383
}
384
+ extract_msrv_attr ! ( LateContext ) ;
325
385
}
0 commit comments