@@ -6,11 +6,11 @@ use clippy_utils::ty::is_type_diagnostic_item;
6
6
use clippy_utils:: { eq_expr_value, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt} ;
7
7
use if_chain:: if_chain;
8
8
use rustc_errors:: Applicability ;
9
- use rustc_hir:: LangItem :: { OptionNone , OptionSome , ResultOk } ;
10
- use rustc_hir:: { BindingAnnotation , Expr , ExprKind , PatKind } ;
9
+ use rustc_hir:: LangItem :: { OptionNone , OptionSome , ResultErr , ResultOk } ;
10
+ use rustc_hir:: { BindingAnnotation , Expr , ExprKind , PatKind , PathSegment , QPath } ;
11
11
use rustc_lint:: { LateContext , LateLintPass } ;
12
12
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
13
- use rustc_span:: sym;
13
+ use rustc_span:: { sym, symbol :: Symbol } ;
14
14
15
15
declare_clippy_lint ! {
16
16
/// ### What it does
@@ -61,21 +61,21 @@ impl QuestionMark {
61
61
if let ExprKind :: MethodCall ( segment, args, _) = & cond. kind;
62
62
if let Some ( subject) = args. get( 0 ) ;
63
63
if ( Self :: option_check_and_early_return( cx, subject, then) && segment. ident. name == sym!( is_none) ) ||
64
- ( Self :: result_check_and_early_return( cx, subject, then) && segment. ident. name == sym!( is_err) ) ;
64
+ ( Self :: result_check_and_early_return( cx, subject, then, None ) && segment. ident. name == sym!( is_err) ) ;
65
65
then {
66
66
let mut applicability = Applicability :: MachineApplicable ;
67
- let receiver_str = & Sugg :: hir_with_applicability( cx, subject, ".." , & mut applicability) ;
67
+ let suggestion = & Sugg :: hir_with_applicability( cx, subject, ".." , & mut applicability) ;
68
68
let mut replacement: Option <String > = None ;
69
69
if let Some ( else_inner) = r#else {
70
70
if eq_expr_value( cx, subject, peel_blocks( else_inner) ) {
71
- replacement = Some ( format!( "Some({}?)" , receiver_str ) ) ;
71
+ replacement = Some ( format!( "Some({}?)" , suggestion ) ) ;
72
72
}
73
73
} else if Self :: moves_by_default( cx, subject)
74
74
&& !matches!( subject. kind, ExprKind :: Call ( ..) | ExprKind :: MethodCall ( ..) )
75
75
{
76
- replacement = Some ( format!( "{}.as_ref()?;" , receiver_str ) ) ;
76
+ replacement = Some ( format!( "{}.as_ref()?;" , suggestion ) ) ;
77
77
} else {
78
- replacement = Some ( format!( "{}?;" , receiver_str ) ) ;
78
+ replacement = Some ( format!( "{}?;" , suggestion ) ) ;
79
79
}
80
80
81
81
if let Some ( replacement_str) = replacement {
@@ -93,21 +93,52 @@ impl QuestionMark {
93
93
}
94
94
}
95
95
96
+ /// Checks if the given expression on the given context matches the following structure:
97
+ ///
98
+ /// ```ignore
99
+ /// if let Some(x) = option {
100
+ /// return x;
101
+ /// }
102
+ /// ```
103
+ ///
104
+ /// ```ignore
105
+ /// if let Err(err) = result {
106
+ /// return result;
107
+ /// }
108
+ /// ```
109
+ ///
110
+ /// ```ignore
111
+ /// if let Ok(x) = result {
112
+ /// return x;
113
+ /// }
114
+ /// ```
115
+ ///
116
+ /// If it matches, it will suggest to use the question mark operator instead
96
117
fn check_if_let_some_or_err_and_early_return ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
97
118
if_chain ! {
98
- if let Some ( higher:: IfLet { let_pat, let_expr, if_then, if_else: Some ( if_else ) } )
119
+ if let Some ( higher:: IfLet { let_pat, let_expr, if_then, if_else } )
99
120
= higher:: IfLet :: hir( cx, expr) ;
121
+ // Make sure this expression is not part of `.. else if ..`,
122
+ // otherwise it will cause trouble when replacing blocks with question mark.
123
+ if !Self :: expr_is_else_if( cx, expr) ;
100
124
if let PatKind :: TupleStruct ( ref path1, fields, None ) = let_pat. kind;
101
- if ( Self :: option_check_and_early_return( cx, let_expr, if_else) && is_lang_ctor( cx, path1, OptionSome ) ) ||
102
- ( Self :: result_check_and_early_return( cx, let_expr, if_else) && is_lang_ctor( cx, path1, ResultOk ) ) ;
103
-
104
- if let PatKind :: Binding ( annot, bind_id, _, _) = fields[ 0 ] . kind;
125
+ let nested_expr = if_else. unwrap_or( if_then) ;
126
+ if let PatKind :: Binding ( annot, bind_id, ident, _) = fields[ 0 ] . kind;
127
+ if ( Self :: result_check_and_early_return( cx, let_expr, nested_expr, Some ( ident. name) ) &&
128
+ ( is_lang_ctor( cx, path1, ResultOk ) || is_lang_ctor( cx, path1, ResultErr ) ) ) ||
129
+ ( Self :: option_check_and_early_return( cx, let_expr, nested_expr) &&
130
+ is_lang_ctor( cx, path1, OptionSome ) ) && path_to_local_id( peel_blocks( if_then) , bind_id) ;
105
131
let by_ref = matches!( annot, BindingAnnotation :: Ref | BindingAnnotation :: RefMut ) ;
106
- if path_to_local_id( peel_blocks( if_then) , bind_id) ;
107
132
then {
108
133
let mut applicability = Applicability :: MachineApplicable ;
109
134
let receiver_str = snippet_with_applicability( cx, let_expr. span, ".." , & mut applicability) ;
110
- let replacement = format!( "{}{}?" , receiver_str, if by_ref { ".as_ref()" } else { "" } , ) ;
135
+ let append_semi = Self :: is_semicolon_needed( if_then, if_else) ;
136
+ let replacement = format!(
137
+ "{}{}?{}" ,
138
+ receiver_str,
139
+ if by_ref { ".as_ref()" } else { "" } ,
140
+ if append_semi { ";" } else { "" }
141
+ ) ;
111
142
112
143
span_lint_and_sugg(
113
144
cx,
@@ -122,8 +153,54 @@ impl QuestionMark {
122
153
}
123
154
}
124
155
125
- fn result_check_and_early_return ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , nested_expr : & Expr < ' _ > ) -> bool {
126
- Self :: is_result ( cx, expr) && Self :: expression_returns_unmodified_err ( cx, nested_expr, expr)
156
+ /// Check whether the encountered expression contains `else if`
157
+ fn expr_is_else_if ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
158
+ if let ExprKind :: If ( ..) = expr. kind
159
+ && let Some ( parent_expr) = clippy_utils:: get_parent_expr ( cx, expr)
160
+ && let ExprKind :: If ( ..) = parent_expr. kind
161
+ {
162
+ return true ;
163
+ }
164
+ false
165
+ }
166
+
167
+ /// Check whether the fixed code needs semicolon after `?`
168
+ ///
169
+ /// The fixed code of this expression doesn't need semi
170
+ /// ```ignore
171
+ /// let _ = if let Some(x) = Some(0) {
172
+ /// x
173
+ /// } else {
174
+ /// return None;
175
+ /// };
176
+ /// ```
177
+ ///
178
+ /// The fixed code of this expression needs semi
179
+ /// ```ignore
180
+ /// if let Err(err) = foo() {
181
+ /// return Err(err);
182
+ /// }
183
+ /// ```
184
+ fn is_semicolon_needed ( if_then : & Expr < ' _ > , if_else : Option < & ' _ Expr < ' _ > > ) -> bool {
185
+ let if_then_kind = & peel_blocks_with_stmt ( if_then) . kind ;
186
+ let if_then_is_ret_stmt = matches ! ( if_then_kind, ExprKind :: Ret ( _) ) ;
187
+
188
+ if if_else. is_none ( ) {
189
+ return if_then_is_ret_stmt;
190
+ }
191
+ if let ExprKind :: Ret ( _) = peel_blocks_with_stmt ( if_else. unwrap ( ) ) . kind {
192
+ return if_then_is_ret_stmt;
193
+ }
194
+ false
195
+ }
196
+
197
+ fn result_check_and_early_return (
198
+ cx : & LateContext < ' _ > ,
199
+ expr : & Expr < ' _ > ,
200
+ nested_expr : & Expr < ' _ > ,
201
+ pat_symbol : Option < Symbol > ,
202
+ ) -> bool {
203
+ Self :: is_result ( cx, expr) && Self :: expression_returns_unmodified_err ( cx, nested_expr, expr, pat_symbol)
127
204
}
128
205
129
206
fn option_check_and_early_return ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , nested_expr : & Expr < ' _ > ) -> bool {
@@ -156,10 +233,32 @@ impl QuestionMark {
156
233
}
157
234
}
158
235
159
- fn expression_returns_unmodified_err ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , cond_expr : & Expr < ' _ > ) -> bool {
160
- match peel_blocks_with_stmt ( expr) . kind {
161
- ExprKind :: Ret ( Some ( ret_expr) ) => Self :: expression_returns_unmodified_err ( cx, ret_expr, cond_expr) ,
236
+ fn expression_returns_unmodified_err (
237
+ cx : & LateContext < ' _ > ,
238
+ expr : & Expr < ' _ > ,
239
+ cond_expr : & Expr < ' _ > ,
240
+ pat_symbol : Option < Symbol > ,
241
+ ) -> bool {
242
+ match & peel_blocks_with_stmt ( expr) . kind {
243
+ ExprKind :: Ret ( Some ( ret_expr) ) => {
244
+ Self :: expression_returns_unmodified_err ( cx, ret_expr, cond_expr, pat_symbol)
245
+ } ,
162
246
ExprKind :: Path ( _) => path_to_local ( expr) . is_some ( ) && path_to_local ( expr) == path_to_local ( cond_expr) ,
247
+ ExprKind :: Call ( call_expr, args_expr) => {
248
+ if let ExprKind :: Path ( qpath) = & call_expr. kind
249
+ && let QPath :: Resolved ( _, path) = qpath
250
+ && let Some ( segment) = path. segments . first ( )
251
+ && let Some ( pat_sym) = pat_symbol
252
+ && let Some ( arg) = args_expr. first ( )
253
+ && let ExprKind :: Path ( arg_qpath) = & arg. kind
254
+ && let QPath :: Resolved ( _, arg_path) = arg_qpath
255
+ && let Some ( PathSegment { ident, .. } ) = arg_path. segments . first ( )
256
+ {
257
+ return segment. ident . name == sym:: Err && pat_sym == ident. name
258
+ }
259
+
260
+ false
261
+ } ,
163
262
_ => false ,
164
263
}
165
264
}
0 commit comments