@@ -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,
12
- span_contains_comment,
11
+ pat_and_expr_can_be_question_mark, path_res , path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
12
+ span_contains_cfg , 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 , FnRetTy , HirId , LetStmt , MatchSource , Mutability , Node , Pat ,
19
+ PatKind , 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 ;
@@ -58,6 +58,9 @@ pub struct QuestionMark {
58
58
/// if it is greater than zero.
59
59
/// As for why we need this in the first place: <https://github.com/rust-lang/rust-clippy/issues/8628>
60
60
try_block_depth_stack : Vec < u32 > ,
61
+ /// Keeps track of the number of inferred return type closures we are inside, to avoid problems
62
+ /// with the `Err(x.into())` expansion being ambiguious.
63
+ inferred_ret_closure_stack : u16 ,
61
64
}
62
65
63
66
impl_lint_pass ! ( QuestionMark => [ QUESTION_MARK , MANUAL_LET_ELSE ] ) ;
@@ -68,6 +71,7 @@ impl QuestionMark {
68
71
msrv : conf. msrv . clone ( ) ,
69
72
matches_behaviour : conf. matches_for_let_else ,
70
73
try_block_depth_stack : Vec :: new ( ) ,
74
+ inferred_ret_closure_stack : 0 ,
71
75
}
72
76
}
73
77
}
@@ -271,6 +275,135 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex
271
275
}
272
276
}
273
277
278
+ #[ derive( Clone , Copy , Debug ) ]
279
+ enum TryMode {
280
+ Result ,
281
+ Option ,
282
+ }
283
+
284
+ fn find_try_mode < ' tcx > ( cx : & LateContext < ' tcx > , scrutinee : & Expr < ' tcx > ) -> Option < TryMode > {
285
+ let scrutinee_ty = cx. typeck_results ( ) . expr_ty_adjusted ( scrutinee) ;
286
+ let ty:: Adt ( scrutinee_adt_def, _) = scrutinee_ty. kind ( ) else {
287
+ return None ;
288
+ } ;
289
+
290
+ match cx. tcx . get_diagnostic_name ( scrutinee_adt_def. did ( ) ) ? {
291
+ sym:: Result => Some ( TryMode :: Result ) ,
292
+ sym:: Option => Some ( TryMode :: Option ) ,
293
+ _ => None ,
294
+ }
295
+ }
296
+
297
+ // Check that `pat` is `{ctor_lang_item}(val)`, returning `val`.
298
+ fn extract_ctor_call < ' a , ' tcx > (
299
+ cx : & LateContext < ' tcx > ,
300
+ expected_ctor : LangItem ,
301
+ pat : & ' a Pat < ' tcx > ,
302
+ ) -> Option < & ' a Pat < ' tcx > > {
303
+ if let PatKind :: TupleStruct ( variant_path, [ val_binding] , _) = & pat. kind
304
+ && is_res_lang_ctor ( cx, cx. qpath_res ( variant_path, pat. hir_id ) , expected_ctor)
305
+ {
306
+ Some ( val_binding)
307
+ } else {
308
+ None
309
+ }
310
+ }
311
+
312
+ // Extracts the local ID of a plain `val` pattern.
313
+ fn extract_binding_pat ( pat : & Pat < ' _ > ) -> Option < HirId > {
314
+ if let PatKind :: Binding ( BindingMode :: NONE , binding, _, None ) = pat. kind {
315
+ Some ( binding)
316
+ } else {
317
+ None
318
+ }
319
+ }
320
+
321
+ fn check_arm_is_some_or_ok < ' tcx > ( cx : & LateContext < ' tcx > , mode : TryMode , arm : & Arm < ' tcx > ) -> bool {
322
+ let happy_ctor = match mode {
323
+ TryMode :: Result => ResultOk ,
324
+ TryMode :: Option => OptionSome ,
325
+ } ;
326
+
327
+ // Check for `Ok(val)` or `Some(val)`
328
+ if arm. guard . is_none ( )
329
+ && let Some ( val_binding) = extract_ctor_call ( cx, happy_ctor, arm. pat )
330
+ // Extract out `val`
331
+ && let Some ( binding) = extract_binding_pat ( val_binding)
332
+ // Check body is just `=> val`
333
+ && path_to_local_id ( peel_blocks ( arm. body ) , binding)
334
+ {
335
+ true
336
+ } else {
337
+ false
338
+ }
339
+ }
340
+
341
+ fn check_arm_is_none_or_err < ' tcx > ( cx : & LateContext < ' tcx > , mode : TryMode , arm : & Arm < ' tcx > ) -> bool {
342
+ if arm. guard . is_some ( ) {
343
+ return false ;
344
+ }
345
+
346
+ let arm_body = peel_blocks ( arm. body ) ;
347
+ match mode {
348
+ TryMode :: Result => {
349
+ // Check that pat is Err(val)
350
+ if let Some ( ok_pat) = extract_ctor_call ( cx, ResultErr , arm. pat )
351
+ && let Some ( ok_val) = extract_binding_pat ( ok_pat)
352
+ // check `=> return Err(...)`
353
+ && let ExprKind :: Ret ( Some ( wrapped_ret_expr) ) = arm_body. kind
354
+ && let ExprKind :: Call ( ok_ctor, [ ret_expr] ) = wrapped_ret_expr. kind
355
+ && is_res_lang_ctor ( cx, path_res ( cx, ok_ctor) , ResultErr )
356
+ // check `...` is `val` from binding
357
+ && path_to_local_id ( ret_expr, ok_val)
358
+ {
359
+ true
360
+ } else {
361
+ false
362
+ }
363
+ } ,
364
+ TryMode :: Option => {
365
+ // Check the pat is `None`
366
+ if is_res_lang_ctor ( cx, path_res ( cx, arm. pat ) , OptionNone )
367
+ // Check `=> return None`
368
+ && let ExprKind :: Ret ( Some ( ret_expr) ) = arm_body. kind
369
+ && is_res_lang_ctor ( cx, path_res ( cx, ret_expr) , OptionNone )
370
+ && !ret_expr. span . from_expansion ( )
371
+ {
372
+ true
373
+ } else {
374
+ false
375
+ }
376
+ } ,
377
+ }
378
+ }
379
+
380
+ fn check_arms_are_try < ' tcx > ( cx : & LateContext < ' tcx > , mode : TryMode , arm1 : & Arm < ' tcx > , arm2 : & Arm < ' tcx > ) -> bool {
381
+ ( check_arm_is_some_or_ok ( cx, mode, arm1) && check_arm_is_none_or_err ( cx, mode, arm2) )
382
+ || ( check_arm_is_some_or_ok ( cx, mode, arm2) && check_arm_is_none_or_err ( cx, mode, arm1) )
383
+ }
384
+
385
+ fn check_if_try_match < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
386
+ if let ExprKind :: Match ( scrutinee, [ arm1, arm2] , MatchSource :: Normal | MatchSource :: Postfix ) = expr. kind
387
+ && !expr. span . from_expansion ( )
388
+ && let Some ( mode) = find_try_mode ( cx, scrutinee)
389
+ && !span_contains_cfg ( cx, expr. span )
390
+ && check_arms_are_try ( cx, mode, arm1, arm2)
391
+ {
392
+ let mut applicability = Applicability :: MachineApplicable ;
393
+ let snippet = snippet_with_applicability ( cx, scrutinee. span . source_callsite ( ) , ".." , & mut applicability) ;
394
+
395
+ span_lint_and_sugg (
396
+ cx,
397
+ QUESTION_MARK ,
398
+ expr. span ,
399
+ "this `match` expression can be replaced with `?`" ,
400
+ "try instead" ,
401
+ snippet. into_owned ( ) + "?" ,
402
+ applicability,
403
+ ) ;
404
+ }
405
+ }
406
+
274
407
fn check_if_let_some_or_err_and_early_return < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
275
408
if let Some ( higher:: IfLet {
276
409
let_pat,
@@ -339,6 +472,17 @@ fn is_try_block(cx: &LateContext<'_>, bl: &Block<'_>) -> bool {
339
472
}
340
473
}
341
474
475
+ fn is_inferred_ret_closure ( expr : & Expr < ' _ > ) -> bool {
476
+ let ExprKind :: Closure ( closure) = expr. kind else {
477
+ return false ;
478
+ } ;
479
+
480
+ match closure. fn_decl . output {
481
+ FnRetTy :: Return ( ret_ty) => ret_ty. is_suggestable_infer_ty ( ) ,
482
+ FnRetTy :: DefaultReturn ( _) => true ,
483
+ }
484
+ }
485
+
342
486
impl < ' tcx > LateLintPass < ' tcx > for QuestionMark {
343
487
fn check_stmt ( & mut self , cx : & LateContext < ' tcx > , stmt : & ' tcx Stmt < ' _ > ) {
344
488
if !is_lint_allowed ( cx, QUESTION_MARK_USED , stmt. hir_id ) {
@@ -350,11 +494,27 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
350
494
}
351
495
self . check_manual_let_else ( cx, stmt) ;
352
496
}
497
+
353
498
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
499
+ if is_inferred_ret_closure ( expr) {
500
+ self . inferred_ret_closure_stack += 1 ;
501
+ return ;
502
+ }
503
+
354
504
if !self . inside_try_block ( ) && !is_in_const_context ( cx) && is_lint_allowed ( cx, QUESTION_MARK_USED , expr. hir_id )
355
505
{
356
506
check_is_none_or_err_and_early_return ( cx, expr) ;
357
507
check_if_let_some_or_err_and_early_return ( cx, expr) ;
508
+
509
+ if self . inferred_ret_closure_stack == 0 {
510
+ check_if_try_match ( cx, expr) ;
511
+ }
512
+ }
513
+ }
514
+
515
+ fn check_expr_post ( & mut self , _: & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
516
+ if is_inferred_ret_closure ( expr) {
517
+ self . inferred_ret_closure_stack -= 1 ;
358
518
}
359
519
}
360
520
0 commit comments