Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lints for find_map #4039

Merged
merged 2 commits into from
Apr 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,9 @@ All notable changes to this project will be documented in this file.
[`extra_unused_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_lifetimes
[`fallible_impl_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#fallible_impl_from
[`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map
[`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next
[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next
[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map
[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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

[There are 299 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are 301 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)

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

Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,8 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
loops::EXPLICIT_ITER_LOOP,
matches::SINGLE_MATCH_ELSE,
methods::FILTER_MAP,
methods::FILTER_MAP_NEXT,
methods::FIND_MAP,
methods::MAP_FLATTEN,
methods::OPTION_MAP_UNWRAP_OR,
methods::OPTION_MAP_UNWRAP_OR_ELSE,
Expand Down
84 changes: 84 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,50 @@ declare_clippy_lint! {
"using combinations of `filter`, `map`, `filter_map` and `flat_map` which can usually be written as a single method call"
}

declare_clippy_lint! {
/// **What it does:** Checks for usage of `_.filter_map(_).next()`.
///
/// **Why is this bad?** Readability, this can be written more concisely as a
/// single method call.
///
/// **Known problems:** None
///
/// **Example:**
/// ```rust
/// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next();
/// ```
/// Can be written as
///
/// ```rust
/// (0..3).find_map(|x| if x == 2 { Some(x) } else { None });
/// ```
pub FILTER_MAP_NEXT,
pedantic,
"using combination of `filter_map` and `next` which can usually be written as a single method call"
}

declare_clippy_lint! {
/// **What it does:** Checks for usage of `_.find(_).map(_)`.
///
/// **Why is this bad?** Readability, this can be written more concisely as a
/// single method call.
///
/// **Known problems:** Often requires a condition + Option/Iterator creation
/// inside the closure.
///
/// **Example:**
/// ```rust
/// (0..3).find(|x| x == 2).map(|x| x * 2);
/// ```
/// Can be written as
/// ```rust
/// (0..3).find_map(|x| if x == 2 { Some(x * 2) } else { None });
/// ```
pub FIND_MAP,
pedantic,
"using a combination of `find` and `map` can usually be written as a single method call"
}

declare_clippy_lint! {
/// **What it does:** Checks for an iterator search (such as `find()`,
/// `position()`, or `rposition()`) followed by a call to `is_some()`.
Expand Down Expand Up @@ -798,6 +842,8 @@ declare_lint_pass!(Methods => [
TEMPORARY_CSTRING_AS_PTR,
FILTER_NEXT,
FILTER_MAP,
FILTER_MAP_NEXT,
FIND_MAP,
MAP_FLATTEN,
ITER_NTH,
ITER_SKIP_NEXT,
Expand Down Expand Up @@ -833,6 +879,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]),
["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]),
["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1]),
["map", "find"] => lint_find_map(cx, expr, arg_lists[1], arg_lists[0]),
["flat_map", "filter"] => lint_filter_flat_map(cx, expr, arg_lists[1], arg_lists[0]),
["flat_map", "filter_map"] => lint_filter_map_flat_map(cx, expr, arg_lists[1], arg_lists[0]),
["flatten", "map"] => lint_map_flatten(cx, expr, arg_lists[1]),
Expand Down Expand Up @@ -1911,6 +1959,42 @@ fn lint_filter_map<'a, 'tcx>(
}
}

/// lint use of `filter_map().next()` for `Iterators`
fn lint_filter_map_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr, filter_args: &'tcx [hir::Expr]) {
if match_trait_method(cx, expr, &paths::ITERATOR) {
let msg = "called `filter_map(p).next()` on an `Iterator`. This is more succinctly expressed by calling \
`.find_map(p)` instead.";
let filter_snippet = snippet(cx, filter_args[1].span, "..");
if filter_snippet.lines().count() <= 1 {
andrehjr marked this conversation as resolved.
Show resolved Hide resolved
span_note_and_lint(
cx,
FILTER_MAP_NEXT,
expr.span,
msg,
expr.span,
&format!("replace `filter_map({0}).next()` with `find_map({0})`", filter_snippet),
);
} else {
span_lint(cx, FILTER_MAP_NEXT, expr.span, msg);
}
}
}

/// lint use of `find().map()` for `Iterators`
fn lint_find_map<'a, 'tcx>(
cx: &LateContext<'a, 'tcx>,
expr: &'tcx hir::Expr,
_find_args: &'tcx [hir::Expr],
map_args: &'tcx [hir::Expr],
) {
// lint if caller of `.filter().map()` is an Iterator
if match_trait_method(cx, &map_args[0], &paths::ITERATOR) {
let msg = "called `find(p).map(q)` on an `Iterator`. \
This is more succinctly expressed by calling `.find_map(..)` instead.";
span_lint(cx, FIND_MAP, expr.span, msg);
}
}

/// lint use of `filter().map()` for `Iterators`
fn lint_filter_map_map<'a, 'tcx>(
cx: &LateContext<'a, 'tcx>,
Expand Down
4 changes: 3 additions & 1 deletion clippy_lints/src/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ fn check_pat<'a, 'tcx>(
if let ExprKind::Struct(_, ref efields, _) = init_struct.node {
for field in pfields {
let name = field.node.ident.name;
let efield = efields.iter().find(|f| f.ident.name == name).map(|f| &*f.expr);
let efield = efields
.iter()
.find_map(|f| if f.ident.name == name { Some(&*f.expr) } else { None });
check_pat(cx, &field.node.pat, efield, span, bindings);
}
} else {
Expand Down
14 changes: 10 additions & 4 deletions clippy_lints/src/utils/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,16 @@ pub fn get_attr<'a>(
attrs.iter().filter(move |attr| {
let attr_segments = &attr.path.segments;
if attr_segments.len() == 2 && attr_segments[0].ident.to_string() == "clippy" {
if let Some(deprecation_status) = BUILTIN_ATTRIBUTES
.iter()
.find(|(builtin_name, _)| *builtin_name == attr_segments[1].ident.to_string())
.map(|(_, deprecation_status)| deprecation_status)
if let Some(deprecation_status) =
BUILTIN_ATTRIBUTES
.iter()
.find_map(|(builtin_name, deprecation_status)| {
if *builtin_name == attr_segments[1].ident.to_string() {
Some(deprecation_status)
} else {
None
}
})
{
let mut db = sess.struct_span_err(attr_segments[1].ident.span, "Usage of deprecated attribute");
match deprecation_status {
Expand Down
11 changes: 7 additions & 4 deletions tests/compile-test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,13 @@ fn config(mode: &str, dir: PathBuf) -> compiletest::Config {
let name = path.file_name()?.to_string_lossy();
// NOTE: This only handles a single dep
// https://github.com/laumann/compiletest-rs/issues/101
needs_disambiguation
.iter()
.find(|dep| name.starts_with(&format!("lib{}-", dep)))
.map(|dep| format!("--extern {}={}", dep, path.display()))
needs_disambiguation.iter().find_map(|dep| {
if name.starts_with(&format!("lib{}-", dep)) {
Some(format!("--extern {}={}", dep, path.display()))
} else {
None
}
})
})
.collect::<Vec<_>>();

Expand Down
20 changes: 20 additions & 0 deletions tests/ui/filter_map_next.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#![warn(clippy::all, clippy::pedantic)]

fn main() {
let a = ["1", "lol", "3", "NaN", "5"];

let element: Option<i32> = a.iter().filter_map(|s| s.parse().ok()).next();
assert_eq!(element, Some(1));

#[rustfmt::skip]
let _: Option<u32> = vec![1, 2, 3, 4, 5, 6]
.into_iter()
.filter_map(|x| {
if x == 2 {
Some(x * 2)
} else {
None
}
})
.next();
}
24 changes: 24 additions & 0 deletions tests/ui/filter_map_next.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
error: called `filter_map(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find_map(p)` instead.
--> $DIR/filter_map_next.rs:6:32
|
LL | let element: Option<i32> = a.iter().filter_map(|s| s.parse().ok()).next();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::filter-map-next` implied by `-D warnings`
= note: replace `filter_map(|s| s.parse().ok()).next()` with `find_map(|s| s.parse().ok())`

error: called `filter_map(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find_map(p)` instead.
--> $DIR/filter_map_next.rs:10:26
|
LL | let _: Option<u32> = vec![1, 2, 3, 4, 5, 6]
| __________________________^
LL | | .into_iter()
LL | | .filter_map(|x| {
LL | | if x == 2 {
... |
LL | | })
LL | | .next();
| |_______________^

error: aborting due to 2 previous errors

32 changes: 32 additions & 0 deletions tests/ui/find_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#![warn(clippy::all, clippy::pedantic)]

#[derive(Debug, Copy, Clone)]
enum Flavor {
Chocolate,
}

#[derive(Debug, Copy, Clone)]
enum Dessert {
Banana,
Pudding,
Cake(Flavor),
}

fn main() {
let desserts_of_the_week = vec![Dessert::Banana, Dessert::Cake(Flavor::Chocolate), Dessert::Pudding];

let a = ["lol", "NaN", "2", "5", "Xunda"];

let _: Option<i32> = a.iter().find(|s| s.parse::<i32>().is_ok()).map(|s| s.parse().unwrap());

let _: Option<Flavor> = desserts_of_the_week
.iter()
.find(|dessert| match *dessert {
Dessert::Cake(_) => true,
_ => false,
})
.map(|dessert| match *dessert {
Dessert::Cake(ref flavor) => *flavor,
_ => unreachable!(),
});
}
23 changes: 23 additions & 0 deletions tests/ui/find_map.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
error: called `find(p).map(q)` on an `Iterator`. This is more succinctly expressed by calling `.find_map(..)` instead.
--> $DIR/find_map.rs:20:26
|
LL | let _: Option<i32> = a.iter().find(|s| s.parse::<i32>().is_ok()).map(|s| s.parse().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::find-map` implied by `-D warnings`

error: called `find(p).map(q)` on an `Iterator`. This is more succinctly expressed by calling `.find_map(..)` instead.
--> $DIR/find_map.rs:22:29
|
LL | let _: Option<Flavor> = desserts_of_the_week
| _____________________________^
LL | | .iter()
LL | | .find(|dessert| match *dessert {
LL | | Dessert::Cake(_) => true,
... |
LL | | _ => unreachable!(),
LL | | });
| |__________^

error: aborting due to 2 previous errors