11use clippy_utils:: diagnostics:: span_lint_and_sugg;
22use clippy_utils:: sugg:: Sugg ;
3- use clippy_utils:: ty:: is_type_diagnostic_item;
43use clippy_utils:: {
54 can_move_expr_to_closure, eager_or_lazy, higher, in_constant, is_else_clause, is_lang_ctor, peel_blocks,
65 peel_hir_expr_while, CaptureKind ,
76} ;
87use if_chain:: if_chain;
98use rustc_errors:: Applicability ;
10- use rustc_hir:: LangItem :: OptionSome ;
11- use rustc_hir:: { def:: Res , BindingAnnotation , Expr , ExprKind , Mutability , PatKind , Path , QPath , UnOp } ;
9+ use rustc_hir:: LangItem :: { OptionNone , OptionSome , ResultErr , ResultOk } ;
10+ use rustc_hir:: {
11+ def:: Res , Arm , BindingAnnotation , Expr , ExprKind , MatchSource , Mutability , Pat , PatKind , Path , QPath , UnOp ,
12+ } ;
1213use rustc_lint:: { LateContext , LateLintPass } ;
1314use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
14- use rustc_span:: sym;
1515
1616declare_clippy_lint ! {
1717 /// ### What it does
18- /// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
18+ /// Lints usage of `if let Some(v) = ... { y } else { x }` and
19+ /// `match .. { Some(v) => y, None/_ => x }` which are more
1920 /// idiomatically done with `Option::map_or` (if the else bit is a pure
2021 /// expression) or `Option::map_or_else` (if the else bit is an impure
2122 /// expression).
@@ -39,6 +40,10 @@ declare_clippy_lint! {
3940 /// } else {
4041 /// 5
4142 /// };
43+ /// let _ = match optional {
44+ /// Some(val) => val + 1,
45+ /// None => 5
46+ /// };
4247 /// let _ = if let Some(foo) = optional {
4348 /// foo
4449 /// } else {
@@ -53,11 +58,14 @@ declare_clippy_lint! {
5358 /// # let optional: Option<u32> = Some(0);
5459 /// # fn do_complicated_function() -> u32 { 5 };
5560 /// let _ = optional.map_or(5, |foo| foo);
61+ /// let _ = optional.map_or(5, |val| val + 1);
5662 /// let _ = optional.map_or_else(||{
5763 /// let y = do_complicated_function();
5864 /// y*y
5965 /// }, |foo| foo);
6066 /// ```
67+ // FIXME: Before moving this lint out of nursery, the lint name needs to be updated. It now also
68+ // covers matches and `Result`.
6169 #[ clippy:: version = "1.47.0" ]
6270 pub OPTION_IF_LET_ELSE ,
6371 nursery,
@@ -66,19 +74,21 @@ declare_clippy_lint! {
6674
6775declare_lint_pass ! ( OptionIfLetElse => [ OPTION_IF_LET_ELSE ] ) ;
6876
69- /// Returns true iff the given expression is the result of calling `Result::ok`
70- fn is_result_ok ( cx : & LateContext < ' _ > , expr : & ' _ Expr < ' _ > ) -> bool {
71- if let ExprKind :: MethodCall ( path, & [ ref receiver] , _) = & expr. kind {
72- path. ident . name . as_str ( ) == "ok"
73- && is_type_diagnostic_item ( cx, cx. typeck_results ( ) . expr_ty ( receiver) , sym:: Result )
74- } else {
75- false
76- }
77- }
78-
79- /// A struct containing information about occurrences of the
80- /// `if let Some(..) = .. else` construct that this lint detects.
81- struct OptionIfLetElseOccurrence {
77+ /// A struct containing information about occurrences of construct that this lint detects
78+ ///
79+ /// Such as:
80+ ///
81+ /// ```ignore
82+ /// if let Some(..) = {..} else {..}
83+ /// ```
84+ /// or
85+ /// ```ignore
86+ /// match x {
87+ /// Some(..) => {..},
88+ /// None/_ => {..}
89+ /// }
90+ /// ```
91+ struct OptionOccurence {
8292 option : String ,
8393 method_sugg : String ,
8494 some_expr : String ,
@@ -99,43 +109,38 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo
99109 )
100110}
101111
102- /// If this expression is the option if let/else construct we're detecting, then
103- /// this function returns an `OptionIfLetElseOccurrence` struct with details if
104- /// this construct is found, or None if this construct is not found.
105- fn detect_option_if_let_else < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) -> Option < OptionIfLetElseOccurrence > {
112+ fn try_get_option_occurence < ' tcx > (
113+ cx : & LateContext < ' tcx > ,
114+ pat : & Pat < ' tcx > ,
115+ expr : & Expr < ' _ > ,
116+ if_then : & ' tcx Expr < ' _ > ,
117+ if_else : & ' tcx Expr < ' _ > ,
118+ ) -> Option < OptionOccurence > {
119+ let cond_expr = match expr. kind {
120+ ExprKind :: Unary ( UnOp :: Deref , inner_expr) | ExprKind :: AddrOf ( _, _, inner_expr) => inner_expr,
121+ _ => expr,
122+ } ;
123+ let inner_pat = try_get_inner_pat ( cx, pat) ?;
106124 if_chain ! {
107- if !expr. span. from_expansion( ) ; // Don't lint macros, because it behaves weirdly
108- if !in_constant( cx, expr. hir_id) ;
109- if let Some ( higher:: IfLet { let_pat, let_expr, if_then, if_else: Some ( if_else) } )
110- = higher:: IfLet :: hir( cx, expr) ;
111- if !is_else_clause( cx. tcx, expr) ;
112- if !is_result_ok( cx, let_expr) ; // Don't lint on Result::ok because a different lint does it already
113- if let PatKind :: TupleStruct ( struct_qpath, [ inner_pat] , _) = & let_pat. kind;
114- if is_lang_ctor( cx, struct_qpath, OptionSome ) ;
115- if let PatKind :: Binding ( bind_annotation, _, id, None ) = & inner_pat. kind;
125+ if let PatKind :: Binding ( bind_annotation, _, id, None ) = inner_pat. kind;
116126 if let Some ( some_captures) = can_move_expr_to_closure( cx, if_then) ;
117127 if let Some ( none_captures) = can_move_expr_to_closure( cx, if_else) ;
118128 if some_captures
119129 . iter( )
120130 . filter_map( |( id, & c) | none_captures. get( id) . map( |& c2| ( c, c2) ) )
121131 . all( |( x, y) | x. is_imm_ref( ) && y. is_imm_ref( ) ) ;
122-
123132 then {
124- let capture_mut = if bind_annotation == & BindingAnnotation :: Mutable { "mut " } else { "" } ;
133+ let capture_mut = if bind_annotation == BindingAnnotation :: Mutable { "mut " } else { "" } ;
125134 let some_body = peel_blocks( if_then) ;
126135 let none_body = peel_blocks( if_else) ;
127136 let method_sugg = if eager_or_lazy:: switch_to_eager_eval( cx, none_body) { "map_or" } else { "map_or_else" } ;
128137 let capture_name = id. name. to_ident_string( ) ;
129- let ( as_ref, as_mut) = match & let_expr . kind {
138+ let ( as_ref, as_mut) = match & expr . kind {
130139 ExprKind :: AddrOf ( _, Mutability :: Not , _) => ( true , false ) ,
131140 ExprKind :: AddrOf ( _, Mutability :: Mut , _) => ( false , true ) ,
132- _ => ( bind_annotation == & BindingAnnotation :: Ref , bind_annotation == & BindingAnnotation :: RefMut ) ,
133- } ;
134- let cond_expr = match let_expr. kind {
135- // Pointer dereferencing happens automatically, so we can omit it in the suggestion
136- ExprKind :: Unary ( UnOp :: Deref , expr) | ExprKind :: AddrOf ( _, _, expr) => expr,
137- _ => let_expr,
141+ _ => ( bind_annotation == BindingAnnotation :: Ref , bind_annotation == BindingAnnotation :: RefMut ) ,
138142 } ;
143+
139144 // Check if captures the closure will need conflict with borrows made in the scrutinee.
140145 // TODO: check all the references made in the scrutinee expression. This will require interacting
141146 // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
@@ -154,30 +159,100 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) ->
154159 }
155160 }
156161 }
157- Some ( OptionIfLetElseOccurrence {
162+
163+ return Some ( OptionOccurence {
158164 option: format_option_in_sugg( cx, cond_expr, as_ref, as_mut) ,
159165 method_sugg: method_sugg. to_string( ) ,
160166 some_expr: format!( "|{}{}| {}" , capture_mut, capture_name, Sugg :: hir_with_macro_callsite( cx, some_body, ".." ) ) ,
161167 none_expr: format!( "{}{}" , if method_sugg == "map_or" { "" } else { "|| " } , Sugg :: hir_with_macro_callsite( cx, none_body, ".." ) ) ,
162- } )
168+ } ) ;
169+ }
170+ }
171+
172+ None
173+ }
174+
175+ fn try_get_inner_pat < ' tcx > ( cx : & LateContext < ' tcx > , pat : & Pat < ' tcx > ) -> Option < & ' tcx Pat < ' tcx > > {
176+ if let PatKind :: TupleStruct ( ref qpath, [ inner_pat] , ..) = pat. kind {
177+ if is_lang_ctor ( cx, qpath, OptionSome ) || is_lang_ctor ( cx, qpath, ResultOk ) {
178+ return Some ( inner_pat) ;
179+ }
180+ }
181+ None
182+ }
183+
184+ /// If this expression is the option if let/else construct we're detecting, then
185+ /// this function returns an `OptionOccurence` struct with details if
186+ /// this construct is found, or None if this construct is not found.
187+ fn detect_option_if_let_else < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) -> Option < OptionOccurence > {
188+ if let Some ( higher:: IfLet {
189+ let_pat,
190+ let_expr,
191+ if_then,
192+ if_else : Some ( if_else) ,
193+ } ) = higher:: IfLet :: hir ( cx, expr)
194+ {
195+ if !is_else_clause ( cx. tcx , expr) {
196+ return try_get_option_occurence ( cx, let_pat, let_expr, if_then, if_else) ;
197+ }
198+ }
199+ None
200+ }
201+
202+ fn detect_option_match < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) -> Option < OptionOccurence > {
203+ if let ExprKind :: Match ( ex, arms, MatchSource :: Normal ) = expr. kind {
204+ if let Some ( ( let_pat, if_then, if_else) ) = try_convert_match ( cx, arms) {
205+ return try_get_option_occurence ( cx, let_pat, ex, if_then, if_else) ;
206+ }
207+ }
208+ None
209+ }
210+
211+ fn try_convert_match < ' tcx > (
212+ cx : & LateContext < ' tcx > ,
213+ arms : & [ Arm < ' tcx > ] ,
214+ ) -> Option < ( & ' tcx Pat < ' tcx > , & ' tcx Expr < ' tcx > , & ' tcx Expr < ' tcx > ) > {
215+ if arms. len ( ) == 2 {
216+ return if is_none_or_err_arm ( cx, & arms[ 1 ] ) {
217+ Some ( ( arms[ 0 ] . pat , arms[ 0 ] . body , arms[ 1 ] . body ) )
218+ } else if is_none_or_err_arm ( cx, & arms[ 0 ] ) {
219+ Some ( ( arms[ 1 ] . pat , arms[ 1 ] . body , arms[ 0 ] . body ) )
163220 } else {
164221 None
165- }
222+ } ;
223+ }
224+ None
225+ }
226+
227+ fn is_none_or_err_arm ( cx : & LateContext < ' _ > , arm : & Arm < ' _ > ) -> bool {
228+ match arm. pat . kind {
229+ PatKind :: Path ( ref qpath) => is_lang_ctor ( cx, qpath, OptionNone ) ,
230+ PatKind :: TupleStruct ( ref qpath, [ first_pat] , _) => {
231+ is_lang_ctor ( cx, qpath, ResultErr ) && matches ! ( first_pat. kind, PatKind :: Wild )
232+ } ,
233+ PatKind :: Wild => true ,
234+ _ => false ,
166235 }
167236}
168237
169238impl < ' tcx > LateLintPass < ' tcx > for OptionIfLetElse {
170239 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
171- if let Some ( detection) = detect_option_if_let_else ( cx, expr) {
240+ // Don't lint macros and constants
241+ if expr. span . from_expansion ( ) || in_constant ( cx, expr. hir_id ) {
242+ return ;
243+ }
244+
245+ let detection = detect_option_if_let_else ( cx, expr) . or_else ( || detect_option_match ( cx, expr) ) ;
246+ if let Some ( det) = detection {
172247 span_lint_and_sugg (
173248 cx,
174249 OPTION_IF_LET_ELSE ,
175250 expr. span ,
176- format ! ( "use Option::{} instead of an if let/else" , detection . method_sugg) . as_str ( ) ,
251+ format ! ( "use Option::{} instead of an if let/else" , det . method_sugg) . as_str ( ) ,
177252 "try" ,
178253 format ! (
179254 "{}.{}({}, {})" ,
180- detection . option, detection . method_sugg, detection . none_expr, detection . some_expr,
255+ det . option, det . method_sugg, det . none_expr, det . some_expr
181256 ) ,
182257 Applicability :: MaybeIncorrect ,
183258 ) ;
0 commit comments