Skip to content

Commit fef65c9

Browse files
committed
implement fn_arg_mut_rebindings lint
1 parent 13d91b7 commit fef65c9

7 files changed

Lines changed: 236 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6625,6 +6625,7 @@ Released 2018-09-13
66256625
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
66266626
[`float_equality_without_abs`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_equality_without_abs
66276627
[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
6628+
[`fn_arg_mut_rebindings`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_arg_mut_rebindings
66286629
[`fn_null_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_null_check
66296630
[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
66306631
[`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
@@ -168,6 +168,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
168168
crate::float_literal::LOSSY_FLOAT_LITERAL_INFO,
169169
crate::floating_point_arithmetic::IMPRECISE_FLOPS_INFO,
170170
crate::floating_point_arithmetic::SUBOPTIMAL_FLOPS_INFO,
171+
crate::fn_arg_mut_rebindings::FN_ARG_MUT_REBINDINGS_INFO,
171172
crate::format::USELESS_FORMAT_INFO,
172173
crate::format_args::FORMAT_IN_FORMAT_ARGS_INFO,
173174
crate::format_args::POINTER_FORMAT_INFO,
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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.96.0"]
37+
pub FN_ARG_MUT_REBINDINGS,
38+
style,
39+
"non-mutable function argument rebound as mutable"
40+
}
41+
42+
declare_lint_pass!(FnArgMutRebindings => [FN_ARG_MUT_REBINDINGS]);
43+
44+
impl LateLintPass<'_> for FnArgMutRebindings {
45+
fn check_local(&mut self, cx: &LateContext<'_>, st: &'_ LetStmt<'_>) {
46+
if !st.span.in_external_macro(cx.tcx.sess.source_map())
47+
48+
// check let statement binds as mutable
49+
&& let PatKind::Binding(BindingMode::MUT, _, ident, None) = st.pat.kind
50+
&& let Some(init) = st.init
51+
52+
// check let statement binds to variable with same name
53+
&& let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
54+
&& path.segments.len() == 1
55+
&& path.segments[0].ident == ident
56+
&& let Res::Local(id) = path.res
57+
58+
// check let statement scope is whole function body
59+
&& let Some((_, owner)) = cx.tcx.hir_parent_owner_iter(st.hir_id).next()
60+
&& let Some(body_id) = fn_body_id(&owner)
61+
&& let &Body { params, value } = cx.tcx.hir_body(body_id)
62+
&& let ExprKind::Block(fn_block, _) = value.kind
63+
&& let Some(st_block) = get_enclosing_block(cx, st.hir_id)
64+
&& fn_block.hir_id == st_block.hir_id
65+
66+
// check param declares as immutable
67+
&& let Some(&Param {
68+
span: pat_span,
69+
pat:
70+
Pat {
71+
kind: PatKind::Binding(BindingMode::NONE, ..),
72+
..
73+
},
74+
..
75+
}) = params.iter().find(|p| p.pat.hir_id == id)
76+
{
77+
span_lint_and_then(
78+
cx,
79+
FN_ARG_MUT_REBINDINGS,
80+
pat_span,
81+
format!(
82+
"argument `{}` is declared as not mutable, and later rebound as mutable",
83+
ident.name
84+
),
85+
|diag| {
86+
diag.span_suggestion(
87+
pat_span,
88+
"consider just declaring as mutable",
89+
format!("mut {}", snippet(cx, pat_span, "_")),
90+
Applicability::MaybeIncorrect,
91+
);
92+
diag.span_help(st.span, "and remove this");
93+
},
94+
);
95+
}
96+
}
97+
}
98+
99+
fn fn_body_id(node: &OwnerNode<'_>) -> Option<BodyId> {
100+
match node {
101+
OwnerNode::Item(Item {
102+
kind: ItemKind::Fn { body: body_id, .. },
103+
..
104+
})
105+
| OwnerNode::ImplItem(ImplItem {
106+
kind: ImplItemKind::Fn(_, body_id),
107+
// avoid false-positive: trait-fn can come from external crate
108+
impl_kind: ImplItemImplKind::Inherent { .. },
109+
..
110+
})
111+
| OwnerNode::TraitItem(TraitItem {
112+
kind: TraitItemKind::Fn(_, TraitFn::Provided(body_id)),
113+
..
114+
}) => Some(*body_id),
115+
_ => None,
116+
}
117+
}

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;
@@ -866,6 +867,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
866867
Box::new(move |_| Box::new(manual_take::ManualTake::new(conf))),
867868
Box::new(|_| Box::new(manual_checked_ops::ManualCheckedOps)),
868869
Box::new(move |_| Box::new(manual_pop_if::ManualPopIf::new(conf))),
870+
Box::new(|_| Box::new(fn_arg_mut_rebindings::FnArgMutRebindings)),
869871
// add late passes here, used by `cargo dev new_lint`
870872
];
871873
store.late_passes.extend(late_lints);

tests/ui/fn_arg_mut_rebindings.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//@aux-build:proc_macros.rs
2+
//@no-rustfix
3+
4+
#![warn(clippy::fn_arg_mut_rebindings)]
5+
6+
extern crate proc_macros;
7+
use proc_macros::external;
8+
9+
external! {
10+
fn external_macros_rebinding(x: bool, y: bool) {
11+
let mut x = x;
12+
}
13+
}
14+
15+
fn fn_body_rebinding(x: bool) {
16+
//~^ fn_arg_mut_rebindings
17+
let mut x = x;
18+
}
19+
20+
fn branch_rebinding(x: bool, m: u32) {
21+
if x {
22+
let mut m = m;
23+
}
24+
}
25+
26+
fn arm_rebinding(x: Option<u32>, m: u32) {
27+
match x {
28+
Some(_) => {
29+
let mut m = m;
30+
},
31+
None => {
32+
let mut m = 1;
33+
},
34+
}
35+
}
36+
37+
fn inner_block_rebinding(x: bool) {
38+
{
39+
let mut x = x;
40+
}
41+
}
42+
43+
fn shadowed_rebinding(x: bool) {
44+
let x = 0;
45+
let mut x = x;
46+
}
47+
48+
trait MyTrait {
49+
fn provided_fn_rebinding(&self, x: bool) {
50+
//~^ fn_arg_mut_rebindings
51+
let mut x = x;
52+
}
53+
54+
fn inherent_fn_rebinding(&self, x: bool);
55+
}
56+
57+
struct MyStruct;
58+
59+
impl MyStruct {
60+
fn impl_fn_rebinding(&self, x: bool) {
61+
//~^ fn_arg_mut_rebindings
62+
let mut x = x;
63+
}
64+
}
65+
66+
impl MyTrait for MyStruct {
67+
fn inherent_fn_rebinding(&self, x: bool) {
68+
let mut x = x;
69+
}
70+
}
71+
72+
fn main() {
73+
// test code goes here
74+
}
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:15: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:17: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:49: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:51: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:60: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:62: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)