Skip to content

Commit bbd439e

Browse files
Add FILTER_NEXT lint
1 parent ee236da commit bbd439e

File tree

4 files changed

+72
-6
lines changed

4 files changed

+72
-6
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
66
[Jump to usage instructions](#usage)
77

88
##Lints
9-
There are 86 lints included in this crate:
9+
There are 87 lints included in this crate:
1010

1111
name | default | meaning
1212
---------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -28,6 +28,7 @@ name
2828
[eq_op](https://github.com/Manishearth/rust-clippy/wiki#eq_op) | warn | equal operands on both sides of a comparison or bitwise combination (e.g. `x == x`)
2929
[explicit_counter_loop](https://github.com/Manishearth/rust-clippy/wiki#explicit_counter_loop) | warn | for-looping with an explicit counter when `_.enumerate()` would do
3030
[explicit_iter_loop](https://github.com/Manishearth/rust-clippy/wiki#explicit_iter_loop) | warn | for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do
31+
[filter_next](https://github.com/Manishearth/rust-clippy/wiki#filter_next) | warn | using `filter(p).next()`, which is more succinctly expressed as `.find(p)`
3132
[float_cmp](https://github.com/Manishearth/rust-clippy/wiki#float_cmp) | warn | using `==` or `!=` on float values (as floating-point operations usually involve rounding errors, it is always better to check for approximate equality within small bounds)
3233
[identity_op](https://github.com/Manishearth/rust-clippy/wiki#identity_op) | warn | using identity operations, e.g. `x + 0` or `y / 1`
3334
[ineffective_bit_mask](https://github.com/Manishearth/rust-clippy/wiki#ineffective_bit_mask) | warn | expressions where a bit mask will be rendered useless by a comparison, e.g. `(x | 1) > 2`
@@ -55,8 +56,8 @@ name
5556
[non_ascii_literal](https://github.com/Manishearth/rust-clippy/wiki#non_ascii_literal) | allow | using any literal non-ASCII chars in a string literal; suggests using the \\u escape instead
5657
[nonsensical_open_options](https://github.com/Manishearth/rust-clippy/wiki#nonsensical_open_options) | warn | nonsensical combination of options for opening a file
5758
[ok_expect](https://github.com/Manishearth/rust-clippy/wiki#ok_expect) | warn | using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result
58-
[option_map_unwrap_or](https://github.com/Manishearth/rust-clippy/wiki#option_map_unwrap_or) | warn | using `Option.map(f).unwrap_or(a)`, which is more succinctly expressed as `map_or(a, f)`)
59-
[option_map_unwrap_or_else](https://github.com/Manishearth/rust-clippy/wiki#option_map_unwrap_or_else) | warn | using `Option.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `map_or_else(g, f)`)
59+
[option_map_unwrap_or](https://github.com/Manishearth/rust-clippy/wiki#option_map_unwrap_or) | warn | using `Option.map(f).unwrap_or(a)`, which is more succinctly expressed as `map_or(a, f)`
60+
[option_map_unwrap_or_else](https://github.com/Manishearth/rust-clippy/wiki#option_map_unwrap_or_else) | warn | using `Option.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `map_or_else(g, f)`
6061
[option_unwrap_used](https://github.com/Manishearth/rust-clippy/wiki#option_unwrap_used) | allow | using `Option.unwrap()`, which should at least get a better message using `expect()`
6162
[out_of_bounds_indexing](https://github.com/Manishearth/rust-clippy/wiki#out_of_bounds_indexing) | deny | out of bound constant indexing
6263
[panic_params](https://github.com/Manishearth/rust-clippy/wiki#panic_params) | warn | missing parameters in `panic!`

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
176176
matches::MATCH_BOOL,
177177
matches::MATCH_REF_PATS,
178178
matches::SINGLE_MATCH,
179+
methods::FILTER_NEXT,
179180
methods::OK_EXPECT,
180181
methods::OPTION_MAP_UNWRAP_OR,
181182
methods::OPTION_MAP_UNWRAP_OR_ELSE,

src/methods.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::iter;
66
use std::borrow::Cow;
77

88
use utils::{snippet, span_lint, span_note_and_lint, match_path, match_type, method_chain_args,
9-
walk_ptrs_ty_depth, walk_ptrs_ty};
9+
match_trait_method, walk_ptrs_ty_depth, walk_ptrs_ty};
1010
use utils::{OPTION_PATH, RESULT_PATH, STRING_PATH};
1111
use utils::MethodArgs;
1212

@@ -135,7 +135,7 @@ declare_lint!(pub OK_EXPECT, Warn,
135135
/// **Example:** `x.map(|a| a + 1).unwrap_or(0)`
136136
declare_lint!(pub OPTION_MAP_UNWRAP_OR, Warn,
137137
"using `Option.map(f).unwrap_or(a)`, which is more succinctly expressed as \
138-
`map_or(a, f)`)");
138+
`map_or(a, f)`");
139139

140140
/// **What it does:** This lint `Warn`s on `_.map(_).unwrap_or_else(_)`.
141141
///
@@ -146,7 +146,17 @@ declare_lint!(pub OPTION_MAP_UNWRAP_OR, Warn,
146146
/// **Example:** `x.map(|a| a + 1).unwrap_or_else(some_function)`
147147
declare_lint!(pub OPTION_MAP_UNWRAP_OR_ELSE, Warn,
148148
"using `Option.map(f).unwrap_or_else(g)`, which is more succinctly expressed as \
149-
`map_or_else(g, f)`)");
149+
`map_or_else(g, f)`");
150+
151+
/// **What it does:** This lint `Warn`s on `_.filter(_).next()`.
152+
///
153+
/// **Why is this bad?** Readability, this can be written more concisely as `_.find(_)`.
154+
///
155+
/// **Known problems:** None.
156+
///
157+
/// **Example:** `iter.filter(|x| x == 0).next()`
158+
declare_lint!(pub FILTER_NEXT, Warn,
159+
"using `filter(p).next()`, which is more succinctly expressed as `.find(p)`");
150160

151161
impl LintPass for MethodsPass {
152162
fn get_lints(&self) -> LintArray {
@@ -174,6 +184,9 @@ impl LateLintPass for MethodsPass {
174184
else if let Some(arglists) = method_chain_args(expr, &["map", "unwrap_or_else"]) {
175185
lint_map_unwrap_or_else(cx, expr, arglists[0], arglists[1]);
176186
}
187+
else if let Some(arglists) = method_chain_args(expr, &["filter", "next"]) {
188+
lint_filter_next(cx, expr, arglists[0]);
189+
}
177190
}
178191
}
179192

@@ -331,6 +344,24 @@ fn lint_map_unwrap_or_else(cx: &LateContext, expr: &Expr, unwrap_args: &MethodAr
331344
}
332345
}
333346

347+
#[allow(ptr_arg)] // Type of MethodArgs is potentially a Vec
348+
/// lint use of `filter().next() for Iterators`
349+
fn lint_filter_next(cx: &LateContext, expr: &Expr, filter_args: &MethodArgs) {
350+
// lint if caller of `.filter().next()` is an Iterator
351+
if match_trait_method(cx, expr, &["core", "iter", "Iterator"]) {
352+
let msg = "called `filter(p).next()` on an Iterator. This is more succinctly expressed by \
353+
calling `.find(p)` instead.";
354+
let filter_snippet = snippet(cx, filter_args[1].span, "..");
355+
if filter_snippet.lines().count() <= 1 { // add note if not multi-line
356+
span_note_and_lint(cx, FILTER_NEXT, expr.span, msg, expr.span,
357+
&format!("replace this with `find({})`)", filter_snippet));
358+
}
359+
else {
360+
span_lint(cx, FILTER_NEXT, expr.span, msg);
361+
}
362+
}
363+
}
364+
334365
// Given a `Result<T, E>` type, return its error type (`E`)
335366
fn get_error_type<'a>(cx: &LateContext, ty: ty::Ty<'a>) -> Option<ty::Ty<'a>> {
336367
if !match_type(cx, ty, &RESULT_PATH) {

tests/compile-fail/methods.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,39 @@ fn option_methods() {
8383

8484
}
8585

86+
/// Struct to generate false positive for FILTER_NEXT lint
87+
struct FilterNextTest {
88+
_foo: u32,
89+
}
90+
91+
impl FilterNextTest {
92+
fn filter(self) -> FilterNextTest {
93+
self
94+
}
95+
fn next(self) -> FilterNextTest {
96+
self
97+
}
98+
}
99+
100+
/// Checks implementation of FILTER_NEXT lint
101+
fn filter_next() {
102+
let v = vec![3, 2, 1, 0, -1, -2, -3];
103+
104+
// check single-line case
105+
let _ = v.iter().filter(|&x| *x < 0).next(); //~ERROR called `filter(p).next()` on an Iterator.
106+
//~| NOTE replace this
107+
108+
// check multi-line case
109+
let _ = v.iter().filter(|&x| { //~ERROR called `filter(p).next()` on an Iterator.
110+
*x < 0
111+
}
112+
).next();
113+
114+
// check that we don't lint if the caller is not an Iterator
115+
let foo = FilterNextTest { _foo: 0 };
116+
let _ = foo.filter().next();
117+
}
118+
86119
fn main() {
87120
use std::io;
88121

0 commit comments

Comments
 (0)