Skip to content

Commit

Permalink
suspicious_map: accept mutation in map().count()
Browse files Browse the repository at this point in the history
If the closure in the `map` call ends up mutating a variable, the call
is assumed to no longer be suspicious.

Given just a function name or path, there's no way to detect that there
is interiour mutability, so the lint still fires in that case. However,
it is now documented as a known problem.

Fixes #5253
  • Loading branch information
mathstuf committed Mar 26, 2020
1 parent 1ff81c1 commit fbca9f5
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 5 deletions.
17 changes: 14 additions & 3 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1084,7 +1084,8 @@ declare_clippy_lint! {
/// If the `map` call is intentional, this should be rewritten. Or, if you intend to
/// drive the iterator to completion, you can just use `for_each` instead.
///
/// **Known problems:** None
/// **Known problems:** Named functions which mutate other data internally are not
/// detected as non-suspicious.
///
/// **Example:**
///
Expand Down Expand Up @@ -1341,7 +1342,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
["as_mut"] => lint_asref(cx, expr, "as_mut", arg_lists[0]),
["fold", ..] => lint_unnecessary_fold(cx, expr, arg_lists[0], method_spans[0]),
["filter_map", ..] => unnecessary_filter_map::lint(cx, expr, arg_lists[0]),
["count", "map"] => lint_suspicious_map(cx, expr),
["count", "map"] => lint_suspicious_map(cx, expr, &arg_lists[1][1]),
["assume_init"] => lint_maybe_uninit(cx, &arg_lists[0][0], expr),
["unwrap_or", arith @ "checked_add"]
| ["unwrap_or", arith @ "checked_sub"]
Expand Down Expand Up @@ -3142,7 +3143,17 @@ fn is_maybe_uninit_ty_valid(cx: &LateContext<'_, '_>, ty: Ty<'_>) -> bool {
}
}

fn lint_suspicious_map(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) {
fn lint_suspicious_map<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &hir::Expr<'tcx>, map_arg: &'tcx hir::Expr<'_>) {
if let Some(body_id) = cx.tcx.hir().maybe_body_owned_by(map_arg.hir_id) {
let closure_body = cx.tcx.hir().body(body_id);
if let Some(map_mutated_vars) = mutated_variables(&closure_body.value, cx) {
// A variable is used mutably inside of the closure. Suppress the lint.
if !map_mutated_vars.is_empty() {
return;
}
}
}

span_lint_and_help(
cx,
SUSPICIOUS_MAP,
Expand Down
14 changes: 14 additions & 0 deletions tests/ui/suspicious_map.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
#![warn(clippy::suspicious_map)]
#![allow(clippy::redundant_closure)]

fn main() {
let _ = (0..3).map(|x| x + 2).count();

// This usage is OK because the `sum` side effect makes the `map` useful.
let mut sum = 0;
let _ = (0..3).map(|x| sum += x).count();

// The linter is blind to path-based arguments however.
let mut ext_sum = 0;
let ext_closure = |x| { ext_sum += x };
let _ = (0..3).map(ext_closure).count();

// The linter can see `FnMut` calls however.
let mut ext_closure_inner = |x| { ext_sum += x };
let _ = (0..3).map(|x| ext_closure_inner(x)).count();
}
12 changes: 10 additions & 2 deletions tests/ui/suspicious_map.stderr
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
error: this call to `map()` won't have an effect on the call to `count()`
--> $DIR/suspicious_map.rs:4:13
--> $DIR/suspicious_map.rs:5:13
|
LL | let _ = (0..3).map(|x| x + 2).count();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::suspicious-map` implied by `-D warnings`
= help: make sure you did not confuse `map` with `filter` or `for_each`

error: aborting due to previous error
error: this call to `map()` won't have an effect on the call to `count()`
--> $DIR/suspicious_map.rs:14:13
|
LL | let _ = (0..3).map(ext_closure).count();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: make sure you did not confuse `map` with `filter` or `for_each`

error: aborting due to 2 previous errors

0 comments on commit fbca9f5

Please sign in to comment.