-
Notifications
You must be signed in to change notification settings - Fork 1.7k
issue#3424 improve filter_map lint docs #3461
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
Changes from all commits
06f39e1
19d335b
8c64708
eeab7db
f96eea7
9101623
9b45aec
ee9c13d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -13,6 +13,7 @@ use rustc::hir::intravisit::{self, Visitor}; | |||||
use rustc::lint::{in_external_macro, LateContext, LateLintPass, Lint, LintArray, LintContext, LintPass}; | ||||||
use rustc::ty::{self, Predicate, Ty}; | ||||||
use rustc::{declare_lint_pass, declare_tool_lint}; | ||||||
use crate::rustc_data_structures::fx::FxHashMap; | ||||||
use rustc_errors::Applicability; | ||||||
use syntax::ast; | ||||||
use syntax::source_map::{BytePos, Span}; | ||||||
|
@@ -272,8 +273,70 @@ declare_clippy_lint! { | |||||
} | ||||||
|
||||||
declare_clippy_lint! { | ||||||
/// **What it does:** Checks for usage of `_.filter(_).map(_)`, | ||||||
/// `_.filter(_).flat_map(_)`, `_.filter_map(_).flat_map(_)` and similar. | ||||||
/// **What it does:** Checks for usage of `_.filter_map(_).map(_)`, which can be more concisely | ||||||
/// written as `_.filter_map(_)`. | ||||||
/// | ||||||
/// **Why is this bad?** Readability, this can be written more concisely as a | ||||||
/// single method call. | ||||||
/// | ||||||
/// ** Example:** | ||||||
/// ```rust | ||||||
/// let ws: Vec<String> = ["is", "this", "shizzle", "for", "rizzle"] | ||||||
/// .iter() | ||||||
/// .map(|s| s.to_string()) | ||||||
/// .collect(); | ||||||
/// | ||||||
/// let correct: Vec<String> = ws | ||||||
/// .iter() | ||||||
/// .filter_map(|s| if s.contains("izzle") { Some(s) } else { None }) | ||||||
/// .map(|s| format!("{}{}", s, s)) | ||||||
/// .collect(); | ||||||
/// | ||||||
/// let more_correct: Vec<String> = ws | ||||||
/// .iter() | ||||||
/// .filter_map(|s| { | ||||||
/// if s.contains("izzle") { | ||||||
/// Some(format!("{}{}", s, s)) | ||||||
/// } else { | ||||||
/// None | ||||||
/// } | ||||||
/// }) | ||||||
/// .collect(); | ||||||
/// | ||||||
/// println!("{:?}", correct); | ||||||
/// assert_eq!(correct, more_correct); | ||||||
/// ``` | ||||||
/// .iter() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You forgot to remove the old example code 😉 |
||||||
/// .map(|s| s.to_string()) | ||||||
/// .collect(); | ||||||
/// | ||||||
/// let correct: Vec<String> = ws.iter() | ||||||
/// .filter_map(|s| if s.contains("izzle"){ | ||||||
/// Some(s) | ||||||
/// } else { | ||||||
/// None | ||||||
/// }) | ||||||
/// .map(|s| format!("{}{}", s, s)) | ||||||
/// .collect(); | ||||||
/// | ||||||
/// let more_correct: Vec<String> = ws.iter() | ||||||
/// .filter_map(|s| if s.contains("izzle"){ | ||||||
/// Some(format!("{}{}", s, s)) | ||||||
/// } else { | ||||||
/// None | ||||||
/// }) | ||||||
/// .collect(); | ||||||
/// | ||||||
/// println!("{:?}", correct); | ||||||
/// assert_eq!(correct, more_correct); | ||||||
pub FILTER_MAP_MAP, | ||||||
pedantic, | ||||||
"using `filter_map` followed by `map` can more concisely be written as a single method call" | ||||||
} | ||||||
|
||||||
declare_clippy_lint! { | ||||||
/// **What it does:** Checks for usage of `_.filter(_).map(_)` to destructure an iterator of | ||||||
/// Options or Results instead of a single `_.filter_map(_)` call. | ||||||
/// | ||||||
/// **Why is this bad?** Readability, this can be written more concisely as a | ||||||
/// single method call. | ||||||
|
@@ -287,7 +350,7 @@ declare_clippy_lint! { | |||||
/// ``` | ||||||
pub FILTER_MAP, | ||||||
pedantic, | ||||||
"using combinations of `filter`, `map`, `filter_map` and `flat_map` which can usually be written as a single method call" | ||||||
"using `filter` and `map` to destructure an iterator of Options or Results instead of a single `filter_map` call" | ||||||
} | ||||||
|
||||||
declare_clippy_lint! { | ||||||
|
@@ -842,6 +905,7 @@ declare_lint_pass!(Methods => [ | |||||
SEARCH_IS_SOME, | ||||||
TEMPORARY_CSTRING_AS_PTR, | ||||||
FILTER_NEXT, | ||||||
FILTER_MAP_MAP, | ||||||
FILTER_MAP, | ||||||
FILTER_MAP_NEXT, | ||||||
FIND_MAP, | ||||||
|
@@ -882,8 +946,6 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { | |||||
["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]), | ||||||
["is_some", "find"] => lint_search_is_some(cx, expr, "find", arg_lists[1], arg_lists[0]), | ||||||
["is_some", "position"] => lint_search_is_some(cx, expr, "position", arg_lists[1], arg_lists[0]), | ||||||
|
@@ -1998,14 +2060,35 @@ fn lint_filter_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr, | |||||
fn lint_filter_map<'a, 'tcx>( | ||||||
cx: &LateContext<'a, 'tcx>, | ||||||
expr: &'tcx hir::Expr, | ||||||
_filter_args: &'tcx [hir::Expr], | ||||||
_map_args: &'tcx [hir::Expr], | ||||||
filter_args: &'tcx [hir::Expr], | ||||||
map_args: &'tcx [hir::Expr], | ||||||
) { | ||||||
// lint if caller of `.filter().map()` is an Iterator | ||||||
if match_trait_method(cx, expr, &paths::ITERATOR) { | ||||||
let msg = "called `filter(p).map(q)` on an `Iterator`. \ | ||||||
This is more succinctly expressed by calling `.filter_map(..)` instead."; | ||||||
span_lint(cx, FILTER_MAP, expr.span, msg); | ||||||
let mut responses: FxHashMap<String, &str> = FxHashMap::default(); | ||||||
responses.insert( | ||||||
"is_some".to_string(), | ||||||
"called `filter(p).map(q)` on an `Iterator<Option<_>>`. \ | ||||||
Consider calling `.filter_map(..)` instead.", | ||||||
); | ||||||
responses.insert( | ||||||
"is_ok".to_string(), | ||||||
"called `filter(p).map(q)` on an `Iterator<Result<_>>`. \ | ||||||
Consider calling `.filter_map(..)` instead.", | ||||||
); | ||||||
|
||||||
if_chain! { | ||||||
if match_trait_method(cx, expr, &paths::ITERATOR); | ||||||
if let hir::ExprKind::Closure(_, _, ref map_body_id, _, _) = map_args[1].node; | ||||||
if let hir::ExprKind::MethodCall(ref map_ps, _, _) = cx.tcx.hir().body(*map_body_id).value.node; | ||||||
if map_ps.ident.name == sym!(unwrap); | ||||||
if let hir::ExprKind::Closure(_, _, ref filter_body_id, _, _) = filter_args[1].node; | ||||||
if let hir::ExprKind::MethodCall(ref filter_ps, _, _) = cx.tcx.hir().body(*filter_body_id).value.node; | ||||||
if responses.contains_key(&filter_ps.ident.name.to_string()); | ||||||
if let hir::ExprKind::MethodCall(_, _, ref expr_vec) = expr.node; | ||||||
if let hir::ExprKind::MethodCall(_, ref filter_span, _) = expr_vec[0].node; | ||||||
then { | ||||||
let new_span = Span::new(filter_span.lo(), expr.span.hi(), expr.span.ctxt()); | ||||||
span_lint(cx, FILTER_MAP, new_span, responses[&filter_ps.ident.name.to_string()]); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
|
@@ -2052,43 +2135,18 @@ fn lint_filter_map_map<'a, 'tcx>( | |||||
_filter_args: &'tcx [hir::Expr], | ||||||
_map_args: &'tcx [hir::Expr], | ||||||
) { | ||||||
// lint if caller of `.filter().map()` is an Iterator | ||||||
if match_trait_method(cx, expr, &paths::ITERATOR) { | ||||||
let msg = "called `filter_map(p).map(q)` on an `Iterator`. \ | ||||||
This is more succinctly expressed by only calling `.filter_map(..)` instead."; | ||||||
span_lint(cx, FILTER_MAP, expr.span, msg); | ||||||
} | ||||||
} | ||||||
|
||||||
/// lint use of `filter().flat_map()` for `Iterators` | ||||||
fn lint_filter_flat_map<'a, 'tcx>( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mikerite I added a commit to this PR that removes the linting of |
||||||
cx: &LateContext<'a, 'tcx>, | ||||||
expr: &'tcx hir::Expr, | ||||||
_filter_args: &'tcx [hir::Expr], | ||||||
_map_args: &'tcx [hir::Expr], | ||||||
) { | ||||||
// lint if caller of `.filter().flat_map()` is an Iterator | ||||||
if match_trait_method(cx, expr, &paths::ITERATOR) { | ||||||
let msg = "called `filter(p).flat_map(q)` on an `Iterator`. \ | ||||||
This is more succinctly expressed by calling `.flat_map(..)` \ | ||||||
and filtering by returning an empty Iterator."; | ||||||
span_lint(cx, FILTER_MAP, expr.span, msg); | ||||||
let mut span = expr.span; | ||||||
if let hir::ExprKind::MethodCall(_, _, ref expr_vec) = expr.node { | ||||||
if let hir::ExprKind::MethodCall(_, ref filter_span, _) = expr_vec[0].node { | ||||||
span = Span::new(filter_span.lo(), expr.span.hi(), expr.span.ctxt()); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's already a function for this on
Suggested change
|
||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
/// lint use of `filter_map().flat_map()` for `Iterators` | ||||||
fn lint_filter_map_flat_map<'a, 'tcx>( | ||||||
cx: &LateContext<'a, 'tcx>, | ||||||
expr: &'tcx hir::Expr, | ||||||
_filter_args: &'tcx [hir::Expr], | ||||||
_map_args: &'tcx [hir::Expr], | ||||||
) { | ||||||
// lint if caller of `.filter_map().flat_map()` is an Iterator | ||||||
// lint if caller of `.filter_map().map()` is an Iterator | ||||||
if match_trait_method(cx, expr, &paths::ITERATOR) { | ||||||
let msg = "called `filter_map(p).flat_map(q)` on an `Iterator`. \ | ||||||
This is more succinctly expressed by calling `.flat_map(..)` \ | ||||||
and filtering by returning an empty Iterator."; | ||||||
span_lint(cx, FILTER_MAP, expr.span, msg); | ||||||
let msg = "called `filter_map(p).map(q)` on an `Iterator`. \ | ||||||
This is more succinctly expressed by only calling `.filter_map(..)` instead."; | ||||||
span_lint(cx, FILTER_MAP_MAP, span, msg); | ||||||
} | ||||||
} | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,55 @@ | ||
#![warn(clippy::all, clippy::pedantic)] | ||
#![allow(clippy::missing_docs_in_private_items)] | ||
#![allow(clippy::redundant_closure)] | ||
#![allow(clippy::unnecessary_filter_map)] | ||
|
||
fn main() { | ||
let _: Vec<_> = vec![5; 6].into_iter().filter(|&x| x == 0).map(|x| x * 2).collect(); | ||
let options: Vec<Option<i8>> = vec![Some(5), None, Some(6)]; | ||
let results = ["1", "lol", "3", "NaN", "5"]; | ||
|
||
let _: Vec<_> = vec![5_i8; 6] | ||
.into_iter() | ||
.filter(|&x| x == 0) | ||
.flat_map(|x| x.checked_mul(2)) | ||
// validate iterator of values triggers filter_map + map lint | ||
let _: Vec<_> = vec![5_i8; 6].into_iter() | ||
.filter_map(|x| x.checked_mul(2)) | ||
.map(|x| x.checked_mul(2)) | ||
.collect(); | ||
|
||
let _: Vec<_> = vec![5_i8; 6] | ||
.into_iter() | ||
.filter_map(|x| x.checked_mul(2)) | ||
.flat_map(|x| x.checked_mul(2)) | ||
waynr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// validate iterator of options triggers filter + map lint | ||
let _: Vec<i8> = options.clone().into_iter() | ||
.filter(|x| x.is_some()) | ||
.map(|x| x.unwrap()) | ||
.collect(); | ||
|
||
// validate iterator of options triggers filter + flat_map lint | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was removed in this PR, wasn't it? |
||
let _: Vec<Option<i8>> = std::iter::repeat(options.clone()) | ||
.take(5) | ||
.map(|v| Some(v.into_iter())) | ||
.filter(|x| x.is_some()) | ||
.flat_map(|x| x.unwrap()) | ||
.collect(); | ||
|
||
// validate iterator of results triggers filter + map lint | ||
let _: Vec<i8> = results.iter() | ||
.map(|s| s.parse()) | ||
.filter(|s| s.is_ok()) | ||
.map(|s| s.unwrap()) | ||
.collect(); | ||
|
||
// validate iterator of values **does not** trigger filter + map lint | ||
let _: Vec<String> = results.iter() | ||
.filter(|s| s.len() > 3) | ||
.map(|s| format!("{}{}", s, s)) | ||
.collect(); | ||
|
||
// validate iterator of values **does not** trigger filter + flat_map lint | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above: Wasn't this removed completely anyway? |
||
let _: String = results.iter() | ||
.filter(|i| i.len() > 1) | ||
.flat_map(|s| s.chars()) | ||
.collect(); | ||
|
||
// validate filter_map + flat_map **does not** trigger linter | ||
let _: Vec<_> = vec![5_i8; 6] | ||
.into_iter() | ||
.filter_map(|x| x.checked_mul(2)) | ||
.map(|x| x.checked_mul(2)) | ||
.flat_map(|x| x.checked_mul(2)) | ||
.collect(); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.