@@ -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 , HirId , LetStmt , MatchSource , Mutability , Node , Pat , PatKind ,
19+ PathSegment , QPath , Stmt , StmtKind ,
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 ;
@@ -271,6 +271,140 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex
271271 }
272272}
273273
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+
274408fn check_if_let_some_or_err_and_early_return < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
275409 if let Some ( higher:: IfLet {
276410 let_pat,
@@ -350,11 +484,15 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
350484 }
351485 self . check_manual_let_else ( cx, stmt) ;
352486 }
487+
353488 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) ;
358496 }
359497 }
360498
0 commit comments