Skip to content

Commit c292da8

Browse files
committed
Add rebind_fn_arg_as_mut lint
1 parent 83bb5ec commit c292da8

File tree

6 files changed

+192
-0
lines changed

6 files changed

+192
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,6 +1906,7 @@ Released 2018-09-13
19061906
[`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero
19071907
[`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len
19081908
[`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer
1909+
[`rebind_fn_arg_as_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#rebind_fn_arg_as_mut
19091910
[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
19101911
[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
19111912
[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure

clippy_lints/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ mod ptr_eq;
288288
mod ptr_offset_with_cast;
289289
mod question_mark;
290290
mod ranges;
291+
mod rebind_fn_arg_as_mut;
291292
mod redundant_clone;
292293
mod redundant_closure_call;
293294
mod redundant_field_names;
@@ -798,6 +799,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
798799
&ranges::RANGE_PLUS_ONE,
799800
&ranges::RANGE_ZIP_WITH_LEN,
800801
&ranges::REVERSED_EMPTY_RANGES,
802+
&rebind_fn_arg_as_mut::REBIND_FN_ARG_AS_MUT,
801803
&redundant_clone::REDUNDANT_CLONE,
802804
&redundant_closure_call::REDUNDANT_CLOSURE_CALL,
803805
&redundant_field_names::REDUNDANT_FIELD_NAMES,
@@ -959,6 +961,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
959961
store.register_late_pass(|| box lifetimes::Lifetimes);
960962
store.register_late_pass(|| box entry::HashMapPass);
961963
store.register_late_pass(|| box ranges::Ranges);
964+
store.register_late_pass(|| box rebind_fn_arg_as_mut::RebindFnArgAsMut);
962965
store.register_late_pass(|| box types::Casts);
963966
let type_complexity_threshold = conf.type_complexity_threshold;
964967
store.register_late_pass(move || box types::TypeComplexity::new(type_complexity_threshold));
@@ -1489,6 +1492,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14891492
LintId::of(&ranges::MANUAL_RANGE_CONTAINS),
14901493
LintId::of(&ranges::RANGE_ZIP_WITH_LEN),
14911494
LintId::of(&ranges::REVERSED_EMPTY_RANGES),
1495+
LintId::of(&rebind_fn_arg_as_mut::REBIND_FN_ARG_AS_MUT),
14921496
LintId::of(&redundant_clone::REDUNDANT_CLONE),
14931497
LintId::of(&redundant_closure_call::REDUNDANT_CLOSURE_CALL),
14941498
LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES),
@@ -1646,6 +1650,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16461650
LintId::of(&ptr_eq::PTR_EQ),
16471651
LintId::of(&question_mark::QUESTION_MARK),
16481652
LintId::of(&ranges::MANUAL_RANGE_CONTAINS),
1653+
LintId::of(&rebind_fn_arg_as_mut::REBIND_FN_ARG_AS_MUT),
16491654
LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES),
16501655
LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
16511656
LintId::of(&regex::TRIVIAL_REGEX),
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use crate::utils::{snippet, span_lint_and_then};
2+
use if_chain::if_chain;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{
5+
BindingAnnotation, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, Local, Node, PatKind, QPath, TraitFn,
6+
TraitItem, TraitItemKind,
7+
};
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_middle::lint::in_external_macro;
10+
use rustc_session::{declare_lint_pass, declare_tool_lint};
11+
12+
declare_clippy_lint! {
13+
/// **What it does:** Checks for function arguments declared as not mutable and
14+
/// later rebound as mutable.
15+
///
16+
/// **Why is this bad?** It can be easily improved by just declaring the function
17+
/// argument as mutable and removing the unnecessary assignment.
18+
///
19+
/// **Known problems:** The function argument might have been shadowed by another
20+
/// value before the assignment.
21+
///
22+
/// **Example:**
23+
///
24+
/// ```rust
25+
/// fn foo_bad(bar: Vec<i32>) -> Vec<i32> {
26+
/// let mut bar = bar;
27+
/// bar.push(42);
28+
/// bar
29+
/// }
30+
/// ```
31+
/// Use instead:
32+
/// ```rust
33+
/// fn foo(mut bar: Vec<i32>) -> Vec<i32> {
34+
/// bar.push(42);
35+
/// bar
36+
/// }
37+
/// ```
38+
pub REBIND_FN_ARG_AS_MUT,
39+
style,
40+
"non-mutable function argument rebound as mutable"
41+
}
42+
43+
declare_lint_pass!(RebindFnArgAsMut => [REBIND_FN_ARG_AS_MUT]);
44+
45+
impl LateLintPass<'_> for RebindFnArgAsMut {
46+
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &Local<'tcx>) {
47+
if_chain! {
48+
if !in_external_macro(cx.tcx.sess, local.span);
49+
50+
// LHS
51+
if let PatKind::Binding(BindingAnnotation::Mutable, _, name, None) = local.pat.kind;
52+
53+
// RHS
54+
if let Some(init) = local.init;
55+
if let ExprKind::Path(QPath::Resolved(_, path)) = init.kind;
56+
if path.segments.len() == 1;
57+
if path.segments[0].ident == name;
58+
59+
if let rustc_hir::def::Res::Local(id) = path.res;
60+
61+
// Fn
62+
let parent_id = cx.tcx.hir().get_parent_item(id);
63+
64+
if let Node::Item(&Item { kind: ItemKind::Fn(_, _, body_id), .. })
65+
| Node::ImplItem(&ImplItem { kind: ImplItemKind::Fn(_, body_id), .. })
66+
| Node::TraitItem(&TraitItem { kind: TraitItemKind::Fn(_, TraitFn::Provided(body_id)), .. })
67+
= cx.tcx.hir().get(parent_id);
68+
69+
let body = cx.tcx.hir().body(body_id);
70+
if let Some(param) = body.params.iter().find(|param| param.pat.hir_id == id);
71+
if let PatKind::Binding(BindingAnnotation::Unannotated, ..) = param.pat.kind;
72+
73+
then {
74+
span_lint_and_then(
75+
cx,
76+
REBIND_FN_ARG_AS_MUT,
77+
param.pat.span,
78+
&format!("argument `{}` is declared as not mutable, and later rebound as mutable", name),
79+
|diag| {
80+
diag.span_suggestion(
81+
param.pat.span,
82+
"consider just declaring as mutable",
83+
format!("mut {}", snippet(cx, param.pat.span, "_")),
84+
Applicability::MaybeIncorrect,
85+
);
86+
diag.span_help(local.span, "and remove this");
87+
},
88+
);
89+
}
90+
}
91+
}
92+
}

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,6 +1936,13 @@ vec![
19361936
deprecation: None,
19371937
module: "types",
19381938
},
1939+
Lint {
1940+
name: "rebind_fn_arg_as_mut",
1941+
group: "style",
1942+
desc: "non-mutable function argument rebound as mutable",
1943+
deprecation: None,
1944+
module: "rebind_fn_arg_as_mut",
1945+
},
19391946
Lint {
19401947
name: "redundant_allocation",
19411948
group: "perf",

tests/ui/rebind_fn_arg_as_mut.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// edition:2018
2+
3+
#![warn(clippy::rebind_fn_arg_as_mut)]
4+
5+
fn f(x: bool) {
6+
let mut x = x;
7+
}
8+
9+
trait T {
10+
fn tm1(x: bool) {
11+
let mut x = x;
12+
}
13+
fn tm2(x: bool);
14+
}
15+
16+
struct S;
17+
18+
impl S {
19+
fn m(x: bool) {
20+
let mut x = x;
21+
}
22+
}
23+
24+
impl T for S {
25+
fn tm2(x: bool) {
26+
let mut x = x;
27+
}
28+
}
29+
30+
fn f_no_lint(mut x: bool) {
31+
let mut x = x;
32+
}
33+
34+
async fn expansion<T>(_: T) {}
35+
36+
fn main() {}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
error: argument `x` is declared as not mutable, and later rebound as mutable
2+
--> $DIR/rebind_fn_arg_as_mut.rs:5:6
3+
|
4+
LL | fn f(x: bool) {
5+
| ^ help: consider just declaring as mutable: `mut x`
6+
|
7+
= note: `-D clippy::rebind-fn-arg-as-mut` implied by `-D warnings`
8+
help: and remove this
9+
--> $DIR/rebind_fn_arg_as_mut.rs:6:5
10+
|
11+
LL | let mut x = x;
12+
| ^^^^^^^^^^^^^^
13+
14+
error: argument `x` is declared as not mutable, and later rebound as mutable
15+
--> $DIR/rebind_fn_arg_as_mut.rs:10:12
16+
|
17+
LL | fn tm1(x: bool) {
18+
| ^ help: consider just declaring as mutable: `mut x`
19+
|
20+
help: and remove this
21+
--> $DIR/rebind_fn_arg_as_mut.rs:11:9
22+
|
23+
LL | let mut x = x;
24+
| ^^^^^^^^^^^^^^
25+
26+
error: argument `x` is declared as not mutable, and later rebound as mutable
27+
--> $DIR/rebind_fn_arg_as_mut.rs:19:10
28+
|
29+
LL | fn m(x: bool) {
30+
| ^ help: consider just declaring as mutable: `mut x`
31+
|
32+
help: and remove this
33+
--> $DIR/rebind_fn_arg_as_mut.rs:20:9
34+
|
35+
LL | let mut x = x;
36+
| ^^^^^^^^^^^^^^
37+
38+
error: argument `x` is declared as not mutable, and later rebound as mutable
39+
--> $DIR/rebind_fn_arg_as_mut.rs:25:12
40+
|
41+
LL | fn tm2(x: bool) {
42+
| ^ help: consider just declaring as mutable: `mut x`
43+
|
44+
help: and remove this
45+
--> $DIR/rebind_fn_arg_as_mut.rs:26:9
46+
|
47+
LL | let mut x = x;
48+
| ^^^^^^^^^^^^^^
49+
50+
error: aborting due to 4 previous errors
51+

0 commit comments

Comments
 (0)