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

suspicious_map: accept mutation in map().count() #5375

Closed
wants to merge 1 commit into from
Closed
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
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this case still gets linted...

Copy link
Contributor Author

@mathstuf mathstuf Mar 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All we get is the name of the function. Should we do name lookup and look into the implementation to check for mutated (non-local) variables?

Which makes me think…I suppose mutable local variables will false-negative this as well :/ . Hrm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or can we do a name lookup and somehow ask if it is FnMut?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping, that maybe_body_owned_by will get the BodyId of the closure and that it would Just Work™ with mutable_variables. Can you check if it even does get a BodyId?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does get a BodyId, but doesn't detect any mutated variables in it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So none of the examples in the test are Upvar instances when going through mutated_variables. I'm not sure what's going on here right now, but I suspect the Copy-ness is confusing things here?

| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: make sure you did not confuse `map` with `filter` or `for_each`

error: aborting due to 2 previous errors