diff --git a/.gitignore b/.gitignore index 37727f91cbe..ac98a7d842f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ # Generated by Cargo /target/ + +# We don't pin yet +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 3c3e700eea5..95de98e8ee5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.0.3" +version = "0.0.4" authors = [ "Manish Goregaokar ", "Andre Bogus " diff --git a/README.md b/README.md index 7d3e3fd8c65..004520f5bd7 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Lints included in this crate: - `cmp_nan`: Denies comparisons to NAN (which will always return false, which is probably not intended) - `float_cmp`: Warns on `==` or `!=` comparisons of floaty typed values. As floating-point operations usually involve rounding errors, it is always better to check for approximate equality within some small bounds - `precedence`: Warns on expressions where precedence may trip up the unwary reader of the source and suggests adding parenthesis, e.g. `x << 2 + y` will be parsed as `x << (2 + y)` + - `redundant_closure`: Warns on usage of eta-reducible closures like `|a| foo(a)` (which can be written as just `foo`) To use, add the following lines to your Cargo.toml: diff --git a/src/eta_reduction.rs b/src/eta_reduction.rs new file mode 100644 index 00000000000..b89eef8c8bb --- /dev/null +++ b/src/eta_reduction.rs @@ -0,0 +1,59 @@ +use syntax::ast::*; +use rustc::lint::{Context, LintPass, LintArray, Lint, Level}; +use syntax::codemap::{Span, Spanned}; +use syntax::print::pprust::expr_to_string; + + +#[allow(missing_copy_implementations)] +pub struct EtaPass; + + +declare_lint!(pub REDUNDANT_CLOSURE, Warn, + "Warn on usage of redundant closures, i.e. `|a| foo(a)`"); + +impl LintPass for EtaPass { + fn get_lints(&self) -> LintArray { + lint_array!(REDUNDANT_CLOSURE) + } + + fn check_expr(&mut self, cx: &Context, expr: &Expr) { + if let ExprClosure(_, ref decl, ref blk) = expr.node { + if blk.stmts.len() != 0 { + // || {foo(); bar()}; can't be reduced here + return; + } + if let Some(ref ex) = blk.expr { + if let ExprCall(ref caller, ref args) = ex.node { + if args.len() != decl.inputs.len() { + // Not the same number of arguments, there + // is no way the closure is the same as the function + return; + } + for (ref a1, ref a2) in decl.inputs.iter().zip(args) { + if let PatIdent(_, ident, _) = a1.pat.node { + // XXXManishearth Should I be checking the binding mode here? + if let ExprPath(None, ref p) = a2.node { + if p.segments.len() != 1 { + // If it's a proper path, it can't be a local variable + return; + } + if p.segments[0].identifier != ident.node { + // The two idents should be the same + return + } + } else { + return + } + } else { + return + } + } + cx.span_lint(REDUNDANT_CLOSURE, expr.span, + &format!("Redundant closure found, consider using `{}` in its place", + expr_to_string(caller))[..]) + } + } + } + } +} + diff --git a/src/lib.rs b/src/lib.rs index ffee122f776..7e21a72dcf2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod bit_mask; pub mod ptr_arg; pub mod needless_bool; pub mod approx_const; +pub mod eta_reduction; #[plugin_registrar] pub fn plugin_registrar(reg: &mut Registry) { @@ -36,6 +37,7 @@ pub fn plugin_registrar(reg: &mut Registry) { reg.register_lint_pass(box approx_const::ApproxConstant as LintPassObject); reg.register_lint_pass(box misc::FloatCmp as LintPassObject); reg.register_lint_pass(box misc::Precedence as LintPassObject); + reg.register_lint_pass(box eta_reduction::EtaPass as LintPassObject); reg.register_lint_group("clippy", vec![types::BOX_VEC, types::LINKEDLIST, misc::SINGLE_MATCH, misc::STR_TO_STRING, @@ -45,5 +47,6 @@ pub fn plugin_registrar(reg: &mut Registry) { approx_const::APPROX_CONSTANT, misc::CMP_NAN, misc::FLOAT_CMP, misc::PRECEDENCE, + eta_reduction::REDUNDANT_CLOSURE, ]); } diff --git a/tests/compile-fail/eta.rs b/tests/compile-fail/eta.rs new file mode 100644 index 00000000000..8ca88eecbd2 --- /dev/null +++ b/tests/compile-fail/eta.rs @@ -0,0 +1,21 @@ +#![feature(plugin)] +#![plugin(clippy)] +#![allow(unknown_lints, unused)] +#![deny(redundant_closure)] + +fn main() { + let a = |a, b| foo(a, b); + //~^ ERROR Redundant closure found, consider using `foo` in its place + let c = |a, b| {1+2; foo}(a, b); + //~^ ERROR Redundant closure found, consider using `{ 1 + 2; foo }` in its place + let d = |a, b| foo((|c, d| foo2(c,d))(a,b), b); + //~^ ERROR Redundant closure found, consider using `foo2` in its place +} + +fn foo(_: u8, _: u8) { + +} + +fn foo2(_: u8, _: u8) -> u8 { + 1u8 +} \ No newline at end of file