Skip to content

Commit 2828179

Browse files
committed
implement fn_arg_mut_rebindings lint
1 parent dc8b277 commit 2828179

8 files changed

Lines changed: 307 additions & 1 deletion

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6523,6 +6523,7 @@ Released 2018-09-13
65236523
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
65246524
[`float_equality_without_abs`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_equality_without_abs
65256525
[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
6526+
[`fn_arg_mut_rebindings`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_arg_mut_rebindings
65266527
[`fn_null_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_null_check
65276528
[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
65286529
[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
167167
crate::float_literal::LOSSY_FLOAT_LITERAL_INFO,
168168
crate::floating_point_arithmetic::IMPRECISE_FLOPS_INFO,
169169
crate::floating_point_arithmetic::SUBOPTIMAL_FLOPS_INFO,
170+
crate::fn_arg_mut_rebindings::FN_ARG_MUT_REBINDINGS_INFO,
170171
crate::format::USELESS_FORMAT_INFO,
171172
crate::format_args::FORMAT_IN_FORMAT_ARGS_INFO,
172173
crate::format_args::POINTER_FORMAT_INFO,
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::get_enclosing_block;
3+
use clippy_utils::source::snippet;
4+
use rustc_errors::Applicability;
5+
use rustc_hir::def::Res;
6+
use rustc_hir::{
7+
BindingMode, Body, BodyId, ExprKind, ImplItem, ImplItemImplKind, ImplItemKind, Item, ItemKind, LetStmt, OwnerNode,
8+
Param, Pat, PatKind, QPath, TraitFn, TraitItem, TraitItemKind,
9+
};
10+
use rustc_lint::{LateContext, LateLintPass};
11+
use rustc_session::declare_lint_pass;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// Checks for function arguments declared as not mutable and later rebound as mutable.
16+
///
17+
/// ### Why is this bad?
18+
/// It can be easily improved by just declaring the function argument as mutable and
19+
/// removing the unnecessary assignment.
20+
///
21+
/// ### Example
22+
/// ```no_run
23+
/// fn foo_bad(bar: Vec<i32>) -> Vec<i32> {
24+
/// let mut bar = bar;
25+
/// bar.push(42);
26+
/// bar
27+
/// }
28+
/// ```
29+
/// Use instead:
30+
/// ```no_run
31+
/// fn foo(mut bar: Vec<i32>) -> Vec<i32> {
32+
/// bar.push(42);
33+
/// bar
34+
/// }
35+
/// ```
36+
#[clippy::version = "1.95.0"]
37+
pub FN_ARG_MUT_REBINDINGS,
38+
style,
39+
"non-mutable function argument rebound as mutable"
40+
}
41+
declare_lint_pass!(FnArgMutRebindings => [FN_ARG_MUT_REBINDINGS]);
42+
43+
impl LateLintPass<'_> for FnArgMutRebindings {
44+
fn check_local(&mut self, ctx: &LateContext<'_>, st: &'_ LetStmt<'_>) {
45+
if !st.span.in_external_macro(ctx.tcx.sess.source_map())
46+
47+
// check let statement binds as mutable
48+
&& let PatKind::Binding(BindingMode::MUT, _, ident, None) = st.pat.kind
49+
&& let Some(init) = st.init
50+
51+
// check let statement binds to variable with same name
52+
&& let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
53+
&& path.segments.len() == 1
54+
&& path.segments[0].ident == ident
55+
&& let Res::Local(id) = path.res
56+
57+
// check let statement scope is whole function body
58+
&& let Some((_, owner)) = ctx.tcx.hir_parent_owner_iter(st.hir_id).next()
59+
&& let Some(body_id) = fn_body_id(&owner)
60+
&& let &Body { params, value } = ctx.tcx.hir_body(body_id)
61+
&& let ExprKind::Block(fn_block, _) = value.kind
62+
&& let Some(st_block) = get_enclosing_block(ctx, st.hir_id)
63+
&& fn_block.hir_id == st_block.hir_id
64+
65+
// check param declares as immutable
66+
&& let Some(&Param {
67+
span: pat_span,
68+
pat:
69+
Pat {
70+
kind: PatKind::Binding(BindingMode::NONE, ..),
71+
..
72+
},
73+
..
74+
}) = params.iter().find(|p| p.pat.hir_id == id)
75+
{
76+
span_lint_and_then(
77+
ctx,
78+
FN_ARG_MUT_REBINDINGS,
79+
pat_span,
80+
format!(
81+
"argument `{}` is declared as not mutable, and later rebound as mutable",
82+
ident.name
83+
),
84+
|diag| {
85+
diag.span_suggestion(
86+
pat_span,
87+
"consider just declaring as mutable",
88+
format!("mut {}", snippet(ctx, pat_span, "_")),
89+
Applicability::MaybeIncorrect,
90+
);
91+
diag.span_help(st.span, "and remove this");
92+
},
93+
);
94+
}
95+
}
96+
}
97+
98+
fn fn_body_id(node: &OwnerNode<'_>) -> Option<BodyId> {
99+
match node {
100+
OwnerNode::Item(Item {
101+
kind: ItemKind::Fn { body: body_id, .. },
102+
..
103+
})
104+
| OwnerNode::ImplItem(ImplItem {
105+
kind: ImplItemKind::Fn(_, body_id),
106+
// avoid false-positive: trait-fn can come from external crate
107+
impl_kind: ImplItemImplKind::Inherent { .. },
108+
..
109+
})
110+
| OwnerNode::TraitItem(TraitItem {
111+
kind: TraitItemKind::Fn(_, TraitFn::Provided(body_id)),
112+
..
113+
}) => Some(*body_id),
114+
_ => None,
115+
}
116+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ mod fallible_impl_from;
136136
mod field_scoped_visibility_modifiers;
137137
mod float_literal;
138138
mod floating_point_arithmetic;
139+
mod fn_arg_mut_rebindings;
139140
mod format;
140141
mod format_args;
141142
mod format_impl;
@@ -863,6 +864,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
863864
Box::new(move |tcx| Box::new(duration_suboptimal_units::DurationSuboptimalUnits::new(tcx, conf))),
864865
Box::new(move |_| Box::new(manual_take::ManualTake::new(conf))),
865866
Box::new(|_| Box::new(manual_checked_ops::ManualCheckedOps)),
867+
Box::new(|_| Box::new(fn_arg_mut_rebindings::FnArgMutRebindings)),
866868
// add late passes here, used by `cargo dev new_lint`
867869
];
868870
store.late_passes.extend(late_lints);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//@aux-build:proc_macros.rs
2+
3+
#![warn(clippy::fn_arg_mut_rebindings)]
4+
5+
extern crate proc_macros;
6+
use proc_macros::external;
7+
8+
external! {
9+
fn external_macros_rebinding(x: bool, y: bool) {
10+
let mut x = x;
11+
}
12+
}
13+
14+
fn fn_body_rebinding(mut x: bool) {
15+
//~^ fn_arg_mut_rebindings
16+
let mut x = x;
17+
}
18+
19+
fn branch_rebinding(x: bool, m: u32) {
20+
if x {
21+
let mut m = m;
22+
}
23+
}
24+
25+
fn arm_rebinding(x: Option<u32>, m: u32) {
26+
match x {
27+
Some(_) => {
28+
let mut m = m;
29+
},
30+
None => {
31+
let mut m = 1;
32+
},
33+
}
34+
}
35+
36+
fn inner_block_rebinding(x: bool) {
37+
{
38+
let mut x = x;
39+
}
40+
}
41+
42+
fn shadowed_rebinding(x: bool) {
43+
let x = 0;
44+
let mut x = x;
45+
}
46+
47+
trait MyTrait {
48+
fn provided_fn_rebinding(&self, mut x: bool) {
49+
//~^ fn_arg_mut_rebindings
50+
let mut x = x;
51+
}
52+
53+
fn inherent_fn_rebinding(&self, x: bool);
54+
}
55+
56+
struct MyStruct;
57+
58+
impl MyStruct {
59+
fn impl_fn_rebinding(&self, mut x: bool) {
60+
//~^ fn_arg_mut_rebindings
61+
let mut x = x;
62+
}
63+
}
64+
65+
impl MyTrait for MyStruct {
66+
fn inherent_fn_rebinding(&self, x: bool) {
67+
let mut x = x;
68+
}
69+
}
70+
71+
fn main() {
72+
// test code goes here
73+
}

tests/ui/fn_arg_mut_rebindings.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//@aux-build:proc_macros.rs
2+
3+
#![warn(clippy::fn_arg_mut_rebindings)]
4+
5+
extern crate proc_macros;
6+
use proc_macros::external;
7+
8+
external! {
9+
fn external_macros_rebinding(x: bool, y: bool) {
10+
let mut x = x;
11+
}
12+
}
13+
14+
fn fn_body_rebinding(x: bool) {
15+
//~^ fn_arg_mut_rebindings
16+
let mut x = x;
17+
}
18+
19+
fn branch_rebinding(x: bool, m: u32) {
20+
if x {
21+
let mut m = m;
22+
}
23+
}
24+
25+
fn arm_rebinding(x: Option<u32>, m: u32) {
26+
match x {
27+
Some(_) => {
28+
let mut m = m;
29+
},
30+
None => {
31+
let mut m = 1;
32+
},
33+
}
34+
}
35+
36+
fn inner_block_rebinding(x: bool) {
37+
{
38+
let mut x = x;
39+
}
40+
}
41+
42+
fn shadowed_rebinding(x: bool) {
43+
let x = 0;
44+
let mut x = x;
45+
}
46+
47+
trait MyTrait {
48+
fn provided_fn_rebinding(&self, x: bool) {
49+
//~^ fn_arg_mut_rebindings
50+
let mut x = x;
51+
}
52+
53+
fn inherent_fn_rebinding(&self, x: bool);
54+
}
55+
56+
struct MyStruct;
57+
58+
impl MyStruct {
59+
fn impl_fn_rebinding(&self, x: bool) {
60+
//~^ fn_arg_mut_rebindings
61+
let mut x = x;
62+
}
63+
}
64+
65+
impl MyTrait for MyStruct {
66+
fn inherent_fn_rebinding(&self, x: bool) {
67+
let mut x = x;
68+
}
69+
}
70+
71+
fn main() {
72+
// test code goes here
73+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error: argument `x` is declared as not mutable, and later rebound as mutable
2+
--> tests/ui/fn_arg_mut_rebindings.rs:14:22
3+
|
4+
LL | fn fn_body_rebinding(x: bool) {
5+
| ^^^^^^^ help: consider just declaring as mutable: `mut x: bool`
6+
|
7+
help: and remove this
8+
--> tests/ui/fn_arg_mut_rebindings.rs:16:5
9+
|
10+
LL | let mut x = x;
11+
| ^^^^^^^^^^^^^^
12+
= note: `-D clippy::fn-arg-mut-rebindings` implied by `-D warnings`
13+
= help: to override `-D warnings` add `#[allow(clippy::fn_arg_mut_rebindings)]`
14+
15+
error: argument `x` is declared as not mutable, and later rebound as mutable
16+
--> tests/ui/fn_arg_mut_rebindings.rs:48:37
17+
|
18+
LL | fn provided_fn_rebinding(&self, x: bool) {
19+
| ^^^^^^^ help: consider just declaring as mutable: `mut x: bool`
20+
|
21+
help: and remove this
22+
--> tests/ui/fn_arg_mut_rebindings.rs:50:9
23+
|
24+
LL | let mut x = x;
25+
| ^^^^^^^^^^^^^^
26+
27+
error: argument `x` is declared as not mutable, and later rebound as mutable
28+
--> tests/ui/fn_arg_mut_rebindings.rs:59:33
29+
|
30+
LL | fn impl_fn_rebinding(&self, x: bool) {
31+
| ^^^^^^^ help: consider just declaring as mutable: `mut x: bool`
32+
|
33+
help: and remove this
34+
--> tests/ui/fn_arg_mut_rebindings.rs:61:9
35+
|
36+
LL | let mut x = x;
37+
| ^^^^^^^^^^^^^^
38+
39+
error: aborting due to 3 previous errors
40+

tests/ui/unused_io_amount.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![allow(dead_code, clippy::needless_pass_by_ref_mut)]
1+
#![allow(dead_code, clippy::needless_pass_by_ref_mut, clippy::fn_arg_mut_rebindings)]
22
#![allow(clippy::redundant_pattern_matching)]
33
#![warn(clippy::unused_io_amount)]
44

0 commit comments

Comments
 (0)