11use if_chain:: if_chain;
22use rustc_errors:: Applicability ;
33use rustc_hir:: def:: { DefKind , Res } ;
4- use rustc_hir:: { def, Block , Expr , ExprKind , StmtKind } ;
4+ use rustc_hir:: { def, BindingAnnotation , Block , Expr , ExprKind , MatchSource , PatKind , StmtKind } ;
55use rustc_lint:: { LateContext , LateLintPass } ;
66use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
77
88use crate :: utils:: paths:: { OPTION , OPTION_NONE } ;
99use crate :: utils:: sugg:: Sugg ;
10- use crate :: utils:: { higher, match_def_path, match_type, span_lint_and_then, SpanlessEq } ;
10+ use crate :: utils:: {
11+ higher, match_def_path, match_qpath, match_type, snippet_with_applicability, span_lint_and_sugg, SpanlessEq ,
12+ } ;
1113
1214declare_clippy_lint ! {
1315 /// **What it does:** Checks for expressions that could be replaced by the question mark operator.
@@ -55,7 +57,8 @@ impl QuestionMark {
5557 if Self :: is_option( cx, subject) ;
5658
5759 then {
58- let receiver_str = & Sugg :: hir( cx, subject, ".." ) ;
60+ let mut applicability = Applicability :: MachineApplicable ;
61+ let receiver_str = & Sugg :: hir_with_applicability( cx, subject, ".." , & mut applicability) ;
5962 let mut replacement: Option <String > = None ;
6063 if let Some ( else_) = else_ {
6164 if_chain! {
@@ -74,25 +77,61 @@ impl QuestionMark {
7477 }
7578
7679 if let Some ( replacement_str) = replacement {
77- span_lint_and_then (
80+ span_lint_and_sugg (
7881 cx,
7982 QUESTION_MARK ,
8083 expr. span,
8184 "this block may be rewritten with the `?` operator" ,
82- |db| {
83- db. span_suggestion(
84- expr. span,
85- "replace_it_with" ,
86- replacement_str,
87- Applicability :: MaybeIncorrect , // snippet
88- ) ;
89- }
85+ "replace it with" ,
86+ replacement_str,
87+ applicability,
9088 )
9189 }
9290 }
9391 }
9492 }
9593
94+ fn check_if_let_some_and_early_return_none ( cx : & LateContext < ' _ , ' _ > , expr : & Expr < ' _ > ) {
95+ if_chain ! {
96+ if let ExprKind :: Match ( subject, arms, source) = & expr. kind;
97+ if * source == MatchSource :: IfLetDesugar { contains_else_clause: true } ;
98+ if Self :: is_option( cx, subject) ;
99+
100+ if let PatKind :: TupleStruct ( path1, fields, None ) = & arms[ 0 ] . pat. kind;
101+ if match_qpath( path1, & [ "Some" ] ) ;
102+ if let PatKind :: Binding ( annot, _, bind, _) = & fields[ 0 ] . kind;
103+ let by_ref = matches!( annot, BindingAnnotation :: Ref | BindingAnnotation :: RefMut ) ;
104+
105+ if let ExprKind :: Block ( block, None ) = & arms[ 0 ] . body. kind;
106+ if block. stmts. is_empty( ) ;
107+ if let Some ( trailing_expr) = & block. expr;
108+ if let ExprKind :: Path ( path) = & trailing_expr. kind;
109+ if match_qpath( path, & [ & bind. as_str( ) ] ) ;
110+
111+ if let PatKind :: Wild = arms[ 1 ] . pat. kind;
112+ if Self :: expression_returns_none( cx, arms[ 1 ] . body) ;
113+ then {
114+ let mut applicability = Applicability :: MachineApplicable ;
115+ let receiver_str = snippet_with_applicability( cx, subject. span, ".." , & mut applicability) ;
116+ let replacement = format!(
117+ "{}{}?" ,
118+ receiver_str,
119+ if by_ref { ".as_ref()" } else { "" } ,
120+ ) ;
121+
122+ span_lint_and_sugg(
123+ cx,
124+ QUESTION_MARK ,
125+ expr. span,
126+ "this if-let-else may be rewritten with the `?` operator" ,
127+ "replace it with" ,
128+ replacement,
129+ applicability,
130+ )
131+ }
132+ }
133+ }
134+
96135 fn moves_by_default ( cx : & LateContext < ' _ , ' _ > , expression : & Expr < ' _ > ) -> bool {
97136 let expr_ty = cx. tables . expr_ty ( expression) ;
98137
@@ -158,5 +197,6 @@ impl QuestionMark {
158197impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for QuestionMark {
159198 fn check_expr ( & mut self , cx : & LateContext < ' a , ' tcx > , expr : & ' tcx Expr < ' _ > ) {
160199 Self :: check_is_none_and_early_return_none ( cx, expr) ;
200+ Self :: check_if_let_some_and_early_return_none ( cx, expr) ;
161201 }
162202}
0 commit comments