@@ -8,18 +8,18 @@ use clippy_utils::source::snippet_with_applicability;
88use clippy_utils:: ty:: { implements_trait, is_type_diagnostic_item} ;
99use clippy_utils:: {
1010 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,
1212 span_contains_comment,
1313} ;
1414use rustc_errors:: Applicability ;
1515use rustc_hir:: LangItem :: { self , OptionNone , OptionSome , ResultErr , ResultOk } ;
1616use rustc_hir:: def:: Res ;
1717use 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 , TyKind ,
2020} ;
2121use rustc_lint:: { LateContext , LateLintPass } ;
22- use rustc_middle:: ty:: Ty ;
22+ use rustc_middle:: ty:: { self , Ty } ;
2323use rustc_session:: impl_lint_pass;
2424use rustc_span:: sym;
2525use rustc_span:: symbol:: Symbol ;
@@ -58,6 +58,9 @@ pub struct QuestionMark {
5858 /// if it is greater than zero.
5959 /// As for why we need this in the first place: <https://github.com/rust-lang/rust-clippy/issues/8628>
6060 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 ,
6164}
6265
6366impl_lint_pass ! ( QuestionMark => [ QUESTION_MARK , MANUAL_LET_ELSE ] ) ;
@@ -68,6 +71,7 @@ impl QuestionMark {
6871 msrv : conf. msrv . clone ( ) ,
6972 matches_behaviour : conf. matches_for_let_else ,
7073 try_block_depth_stack : Vec :: new ( ) ,
74+ inferred_ret_closure_stack : 0 ,
7175 }
7276 }
7377}
@@ -271,6 +275,139 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex
271275 }
272276}
273277
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+ // Check `arm` is `Ok(val) => val` or `Some(val) => val`.
322+ fn check_arm_is_happy < ' tcx > ( cx : & LateContext < ' tcx > , mode : TryMode , arm : & Arm < ' tcx > ) -> bool {
323+ let arm_body = arm. body . peel_blocks ( ) . peel_drop_temps ( ) ;
324+ let happy_ctor = match mode {
325+ TryMode :: Result => ResultOk ,
326+ TryMode :: Option => OptionSome ,
327+ } ;
328+
329+ // Check for `Ok(val)` or `Some(val)`
330+ if arm. guard . is_none ( )
331+ && let Some ( val_binding) = extract_ctor_call ( cx, happy_ctor, arm. pat )
332+ // Extract out `val`
333+ && let Some ( binding) = extract_binding_pat ( val_binding)
334+ // Check body is just `=> val`
335+ && let Res :: Local ( body_local_ref) = path_res ( cx, arm_body)
336+ && binding == body_local_ref
337+ {
338+ true
339+ } else {
340+ false
341+ }
342+ }
343+
344+ // Check `arm` is `Err(err) => return Err(err)` or `None => return None`.
345+ fn check_arm_is_sad < ' tcx > ( cx : & LateContext < ' tcx > , mode : TryMode , arm : & Arm < ' tcx > ) -> bool {
346+ if arm. guard . is_some ( ) {
347+ return false ;
348+ }
349+
350+ let arm_body = arm. body . peel_blocks ( ) . peel_drop_temps ( ) ;
351+ match mode {
352+ TryMode :: Result => {
353+ // Check that pat is Err(val)
354+ if let Some ( ok_pat) = extract_ctor_call ( cx, ResultErr , arm. pat )
355+ && let Some ( ok_val) = extract_binding_pat ( ok_pat)
356+ // check `=> return Err(...)`
357+ && let ExprKind :: Ret ( Some ( wrapped_ret_expr) ) = arm_body. kind
358+ && let ExprKind :: Call ( ok_ctor, [ ret_expr] ) = wrapped_ret_expr. kind
359+ && is_res_lang_ctor ( cx, path_res ( cx, ok_ctor) , ResultErr )
360+ // check `...` is `val` from binding
361+ && let Res :: Local ( ret_local_ref) = path_res ( cx, ret_expr)
362+ && ok_val == ret_local_ref
363+ {
364+ true
365+ } else {
366+ false
367+ }
368+ } ,
369+ TryMode :: Option => {
370+ // Check the pat is `None`
371+ if is_res_lang_ctor ( cx, path_res ( cx, arm. pat ) , OptionNone )
372+ // Check `=> return None`
373+ && let ExprKind :: Ret ( Some ( ret_expr) ) = arm_body. kind
374+ && is_res_lang_ctor ( cx, path_res ( cx, ret_expr) , OptionNone )
375+ {
376+ true
377+ } else {
378+ false
379+ }
380+ } ,
381+ }
382+ }
383+
384+ fn check_arms_are_try < ' tcx > ( cx : & LateContext < ' tcx > , mode : TryMode , arm1 : & Arm < ' tcx > , arm2 : & Arm < ' tcx > ) -> bool {
385+ ( check_arm_is_happy ( cx, mode, arm1) && check_arm_is_sad ( cx, mode, arm2) )
386+ || ( check_arm_is_happy ( cx, mode, arm2) && check_arm_is_sad ( cx, mode, arm1) )
387+ }
388+
389+ fn check_if_try_match < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
390+ if let ExprKind :: Match ( scrutinee, [ arm1, arm2] , MatchSource :: Normal | MatchSource :: Postfix ) = expr. kind
391+ && !expr. span . from_expansion ( )
392+ && let Some ( mode) = find_try_mode ( cx, scrutinee)
393+ && check_arms_are_try ( cx, mode, arm1, arm2)
394+ {
395+ let mut applicability = Applicability :: MachineApplicable ;
396+ let mut snippet = snippet_with_applicability ( cx, scrutinee. span , "..." , & mut applicability) . into_owned ( ) ;
397+ snippet. push ( '?' ) ;
398+
399+ span_lint_and_sugg (
400+ cx,
401+ QUESTION_MARK ,
402+ expr. span ,
403+ "this `match` expression can be replaced with `?`" ,
404+ "try instead" ,
405+ snippet,
406+ applicability,
407+ ) ;
408+ }
409+ }
410+
274411fn check_if_let_some_or_err_and_early_return < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
275412 if let Some ( higher:: IfLet {
276413 let_pat,
@@ -339,6 +476,17 @@ fn is_try_block(cx: &LateContext<'_>, bl: &Block<'_>) -> bool {
339476 }
340477}
341478
479+ fn is_inferred_ret_closure ( expr : & Expr < ' _ > ) -> bool {
480+ let ExprKind :: Closure ( closure) = expr. kind else {
481+ return false ;
482+ } ;
483+
484+ match closure. fn_decl . output {
485+ FnRetTy :: Return ( ret_ty) => matches ! ( ret_ty. kind, TyKind :: Infer ) ,
486+ FnRetTy :: DefaultReturn ( _) => true ,
487+ }
488+ }
489+
342490impl < ' tcx > LateLintPass < ' tcx > for QuestionMark {
343491 fn check_stmt ( & mut self , cx : & LateContext < ' tcx > , stmt : & ' tcx Stmt < ' _ > ) {
344492 if !is_lint_allowed ( cx, QUESTION_MARK_USED , stmt. hir_id ) {
@@ -350,11 +498,28 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
350498 }
351499 self . check_manual_let_else ( cx, stmt) ;
352500 }
501+
353502 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) ;
503+ if is_inferred_ret_closure ( expr) {
504+ self . inferred_ret_closure_stack += 1 ;
505+ return ;
506+ }
507+
508+ if !is_in_const_context ( cx) && is_lint_allowed ( cx, QUESTION_MARK_USED , expr. hir_id ) {
509+ if !self . inside_try_block ( ) {
510+ check_is_none_or_err_and_early_return ( cx, expr) ;
511+ check_if_let_some_or_err_and_early_return ( cx, expr) ;
512+ }
513+
514+ if self . inferred_ret_closure_stack == 0 {
515+ check_if_try_match ( cx, expr) ;
516+ }
517+ }
518+ }
519+
520+ fn check_expr_post ( & mut self , _: & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
521+ if is_inferred_ret_closure ( expr) {
522+ self . inferred_ret_closure_stack -= 1 ;
358523 }
359524 }
360525
0 commit comments