Skip to content

[WIP] - new lint: using let mut a = a at start of function #5138

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

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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,7 @@ Released 2018-09-13
[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
[`fn_param_redef_as_mutable`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_param_redef_as_mutable
[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation
[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map
Expand Down
96 changes: 96 additions & 0 deletions clippy_lints/src/fn_param_redef_as_mutable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use rustc_lint::{EarlyLintPass, EarlyContext};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use syntax::ast::*;
use syntax::visit::FnKind;
use rustc_span::Span;
use rustc_errors::DiagnosticBuilder;
use crate::utils::{span_lint_and_then, multispan_sugg};
use if_chain::if_chain;

declare_clippy_lint! {
/// **What it does:** checks if any fn parameters have been assigned to a local mutable
/// variable.
///
/// **Why is this bad?** reduces the complexity of the code by removing a redundant local
/// mutable variable.
Comment on lines +14 to +15
Copy link

Choose a reason for hiding this comment

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

Making this mistake is bad because you add unnecessary complexity, not reduce it ;)

Maybe something like

Suggested change
/// **Why is this bad?** reduces the complexity of the code by removing a redundant local
/// mutable variable.
/// **Why is this bad?** Introduces a local variables that increases complexity unnecessarily.

///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// fn f(a: Vec<bool>) {
/// let mut a = a;
/// // rest of code
/// }
/// ```
///
/// could be defined as
///
/// ```rust
/// fn f(mut a: Vec<bool>) {
/// // rest of code
/// }
/// ```
pub FN_PARAM_REDEF_AS_MUTABLE,
Copy link
Contributor

Choose a reason for hiding this comment

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

bikeshedding: I think we usually call them variable "bindings" rather than "definitions". So, this can be named FN_PARAM_REBIND_AS_MUT.

Copy link
Author

Choose a reason for hiding this comment

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

Sweet, I'll rename it cause that sounds a lot better.

complexity,
"local variables that can be eliminated by updating fn params mutability"
}

declare_lint_pass!(FnParamRedefAsMutable => [FN_PARAM_REDEF_AS_MUTABLE]);

impl EarlyLintPass for FnParamRedefAsMutable {
fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, fn_decl: &FnDecl, span: Span, _: NodeId) {
if let FnKind::ItemFn(_, _, _, block) | FnKind::Method(_, _, _, block) = fn_kind {
for stmt in &block.stmts {
check_statement(cx, fn_decl, span, stmt);
}
}
}
}

fn check_statement(cx: &EarlyContext<'_>, fn_decl: &FnDecl, fn_span: Span, stmt: &Stmt) {
if_chain! {
// Check to see if the local variable is defined as mutable
if let StmtKind::Local(ref local) = stmt.kind;
if let PatKind::Ident(mode, ..) = local.pat.kind;
if let BindingMode::ByValue(mutability) = mode;
if let Mutability::Mut = mutability;
Comment on lines +57 to +58
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if let BindingMode::ByValue(mutability) = mode;
if let Mutability::Mut = mutability;
if let BindingMode::ByValue(Mutability::Mut) = mode;

Copy link
Author

Choose a reason for hiding this comment

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

Sweet, learning more Rust as I go!


if let Some(ref expr) = local.init;
if let ExprKind::Path(_, ref path) = expr.kind;
if let Some(ref segment) = path.segments.last();
if let name = segment.ident.name;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if let name = segment.ident.name;
let name = segment.ident.name;


// The path to fn parameters is 1 in length.
if path.segments.len() == 1;
then {
for param in &fn_decl.inputs {
if_chain! {
if let PatKind::Ident(param_mode, ident, ..) = param.pat.kind;
// Make sure they have the same name & it's not mutable
if ident.name == name;
if let BindingMode::ByValue(param_mut) = param_mode;
if let Mutability::Not = param_mut;
Comment on lines +73 to +74
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if let BindingMode::ByValue(param_mut) = param_mode;
if let Mutability::Not = param_mut;
if let BindingMode::ByValue(Mutability::Not) = param_mode;


then {
let sugg = |db: &mut DiagnosticBuilder<'_>| {
db.span_help(param.span, "consider making this param `mut`");
db.span_help(stmt.span, "consider removing this local variable");

multispan_sugg(db, "...".to_string(), vec![]);
Copy link
Member

Choose a reason for hiding this comment

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

What does this do?

Copy link
Author

Choose a reason for hiding this comment

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

I'm going to remove this, initially I was going to use this to do what line 78~79 was doing.

};

span_lint_and_then(
cx,
FN_PARAM_REDEF_AS_MUTABLE,
fn_span,
"a parameter was redefined as mutable, can be removed",
Copy link
Member

Choose a reason for hiding this comment

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

NIT:

Suggested change
"a parameter was redefined as mutable, can be removed",
"a parameter was redefined as mutable and can be removed",

sugg,
);
}
}
}
}
}
}
5 changes: 5 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ pub mod excessive_precision;
pub mod exit;
pub mod explicit_write;
pub mod fallible_impl_from;
pub mod fn_param_redef_as_mutable;
pub mod format;
pub mod formatting;
pub mod functions;
Expand Down Expand Up @@ -536,6 +537,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&exit::EXIT,
&explicit_write::EXPLICIT_WRITE,
&fallible_impl_from::FALLIBLE_IMPL_FROM,
&fn_param_redef_as_mutable::FN_PARAM_REDEF_AS_MUTABLE,
&format::USELESS_FORMAT,
&formatting::POSSIBLE_MISSING_COMMA,
&formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
Expand Down Expand Up @@ -1006,6 +1008,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
let max_struct_bools = conf.max_struct_bools;
store.register_early_pass(move || box excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools));
store.register_early_pass(|| box option_env_unwrap::OptionEnvUnwrap);
store.register_early_pass(|| box fn_param_redef_as_mutable::FnParamRedefAsMutable);

