@@ -7,15 +7,18 @@ use clippy_utils::{
77} ;
88use if_chain:: if_chain;
99use rustc_errors:: Applicability ;
10- use rustc_hir:: LangItem :: OptionSome ;
11- use rustc_hir:: { def:: Res , BindingAnnotation , Expr , ExprKind , Mutability , PatKind , Path , QPath , UnOp } ;
10+ use rustc_hir:: LangItem :: { OptionNone , OptionSome } ;
11+ use rustc_hir:: {
12+ def:: Res , Arm , BindingAnnotation , Expr , ExprKind , MatchSource , Mutability , Pat , PatKind , Path , QPath , UnOp ,
13+ } ;
1214use rustc_lint:: { LateContext , LateLintPass } ;
1315use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1416use rustc_span:: sym;
1517
1618declare_clippy_lint ! {
1719 /// ### What it does
18- /// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
20+ /// Lints usage of `if let Some(v) = ... { y } else { x }` and
21+ /// `match .. { Some(v) => y, None/_ => x }` which are more
1922 /// idiomatically done with `Option::map_or` (if the else bit is a pure
2023 /// expression) or `Option::map_or_else` (if the else bit is an impure
2124 /// expression).
@@ -39,6 +42,10 @@ declare_clippy_lint! {
3942 /// } else {
4043 /// 5
4144 /// };
45+ /// let _ = match optional {
46+ /// Some(val) => val + 1,
47+ /// None => 5
48+ /// };
4249 /// let _ = if let Some(foo) = optional {
4350 /// foo
4451 /// } else {
@@ -53,6 +60,7 @@ declare_clippy_lint! {
5360 /// # let optional: Option<u32> = Some(0);
5461 /// # fn do_complicated_function() -> u32 { 5 };
5562 /// let _ = optional.map_or(5, |foo| foo);
63+ /// let _ = optional.map_or(5, |val| val + 1);
5664 /// let _ = optional.map_or_else(||{
5765 /// let y = do_complicated_function();
5866 /// y*y
@@ -76,9 +84,21 @@ fn is_result_ok(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
7684 }
7785}
7886
79- /// A struct containing information about occurrences of the
80- /// `if let Some(..) = .. else` construct that this lint detects.
81- struct OptionIfLetElseOccurrence {
87+ /// A struct containing information about occurrences of construct that this lint detects
88+ ///
89+ /// Such as:
90+ ///
91+ /// ```ignore
92+ /// if let Some(..) = {..} else {..}
93+ /// ```
94+ /// or
95+ /// ```ignore
96+ /// match x {
97+ /// Some(..) => {..},
98+ /// None/_ => {..}
99+ /// }
100+ /// ```
101+ struct OptionOccurence {
82102 option : String ,
83103 method_sugg : String ,
84104 some_expr : String ,
@@ -99,43 +119,39 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo
99119 )
100120}
101121
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 > {
122+ fn try_get_option_occurence < ' tcx > (
123+ cx : & LateContext < ' tcx > ,
124+ pat : & Pat < ' tcx > ,
125+ expr : & Expr < ' _ > ,
126+ if_then : & ' tcx Expr < ' _ > ,
127+ if_else : & ' tcx Expr < ' _ > ,
128+ ) -> Option < OptionOccurence > {
129+ let cond_expr = match expr. kind {
130+ ExprKind :: Unary ( UnOp :: Deref , inner_expr) | ExprKind :: AddrOf ( _, _, inner_expr) => inner_expr,
131+ _ => expr,
132+ } ;
106133 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;
134+ if !is_result_ok( cx, cond_expr) ; // Don't lint on Result::ok because a different lint does it already
135+ let inner_pat = try_get_inner_pat( cx, pat) ?;
136+ if let PatKind :: Binding ( bind_annotation, _, id, None ) = inner_pat. kind;
116137 if let Some ( some_captures) = can_move_expr_to_closure( cx, if_then) ;
117138 if let Some ( none_captures) = can_move_expr_to_closure( cx, if_else) ;
118139 if some_captures
119140 . iter( )
120141 . filter_map( |( id, & c) | none_captures. get( id) . map( |& c2| ( c, c2) ) )
121142 . all( |( x, y) | x. is_imm_ref( ) && y. is_imm_ref( ) ) ;
122-
123143 then {
124- let capture_mut = if bind_annotation == & BindingAnnotation :: Mutable { "mut " } else { "" } ;
144+ let capture_mut = if bind_annotation == BindingAnnotation :: Mutable { "mut " } else { "" } ;
125145 let some_body = peel_blocks( if_then) ;
126146 let none_body = peel_blocks( if_else) ;
127147 let method_sugg = if eager_or_lazy:: switch_to_eager_eval( cx, none_body) { "map_or" } else { "map_or_else" } ;
128148 let capture_name = id. name. to_ident_string( ) ;
129- let ( as_ref, as_mut) = match & let_expr . kind {
149+ let ( as_ref, as_mut) = match & expr . kind {
130150 ExprKind :: AddrOf ( _, Mutability :: Not , _) => ( true , false ) ,
131151 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,
152+ _ => ( bind_annotation == BindingAnnotation :: Ref , bind_annotation == BindingAnnotation :: RefMut ) ,
138153 } ;
154+
139155 // Check if captures the closure will need conflict with borrows made in the scrutinee.
140156 // TODO: check all the references made in the scrutinee expression. This will require interacting
141157 // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
@@ -154,33 +170,99 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) ->
154170 }
155171 }
156172 }
157- Some ( OptionIfLetElseOccurrence {
173+
174+ return Some ( OptionOccurence {
158175 option: format_option_in_sugg( cx, cond_expr, as_ref, as_mut) ,
159176 method_sugg: method_sugg. to_string( ) ,
160177 some_expr: format!( "|{}{}| {}" , capture_mut, capture_name, Sugg :: hir_with_macro_callsite( cx, some_body, ".." ) ) ,
161178 none_expr: format!( "{}{}" , if method_sugg == "map_or" { "" } else { "|| " } , Sugg :: hir_with_macro_callsite( cx, none_body, ".." ) ) ,
162- } )
179+ } ) ;
180+ }
181+ }
182+
183+ None
184+ }
185+
186+ fn try_get_inner_pat < ' tcx > ( cx : & LateContext < ' tcx > , pat : & Pat < ' tcx > ) -> Option < & ' tcx Pat < ' tcx > > {
187+ if let PatKind :: TupleStruct ( ref qpath, [ inner_pat] , ..) = pat. kind {
188+ if is_lang_ctor ( cx, qpath, OptionSome ) {
189+ return Some ( inner_pat) ;
190+ }
191+ }
192+ None
193+ }
194+
195+ /// If this expression is the option if let/else construct we're detecting, then
196+ /// this function returns an `OptionOccurence` struct with details if
197+ /// this construct is found, or None if this construct is not found.
198+ fn detect_option_if_let_else < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) -> Option < OptionOccurence > {
199+ if let Some ( higher:: IfLet {
200+ let_pat,
201+ let_expr,
202+ if_then,
203+ if_else : Some ( if_else) ,
204+ } ) = higher:: IfLet :: hir ( cx, expr)
205+ {
206+ if !is_else_clause ( cx. tcx , expr) {
207+ return try_get_option_occurence ( cx, let_pat, let_expr, if_then, if_else) ;
208+ }
209+ }
210+ None
211+ }
212+
213+ fn detect_option_match < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) -> Option < OptionOccurence > {
214+ if let ExprKind :: Match ( ex, arms, MatchSource :: Normal ) = expr. kind {
215+ if let Some ( ( let_pat, if_then, if_else) ) = try_convert_match ( cx, arms) {
216+ return try_get_option_occurence ( cx, let_pat, ex, if_then, if_else) ;
217+ }
218+ }
219+ None
220+ }
221+
222+ fn try_convert_match < ' tcx > (
223+ cx : & LateContext < ' tcx > ,
224+ arms : & [ Arm < ' tcx > ] ,
225+ ) -> Option < ( & ' tcx Pat < ' tcx > , & ' tcx Expr < ' tcx > , & ' tcx Expr < ' tcx > ) > {
226+ if arms. len ( ) == 2 {
227+ return if is_none_arm ( cx, & arms[ 1 ] ) {
228+ Some ( ( arms[ 0 ] . pat , arms[ 0 ] . body , arms[ 1 ] . body ) )
229+ } else if is_none_arm ( cx, & arms[ 0 ] ) {
230+ Some ( ( arms[ 1 ] . pat , arms[ 1 ] . body , arms[ 0 ] . body ) )
163231 } else {
164232 None
165- }
233+ } ;
234+ }
235+ None
236+ }
237+
238+ fn is_none_arm ( cx : & LateContext < ' _ > , arm : & Arm < ' _ > ) -> bool {
239+ match arm. pat . kind {
240+ PatKind :: Path ( ref qpath) => is_lang_ctor ( cx, qpath, OptionNone ) ,
241+ PatKind :: Wild => true ,
242+ _ => false ,
166243 }
167244}
168245
169246impl < ' tcx > LateLintPass < ' tcx > for OptionIfLetElse {
170247 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
171- if let Some ( detection) = detect_option_if_let_else ( cx, expr) {
172- span_lint_and_sugg (
173- cx,
174- OPTION_IF_LET_ELSE ,
175- expr. span ,
176- format ! ( "use Option::{} instead of an if let/else" , detection. method_sugg) . as_str ( ) ,
177- "try" ,
178- format ! (
179- "{}.{}({}, {})" ,
180- detection. option, detection. method_sugg, detection. none_expr, detection. some_expr,
181- ) ,
182- Applicability :: MaybeIncorrect ,
183- ) ;
248+ // Don't lint macros, because it behaves weirdly
249+ // and don't lint constant as well
250+ if !expr. span . from_expansion ( ) && !in_constant ( cx, expr. hir_id ) {
251+ let detection = detect_option_if_let_else ( cx, expr) . or_else ( || detect_option_match ( cx, expr) ) ;
252+ if let Some ( det) = detection {
253+ span_lint_and_sugg (
254+ cx,
255+ OPTION_IF_LET_ELSE ,
256+ expr. span ,
257+ format ! ( "use Option::{} instead of an if let/else" , det. method_sugg) . as_str ( ) ,
258+ "try" ,
259+ format ! (
260+ "{}.{}({}, {})" ,
261+ det. option, det. method_sugg, det. none_expr, det. some_expr
262+ ) ,
263+ Applicability :: MaybeIncorrect ,
264+ ) ;
265+ }
184266 }
185267 }
186268}
0 commit comments