1
1
use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
- use clippy_utils:: higher;
3
2
use clippy_utils:: source:: snippet_with_applicability;
4
3
use clippy_utils:: ty:: is_type_diagnostic_item;
5
4
use clippy_utils:: {
6
5
eq_expr_value, get_parent_node, in_constant, is_else_clause, is_res_lang_ctor, path_to_local, path_to_local_id,
7
6
peel_blocks, peel_blocks_with_stmt,
8
7
} ;
8
+ use clippy_utils:: { higher, is_path_lang_item} ;
9
9
use if_chain:: if_chain;
10
10
use rustc_errors:: Applicability ;
11
11
use rustc_hir:: def:: Res ;
12
- use rustc_hir:: LangItem :: { OptionNone , OptionSome , ResultErr , ResultOk } ;
12
+ use rustc_hir:: LangItem :: { self , OptionNone , OptionSome , ResultErr , ResultOk } ;
13
13
use rustc_hir:: { BindingAnnotation , ByRef , Expr , ExprKind , Node , PatKind , PathSegment , QPath } ;
14
14
use rustc_lint:: { LateContext , LateLintPass } ;
15
15
use rustc_middle:: ty:: Ty ;
16
- use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
16
+ use rustc_session:: declare_tool_lint;
17
+ use rustc_session:: impl_lint_pass;
17
18
use rustc_span:: { sym, symbol:: Symbol } ;
18
19
19
20
declare_clippy_lint ! {
@@ -41,7 +42,16 @@ declare_clippy_lint! {
41
42
"checks for expressions that could be replaced by the question mark operator"
42
43
}
43
44
44
- declare_lint_pass ! ( QuestionMark => [ QUESTION_MARK ] ) ;
45
+ #[ derive( Default ) ]
46
+ pub struct QuestionMark {
47
+ /// Keeps track of how many try blocks we are in at any point during linting.
48
+ /// This allows us to answer the question "are we inside of a try block"
49
+ /// very quickly, without having to walk up the parent chain, by simply checking
50
+ /// if it is greater than zero.
51
+ /// As for why we need this in the first place: <https://github.com/rust-lang/rust-clippy/issues/8628>
52
+ try_block_depth_stack : Vec < u32 > ,
53
+ }
54
+ impl_lint_pass ! ( QuestionMark => [ QUESTION_MARK ] ) ;
45
55
46
56
enum IfBlockType < ' hir > {
47
57
/// An `if x.is_xxx() { a } else { b } ` expression.
@@ -68,98 +78,6 @@ enum IfBlockType<'hir> {
68
78
) ,
69
79
}
70
80
71
- /// Checks if the given expression on the given context matches the following structure:
72
- ///
73
- /// ```ignore
74
- /// if option.is_none() {
75
- /// return None;
76
- /// }
77
- /// ```
78
- ///
79
- /// ```ignore
80
- /// if result.is_err() {
81
- /// return result;
82
- /// }
83
- /// ```
84
- ///
85
- /// If it matches, it will suggest to use the question mark operator instead
86
- fn check_is_none_or_err_and_early_return < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
87
- if_chain ! {
88
- if let Some ( higher:: If { cond, then, r#else } ) = higher:: If :: hir( expr) ;
89
- if !is_else_clause( cx. tcx, expr) ;
90
- if let ExprKind :: MethodCall ( segment, caller, ..) = & cond. kind;
91
- let caller_ty = cx. typeck_results( ) . expr_ty( caller) ;
92
- let if_block = IfBlockType :: IfIs ( caller, caller_ty, segment. ident. name, then, r#else) ;
93
- if is_early_return( sym:: Option , cx, & if_block) || is_early_return( sym:: Result , cx, & if_block) ;
94
- then {
95
- let mut applicability = Applicability :: MachineApplicable ;
96
- let receiver_str = snippet_with_applicability( cx, caller. span, ".." , & mut applicability) ;
97
- let by_ref = !caller_ty. is_copy_modulo_regions( cx. tcx, cx. param_env) &&
98
- !matches!( caller. kind, ExprKind :: Call ( ..) | ExprKind :: MethodCall ( ..) ) ;
99
- let sugg = if let Some ( else_inner) = r#else {
100
- if eq_expr_value( cx, caller, peel_blocks( else_inner) ) {
101
- format!( "Some({receiver_str}?)" )
102
- } else {
103
- return ;
104
- }
105
- } else {
106
- format!( "{receiver_str}{}?;" , if by_ref { ".as_ref()" } else { "" } )
107
- } ;
108
-
109
- span_lint_and_sugg(
110
- cx,
111
- QUESTION_MARK ,
112
- expr. span,
113
- "this block may be rewritten with the `?` operator" ,
114
- "replace it with" ,
115
- sugg,
116
- applicability,
117
- ) ;
118
- }
119
- }
120
- }
121
-
122
- fn check_if_let_some_or_err_and_early_return < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
123
- if_chain ! {
124
- if let Some ( higher:: IfLet { let_pat, let_expr, if_then, if_else } ) = higher:: IfLet :: hir( cx, expr) ;
125
- if !is_else_clause( cx. tcx, expr) ;
126
- if let PatKind :: TupleStruct ( ref path1, [ field] , ddpos) = let_pat. kind;
127
- if ddpos. as_opt_usize( ) . is_none( ) ;
128
- if let PatKind :: Binding ( BindingAnnotation ( by_ref, _) , bind_id, ident, None ) = field. kind;
129
- let caller_ty = cx. typeck_results( ) . expr_ty( let_expr) ;
130
- let if_block = IfBlockType :: IfLet (
131
- cx. qpath_res( path1, let_pat. hir_id) ,
132
- caller_ty,
133
- ident. name,
134
- let_expr,
135
- if_then,
136
- if_else
137
- ) ;
138
- if ( is_early_return( sym:: Option , cx, & if_block) && path_to_local_id( peel_blocks( if_then) , bind_id) )
139
- || is_early_return( sym:: Result , cx, & if_block) ;
140
- if if_else. map( |e| eq_expr_value( cx, let_expr, peel_blocks( e) ) ) . filter( |e| * e) . is_none( ) ;
141
- then {
142
- let mut applicability = Applicability :: MachineApplicable ;
143
- let receiver_str = snippet_with_applicability( cx, let_expr. span, ".." , & mut applicability) ;
144
- let requires_semi = matches!( get_parent_node( cx. tcx, expr. hir_id) , Some ( Node :: Stmt ( _) ) ) ;
145
- let sugg = format!(
146
- "{receiver_str}{}?{}" ,
147
- if by_ref == ByRef :: Yes { ".as_ref()" } else { "" } ,
148
- if requires_semi { ";" } else { "" }
149
- ) ;
150
- span_lint_and_sugg(
151
- cx,
152
- QUESTION_MARK ,
153
- expr. span,
154
- "this block may be rewritten with the `?` operator" ,
155
- "replace it with" ,
156
- sugg,
157
- applicability,
158
- ) ;
159
- }
160
- }
161
- }
162
-
163
81
fn is_early_return ( smbl : Symbol , cx : & LateContext < ' _ > , if_block : & IfBlockType < ' _ > ) -> bool {
164
82
match * if_block {
165
83
IfBlockType :: IfIs ( caller, caller_ty, call_sym, if_then, _) => {
@@ -230,11 +148,147 @@ fn expr_return_none_or_err(
230
148
}
231
149
}
232
150
151
+ impl QuestionMark {
152
+ fn inside_try_block ( & self ) -> bool {
153
+ self . try_block_depth_stack . last ( ) > Some ( & 0 )
154
+ }
155
+
156
+ /// Checks if the given expression on the given context matches the following structure:
157
+ ///
158
+ /// ```ignore
159
+ /// if option.is_none() {
160
+ /// return None;
161
+ /// }
162
+ /// ```
163
+ ///
164
+ /// ```ignore
165
+ /// if result.is_err() {
166
+ /// return result;
167
+ /// }
168
+ /// ```
169
+ ///
170
+ /// If it matches, it will suggest to use the question mark operator instead
171
+ fn check_is_none_or_err_and_early_return < ' tcx > ( & self , cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
172
+ if_chain ! {
173
+ if !self . inside_try_block( ) ;
174
+ if let Some ( higher:: If { cond, then, r#else } ) = higher:: If :: hir( expr) ;
175
+ if !is_else_clause( cx. tcx, expr) ;
176
+ if let ExprKind :: MethodCall ( segment, caller, ..) = & cond. kind;
177
+ let caller_ty = cx. typeck_results( ) . expr_ty( caller) ;
178
+ let if_block = IfBlockType :: IfIs ( caller, caller_ty, segment. ident. name, then, r#else) ;
179
+ if is_early_return( sym:: Option , cx, & if_block) || is_early_return( sym:: Result , cx, & if_block) ;
180
+ then {
181
+ let mut applicability = Applicability :: MachineApplicable ;
182
+ let receiver_str = snippet_with_applicability( cx, caller. span, ".." , & mut applicability) ;
183
+ let by_ref = !caller_ty. is_copy_modulo_regions( cx. tcx, cx. param_env) &&
184
+ !matches!( caller. kind, ExprKind :: Call ( ..) | ExprKind :: MethodCall ( ..) ) ;
185
+ let sugg = if let Some ( else_inner) = r#else {
186
+ if eq_expr_value( cx, caller, peel_blocks( else_inner) ) {
187
+ format!( "Some({receiver_str}?)" )
188
+ } else {
189
+ return ;
190
+ }
191
+ } else {
192
+ format!( "{receiver_str}{}?;" , if by_ref { ".as_ref()" } else { "" } )
193
+ } ;
194
+
195
+ span_lint_and_sugg(
196
+ cx,
197
+ QUESTION_MARK ,
198
+ expr. span,
199
+ "this block may be rewritten with the `?` operator" ,
200
+ "replace it with" ,
201
+ sugg,
202
+ applicability,
203
+ ) ;
204
+ }
205
+ }
206
+ }
207
+
208
+ fn check_if_let_some_or_err_and_early_return < ' tcx > ( & self , cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
209
+ if_chain ! {
210
+ if !self . inside_try_block( ) ;
211
+ if let Some ( higher:: IfLet { let_pat, let_expr, if_then, if_else } ) = higher:: IfLet :: hir( cx, expr) ;
212
+ if !is_else_clause( cx. tcx, expr) ;
213
+ if let PatKind :: TupleStruct ( ref path1, [ field] , ddpos) = let_pat. kind;
214
+ if ddpos. as_opt_usize( ) . is_none( ) ;
215
+ if let PatKind :: Binding ( BindingAnnotation ( by_ref, _) , bind_id, ident, None ) = field. kind;
216
+ let caller_ty = cx. typeck_results( ) . expr_ty( let_expr) ;
217
+ let if_block = IfBlockType :: IfLet (
218
+ cx. qpath_res( path1, let_pat. hir_id) ,
219
+ caller_ty,
220
+ ident. name,
221
+ let_expr,
222
+ if_then,
223
+ if_else
224
+ ) ;
225
+ if ( is_early_return( sym:: Option , cx, & if_block) && path_to_local_id( peel_blocks( if_then) , bind_id) )
226
+ || is_early_return( sym:: Result , cx, & if_block) ;
227
+ if if_else. map( |e| eq_expr_value( cx, let_expr, peel_blocks( e) ) ) . filter( |e| * e) . is_none( ) ;
228
+ then {
229
+ let mut applicability = Applicability :: MachineApplicable ;
230
+ let receiver_str = snippet_with_applicability( cx, let_expr. span, ".." , & mut applicability) ;
231
+ let requires_semi = matches!( get_parent_node( cx. tcx, expr. hir_id) , Some ( Node :: Stmt ( _) ) ) ;
232
+ let sugg = format!(
233
+ "{receiver_str}{}?{}" ,
234
+ if by_ref == ByRef :: Yes { ".as_ref()" } else { "" } ,
235
+ if requires_semi { ";" } else { "" }
236
+ ) ;
237
+ span_lint_and_sugg(
238
+ cx,
239
+ QUESTION_MARK ,
240
+ expr. span,
241
+ "this block may be rewritten with the `?` operator" ,
242
+ "replace it with" ,
243
+ sugg,
244
+ applicability,
245
+ ) ;
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ fn is_try_block ( cx : & LateContext < ' _ > , bl : & rustc_hir:: Block < ' _ > ) -> bool {
252
+ if let Some ( expr) = bl. expr
253
+ && let rustc_hir:: ExprKind :: Call ( callee, _) = expr. kind
254
+ {
255
+ is_path_lang_item ( cx, callee, LangItem :: TryTraitFromOutput )
256
+ } else {
257
+ false
258
+ }
259
+ }
260
+
233
261
impl < ' tcx > LateLintPass < ' tcx > for QuestionMark {
234
262
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
235
263
if !in_constant ( cx, expr. hir_id ) {
236
- check_is_none_or_err_and_early_return ( cx, expr) ;
237
- check_if_let_some_or_err_and_early_return ( cx, expr) ;
264
+ self . check_is_none_or_err_and_early_return ( cx, expr) ;
265
+ self . check_if_let_some_or_err_and_early_return ( cx, expr) ;
266
+ }
267
+ }
268
+
269
+ fn check_block ( & mut self , cx : & LateContext < ' tcx > , block : & ' tcx rustc_hir:: Block < ' tcx > ) {
270
+ if is_try_block ( cx, block) {
271
+ * self
272
+ . try_block_depth_stack
273
+ . last_mut ( )
274
+ . expect ( "blocks are always part of bodies and must have a depth" ) += 1 ;
275
+ }
276
+ }
277
+
278
+ fn check_body ( & mut self , _: & LateContext < ' tcx > , _: & ' tcx rustc_hir:: Body < ' tcx > ) {
279
+ self . try_block_depth_stack . push ( 0 ) ;
280
+ }
281
+
282
+ fn check_body_post ( & mut self , _: & LateContext < ' tcx > , _: & ' tcx rustc_hir:: Body < ' tcx > ) {
283
+ self . try_block_depth_stack . pop ( ) ;
284
+ }
285
+
286
+ fn check_block_post ( & mut self , cx : & LateContext < ' tcx > , block : & ' tcx rustc_hir:: Block < ' tcx > ) {
287
+ if is_try_block ( cx, block) {
288
+ * self
289
+ . try_block_depth_stack
290
+ . last_mut ( )
291
+ . expect ( "blocks are always part of bodies and must have a depth" ) -= 1 ;
238
292
}
239
293
}
240
294
}
0 commit comments