diff --git a/CHANGELOG.md b/CHANGELOG.md index 3658df88d40d..20c970e1373a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/clippy_lints/src/fn_param_redef_as_mutable.rs b/clippy_lints/src/fn_param_redef_as_mutable.rs new file mode 100644 index 000000000000..6efa87f5e8da --- /dev/null +++ b/clippy_lints/src/fn_param_redef_as_mutable.rs @@ -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. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// fn f(a: Vec) { + /// let mut a = a; + /// // rest of code + /// } + /// ``` + /// + /// could be defined as + /// + /// ```rust + /// fn f(mut a: Vec) { + /// // rest of code + /// } + /// ``` + pub FN_PARAM_REDEF_AS_MUTABLE, + 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; + + 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; + + // 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; + + 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![]); + }; + + span_lint_and_then( + cx, + FN_PARAM_REDEF_AS_MUTABLE, + fn_span, + "a parameter was redefined as mutable, can be removed", + sugg, + ); + } + } + } + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index f900a99f314d..fb65b56c42b1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -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; @@ -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, @@ -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), @@ -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), @@ -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), diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index d4f94a9b60a4..45611be5267f 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -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", + deprecation: None, + module: "fn_param_redef_as_mutable", + }, Lint { name: "fn_to_numeric_cast", group: "style", diff --git a/tests/ui/fn_param_redef_as_mutable.rs b/tests/ui/fn_param_redef_as_mutable.rs new file mode 100644 index 000000000000..a1fc8881252e --- /dev/null +++ b/tests/ui/fn_param_redef_as_mutable.rs @@ -0,0 +1,8 @@ +#![warn(clippy::fn_param_redef_as_mutable)] + +fn foobar(a: Vec) { + let mut c = a; +} + +fn main() { +} diff --git a/tests/ui/fn_param_redef_as_mutable.stderr b/tests/ui/fn_param_redef_as_mutable.stderr new file mode 100644 index 000000000000..d8cdf056111f --- /dev/null +++ b/tests/ui/fn_param_redef_as_mutable.stderr @@ -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) { +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) { + | ^^^^^^^^^^^^^ +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 +