Skip to content

Commit 6117bb7

Browse files
committed
First attempt at coding lint
1 parent 329923e commit 6117bb7

File tree

6 files changed

+119
-2
lines changed

6 files changed

+119
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,7 @@ Released 2018-09-13
12821282
[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
12831283
[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
12841284
[`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used
1285+
[`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else
12851286
[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
12861287
[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
12871288
[`option_map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
77

8-
[There are 359 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
8+
[There are 360 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
99

1010
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1111

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ pub mod non_copy_const;
269269
pub mod non_expressive_names;
270270
pub mod open_options;
271271
pub mod option_env_unwrap;
272+
pub mod option_if_let_else;
272273
pub mod overflow_check_conditional;
273274
pub mod panic_unimplemented;
274275
pub mod partialeq_ne_impl;
@@ -719,6 +720,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
719720
&non_expressive_names::SIMILAR_NAMES,
720721
&open_options::NONSENSICAL_OPEN_OPTIONS,
721722
&option_env_unwrap::OPTION_ENV_UNWRAP,
723+
&option_if_let_else::OPTION_IF_LET_ELSE,
722724
&overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
723725
&panic_unimplemented::PANIC,
724726
&panic_unimplemented::PANIC_PARAMS,
@@ -1298,6 +1300,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12981300
LintId::of(&non_expressive_names::MANY_SINGLE_CHAR_NAMES),
12991301
LintId::of(&open_options::NONSENSICAL_OPEN_OPTIONS),
13001302
LintId::of(&option_env_unwrap::OPTION_ENV_UNWRAP),
1303+
LintId::of(&option_if_let_else::OPTION_IF_LET_ELSE),
13011304
LintId::of(&overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
13021305
LintId::of(&panic_unimplemented::PANIC_PARAMS),
13031306
LintId::of(&partialeq_ne_impl::PARTIALEQ_NE_IMPL),
@@ -1446,6 +1449,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14461449
LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT),
14471450
LintId::of(&non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
14481451
LintId::of(&non_expressive_names::MANY_SINGLE_CHAR_NAMES),
1452+
LintId::of(&option_if_let_else::OPTION_IF_LET_ELSE),
14491453
LintId::of(&panic_unimplemented::PANIC_PARAMS),
14501454
LintId::of(&ptr::CMP_NULL),
14511455
LintId::of(&ptr::PTR_ARG),
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use crate::utils::sugg::Sugg;
2+
use crate::utils::{match_type, match_qpath, paths, span_lint_and_sugg};
3+
use if_chain::if_chain;
4+
5+
use rustc_errors::Applicability;
6+
use rustc_hir::*;
7+
use rustc_lint::{LateContext, LateLintPass};
8+
use rustc_session::{declare_lint_pass, declare_tool_lint};
9+
10+
declare_clippy_lint! {
11+
/// **What it does:**
12+
/// Detects people who use `if let Some(v) = ... { y } else { x }`
13+
/// when they could use `Option::map_or` instead.
14+
///
15+
/// **Why is this bad?**
16+
/// Using the dedicated function in the Option class is clearer and
17+
/// more concise than an if let
18+
///
19+
/// **Known problems:** None.
20+
///
21+
/// **Example:**
22+
///
23+
/// ```rust
24+
/// let var: Option<u32> = Some(5u32);
25+
/// let _ = if let Some(foo) = var {
26+
/// foo
27+
/// } else {
28+
/// 5
29+
/// };
30+
/// ```
31+
///
32+
/// should be
33+
///
34+
/// ```rust
35+
/// let var: Option<u32> = Some(5u32);
36+
/// let _ = var.map_or(5, |foo| foo);
37+
/// ```
38+
pub OPTION_IF_LET_ELSE,
39+
style,
40+
"reimplementation of Option::map_or"
41+
}
42+
43+
declare_lint_pass!(OptionIfLetElse => [OPTION_IF_LET_ELSE]);
44+
45+
/// If this is the option_if_let_else thing we're detecting, then this
46+
/// function returns Some(Option<_> compared, value if Option is Some,
47+
/// value if Option is None). Otherwise, it returns None
48+
fn detect_option_if_let_else<'a>(
49+
cx: &LateContext<'_, '_>,
50+
expr: &'a Expr<'_>,
51+
) -> Option<(&'a Expr<'a>, &'a Expr<'a>, &'a Expr<'a>)> {
52+
if_chain! {
53+
if let ExprKind::Match(let_body, arms, MatchSource::IfLetDesugar { contains_else_clause: true } ) = &expr.kind;
54+
if arms.len() == 2;
55+
if match_type(cx, &cx.tables.expr_ty(let_body), &paths::OPTION);
56+
if let PatKind::TupleStruct(path, &[inner_pat], _) = &arms[0].pat.kind;
57+
if let PatKind::Wild | PatKind::Binding(..) = &inner_pat.kind;
58+
then {
59+
let (some_body, none_body) = if match_qpath(path, &paths::OPTION_SOME) {
60+
(arms[0].body, arms[1].body)
61+
} else {
62+
(arms[1].body, arms[0].body)
63+
};
64+
Some((let_body, some_body, none_body))
65+
} else {
66+
None
67+
}
68+
}
69+
}
70+
71+
/// Lint the option_if_let_else thing we're avoiding
72+
fn check_option_if_let_else(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
73+
if let Some((option, map, else_func)) = detect_option_if_let_else(cx, expr) {
74+
span_lint_and_sugg(
75+
cx,
76+
OPTION_IF_LET_ELSE,
77+
expr.span,
78+
"use Option::map_or here",
79+
"try",
80+
format!(
81+
"{}.map_or({}, {})",
82+
Sugg::hir(cx, option, ".."),
83+
Sugg::hir(cx, else_func, ".."),
84+
Sugg::hir(cx, map, "..")
85+
),
86+
Applicability::MachineApplicable,
87+
);
88+
}
89+
}
90+
91+
impl LateLintPass<'_, '_> for OptionIfLetElse {
92+
fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
93+
check_option_if_let_else(cx, expr);
94+
}
95+
}

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 359] = [
9+
pub const ALL_LINTS: [Lint; 360] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -1519,6 +1519,13 @@ pub const ALL_LINTS: [Lint; 359] = [
15191519
deprecation: None,
15201520
module: "methods",
15211521
},
1522+
Lint {
1523+
name: "option_if_let_else",
1524+
group: "style",
1525+
desc: "default lint description",
1526+
deprecation: None,
1527+
module: "option_if_let_else",
1528+
},
15221529
Lint {
15231530
name: "option_map_or_none",
15241531
group: "style",

tests/ui/option_if_let_else.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![warn(clippy::option_if_let_else)]
2+
3+
fn main() {
4+
let optional = Some(5);
5+
if let Some(x) = optional {
6+
x+2
7+
} else {
8+
5
9+
}
10+
}

0 commit comments

Comments
 (0)