11use clippy_utils:: diagnostics:: span_lint_and_then;
22use clippy_utils:: higher;
33use 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} ;
55use if_chain:: if_chain;
6+ use rustc_errors:: Applicability ;
67use 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 } ;
89use rustc_lint:: { LateContext , LateLintPass } ;
910use rustc_middle:: hir:: map:: Map ;
1011use rustc_middle:: lint:: in_external_macro;
@@ -74,26 +75,61 @@ struct UnwrappableVariablesVisitor<'a, 'tcx> {
7475 unwrappables : Vec < UnwrapInfo < ' tcx > > ,
7576 cx : & ' a LateContext < ' tcx > ,
7677}
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+
77102/// Contains information about whether a variable can be unwrapped.
78103#[ derive( Copy , Clone , Debug ) ]
79104struct UnwrapInfo < ' tcx > {
80105 /// The variable that is checked
81- ident : & ' tcx Path < ' tcx > ,
106+ local_id : HirId ,
107+ /// The if itself
108+ if_expr : & ' tcx Expr < ' tcx > ,
82109 /// The check, like `x.is_ok()`
83110 check : & ' tcx Expr < ' tcx > ,
111+ /// The check's name, like `is_ok`
112+ check_name : & ' tcx PathSegment < ' tcx > ,
84113 /// The branch where the check takes place, like `if x.is_ok() { .. }`
85114 branch : & ' tcx Expr < ' tcx > ,
86115 /// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`).
87116 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 ,
88122}
89123
90124/// Collects the information about unwrappable variables from an if condition
91125/// The `invert` argument tells us whether the condition is negated.
92126fn collect_unwrap_info < ' tcx > (
93127 cx : & LateContext < ' tcx > ,
128+ if_expr : & ' tcx Expr < ' _ > ,
94129 expr : & ' tcx Expr < ' _ > ,
95130 branch : & ' tcx Expr < ' _ > ,
96131 invert : bool ,
132+ is_entire_condition : bool ,
97133) -> Vec < UnwrapInfo < ' tcx > > {
98134 fn is_relevant_option_call ( cx : & LateContext < ' _ > , ty : Ty < ' _ > , method_name : & str ) -> bool {
99135 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>(
106142 if let ExprKind :: Binary ( op, left, right) = & expr. kind {
107143 match ( invert, op. node ) {
108144 ( 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 ) ) ;
111147 return unwrap_info;
112148 } ,
113149 _ => ( ) ,
114150 }
115151 } 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 ) ;
117153 } else {
118154 if_chain ! {
119155 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 ] ) ;
121157 let ty = cx. typeck_results( ) . expr_ty( & args[ 0 ] ) ;
122158 let name = method_name. ident. as_str( ) ;
123159 if is_relevant_option_call( cx, ty, & name) || is_relevant_result_call( cx, ty, & name) ;
@@ -129,19 +165,42 @@ fn collect_unwrap_info<'tcx>(
129165 _ => unreachable!( ) ,
130166 } ;
131167 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+ ]
133186 }
134187 }
135188 }
136189 Vec :: new ( )
137190}
138191
139192impl < ' 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+ ) {
141200 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 )
145204 {
146205 // if the variable is mutated, we don't know whether it can be unwrapped:
147206 continue ;
@@ -163,32 +222,62 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
163222 }
164223 if let Some ( higher:: If { cond, then, r#else } ) = higher:: If :: hir ( expr) {
165224 walk_expr ( self , cond) ;
166- self . visit_branch ( cond, then, false ) ;
225+ self . visit_branch ( expr , cond, then, false ) ;
167226 if let Some ( else_inner) = r#else {
168- self . visit_branch ( cond, else_inner, true ) ;
227+ self . visit_branch ( expr , cond, else_inner, true ) ;
169228 }
170229 } else {
171230 // find `unwrap[_err]()` calls:
172231 if_chain ! {
173232 if let ExprKind :: MethodCall ( method_name, _, args, _) = expr. kind;
174- if let ExprKind :: Path ( QPath :: Resolved ( None , path ) ) = args[ 0 ] . kind ;
233+ if let Some ( id ) = path_to_local ( & args[ 0 ] ) ;
175234 if [ sym:: unwrap, sym:: expect, sym!( unwrap_err) ] . contains( & method_name. ident. name) ;
176235 let call_to_unwrap = [ sym:: unwrap, sym:: expect] . contains( & method_name. ident. name) ;
177236 if let Some ( unwrappable) = self . unwrappables. iter( )
178- . find( |u| u. ident . res == path . res ) ;
237+ . find( |u| u. local_id == id ) ;
179238 // Span contexts should not differ with the conditional branch
180239 if !differing_macro_contexts( unwrappable. branch. span, expr. span) ;
181240 if !differing_macro_contexts( unwrappable. branch. span, unwrappable. check. span) ;
182241 then {
183242 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+
184251 span_lint_and_then(
185252 self . cx,
186253 UNNECESSARY_UNWRAP ,
187254 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+ } ,
192281 ) ;
193282 } else {
194283 span_lint_and_then(
0 commit comments