@@ -8,18 +8,18 @@ use clippy_utils::source::snippet_with_applicability;
8
8
use clippy_utils:: ty:: { implements_trait, is_type_diagnostic_item} ;
9
9
use clippy_utils:: {
10
10
eq_expr_value, higher, is_else_clause, is_in_const_context, is_lint_allowed, is_path_lang_item, is_res_lang_ctor,
11
- pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
11
+ pat_and_expr_can_be_question_mark, path_res , path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
12
12
span_contains_comment,
13
13
} ;
14
14
use rustc_errors:: Applicability ;
15
15
use rustc_hir:: LangItem :: { self , OptionNone , OptionSome , ResultErr , ResultOk } ;
16
16
use rustc_hir:: def:: Res ;
17
17
use rustc_hir:: {
18
- BindingMode , Block , Body , ByRef , Expr , ExprKind , LetStmt , Mutability , Node , PatKind , PathSegment , QPath , Stmt ,
19
- StmtKind ,
18
+ Arm , BindingMode , Block , Body , ByRef , Expr , ExprKind , HirId , LetStmt , MatchSource , Mutability , Node , Pat , PatKind ,
19
+ PathSegment , QPath , Stmt , StmtKind ,
20
20
} ;
21
21
use rustc_lint:: { LateContext , LateLintPass } ;
22
- use rustc_middle:: ty:: Ty ;
22
+ use rustc_middle:: ty:: { self , Ty } ;
23
23
use rustc_session:: impl_lint_pass;
24
24
use rustc_span:: sym;
25
25
use rustc_span:: symbol:: Symbol ;
@@ -271,6 +271,140 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex
271
271
}
272
272
}
273
273
274
+ #[ derive( Clone , Copy , Debug ) ]
275
+ enum TryMode {
276
+ Result ,
277
+ Option ,
278
+ }
279
+
280
+ fn find_try_mode < ' tcx > ( cx : & LateContext < ' tcx > , scrutinee : & Expr < ' tcx > ) -> Option < TryMode > {
281
+ let scrutinee_ty = cx. typeck_results ( ) . expr_ty_adjusted ( scrutinee) ;
282
+ let ty:: Adt ( scrutinee_adt_def, _) = scrutinee_ty. kind ( ) else {
283
+ return None ;
284
+ } ;
285
+
286
+ match cx. tcx . get_diagnostic_name ( scrutinee_adt_def. did ( ) ) ? {
287
+ sym:: Result => Some ( TryMode :: Result ) ,
288
+ sym:: Option => Some ( TryMode :: Option ) ,
289
+ _ => None ,
290
+ }
291
+ }
292
+
293
+ // Check that `pat` is `{ctor_lang_item}(val)`, returning `val`.
294
+ fn extract_ctor_call < ' a , ' tcx > (
295
+ cx : & LateContext < ' tcx > ,
296
+ expected_ctor : LangItem ,
297
+ pat : & ' a Pat < ' tcx > ,
298
+ ) -> Option < & ' a Pat < ' tcx > > {
299
+ if let PatKind :: TupleStruct ( variant_path, [ val_binding] , _) = & pat. kind
300
+ && is_res_lang_ctor ( cx, cx. qpath_res ( variant_path, pat. hir_id ) , expected_ctor)
301
+ {
302
+ Some ( val_binding)
303
+ } else {
304
+ None
305
+ }
306
+ }
307
+
308
+ // Extracts the local ID of a plain `val` pattern.
309
+ fn extract_binding_pat ( pat : & Pat < ' _ > ) -> Option < HirId > {
310
+ if let PatKind :: Binding ( BindingMode :: NONE , binding, _, None ) = pat. kind {
311
+ Some ( binding)
312
+ } else {
313
+ None
314
+ }
315
+ }
316
+
317
+ // Check `arm` is `Ok(val) => val` or `Some(val) => val`.
318
+ fn check_arm_is_happy < ' tcx > ( cx : & LateContext < ' tcx > , mode : TryMode , arm : & Arm < ' tcx > ) -> bool {
319
+ let arm_body = arm. body . peel_blocks ( ) . peel_drop_temps ( ) ;
320
+ let happy_ctor = match mode {
321
+ TryMode :: Result => ResultOk ,
322
+ TryMode :: Option => OptionSome ,
323
+ } ;
324
+
325
+ // Check for `Ok(val)` or `Some(val)`
326
+ if arm. guard . is_none ( )
327
+ && let Some ( val_binding) = extract_ctor_call ( cx, happy_ctor, arm. pat )
328
+ // Extract out `val`
329
+ && let Some ( binding) = extract_binding_pat ( val_binding)
330
+ // Check body is just `=> val`
331
+ && let ExprKind :: Path ( body_path) = & arm_body. kind
332
+ && let Res :: Local ( body_local_ref) = cx. qpath_res ( body_path, arm_body. hir_id )
333
+ && binding == body_local_ref
334
+ {
335
+ true
336
+ } else {
337
+ false
338
+ }
339
+ }
340
+
341
+ // Check `arm` is `Err(err) => return Err(err)` or `None => return None`.
342
+ fn check_arm_is_sad < ' tcx > ( cx : & LateContext < ' tcx > , mode : TryMode , arm : & Arm < ' tcx > ) -> bool {
343
+ if arm. guard . is_some ( ) {
344
+ return false ;
345
+ }
346
+
347
+ let arm_body = arm. body . peel_blocks ( ) . peel_drop_temps ( ) ;
348
+ match mode {
349
+ TryMode :: Result => {
350
+ // Check that pat is Err(val)
351
+ if let Some ( ok_pat) = extract_ctor_call ( cx, ResultErr , arm. pat )
352
+ && let Some ( ok_val) = extract_binding_pat ( ok_pat)
353
+ // check `=> return Err(...)`
354
+ && let ExprKind :: Ret ( Some ( wrapped_ret_expr) ) = arm_body. kind
355
+ && let ExprKind :: Call ( ok_ctor, [ ret_expr] ) = wrapped_ret_expr. kind
356
+ && is_res_lang_ctor ( cx, path_res ( cx, ok_ctor) , ResultErr )
357
+ // check `...` is `val` from binding
358
+ && let Res :: Local ( ret_local_ref) = path_res ( cx, ret_expr)
359
+ && ok_val == ret_local_ref
360
+ {
361
+ true
362
+ } else {
363
+ false
364
+ }
365
+ } ,
366
+ TryMode :: Option => {
367
+ // Check the pat is `None`
368
+ if is_res_lang_ctor ( cx, path_res ( cx, arm. pat ) , OptionNone )
369
+ // Check `=> return None`
370
+ && let ExprKind :: Ret ( Some ( ret_expr) ) = arm_body. kind
371
+ && is_res_lang_ctor ( cx, path_res ( cx, ret_expr) , OptionNone )
372
+ {
373
+ true
374
+ } else {
375
+ false
376
+ }
377
+ } ,
378
+ }
379
+ }
380
+
381
+ fn check_arms_are_try < ' tcx > ( cx : & LateContext < ' tcx > , mode : TryMode , arm1 : & Arm < ' tcx > , arm2 : & Arm < ' tcx > ) -> bool {
382
+ ( check_arm_is_happy ( cx, mode, arm1) && check_arm_is_sad ( cx, mode, arm2) )
383
+ || ( check_arm_is_happy ( cx, mode, arm2) && check_arm_is_sad ( cx, mode, arm1) )
384
+ }
385
+
386
+ fn check_if_try_match < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
387
+ if let ExprKind :: Match ( scrutinee, [ arm1, arm2] , MatchSource :: Normal | MatchSource :: Postfix ) = expr. kind
388
+ && !expr. span . from_expansion ( )
389
+ && let Some ( mode) = find_try_mode ( cx, scrutinee)
390
+ && check_arms_are_try ( cx, mode, arm1, arm2)
391
+ {
392
+ let mut applicability = Applicability :: MachineApplicable ;
393
+ let mut snippet = snippet_with_applicability ( cx, scrutinee. span , "..." , & mut applicability) . into_owned ( ) ;
394
+ snippet. push ( '?' ) ;
395
+
396
+ span_lint_and_sugg (
397
+ cx,
398
+ QUESTION_MARK ,
399
+ expr. span ,
400
+ "this `match` expression can be replaced with `?`" ,
401
+ "try instead" ,
402
+ snippet,
403
+ applicability,
404
+ ) ;
405
+ }
406
+ }
407
+
274
408
fn check_if_let_some_or_err_and_early_return < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
275
409
if let Some ( higher:: IfLet {
276
410
let_pat,
@@ -350,11 +484,15 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
350
484
}
351
485
self . check_manual_let_else ( cx, stmt) ;
352
486
}
487
+
353
488
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
354
- if !self . inside_try_block ( ) && !is_in_const_context ( cx) && is_lint_allowed ( cx, QUESTION_MARK_USED , expr. hir_id )
355
- {
356
- check_is_none_or_err_and_early_return ( cx, expr) ;
357
- check_if_let_some_or_err_and_early_return ( cx, expr) ;
489
+ if !is_in_const_context ( cx) && is_lint_allowed ( cx, QUESTION_MARK_USED , expr. hir_id ) {
490
+ if !self . inside_try_block ( ) {
491
+ check_is_none_or_err_and_early_return ( cx, expr) ;
492
+ check_if_let_some_or_err_and_early_return ( cx, expr) ;
493
+ }
494
+
495
+ check_if_try_match ( cx, expr) ;
358
496
}
359
497
}
360
498
0 commit comments