store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
Expand Down Expand Up @@ -1159,6 +1162,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&eval_order_dependence::EVAL_ORDER_DEPENDENCE),
LintId::of(&excessive_precision::EXCESSIVE_PRECISION),
LintId::of(&explicit_write::EXPLICIT_WRITE),
LintId::of(&fn_param_redef_as_mutable::FN_PARAM_REDEF_AS_MUTABLE),
LintId::of(&format::USELESS_FORMAT),
LintId::of(&formatting::POSSIBLE_MISSING_COMMA),
LintId::of(&formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
Expand Down Expand Up @@ -1478,6 +1482,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&eval_order_dependence::DIVERGING_SUB_EXPRESSION),
LintId::of(&eval_order_dependence::EVAL_ORDER_DEPENDENCE),
LintId::of(&explicit_write::EXPLICIT_WRITE),
LintId::of(&fn_param_redef_as_mutable::FN_PARAM_REDEF_AS_MUTABLE),
LintId::of(&format::USELESS_FORMAT),
LintId::of(&functions::TOO_MANY_ARGUMENTS),
LintId::of(&get_last_with_len::GET_LAST_WITH_LEN),
Expand Down
7 changes: 7 additions & 0 deletions src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,13 @@ pub const ALL_LINTS: [Lint; 355] = [
deprecation: None,
module: "excessive_bools",
},
Lint {
name: "fn_param_redef_as_mutable",
group: "complexity",
desc: "default lint description",
Copy link

Choose a reason for hiding this comment

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

Update with real description

deprecation: None,
module: "fn_param_redef_as_mutable",
},
Lint {
name: "fn_to_numeric_cast",
group: "style",
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/fn_param_redef_as_mutable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![warn(clippy::fn_param_redef_as_mutable)]

fn foobar(a: Vec<bool>) {
Copy link
Member

@flip1995 flip1995 Feb 6, 2020

Choose a reason for hiding this comment

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

We'll need more tests:

  • more than one statement (lint)
  • binding is not in the top level block (no lint)
  • arg is used after binding (no lint)
  • name of rebind is the same as name of arg (lint)
  • name of rebind is the same as name of arg and arg is used afterwards. (lint)
  • This test: (no lint)
fn f(x: usize) {
    let y = x * 3;

    let x = 42;

    for i in 0..x {
        println!("{}", i);
    }

    let mut z = x; // Should not get linted
    z /= 2;

    println!("x={}, y={}, z={}", x, y, z);
}

Playground

Copy link
Author

Choose a reason for hiding this comment

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

I noticed that you used arg instead of param, is there a preference to the wording here that I should use in the change?

Also just one thing that I think would be good for discussion; What's the intention behind not catching a rebind if they're not the same name of the arg?

Copy link
Member

Choose a reason for hiding this comment

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

What's the intention behind not catching a rebind if they're not the same name of the arg?

Oh, I should have specified, what of the above test cases should get linted and what not. I added it in my comment above

Copy link
Member

Choose a reason for hiding this comment

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

I noticed that you used arg instead of param

arg is shorter to write 😄 I don't know, what they are called in rust, honestly. I think, I heard both.

Copy link
Member

Choose a reason for hiding this comment

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

Looking at the rustc doc, they are called param: https://doc.rust-lang.org/nightly/nightly-rustc/syntax/ast/struct.Param.html

Copy link
Member

Choose a reason for hiding this comment

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

I've heard arguments are the values that get passed to a function while parameters are the formal variables in the type signature. So in fn f(a: usize), a is a parameter, but in Vec::from("b"), "b" is an argument.

let mut c = a;
}

fn main() {
}
22 changes: 22 additions & 0 deletions tests/ui/fn_param_redef_as_mutable.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error: a parameter was redefined as mutable, can be removed
--> $DIR/fn_param_redef_as_mutable.rs:3:1
|
LL | / fn foobar(a: Vec<bool>) {
LL | | let mut c = a;
LL | | }
| |_^
|
= note: `-D clippy::fn-param-redef-as-mutable` implied by `-D warnings`
help: consider making this param `mut`
--> $DIR/fn_param_redef_as_mutable.rs:3:11
|
LL | fn foobar(a: Vec<bool>) {
| ^^^^^^^^^^^^^
help: consider removing this local variable
--> $DIR/fn_param_redef_as_mutable.rs:4:5
|
LL | let mut c = a;
| ^^^^^^^^^^^^^^

error: aborting due to previous error