1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
2
use clippy_utils:: higher;
3
3
use clippy_utils:: ty:: is_type_diagnostic_item;
4
- use clippy_utils:: { differing_macro_contexts, usage:: is_potentially_mutated} ;
4
+ use clippy_utils:: { differing_macro_contexts, path_to_local , usage:: is_potentially_mutated} ;
5
5
use if_chain:: if_chain;
6
+ use rustc_errors:: Applicability ;
6
7
use rustc_hir:: intravisit:: { walk_expr, walk_fn, FnKind , NestedVisitorMap , Visitor } ;
7
- use rustc_hir:: { BinOpKind , Body , Expr , ExprKind , FnDecl , HirId , Path , QPath , UnOp } ;
8
+ use rustc_hir:: { BinOpKind , Body , Expr , ExprKind , FnDecl , HirId , PathSegment , UnOp } ;
8
9
use rustc_lint:: { LateContext , LateLintPass } ;
9
10
use rustc_middle:: hir:: map:: Map ;
10
11
use rustc_middle:: lint:: in_external_macro;
@@ -74,26 +75,61 @@ struct UnwrappableVariablesVisitor<'a, 'tcx> {
74
75
unwrappables : Vec < UnwrapInfo < ' tcx > > ,
75
76
cx : & ' a LateContext < ' tcx > ,
76
77
}
78
+
79
+ /// What kind of unwrappable this is.
80
+ #[ derive( Copy , Clone , Debug ) ]
81
+ enum UnwrappableKind {
82
+ Option ,
83
+ Result ,
84
+ }
85
+
86
+ impl UnwrappableKind {
87
+ fn success_variant_pattern ( self ) -> & ' static str {
88
+ match self {
89
+ UnwrappableKind :: Option => "Some(..)" ,
90
+ UnwrappableKind :: Result => "Ok(..)" ,
91
+ }
92
+ }
93
+
94
+ fn error_variant_pattern ( self ) -> & ' static str {
95
+ match self {
96
+ UnwrappableKind :: Option => "None" ,
97
+ UnwrappableKind :: Result => "Err(..)" ,
98
+ }
99
+ }
100
+ }
101
+
77
102
/// Contains information about whether a variable can be unwrapped.
78
103
#[ derive( Copy , Clone , Debug ) ]
79
104
struct UnwrapInfo < ' tcx > {
80
105
/// The variable that is checked
81
- ident : & ' tcx Path < ' tcx > ,
106
+ local_id : HirId ,
107
+ /// The if itself
108
+ if_expr : & ' tcx Expr < ' tcx > ,
82
109
/// The check, like `x.is_ok()`
83
110
check : & ' tcx Expr < ' tcx > ,
111
+ /// The check's name, like `is_ok`
112
+ check_name : & ' tcx PathSegment < ' tcx > ,
84
113
/// The branch where the check takes place, like `if x.is_ok() { .. }`
85
114
branch : & ' tcx Expr < ' tcx > ,
86
115
/// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`).
87
116
safe_to_unwrap : bool ,
117
+ /// What kind of unwrappable this is.
118
+ kind : UnwrappableKind ,
119
+ /// If the check is the entire condition (`if x.is_ok()`) or only a part of it (`foo() &&
120
+ /// x.is_ok()`)
121
+ is_entire_condition : bool ,
88
122
}
89
123
90
124
/// Collects the information about unwrappable variables from an if condition
91
125
/// The `invert` argument tells us whether the condition is negated.
92
126
fn collect_unwrap_info < ' tcx > (
93
127
cx : & LateContext < ' tcx > ,
128
+ if_expr : & ' tcx Expr < ' _ > ,
94
129
expr : & ' tcx Expr < ' _ > ,
95
130
branch : & ' tcx Expr < ' _ > ,
96
131
invert : bool ,
132
+ is_entire_condition : bool ,
97
133
) -> Vec < UnwrapInfo < ' tcx > > {
98
134
fn is_relevant_option_call ( cx : & LateContext < ' _ > , ty : Ty < ' _ > , method_name : & str ) -> bool {
99
135
is_type_diagnostic_item ( cx, ty, sym:: option_type) && [ "is_some" , "is_none" ] . contains ( & method_name)
@@ -106,18 +142,18 @@ fn collect_unwrap_info<'tcx>(
106
142
if let ExprKind :: Binary ( op, left, right) = & expr. kind {
107
143
match ( invert, op. node ) {
108
144
( false , BinOpKind :: And | BinOpKind :: BitAnd ) | ( true , BinOpKind :: Or | BinOpKind :: BitOr ) => {
109
- let mut unwrap_info = collect_unwrap_info ( cx, left, branch, invert) ;
110
- unwrap_info. append ( & mut collect_unwrap_info ( cx, right, branch, invert) ) ;
145
+ let mut unwrap_info = collect_unwrap_info ( cx, if_expr , left, branch, invert, false ) ;
146
+ unwrap_info. append ( & mut collect_unwrap_info ( cx, if_expr , right, branch, invert, false ) ) ;
111
147
return unwrap_info;
112
148
} ,
113
149
_ => ( ) ,
114
150
}
115
151
} else if let ExprKind :: Unary ( UnOp :: Not , expr) = & expr. kind {
116
- return collect_unwrap_info ( cx, expr, branch, !invert) ;
152
+ return collect_unwrap_info ( cx, if_expr , expr, branch, !invert, false ) ;
117
153
} else {
118
154
if_chain ! {
119
155
if let ExprKind :: MethodCall ( method_name, _, args, _) = & expr. kind;
120
- if let ExprKind :: Path ( QPath :: Resolved ( None , path ) ) = & args[ 0 ] . kind ;
156
+ if let Some ( local_id ) = path_to_local ( & args[ 0 ] ) ;
121
157
let ty = cx. typeck_results( ) . expr_ty( & args[ 0 ] ) ;
122
158
let name = method_name. ident. as_str( ) ;
123
159
if is_relevant_option_call( cx, ty, & name) || is_relevant_result_call( cx, ty, & name) ;
@@ -129,19 +165,42 @@ fn collect_unwrap_info<'tcx>(
129
165
_ => unreachable!( ) ,
130
166
} ;
131
167
let safe_to_unwrap = unwrappable != invert;
132
- return vec![ UnwrapInfo { ident: path, check: expr, branch, safe_to_unwrap } ] ;
168
+ let kind = if is_type_diagnostic_item( cx, ty, sym:: option_type) {
169
+ UnwrappableKind :: Option
170
+ } else {
171
+ UnwrappableKind :: Result
172
+ } ;
173
+
174
+ return vec![
175
+ UnwrapInfo {
176
+ local_id,
177
+ if_expr,
178
+ check: expr,
179
+ check_name: method_name,
180
+ branch,
181
+ safe_to_unwrap,
182
+ kind,
183
+ is_entire_condition,
184
+ }
185
+ ]
133
186
}
134
187
}
135
188
}
136
189
Vec :: new ( )
137
190
}
138
191
139
192
impl < ' a , ' tcx > UnwrappableVariablesVisitor < ' a , ' tcx > {
140
- fn visit_branch ( & mut self , cond : & ' tcx Expr < ' _ > , branch : & ' tcx Expr < ' _ > , else_branch : bool ) {
193
+ fn visit_branch (
194
+ & mut self ,
195
+ if_expr : & ' tcx Expr < ' _ > ,
196
+ cond : & ' tcx Expr < ' _ > ,
197
+ branch : & ' tcx Expr < ' _ > ,
198
+ else_branch : bool ,
199
+ ) {
141
200
let prev_len = self . unwrappables . len ( ) ;
142
- for unwrap_info in collect_unwrap_info ( self . cx , cond, branch, else_branch) {
143
- if is_potentially_mutated ( unwrap_info. ident , cond, self . cx )
144
- || is_potentially_mutated ( unwrap_info. ident , branch, self . cx )
201
+ for unwrap_info in collect_unwrap_info ( self . cx , if_expr , cond, branch, else_branch, true ) {
202
+ if is_potentially_mutated ( unwrap_info. local_id , cond, self . cx )
203
+ || is_potentially_mutated ( unwrap_info. local_id , branch, self . cx )
145
204
{
146
205
// if the variable is mutated, we don't know whether it can be unwrapped:
147
206
continue ;
@@ -163,32 +222,62 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
163
222
}
164
223
if let Some ( higher:: If { cond, then, r#else } ) = higher:: If :: hir ( expr) {
165
224
walk_expr ( self , cond) ;
166
- self . visit_branch ( cond, then, false ) ;
225
+ self . visit_branch ( expr , cond, then, false ) ;
167
226
if let Some ( else_inner) = r#else {
168
- self . visit_branch ( cond, else_inner, true ) ;
227
+ self . visit_branch ( expr , cond, else_inner, true ) ;
169
228
}
170
229
} else {
171
230
// find `unwrap[_err]()` calls:
172
231
if_chain ! {
173
232
if let ExprKind :: MethodCall ( method_name, _, args, _) = expr. kind;
174
- if let ExprKind :: Path ( QPath :: Resolved ( None , path ) ) = args[ 0 ] . kind ;
175
- if [ sym:: unwrap, sym!( unwrap_err) ] . contains( & method_name. ident. name) ;
176
- let call_to_unwrap = method_name. ident. name == sym :: unwrap ;
233
+ if let Some ( id ) = path_to_local ( & args[ 0 ] ) ;
234
+ if [ sym:: unwrap, sym:: expect , sym !( unwrap_err) ] . contains( & method_name. ident. name) ;
235
+ let call_to_unwrap = [ sym :: unwrap , sym :: expect ] . contains ( & method_name. ident. name) ;
177
236
if let Some ( unwrappable) = self . unwrappables. iter( )
178
- . find( |u| u. ident . res == path . res ) ;
237
+ . find( |u| u. local_id == id ) ;
179
238
// Span contexts should not differ with the conditional branch
180
239
if !differing_macro_contexts( unwrappable. branch. span, expr. span) ;
181
240
if !differing_macro_contexts( unwrappable. branch. span, unwrappable. check. span) ;
182
241
then {
183
242
if call_to_unwrap == unwrappable. safe_to_unwrap {
243
+ let is_entire_condition = unwrappable. is_entire_condition;
244
+ let unwrappable_variable_name = self . cx. tcx. hir( ) . name( unwrappable. local_id) ;
245
+ let suggested_pattern = if call_to_unwrap {
246
+ unwrappable. kind. success_variant_pattern( )
247
+ } else {
248
+ unwrappable. kind. error_variant_pattern( )
249
+ } ;
250
+
184
251
span_lint_and_then(
185
252
self . cx,
186
253
UNNECESSARY_UNWRAP ,
187
254
expr. span,
188
- & format!( "you checked before that `{}()` cannot fail, \
189
- instead of checking and unwrapping, it's better to use `if let` or `match`",
190
- method_name. ident. name) ,
191
- |diag| { diag. span_label( unwrappable. check. span, "the check is happening here" ) ; } ,
255
+ & format!(
256
+ "called `{}` on `{}` after checking its variant with `{}`" ,
257
+ method_name. ident. name,
258
+ unwrappable_variable_name,
259
+ unwrappable. check_name. ident. as_str( ) ,
260
+ ) ,
261
+ |diag| {
262
+ if is_entire_condition {
263
+ diag. span_suggestion(
264
+ unwrappable. check. span. with_lo( unwrappable. if_expr. span. lo( ) ) ,
265
+ "try" ,
266
+ format!(
267
+ "if let {} = {}" ,
268
+ suggested_pattern,
269
+ unwrappable_variable_name,
270
+ ) ,
271
+ // We don't track how the unwrapped value is used inside the
272
+ // block or suggest deleting the unwrap, so we can't offer a
273
+ // fixable solution.
274
+ Applicability :: Unspecified ,
275
+ ) ;
276
+ } else {
277
+ diag. span_label( unwrappable. check. span, "the check is happening here" ) ;
278
+ diag. help( "try using `if let` or `match`" ) ;
279
+ }
280
+ } ,
192
281
) ;
193
282
} else {
194
283
span_lint_and_then(
0 commit comments