Skip to content

Commit f5ffac4

Browse files
author
Michael Wright
committed
Implement unnecesary_filter_map lint
1 parent cdde22c commit f5ffac4

File tree

6 files changed

+214
-3
lines changed

6 files changed

+214
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ All notable changes to this project will be documented in this file.
864864
[`unit_arg`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#unit_arg
865865
[`unit_cmp`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#unit_cmp
866866
[`unnecessary_cast`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#unnecessary_cast
867+
[`unnecessary_filter_map`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#unnecessary_filter_map
867868
[`unnecessary_fold`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#unnecessary_fold
868869
[`unnecessary_mut_passed`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
869870
[`unnecessary_operation`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#unnecessary_operation

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ We are currently in the process of discussing Clippy 1.0 via the RFC process in
99

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

12-
[There are 278 lints included in this crate!](https://rust-lang-nursery.github.io/rust-clippy/master/index.html)
12+
[There are 279 lints included in this crate!](https://rust-lang-nursery.github.io/rust-clippy/master/index.html)
1313

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

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
614614
methods::SINGLE_CHAR_PATTERN,
615615
methods::STRING_EXTEND_CHARS,
616616
methods::TEMPORARY_CSTRING_AS_PTR,
617+
methods::UNNECESSARY_FILTER_MAP,
617618
methods::UNNECESSARY_FOLD,
618619
methods::USELESS_ASREF,
619620
methods::WRONG_SELF_CONVENTION,
@@ -829,6 +830,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
829830
methods::CLONE_ON_COPY,
830831
methods::FILTER_NEXT,
831832
methods::SEARCH_IS_SOME,
833+
methods::UNNECESSARY_FILTER_MAP,
832834
methods::USELESS_ASREF,
833835
misc::SHORT_CIRCUIT_STATEMENT,
834836
misc_early::REDUNDANT_CLOSURE_CALL,

clippy_lints/src/methods.rs

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::rustc::hir;
22
use crate::rustc::hir::def::Def;
3+
use crate::rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
34
use crate::rustc::lint::{in_external_macro, LateContext, LateLintPass, Lint, LintArray, LintContext, LintPass};
45
use crate::rustc::ty::{self, Ty};
56
use crate::rustc::{declare_tool_lint, lint_array};
@@ -8,6 +9,7 @@ use crate::syntax::ast;
89
use crate::syntax::source_map::{BytePos, Span};
910
use crate::utils::paths;
1011
use crate::utils::sugg;
12+
use crate::utils::usage::mutated_variables;
1113
use crate::utils::{
1214
get_arg_name, get_trait_def_id, implements_trait, in_macro, is_copy, is_expn_of, is_self, is_self_ty,
1315
iter_input_pats, last_path_segment, match_def_path, match_path, match_qpath, match_trait_method, match_type,
@@ -692,6 +694,27 @@ declare_clippy_lint! {
692694
"using `fold` when a more succinct alternative exists"
693695
}
694696

697+
698+
/// **What it does:** Checks for `filter_map` calls which could be replaced by `filter` or `map`.
699+
///
700+
/// **Why is this bad?** Complexity
701+
///
702+
/// **Known problems:** None
703+
///
704+
/// **Example:**
705+
/// ```rust
706+
/// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None });
707+
/// ```
708+
/// This could be written as:
709+
/// ```rust
710+
/// let _ = (0..3).filter(|&x| x > 2);
711+
/// ```
712+
declare_clippy_lint! {
713+
pub UNNECESSARY_FILTER_MAP,
714+
complexity,
715+
"using `filter_map` when a more succinct alternative exists"
716+
}
717+
695718
impl LintPass for Pass {
696719
fn get_lints(&self) -> LintArray {
697720
lint_array!(
@@ -725,7 +748,8 @@ impl LintPass for Pass {
725748
STRING_EXTEND_CHARS,
726749
ITER_CLONED_COLLECT,
727750
USELESS_ASREF,
728-
UNNECESSARY_FOLD
751+
UNNECESSARY_FOLD,
752+
UNNECESSARY_FILTER_MAP
729753
)
730754
}
731755
}
@@ -791,6 +815,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
791815
lint_asref(cx, expr, "as_mut", arglists[0]);
792816
} else if let Some(arglists) = method_chain_args(expr, &["fold"]) {
793817
lint_unnecessary_fold(cx, expr, arglists[0]);
818+
} else if let Some(arglists) = method_chain_args(expr, &["filter_map"]) {
819+
unnecessary_filter_map::lint(cx, expr, arglists[0]);
794820
}
795821

796822
lint_or_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args);
@@ -1398,6 +1424,144 @@ fn lint_unnecessary_fold(cx: &LateContext<'_, '_>, expr: &hir::Expr, fold_args:
13981424
};
13991425
}
14001426

1427+
mod unnecessary_filter_map {
1428+
use super::*;
1429+
1430+
pub(super) fn lint(cx: &LateContext<'_, '_>, expr: &hir::Expr, args: &[hir::Expr]) {
1431+
1432+
if !match_trait_method(cx, expr, &paths::ITERATOR) {
1433+
return;
1434+
}
1435+
1436+
if let hir::ExprKind::Closure(_, _, body_id, ..) = args[1].node {
1437+
1438+
let body = cx.tcx.hir.body(body_id);
1439+
let arg_id = body.arguments[0].pat.id;
1440+
let mutates_arg = match mutated_variables(&body.value, cx) {
1441+
Some(used_mutably) => used_mutably.contains(&arg_id),
1442+
None => true,
1443+
};
1444+
1445+
let (mut found_mapping, mut found_filtering) = check_expression(&cx, arg_id, &body.value);
1446+
1447+
let mut return_visitor = ReturnVisitor::new(&cx, arg_id);
1448+
return_visitor.visit_expr(&body.value);
1449+
found_mapping |= return_visitor.found_mapping;
1450+
found_filtering |= return_visitor.found_filtering;
1451+
1452+
if !found_filtering {
1453+
span_lint(
1454+
cx,
1455+
UNNECESSARY_FILTER_MAP,
1456+
expr.span,
1457+
"this `.filter_map` can be written more simply using `.map`",
1458+
);
1459+
return;
1460+
}
1461+
1462+
if !found_mapping && !mutates_arg {
1463+
span_lint(
1464+
cx,
1465+
UNNECESSARY_FILTER_MAP,
1466+
expr.span,
1467+
"this `.filter_map` can be written more simply using `.filter`",
1468+
);
1469+
return;
1470+
}
1471+
}
1472+
}
1473+
1474+
// returns (found_mapping, found_filtering)
1475+
fn check_expression<'a, 'tcx: 'a>(cx: &'a LateContext<'a, 'tcx>, arg_id: ast::NodeId, expr: &'tcx hir::Expr) -> (bool, bool) {
1476+
match &expr.node {
1477+
hir::ExprKind::Call(ref func, ref args) => {
1478+
if_chain! {
1479+
if let hir::ExprKind::Path(ref path) = func.node;
1480+
then {
1481+
if match_qpath(path, &paths::OPTION_SOME) {
1482+
if_chain! {
1483+
if let hir::ExprKind::Path(path) = &args[0].node;
1484+
if let Def::Local(ref local) = cx.tables.qpath_def(path, args[0].hir_id);
1485+
then {
1486+
if arg_id == *local {
1487+
return (false, false)
1488+
}
1489+
}
1490+
}
1491+
return (true, false);
1492+
} else {
1493+
// We don't know. It might do anything.
1494+
return (true, true);
1495+
}
1496+
}
1497+
}
1498+
(true, true)
1499+
},
1500+
hir::ExprKind::Block(ref block, _) => {
1501+
if let Some(expr) = &block.expr {
1502+
check_expression(cx, arg_id, &expr)
1503+
} else {
1504+
(false, false)
1505+
}
1506+
},
1507+
// There must be an else_arm or there will be a type error
1508+
hir::ExprKind::If(_, ref if_arm, Some(ref else_arm)) => {
1509+
let if_check = check_expression(cx, arg_id, if_arm);
1510+
let else_check = check_expression(cx, arg_id, else_arm);
1511+
(if_check.0 | else_check.0, if_check.1 | else_check.1)
1512+
},
1513+
hir::ExprKind::Match(_, ref arms, _) => {
1514+
let mut found_mapping = false;
1515+
let mut found_filtering = false;
1516+
for arm in arms {
1517+
let (m, f) = check_expression(cx, arg_id, &arm.body);
1518+
found_mapping |= m;
1519+
found_filtering |= f;
1520+
}
1521+
(found_mapping, found_filtering)
1522+
},
1523+
hir::ExprKind::Path(path) if match_qpath(path, &paths::OPTION_NONE) => (false, true),
1524+
_ => (true, true)
1525+
}
1526+
}
1527+
1528+
struct ReturnVisitor<'a, 'tcx: 'a> {
1529+
cx: &'a LateContext<'a, 'tcx>,
1530+
arg_id: ast::NodeId,
1531+
// Found a non-None return that isn't Some(input)
1532+
found_mapping: bool,
1533+
// Found a return that isn't Some
1534+
found_filtering: bool,
1535+
}
1536+
1537+
impl<'a, 'tcx: 'a> ReturnVisitor<'a, 'tcx> {
1538+
fn new(cx: &'a LateContext<'a, 'tcx>, arg_id: ast::NodeId) -> ReturnVisitor<'a, 'tcx> {
1539+
ReturnVisitor {
1540+
cx,
1541+
arg_id,
1542+
found_mapping: false,
1543+
found_filtering: false,
1544+
}
1545+
}
1546+
}
1547+
1548+
impl<'a, 'tcx> Visitor<'tcx> for ReturnVisitor<'a, 'tcx> {
1549+
fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
1550+
if let hir::ExprKind::Ret(Some(expr)) = &expr.node {
1551+
let (found_mapping, found_filtering) = check_expression(self.cx, self.arg_id, expr);
1552+
self.found_mapping |= found_mapping;
1553+
self.found_filtering |= found_filtering;
1554+
} else {
1555+
walk_expr(self, expr);
1556+
}
1557+
}
1558+
1559+
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
1560+
NestedVisitorMap::None
1561+
}
1562+
}
1563+
}
1564+
14011565
fn lint_iter_nth(cx: &LateContext<'_, '_>, expr: &hir::Expr, iter_args: &[hir::Expr], is_mut: bool) {
14021566
let mut_str = if is_mut { "_mut" } else { "" };
14031567
let caller_type = if derefs_to_slice(cx, &iter_args[0], cx.tables.expr_ty(&iter_args[0])).is_some() {

tests/ui/methods.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,3 +443,17 @@ fn main() {
443443
let opt = Some(0);
444444
let _ = opt.unwrap();
445445
}
446+
447+
/// Checks implementation of `UNNECESSARY_FILTER_MAP` lint
448+
fn unnecessary_filter_map() {
449+
let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None });
450+
let _ = (0..4).filter_map(|x| { if x > 1 { return Some(x); }; None });
451+
let _ = (0..4).filter_map(|x| match x {
452+
0 | 1 => None,
453+
_ => Some(x),
454+
});
455+
456+
let _ = (0..4).filter_map(|x| Some(x + 1));
457+
458+
let _ = (0..4).filter_map(i32::checked_abs);
459+
}

tests/ui/methods.stderr

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,5 +453,35 @@ error: used unwrap() on an Option value. If you don't want to handle the None ca
453453
|
454454
= note: `-D clippy::option-unwrap-used` implied by `-D warnings`
455455

456-
error: aborting due to 56 previous errors
456+
error: this `.filter_map` can be written more simply using `.filter`
457+
--> $DIR/methods.rs:449:13
458+
|
459+
449 | let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None });
460+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
461+
|
462+
= note: `-D clippy::unnecessary-filter-map` implied by `-D warnings`
463+
464+
error: this `.filter_map` can be written more simply using `.filter`
465+
--> $DIR/methods.rs:450:13
466+
|
467+
450 | let _ = (0..4).filter_map(|x| { if x > 1 { return Some(x); }; None });
468+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
469+
470+
error: this `.filter_map` can be written more simply using `.filter`
471+
--> $DIR/methods.rs:451:13
472+
|
473+
451 | let _ = (0..4).filter_map(|x| match x {
474+
| _____________^
475+
452 | | 0 | 1 => None,
476+
453 | | _ => Some(x),
477+
454 | | });
478+
| |______^
479+
480+
error: this `.filter_map` can be written more simply using `.map`
481+
--> $DIR/methods.rs:456:13
482+
|
483+
456 | let _ = (0..4).filter_map(|x| Some(x + 1));
484+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
485+
486+
error: aborting due to 60 previous errors
457487

0 commit comments

Comments
 (0)