@@ -2,11 +2,13 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
2
2
use clippy_utils:: higher;
3
3
use clippy_utils:: source:: snippet_with_applicability;
4
4
use clippy_utils:: ty:: is_type_diagnostic_item;
5
- use clippy_utils:: { eq_expr_value, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt} ;
5
+ use clippy_utils:: {
6
+ eq_expr_value, is_else_clause, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
7
+ } ;
6
8
use if_chain:: if_chain;
7
9
use rustc_errors:: Applicability ;
8
10
use rustc_hir:: LangItem :: { OptionNone , OptionSome , ResultErr , ResultOk } ;
9
- use rustc_hir:: { BindingAnnotation , Expr , ExprKind , PatKind , QPath } ;
11
+ use rustc_hir:: { BindingAnnotation , Expr , ExprKind , PatKind , PathSegment , QPath } ;
10
12
use rustc_lint:: { LateContext , LateLintPass } ;
11
13
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
12
14
use rustc_span:: { sym, symbol:: Symbol } ;
@@ -73,6 +75,7 @@ enum IfBlockType<'hir> {
73
75
fn check_is_none_or_err_and_early_return ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
74
76
if_chain ! {
75
77
if let Some ( higher:: If { cond, then, r#else } ) = higher:: If :: hir( expr) ;
78
+ if !is_else_clause( cx. tcx, expr) ;
76
79
if let ExprKind :: MethodCall ( segment, args, _) = & cond. kind;
77
80
if let Some ( subject) = args. get( 0 ) ;
78
81
let if_block = IfBlockType :: IfIs ( subject, segment. ident. name, then, r#else) ;
@@ -103,6 +106,7 @@ fn move_by_default(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
103
106
fn check_if_let_some_or_err_and_early_return ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
104
107
if_chain ! {
105
108
if let Some ( higher:: IfLet { let_pat, let_expr, if_then, if_else } ) = higher:: IfLet :: hir( cx, expr) ;
109
+ if !is_else_clause( cx. tcx, expr) ;
106
110
if let PatKind :: TupleStruct ( ref path1, fields, None ) = let_pat. kind;
107
111
if let PatKind :: Binding ( annot, bind_id, ident, _) = fields[ 0 ] . kind;
108
112
let if_block = IfBlockType :: IfLet ( path1, ident. name, let_expr, if_then, if_else) ;
@@ -112,7 +116,12 @@ fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'
112
116
let mut applicability = Applicability :: MachineApplicable ;
113
117
let receiver_str = snippet_with_applicability( cx, let_expr. span, ".." , & mut applicability) ;
114
118
let by_ref = matches!( annot, BindingAnnotation :: Ref | BindingAnnotation :: RefMut ) ;
115
- let replacement = format!( "{}{}?" , receiver_str, if by_ref { ".as_ref()" } else { "" } , ) ;
119
+ let replacement = format!(
120
+ "{}{}?{}" ,
121
+ receiver_str,
122
+ if by_ref { ".as_ref()" } else { "" } ,
123
+ if requires_semi( if_then, if_else) { ";" } else { "" }
124
+ ) ;
116
125
offer_suggestion( cx, expr, replacement, applicability) ;
117
126
}
118
127
}
@@ -125,14 +134,14 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
125
134
// If the block could be identified as `if x.is_none()/is_err()`,
126
135
// we then only need to check the if_then return to see if it is none/err.
127
136
is_type_diagnostic_item ( cx, caller_ty, smbl)
128
- && expr_return_none_or_err ( smbl, cx, if_then, caller)
137
+ && expr_return_none_or_err ( smbl, cx, if_then, caller, None )
129
138
&& match smbl {
130
139
sym:: Option => call_sym == sym ! ( is_none) ,
131
140
sym:: Result => call_sym == sym ! ( is_err) ,
132
141
_ => false ,
133
142
}
134
143
} ,
135
- IfBlockType :: IfLet ( qpath, _ , let_expr, if_then, if_else) => {
144
+ IfBlockType :: IfLet ( qpath, let_pat_sym , let_expr, if_then, if_else) => {
136
145
let let_expr_ty = cx. typeck_results ( ) . expr_ty ( let_expr) ;
137
146
is_type_diagnostic_item ( cx, let_expr_ty, smbl)
138
147
&& match smbl {
@@ -144,7 +153,8 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
144
153
} else {
145
154
return false ;
146
155
} ;
147
- expr_return_none_or_err ( smbl, cx, else_expr, let_expr) && is_lang_ctor ( cx, qpath, OptionSome )
156
+ expr_return_none_or_err ( smbl, cx, else_expr, let_expr, None )
157
+ && is_lang_ctor ( cx, qpath, OptionSome )
148
158
} ,
149
159
sym:: Result => {
150
160
if is_lang_ctor ( cx, qpath, ResultOk ) {
@@ -153,9 +163,9 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
153
163
} else {
154
164
return false ;
155
165
} ;
156
- expr_return_none_or_err ( smbl, cx, else_expr, let_expr)
166
+ expr_return_none_or_err ( smbl, cx, else_expr, let_expr, Some ( let_pat_sym ) )
157
167
} else if is_lang_ctor ( cx, qpath, ResultErr ) {
158
- expr_return_none_or_err ( smbl, cx, if_then, let_expr)
168
+ expr_return_none_or_err ( smbl, cx, if_then, let_expr, Some ( let_pat_sym ) )
159
169
} else {
160
170
false
161
171
}
@@ -166,18 +176,55 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
166
176
}
167
177
}
168
178
169
- fn expr_return_none_or_err ( smbl : Symbol , cx : & LateContext < ' _ > , expr : & Expr < ' _ > , cond_expr : & Expr < ' _ > ) -> bool {
179
+ fn expr_return_none_or_err (
180
+ smbl : Symbol ,
181
+ cx : & LateContext < ' _ > ,
182
+ expr : & Expr < ' _ > ,
183
+ cond_expr : & Expr < ' _ > ,
184
+ err_sym : Option < Symbol > ,
185
+ ) -> bool {
170
186
match peel_blocks_with_stmt ( expr) . kind {
171
- ExprKind :: Ret ( Some ( ret_expr) ) => expr_return_none_or_err ( smbl, cx, ret_expr, cond_expr) ,
187
+ ExprKind :: Ret ( Some ( ret_expr) ) => expr_return_none_or_err ( smbl, cx, ret_expr, cond_expr, err_sym ) ,
172
188
ExprKind :: Path ( ref qpath) => match smbl {
173
189
sym:: Option => is_lang_ctor ( cx, qpath, OptionNone ) ,
174
190
sym:: Result => path_to_local ( expr) . is_some ( ) && path_to_local ( expr) == path_to_local ( cond_expr) ,
175
191
_ => false ,
176
192
} ,
193
+ ExprKind :: Call ( call_expr, args_expr) => {
194
+ if_chain ! {
195
+ if smbl == sym:: Result ;
196
+ if let ExprKind :: Path ( QPath :: Resolved ( _, path) ) = & call_expr. kind;
197
+ if let Some ( segment) = path. segments. first( ) ;
198
+ if let Some ( err_sym) = err_sym;
199
+ if let Some ( arg) = args_expr. first( ) ;
200
+ if let ExprKind :: Path ( QPath :: Resolved ( _, arg_path) ) = & arg. kind;
201
+ if let Some ( PathSegment { ident, .. } ) = arg_path. segments. first( ) ;
202
+ then {
203
+ return segment. ident. name == sym:: Err && err_sym == ident. name;
204
+ }
205
+ }
206
+ false
207
+ } ,
177
208
_ => false ,
178
209
}
179
210
}
180
211
212
+ /// Check whether the fixed code needs semicolon after `?`
213
+ ///
214
+ /// It will need a semicolon if all block(s) has one.
215
+ fn requires_semi ( if_then : & Expr < ' _ > , if_else : Option < & Expr < ' _ > > ) -> bool {
216
+ let if_then_kind = & peel_blocks_with_stmt ( if_then) . kind ;
217
+ let if_then_is_ret_stmt = matches ! ( if_then_kind, ExprKind :: Ret ( ..) ) ;
218
+
219
+ if if_else. is_none ( ) {
220
+ return if_then_is_ret_stmt;
221
+ }
222
+ if let ExprKind :: Ret ( _) = peel_blocks_with_stmt ( if_else. unwrap ( ) ) . kind {
223
+ return if_then_is_ret_stmt;
224
+ }
225
+ false
226
+ }
227
+
181
228
fn offer_suggestion ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , suggestion : String , applicability : Applicability ) {
182
229
if !suggestion. is_empty ( ) {
183
230
span_lint_and_sugg (
0 commit comments