Skip to content

Commit a888111

Browse files
committed
allow check for match in lint [option_if_let_else]
and add test case for `Result`
1 parent f70c73f commit a888111

File tree

4 files changed

+181
-45
lines changed

4 files changed

+181
-45
lines changed

clippy_lints/src/option_if_let_else.rs

Lines changed: 126 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ use clippy_utils::{
77
};
88
use if_chain::if_chain;
99
use 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+
};
1214
use rustc_lint::{LateContext, LateLintPass};
1315
use rustc_session::{declare_lint_pass, declare_tool_lint};
1416
use rustc_span::sym;
1517

1618
declare_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 OptionIfLetElseOccurence {
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 `OptionIfLetElseOccurence` 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<OptionIfLetElseOccurence> {
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(OptionIfLetElseOccurence {
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

169246
impl<'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
}

tests/ui/option_if_let_else.fixed

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,18 @@ fn main() {
173173

174174
let _ = pattern_to_vec("hello world");
175175
let _ = complex_subpat();
176+
177+
// issue #8492
178+
let _ = s.map_or(1, |string| string.len());
179+
let _ = Some(10).map_or(5, |a| a + 1);
180+
181+
let res: Result<i32, i32> = Ok(5);
182+
let _ = match res {
183+
Ok(a) => a + 1,
184+
_ => 1,
185+
};
186+
let _ = match res {
187+
Err(_) => 1,
188+
Ok(a) => a + 1,
189+
};
176190
}

tests/ui/option_if_let_else.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,24 @@ fn main() {
202202

203203
let _ = pattern_to_vec("hello world");
204204
let _ = complex_subpat();
205+
206+
// issue #8492
207+
let _ = match s {
208+
Some(string) => string.len(),
209+
None => 1,
210+
};
211+
let _ = match Some(10) {
212+
Some(a) => a + 1,
213+
None => 5,
214+
};
215+
216+
let res: Result<i32, i32> = Ok(5);
217+
let _ = match res {
218+
Ok(a) => a + 1,
219+
_ => 1,
220+
};
221+
let _ = match res {
222+
Err(_) => 1,
223+
Ok(a) => a + 1,
224+
};
205225
}

tests/ui/option_if_let_else.stderr

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,5 +206,25 @@ LL + s.len() + x
206206
LL ~ });
207207
|
208208

209-
error: aborting due to 15 previous errors
209+
error: use Option::map_or instead of an if let/else
210+
--> $DIR/option_if_let_else.rs:207:13
211+
|
212+
LL | let _ = match s {
213+
| _____________^
214+
LL | | Some(string) => string.len(),
215+
LL | | None => 1,
216+
LL | | };
217+
| |_____^ help: try: `s.map_or(1, |string| string.len())`
218+
219+
error: use Option::map_or instead of an if let/else
220+
--> $DIR/option_if_let_else.rs:211:13
221+
|
222+
LL | let _ = match Some(10) {
223+
| _____________^
224+
LL | | Some(a) => a + 1,
225+
LL | | None => 5,
226+
LL | | };
227+
| |_____^ help: try: `Some(10).map_or(5, |a| a + 1)`
228+
229+
error: aborting due to 17 previous errors
210230

0 commit comments

Comments
 (0)