]> git.lizzy.rs Git - rust.git/commitdiff
Merge branch 'pr-78'
authorManish Goregaokar <manishsmail@gmail.com>
Tue, 11 Aug 2015 17:58:06 +0000 (23:28 +0530)
committerManish Goregaokar <manishsmail@gmail.com>
Tue, 11 Aug 2015 17:58:06 +0000 (23:28 +0530)
Conflicts:
src/lib.rs

32 files changed:
CONTRIBUTING.md [new file with mode: 0644]
Cargo.toml
README.md
src/approx_const.rs
src/attrs.rs [new file with mode: 0644]
src/bit_mask.rs
src/collapsible_if.rs
src/eq_op.rs
src/eta_reduction.rs
src/identity_op.rs
src/len_zero.rs
src/lib.rs [changed mode: 0644->0755]
src/misc.rs
src/mut_mut.rs
src/needless_bool.rs
src/ptr_arg.rs
src/strings.rs [new file with mode: 0644]
src/types.rs
src/unicode.rs [new file with mode: 0644]
src/utils.rs [new file with mode: 0644]
tests/compile-fail/attrs.rs [new file with mode: 0644]
tests/compile-fail/bit_masks.rs
tests/compile-fail/cmp_owned.rs
tests/compile-fail/collapsible_if.rs
tests/compile-fail/len_zero.rs
tests/compile-fail/match_if_let.rs
tests/compile-fail/mut_mut.rs
tests/compile-fail/needless_return.rs [new file with mode: 0755]
tests/compile-fail/strings.rs [new file with mode: 0644]
tests/compile-fail/unicode.rs [new file with mode: 0644]
tests/compile-test.rs
tests/mut_mut_macro.rs [new file with mode: 0644]

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644 (file)
index 0000000..d32b0ce
--- /dev/null
@@ -0,0 +1,51 @@
+# Contributing to rust-clippy
+
+Hello fellow Rustacean! Great to see your interest in compiler internals and lints!
+
+## Getting started
+
+All issues on Clippy are mentored, if you want help with a bug just ask @Manishearth or @llogiq.
+
+Some issues are easier than others. The [E-easy](https://github.com/Manishearth/rust-clippy/labels/E-easy)
+label can be used to find the easy issues. If you want to work on an issue, please leave a comment
+so that we can assign it to you!
+
+Issues marked [T-AST](https://github.com/Manishearth/rust-clippy/labels/T-AST) involve simple
+matching of the syntax tree structure, and are generally easier than
+[T-middle](https://github.com/Manishearth/rust-clippy/labels/T-middle) issues, which involve types
+and resolved paths.
+
+Issues marked [E-medium](https://github.com/Manishearth/rust-clippy/labels/E-medium) are generally
+pretty easy too, though it's recommended you work on an E-easy issue first.
+
+[Llogiq's blog post on lints](https://llogiq.github.io/2015/06/04/workflows.html) is a nice primer
+to lint-writing, though it does get into advanced stuff. Most lints consist of an implementation of
+`LintPass` with one or more of its default methods overridden. See the existing lints for examples
+of this.
+
+T-AST issues will generally need you to match against a predefined syntax structure. To figure out
+how this syntax structure is encoded in the AST, it is recommended to run `rustc -Z ast-json` on an
+example of the structure and compare with the
+[nodes in the AST docs](http://manishearth.github.io/rust-internals-docs/syntax/ast/). Usually
+the lint will end up to be a nested series of matches and ifs,
+[like so](https://github.com/Manishearth/rust-clippy/blob/de5ccdfab68a5e37689f3c950ed1532ba9d652a0/src/misc.rs#L34)
+
+T-middle issues can be more involved and require verifying types. The
+[`middle::ty`](http://manishearth.github.io/rust-internals-docs/rustc/middle/ty) module contains a
+lot of methods that are useful, though one of the most useful would be `expr_ty` (gives the type of
+an AST expression). `match_def_path()` in Clippy's `utils` module can also be useful.
+
+## Contributions
+
+Clippy welcomes contributions from everyone.
+
+Contributions to Clippy should be made in the form of GitHub pull requests. Each pull request will
+be reviewed by a core contributor (someone with permission to land patches) and either landed in the
+main tree or given feedback for changes that would be required.
+
+## Conduct
+
+We follow the [Rust Code of Conduct](http://www.rust-lang.org/conduct.html).
+
+
+<!-- adapted from https://github.com/servo/servo/blob/master/CONTRIBUTING.md -->
index 95de98e8ee5bf916309e9a53851f0ecbb7ea6771..ce9ea2b3ed3ed3f278870eb9e8b4b91bd2e315f9 100644 (file)
@@ -1,6 +1,6 @@
 [package]
 name = "clippy"
-version = "0.0.4"
+version = "0.0.9"
 authors = [
        "Manish Goregaokar <manishsmail@gmail.com>",
        "Andre Bogus <bogusandre@gmail.com>"
@@ -17,3 +17,10 @@ plugin = true
 
 [dev-dependencies]
 compiletest_rs = "*"
+regex = "*"
+regex_macros = "*"
+lazy_static = "*"
+
+[features]
+
+structured_logging = []
index 0bea2fad1d6e9688f5a921a339ebec2c0f6b4443..fcd8d38a3b3913033403c93a74166584796080b0 100644 (file)
--- a/README.md
+++ b/README.md
@@ -26,6 +26,10 @@ Lints included in this crate:
  - `len_zero`: Warns on `_.len() == 0` and suggests using `_.is_empty()` (or similar comparisons with `>` or `!=`)
  - `len_without_is_empty`: Warns on traits or impls that have a `.len()` but no `.is_empty()` method
  - `cmp_owned`: Warns on creating owned instances for comparing with others, e.g. `x == "foo".to_string()`
+ - `inline_always`: Warns on `#[inline(always)]`, because in most cases it is a bad idea
+ - `collapsible_if`: Warns on cases where two nested `if`-expressions can be collapsed into one, e.g. `if x { if y { foo() } }` can be written as `if x && y { foo() }`
+ - `zero_width_space`: Warns on encountering a unicode zero-width space
+ - `string_add_assign`: Warns on `x = x + ..` where `x` is a `String` and suggests using `push_str(..)` instead.
 
 To use, add the following lines to your Cargo.toml:
 
@@ -37,6 +41,9 @@ clippy = "*"
 More to come, please [file an issue](https://github.com/Manishearth/rust-clippy/issues) if you have ideas!
 
 ##Usage
+
+Compiler plugins are highly unstable and will only work with a nightly Rust for now. Since stable Rust is backwards compatible, you should be able to compile your stable programs with nightly Rust with clippy plugged in to circumvent this.
+
 Add in your `Cargo.toml`:
 ```toml
 [dependencies.clippy]
@@ -80,5 +87,12 @@ You can add options  to `allow`/`warn`/`deny`:
 
 *`deny` produces error instead of warnings*
 
+To have cargo compile your crate with clippy without needing `#![plugin(clippy)]` 
+in your code, you can use:
+
+```
+cargo rustc -- -L /path/to/clippy_so -Z extra-plugins=clippy
+```
+
 ##License
 Licensed under [MPL](https://www.mozilla.org/MPL/2.0/). If you're having issues with the license, let me know and I'll try to change it to something more permissive.
index 8a93bbfa9334b03981ab611e26118067521bb1a3..03d4da1ab7f403d1b5cc3efbbcc78b34365c41b0 100644 (file)
@@ -7,6 +7,7 @@
 use syntax::ptr::P;
 use syntax::codemap::Span;
 use std::f64::consts as f64;
+use utils::span_lint;
 
 declare_lint! {
     pub APPROX_CONSTANT,
@@ -51,7 +52,7 @@ fn check_known_consts(cx: &Context, span: Span, str: &str, module: &str) {
        if let Ok(value) = str.parse::<f64>() {
                for &(constant, name) in KNOWN_CONSTS {
                        if within_epsilon(constant, value) {
-                               cx.span_lint(APPROX_CONSTANT, span, &format!(
+                               span_lint(cx, APPROX_CONSTANT, span, &format!(
                                        "Approximate value of {}::{} found, consider using it directly.", module, &name));
                        }
                }
diff --git a/src/attrs.rs b/src/attrs.rs
new file mode 100644 (file)
index 0000000..8d9e289
--- /dev/null
@@ -0,0 +1,110 @@
+/// checks for attributes
+
+use rustc::plugin::Registry;
+use rustc::lint::*;
+use syntax::ast::*;
+use syntax::ptr::P;
+use syntax::codemap::{Span, ExpnInfo};
+use syntax::parse::token::InternedString;
+use utils::{in_macro, match_path, span_lint};
+
+declare_lint! { pub INLINE_ALWAYS, Warn,
+    "#[inline(always)] is usually a bad idea."}
+
+
+#[derive(Copy,Clone)]
+pub struct AttrPass;
+
+impl LintPass for AttrPass {
+    fn get_lints(&self) -> LintArray {
+        lint_array!(INLINE_ALWAYS)
+    }
+    
+    fn check_item(&mut self, cx: &Context, item: &Item) {
+               if is_relevant_item(item) {
+                       cx.sess().codemap().with_expn_info(item.span.expn_id, 
+                               |info| check_attrs(cx, info, &item.ident, &item.attrs))
+               }
+       }
+    
+    fn check_impl_item(&mut self, cx: &Context, item: &ImplItem) {
+               if is_relevant_impl(item) {
+                       cx.sess().codemap().with_expn_info(item.span.expn_id, 
+                               |info| check_attrs(cx, info, &item.ident, &item.attrs))
+               }
+       }
+        
+       fn check_trait_item(&mut self, cx: &Context, item: &TraitItem) {
+               if is_relevant_trait(item) {
+                       cx.sess().codemap().with_expn_info(item.span.expn_id, 
+                               |info| check_attrs(cx, info, &item.ident, &item.attrs))
+               }
+       }
+}
+
+fn is_relevant_item(item: &Item) -> bool {
+       if let &ItemFn(_, _, _, _, _, ref block) = &item.node {
+               is_relevant_block(block)
+       } else { false }
+}
+
+fn is_relevant_impl(item: &ImplItem) -> bool {
+       match item.node {
+               MethodImplItem(_, ref block) => is_relevant_block(block),
+               _ => false
+       }
+}
+
+fn is_relevant_trait(item: &TraitItem) -> bool {
+       match item.node {
+               MethodTraitItem(_, None) => true,
+               MethodTraitItem(_, Some(ref block)) => is_relevant_block(block),
+               _ => false
+       }
+}
+
+fn is_relevant_block(block: &Block) -> bool {
+       for stmt in block.stmts.iter() { 
+               match stmt.node {
+                       StmtDecl(_, _) => return true,
+                       StmtExpr(ref expr, _) | StmtSemi(ref expr, _) => {
+                               return is_relevant_expr(expr);
+                       }
+                       _ => ()
+               }
+       }
+       block.expr.as_ref().map_or(false, |e| is_relevant_expr(&*e))
+}
+
+fn is_relevant_expr(expr: &Expr) -> bool {
+       match expr.node {
+               ExprBlock(ref block) => is_relevant_block(block),
+               ExprRet(Some(ref e)) | ExprParen(ref e) => 
+                       is_relevant_expr(&*e),
+               ExprRet(None) | ExprBreak(_) | ExprMac(_) => false,
+               ExprCall(ref path_expr, _) => {
+                       if let ExprPath(_, ref path) = path_expr.node {
+                               !match_path(path, &["std", "rt", "begin_unwind"])
+                       } else { true }
+               }
+               _ => true
+       }
+}
+
+fn check_attrs(cx: &Context, info: Option<&ExpnInfo>, ident: &Ident, 
+               attrs: &[Attribute]) {
+       if in_macro(cx, info) { return; }
+                       
+       for attr in attrs {
+               if let MetaList(ref inline, ref values) = attr.node.value.node {
+                       if values.len() != 1 || inline != &"inline" { continue; }
+                       if let MetaWord(ref always) = values[0].node {
+                               if always != &"always" { continue; }
+                               span_lint(cx, INLINE_ALWAYS, attr.span, &format!(
+                                       "You have declared #[inline(always)] on {}. This \
+                                       is usually a bad idea. Are you sure?", 
+                                       ident.name.as_str()));
+                       }
+               }
+       }
+}
index 352826dffaeacba7b56648ac9811f13420c7683d..5ce574007bca3472fb077e7de21f58191e9289ec 100644 (file)
@@ -6,6 +6,7 @@
 use syntax::ast_util::{is_comparison_binop, binop_to_string};
 use syntax::ptr::P;
 use syntax::codemap::Span;
+use utils::span_lint;
 
 declare_lint! {
     pub BAD_BIT_MASK,
@@ -89,40 +90,47 @@ fn check_compare(cx: &Context, bit_op: &Expr, cmp_op: BinOp_, cmp_value: u64, sp
        }
 }
 
-fn check_bit_mask(cx: &Context, bit_op: BinOp_, cmp_op: BinOp_, mask_value: u64, cmp_value: u64, span: &Span) {
+fn check_bit_mask(cx: &Context, bit_op: BinOp_, cmp_op: BinOp_, 
+               mask_value: u64, cmp_value: u64, span: &Span) {
        match cmp_op {
                BiEq | BiNe => match bit_op {
                        BiBitAnd => if mask_value & cmp_value != mask_value {
-                               cx.span_lint(BAD_BIT_MASK, *span, &format!("incompatible bit mask: _ & {} can never be equal to {}", mask_value,
-                                       cmp_value));
+                               if cmp_value != 0 {
+                                       span_lint(cx, BAD_BIT_MASK, *span, &format!(
+                                               "incompatible bit mask: _ & {} can never be equal to {}", 
+                                               mask_value, cmp_value));
+                               }
                        } else { 
                                if mask_value == 0 {
-                                       cx.span_lint(BAD_BIT_MASK, *span, &format!("&-masking with zero"));
+                                       span_lint(cx, BAD_BIT_MASK, *span, 
+                                               &format!("&-masking with zero"));
                                }
                        },
                        BiBitOr => if mask_value | cmp_value != cmp_value {
-                               cx.span_lint(BAD_BIT_MASK, *span, &format!("incompatible bit mask: _ | {} can never be equal to {}", mask_value,
-                                       cmp_value));
+                               span_lint(cx, BAD_BIT_MASK, *span, &format!(
+                                       "incompatible bit mask: _ | {} can never be equal to {}", 
+                                       mask_value, cmp_value));
                        },
                        _ => ()
                },
                BiLt | BiGe => match bit_op {
                        BiBitAnd => if mask_value < cmp_value {
-                               cx.span_lint(BAD_BIT_MASK, *span, &format!(
+                               span_lint(cx, BAD_BIT_MASK, *span, &format!(
                                        "incompatible bit mask: _ & {} will always be lower than {}", 
                                        mask_value, cmp_value));
                        } else { 
                                if mask_value == 0 {
-                                       cx.span_lint(BAD_BIT_MASK, *span, &format!("&-masking with zero"));
+                                       span_lint(cx, BAD_BIT_MASK, *span, 
+                                               &format!("&-masking with zero"));
                                }
                        },
                        BiBitOr => if mask_value >= cmp_value {
-                               cx.span_lint(BAD_BIT_MASK, *span, &format!(
+                               span_lint(cx, BAD_BIT_MASK, *span, &format!(
                                        "incompatible bit mask: _ | {} will never be lower than {}", 
                                        mask_value, cmp_value));
                        } else {
                                if mask_value < cmp_value {
-                                       cx.span_lint(INEFFECTIVE_BIT_MASK, *span, &format!(
+                                       span_lint(cx, INEFFECTIVE_BIT_MASK, *span, &format!(
                                                "ineffective bit mask: x | {} compared to {} is the same as x compared directly", 
                                                mask_value, cmp_value)); 
                                }
@@ -131,21 +139,22 @@ fn check_bit_mask(cx: &Context, bit_op: BinOp_, cmp_op: BinOp_, mask_value: u64,
                },
                BiLe | BiGt => match bit_op {
                        BiBitAnd => if mask_value <= cmp_value {
-                               cx.span_lint(BAD_BIT_MASK, *span, &format!(
+                               span_lint(cx, BAD_BIT_MASK, *span, &format!(
                                        "incompatible bit mask: _ & {} will never be higher than {}", 
                                        mask_value, cmp_value));
                        } else { 
                                if mask_value == 0 {
-                                       cx.span_lint(BAD_BIT_MASK, *span, &format!("&-masking with zero"));
+                                       span_lint(cx, BAD_BIT_MASK, *span, 
+                                               &format!("&-masking with zero"));
                                }
                        },
                        BiBitOr => if mask_value > cmp_value {
-                               cx.span_lint(BAD_BIT_MASK, *span, &format!(
+                               span_lint(cx, BAD_BIT_MASK, *span, &format!(
                                        "incompatible bit mask: _ | {} will always be higher than {}", 
                                        mask_value, cmp_value));                                
                        } else {
                                if mask_value < cmp_value {
-                                       cx.span_lint(INEFFECTIVE_BIT_MASK, *span, &format!(
+                                       span_lint(cx, INEFFECTIVE_BIT_MASK, *span, &format!(
                                                "ineffective bit mask: x | {} compared to {} is the same as x compared directly", 
                                                mask_value, cmp_value)); 
                                }
index 31ac1e62be6fd7946a41c1b3995cb391596d5531..dc2d385223764675692e8eb5d41b8be7e55425e8 100644 (file)
@@ -17,8 +17,9 @@
 use rustc::middle::def::*;
 use syntax::ast::*;
 use syntax::ptr::P;
-use syntax::codemap::{Span, Spanned};
+use syntax::codemap::{Span, Spanned, ExpnInfo};
 use syntax::print::pprust::expr_to_string;
+use utils::{in_macro, span_lint};
 
 declare_lint! {
     pub COLLAPSIBLE_IF,
@@ -34,20 +35,23 @@ fn get_lints(&self) -> LintArray {
         lint_array!(COLLAPSIBLE_IF)
     }
     
-    fn check_expr(&mut self, cx: &Context, e: &Expr) {
-        if let ExprIf(ref check, ref then_block, None) = e.node {
-            let expr = check_block(then_block);
-            let expr = match expr {
-                Some(e) => e,
-                None => return
-            };
-            if let ExprIf(ref check_inner, _, None) = expr.node {
-                let (check, check_inner) = (check_to_string(check), check_to_string(check_inner));
-                cx.span_lint(COLLAPSIBLE_IF, e.span,
-                             &format!("This if statement can be collapsed. Try: if {} && {}", check, check_inner));
-            }
-                   }
-    }
+       fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+               cx.sess().codemap().with_expn_info(expr.span.expn_id, 
+                       |info| check_expr_expd(cx, expr, info))
+       }
+}
+
+fn check_expr_expd(cx: &Context, e: &Expr, info: Option<&ExpnInfo>) {
+       if in_macro(cx, info) { return; }
+       
+       if let ExprIf(ref check, ref then, None) = e.node {
+               if let Some(&Expr{ node: ExprIf(ref check_inner, _, None), ..}) = 
+                               single_stmt_of_block(then) {
+                       span_lint(cx, COLLAPSIBLE_IF, e.span, &format!(
+                               "This if statement can be collapsed. Try: if {} && {}\n{:?}", 
+                               check_to_string(check), check_to_string(check_inner), e));
+               }
+       }
 }
 
 fn requires_brackets(e: &Expr) -> bool {
@@ -65,16 +69,20 @@ fn check_to_string(e: &Expr) -> String {
     }
 }
 
-fn check_block(b: &Block) -> Option<&P<Expr>> {
-    if b.stmts.len() == 1 && b.expr.is_none() {
-        let stmt = &b.stmts[0];
-        return match stmt.node {
-            StmtExpr(ref e, _) => Some(e),
-            _ => None
-        };
-    }
-    if let Some(ref e) = b.expr {
-        return Some(e);
+fn single_stmt_of_block(block: &Block) -> Option<&Expr> {
+    if block.stmts.len() == 1 && block.expr.is_none() {
+        if let StmtExpr(ref expr, _) = block.stmts[0].node {
+            single_stmt_of_expr(expr)
+        } else { None }
+    } else {
+        if block.stmts.is_empty() {
+            if let Some(ref p) = block.expr { Some(&*p) } else { None }
+        } else { None }
     }
-    None
+}
+
+fn single_stmt_of_expr(expr: &Expr) -> Option<&Expr> {
+    if let ExprBlock(ref block) = expr.node {
+        single_stmt_of_block(block)
+    } else { Some(expr) }
 }
index 94a49e748e2044e7e778088fbdcb1682959992ab..1000d310e39480ff046daf6236d03e13becfac10 100644 (file)
@@ -3,6 +3,7 @@
 use syntax::ast_util as ast_util;
 use syntax::ptr::P;
 use syntax::codemap as code;
+use utils::span_lint;
 
 declare_lint! {
     pub EQ_OP,
@@ -21,233 +22,238 @@ fn get_lints(&self) -> LintArray {
     fn check_expr(&mut self, cx: &Context, e: &Expr) {
         if let ExprBinary(ref op, ref left, ref right) = e.node {
             if is_cmp_or_bit(op) && is_exp_equal(left, right) {
-                cx.span_lint(EQ_OP, e.span, &format!(
-                                       "equal expressions as operands to {}", 
-                                               ast_util::binop_to_string(op.node)));
+                span_lint(cx, EQ_OP, e.span, &format!(
+                    "equal expressions as operands to {}", 
+                        ast_util::binop_to_string(op.node)));
             }
         }
     }
 }
 
-fn is_exp_equal(left : &Expr, right : &Expr) -> bool {
-       match (&left.node, &right.node) {
-               (&ExprBinary(ref lop, ref ll, ref lr), 
-                               &ExprBinary(ref rop, ref rl, ref rr)) => 
-                       lop.node == rop.node && 
-                       is_exp_equal(ll, rl) && is_exp_equal(lr, rr),
-               (&ExprBox(ref lpl, ref lbox), &ExprBox(ref rpl, ref rbox)) => 
-                       both(lpl, rpl, |l, r| is_exp_equal(l, r)) && 
-                               is_exp_equal(lbox, rbox),
-               (&ExprCall(ref lcallee, ref largs), 
-                &ExprCall(ref rcallee, ref rargs)) => is_exp_equal(lcallee, 
-                       rcallee) && is_exps_equal(largs, rargs),
-               (&ExprCast(ref lc, ref lty), &ExprCast(ref rc, ref rty)) => 
-                       is_ty_equal(lty, rty) && is_exp_equal(lc, rc),
-               (&ExprField(ref lfexp, ref lfident), 
-                               &ExprField(ref rfexp, ref rfident)) => 
-                       lfident.node == rfident.node && is_exp_equal(lfexp, rfexp),
-               (&ExprLit(ref l), &ExprLit(ref r)) => l.node == r.node,
-               (&ExprMethodCall(ref lident, ref lcty, ref lmargs), 
-                               &ExprMethodCall(ref rident, ref rcty, ref rmargs)) => 
-                       lident.node == rident.node && is_tys_equal(lcty, rcty) && 
-                               is_exps_equal(lmargs, rmargs),
-               (&ExprParen(ref lparen), _) => is_exp_equal(lparen, right),
-               (_, &ExprParen(ref rparen)) => is_exp_equal(left, rparen),
-               (&ExprPath(ref lqself, ref lsubpath), 
-                               &ExprPath(ref rqself, ref rsubpath)) => 
-                       both(lqself, rqself, |l, r| is_qself_equal(l, r)) && 
-                               is_path_equal(lsubpath, rsubpath),              
-               (&ExprTup(ref ltup), &ExprTup(ref rtup)) => 
-                       is_exps_equal(ltup, rtup),
-               (&ExprUnary(lunop, ref l), &ExprUnary(runop, ref r)) => 
-                       lunop == runop && is_exp_equal(l, r), 
-               (&ExprVec(ref l), &ExprVec(ref r)) => is_exps_equal(l, r),
-               _ => false
-       }
+pub fn is_exp_equal(left : &Expr, right : &Expr) -> bool {
+    match (&left.node, &right.node) {
+        (&ExprBinary(ref lop, ref ll, ref lr), 
+                &ExprBinary(ref rop, ref rl, ref rr)) => 
+            lop.node == rop.node && 
+            is_exp_equal(ll, rl) && is_exp_equal(lr, rr),
+        (&ExprBox(ref lpl, ref lbox), &ExprBox(ref rpl, ref rbox)) => 
+            both(lpl, rpl, |l, r| is_exp_equal(l, r)) && 
+                is_exp_equal(lbox, rbox),
+        (&ExprCall(ref lcallee, ref largs), 
+         &ExprCall(ref rcallee, ref rargs)) => is_exp_equal(lcallee, 
+            rcallee) && is_exps_equal(largs, rargs),
+        (&ExprCast(ref lc, ref lty), &ExprCast(ref rc, ref rty)) => 
+            is_ty_equal(lty, rty) && is_exp_equal(lc, rc),
+        (&ExprField(ref lfexp, ref lfident), 
+                &ExprField(ref rfexp, ref rfident)) => 
+            lfident.node == rfident.node && is_exp_equal(lfexp, rfexp),
+        (&ExprLit(ref l), &ExprLit(ref r)) => l.node == r.node,
+        (&ExprMethodCall(ref lident, ref lcty, ref lmargs), 
+                &ExprMethodCall(ref rident, ref rcty, ref rmargs)) => 
+            lident.node == rident.node && is_tys_equal(lcty, rcty) && 
+                is_exps_equal(lmargs, rmargs),
+        (&ExprParen(ref lparen), _) => is_exp_equal(lparen, right),
+        (_, &ExprParen(ref rparen)) => is_exp_equal(left, rparen),
+        (&ExprPath(ref lqself, ref lsubpath), 
+                &ExprPath(ref rqself, ref rsubpath)) => 
+            both(lqself, rqself, |l, r| is_qself_equal(l, r)) && 
+                is_path_equal(lsubpath, rsubpath),      
+        (&ExprTup(ref ltup), &ExprTup(ref rtup)) => 
+            is_exps_equal(ltup, rtup),
+        (&ExprUnary(lunop, ref l), &ExprUnary(runop, ref r)) => 
+            lunop == runop && is_exp_equal(l, r), 
+        (&ExprVec(ref l), &ExprVec(ref r)) => is_exps_equal(l, r),
+        _ => false
+    }
 }
 
 fn is_exps_equal(left : &[P<Expr>], right : &[P<Expr>]) -> bool {
-       over(left, right, |l, r| is_exp_equal(l, r))
+    over(left, right, |l, r| is_exp_equal(l, r))
 }
 
 fn is_path_equal(left : &Path, right : &Path) -> bool {
-       left.global == right.global && left.segments == right.segments
+    // The == of idents doesn't work with different contexts,
+    // we have to be explicit about hygiene
+    left.global == right.global && over(&left.segments, &right.segments, 
+        |l, r| l.identifier.name == r.identifier.name
+              && l.identifier.ctxt == r.identifier.ctxt
+               && l.parameters == r.parameters)
 }
 
 fn is_qself_equal(left : &QSelf, right : &QSelf) -> bool {
-       left.ty.node == right.ty.node && left.position == right.position
+    left.ty.node == right.ty.node && left.position == right.position
 }
 
 fn is_ty_equal(left : &Ty, right : &Ty) -> bool {
-       match (&left.node, &right.node) {
-       (&TyVec(ref lvec), &TyVec(ref rvec)) => is_ty_equal(lvec, rvec),
-       (&TyFixedLengthVec(ref lfvty, ref lfvexp), 
-                       &TyFixedLengthVec(ref rfvty, ref rfvexp)) => 
-               is_ty_equal(lfvty, rfvty) && is_exp_equal(lfvexp, rfvexp),
-       (&TyPtr(ref lmut), &TyPtr(ref rmut)) => is_mut_ty_equal(lmut, rmut),
-       (&TyRptr(ref ltime, ref lrmut), &TyRptr(ref rtime, ref rrmut)) => 
-               both(ltime, rtime, is_lifetime_equal) && 
-               is_mut_ty_equal(lrmut, rrmut),
-       (&TyBareFn(ref lbare), &TyBareFn(ref rbare)) => 
-               is_bare_fn_ty_equal(lbare, rbare),
+    match (&left.node, &right.node) {
+    (&TyVec(ref lvec), &TyVec(ref rvec)) => is_ty_equal(lvec, rvec),
+    (&TyFixedLengthVec(ref lfvty, ref lfvexp), 
+            &TyFixedLengthVec(ref rfvty, ref rfvexp)) => 
+        is_ty_equal(lfvty, rfvty) && is_exp_equal(lfvexp, rfvexp),
+    (&TyPtr(ref lmut), &TyPtr(ref rmut)) => is_mut_ty_equal(lmut, rmut),
+    (&TyRptr(ref ltime, ref lrmut), &TyRptr(ref rtime, ref rrmut)) => 
+        both(ltime, rtime, is_lifetime_equal) && 
+        is_mut_ty_equal(lrmut, rrmut),
+    (&TyBareFn(ref lbare), &TyBareFn(ref rbare)) => 
+        is_bare_fn_ty_equal(lbare, rbare),
     (&TyTup(ref ltup), &TyTup(ref rtup)) => is_tys_equal(ltup, rtup),
-       (&TyPath(ref lq, ref lpath), &TyPath(ref rq, ref rpath)) => 
-               both(lq, rq, is_qself_equal) && is_path_equal(lpath, rpath),
+    (&TyPath(ref lq, ref lpath), &TyPath(ref rq, ref rpath)) => 
+        both(lq, rq, is_qself_equal) && is_path_equal(lpath, rpath),
     (&TyObjectSum(ref lsumty, ref lobounds), 
-                       &TyObjectSum(ref rsumty, ref robounds)) => 
-               is_ty_equal(lsumty, rsumty) && 
-               is_param_bounds_equal(lobounds, robounds),
-       (&TyPolyTraitRef(ref ltbounds), &TyPolyTraitRef(ref rtbounds)) => 
-               is_param_bounds_equal(ltbounds, rtbounds),
+            &TyObjectSum(ref rsumty, ref robounds)) => 
+        is_ty_equal(lsumty, rsumty) && 
+        is_param_bounds_equal(lobounds, robounds),
+    (&TyPolyTraitRef(ref ltbounds), &TyPolyTraitRef(ref rtbounds)) => 
+        is_param_bounds_equal(ltbounds, rtbounds),
     (&TyParen(ref lty), &TyParen(ref rty)) => is_ty_equal(lty, rty),
     (&TyTypeof(ref lof), &TyTypeof(ref rof)) => is_exp_equal(lof, rof),
-       (&TyInfer, &TyInfer) => true,
-       _ => false
-       }
+    (&TyInfer, &TyInfer) => true,
+    _ => false
+    }
 }
 
 fn is_param_bound_equal(left : &TyParamBound, right : &TyParamBound) 
-               -> bool {
-       match(left, right) {
-       (&TraitTyParamBound(ref lpoly, ref lmod), 
-                       &TraitTyParamBound(ref rpoly, ref rmod)) => 
-               lmod == rmod && is_poly_traitref_equal(lpoly, rpoly),
+        -> bool {
+    match(left, right) {
+    (&TraitTyParamBound(ref lpoly, ref lmod), 
+            &TraitTyParamBound(ref rpoly, ref rmod)) => 
+        lmod == rmod && is_poly_traitref_equal(lpoly, rpoly),
     (&RegionTyParamBound(ref ltime), &RegionTyParamBound(ref rtime)) => 
-               is_lifetime_equal(ltime, rtime),
+        is_lifetime_equal(ltime, rtime),
     _ => false
-       }
+    }
 }
 
 fn is_poly_traitref_equal(left : &PolyTraitRef, right : &PolyTraitRef)
-               -> bool {
-       is_lifetimedefs_equal(&left.bound_lifetimes, &right.bound_lifetimes)
-               && is_path_equal(&left.trait_ref.path, &right.trait_ref.path)
+        -> bool {
+    is_lifetimedefs_equal(&left.bound_lifetimes, &right.bound_lifetimes)
+        && is_path_equal(&left.trait_ref.path, &right.trait_ref.path)
 }
 
 fn is_param_bounds_equal(left : &TyParamBounds, right : &TyParamBounds)
-               -> bool {
-       over(left, right, is_param_bound_equal)
+        -> bool {
+    over(left, right, is_param_bound_equal)
 }
 
 fn is_mut_ty_equal(left : &MutTy, right : &MutTy) -> bool {
-       left.mutbl == right.mutbl && is_ty_equal(&left.ty, &right.ty)
+    left.mutbl == right.mutbl && is_ty_equal(&left.ty, &right.ty)
 }
 
 fn is_bare_fn_ty_equal(left : &BareFnTy, right : &BareFnTy) -> bool {
-       left.unsafety == right.unsafety && left.abi == right.abi && 
-               is_lifetimedefs_equal(&left.lifetimes, &right.lifetimes) && 
-                       is_fndecl_equal(&left.decl, &right.decl)
+    left.unsafety == right.unsafety && left.abi == right.abi && 
+        is_lifetimedefs_equal(&left.lifetimes, &right.lifetimes) && 
+            is_fndecl_equal(&left.decl, &right.decl)
 } 
 
 fn is_fndecl_equal(left : &P<FnDecl>, right : &P<FnDecl>) -> bool {
-       left.variadic == right.variadic && 
-               is_args_equal(&left.inputs, &right.inputs) && 
-               is_fnret_ty_equal(&left.output, &right.output)
+    left.variadic == right.variadic && 
+        is_args_equal(&left.inputs, &right.inputs) && 
+        is_fnret_ty_equal(&left.output, &right.output)
 }
 
 fn is_fnret_ty_equal(left : &FunctionRetTy, right : &FunctionRetTy) 
-               -> bool {
-       match (left, right) {
-       (&NoReturn(_), &NoReturn(_)) | 
-       (&DefaultReturn(_), &DefaultReturn(_)) => true,
-       (&Return(ref lty), &Return(ref rty)) => is_ty_equal(lty, rty),
-       _ => false      
-       }
+        -> bool {
+    match (left, right) {
+    (&NoReturn(_), &NoReturn(_)) | 
+    (&DefaultReturn(_), &DefaultReturn(_)) => true,
+    (&Return(ref lty), &Return(ref rty)) => is_ty_equal(lty, rty),
+    _ => false  
+    }
 }
 
 fn is_arg_equal(l: &Arg, r : &Arg) -> bool {
-       is_ty_equal(&l.ty, &r.ty) && is_pat_equal(&l.pat, &r.pat)
+    is_ty_equal(&l.ty, &r.ty) && is_pat_equal(&l.pat, &r.pat)
 }
 
 fn is_args_equal(left : &[Arg], right : &[Arg]) -> bool {
-       over(left, right, is_arg_equal)
+    over(left, right, is_arg_equal)
 }
 
 fn is_pat_equal(left : &Pat, right : &Pat) -> bool {
-       match(&left.node, &right.node) {
-       (&PatWild(lwild), &PatWild(rwild)) => lwild == rwild,
-       (&PatIdent(ref lmode, ref lident, Option::None), 
-                       &PatIdent(ref rmode, ref rident, Option::None)) =>
-               lmode == rmode && is_ident_equal(&lident.node, &rident.node),
-       (&PatIdent(ref lmode, ref lident, Option::Some(ref lpat)), 
-                       &PatIdent(ref rmode, ref rident, Option::Some(ref rpat))) =>
-               lmode == rmode && is_ident_equal(&lident.node, &rident.node) && 
-                       is_pat_equal(lpat, rpat),
+    match(&left.node, &right.node) {
+    (&PatWild(lwild), &PatWild(rwild)) => lwild == rwild,
+    (&PatIdent(ref lmode, ref lident, Option::None), 
+            &PatIdent(ref rmode, ref rident, Option::None)) =>
+        lmode == rmode && is_ident_equal(&lident.node, &rident.node),
+    (&PatIdent(ref lmode, ref lident, Option::Some(ref lpat)), 
+            &PatIdent(ref rmode, ref rident, Option::Some(ref rpat))) =>
+        lmode == rmode && is_ident_equal(&lident.node, &rident.node) && 
+            is_pat_equal(lpat, rpat),
     (&PatEnum(ref lpath, ref lenum), &PatEnum(ref rpath, ref renum)) => 
-               is_path_equal(lpath, rpath) && both(lenum, renum, |l, r| 
-                       is_pats_equal(l, r)),
+        is_path_equal(lpath, rpath) && both(lenum, renum, |l, r| 
+            is_pats_equal(l, r)),
     (&PatStruct(ref lpath, ref lfieldpat, lbool), 
-                       &PatStruct(ref rpath, ref rfieldpat, rbool)) =>
-               lbool == rbool && is_path_equal(lpath, rpath) && 
-                       is_spanned_fieldpats_equal(lfieldpat, rfieldpat),
+            &PatStruct(ref rpath, ref rfieldpat, rbool)) =>
+        lbool == rbool && is_path_equal(lpath, rpath) && 
+            is_spanned_fieldpats_equal(lfieldpat, rfieldpat),
     (&PatTup(ref ltup), &PatTup(ref rtup)) => is_pats_equal(ltup, rtup), 
     (&PatBox(ref lboxed), &PatBox(ref rboxed)) => 
-               is_pat_equal(lboxed, rboxed),
+        is_pat_equal(lboxed, rboxed),
     (&PatRegion(ref lpat, ref lmut), &PatRegion(ref rpat, ref rmut)) => 
-               is_pat_equal(lpat, rpat) && lmut == rmut,
-       (&PatLit(ref llit), &PatLit(ref rlit)) => is_exp_equal(llit, rlit),
+        is_pat_equal(lpat, rpat) && lmut == rmut,
+    (&PatLit(ref llit), &PatLit(ref rlit)) => is_exp_equal(llit, rlit),
     (&PatRange(ref lfrom, ref lto), &PatRange(ref rfrom, ref rto)) =>
-               is_exp_equal(lfrom, rfrom) && is_exp_equal(lto, rto),
+        is_exp_equal(lfrom, rfrom) && is_exp_equal(lto, rto),
     (&PatVec(ref lfirst, Option::None, ref llast), 
-                       &PatVec(ref rfirst, Option::None, ref rlast)) =>
-               is_pats_equal(lfirst, rfirst) && is_pats_equal(llast, rlast),
+            &PatVec(ref rfirst, Option::None, ref rlast)) =>
+        is_pats_equal(lfirst, rfirst) && is_pats_equal(llast, rlast),
     (&PatVec(ref lfirst, Option::Some(ref lpat), ref llast), 
-                       &PatVec(ref rfirst, Option::Some(ref rpat), ref rlast)) =>
-               is_pats_equal(lfirst, rfirst) && is_pat_equal(lpat, rpat) && 
-                       is_pats_equal(llast, rlast),
-       // I don't match macros for now, the code is slow enough as is ;-)
-       _ => false
-       }
+            &PatVec(ref rfirst, Option::Some(ref rpat), ref rlast)) =>
+        is_pats_equal(lfirst, rfirst) && is_pat_equal(lpat, rpat) && 
+            is_pats_equal(llast, rlast),
+    // I don't match macros for now, the code is slow enough as is ;-)
+    _ => false
+    }
 }
 
 fn is_spanned_fieldpats_equal(left : &[code::Spanned<FieldPat>], 
-               right : &[code::Spanned<FieldPat>]) -> bool {
-       over(left, right, |l, r| is_fieldpat_equal(&l.node, &r.node))
+        right : &[code::Spanned<FieldPat>]) -> bool {
+    over(left, right, |l, r| is_fieldpat_equal(&l.node, &r.node))
 }
 
 fn is_fieldpat_equal(left : &FieldPat, right : &FieldPat) -> bool {
-       left.is_shorthand == right.is_shorthand && 
-               is_ident_equal(&left.ident, &right.ident) && 
-               is_pat_equal(&left.pat, &right.pat) 
+    left.is_shorthand == right.is_shorthand && 
+        is_ident_equal(&left.ident, &right.ident) && 
+        is_pat_equal(&left.pat, &right.pat) 
 }
 
 fn is_ident_equal(left : &Ident, right : &Ident) -> bool {
-       &left.name == &right.name && left.ctxt == right.ctxt
+    &left.name == &right.name && left.ctxt == right.ctxt
 }
 
 fn is_pats_equal(left : &[P<Pat>], right : &[P<Pat>]) -> bool {
-       over(left, right, |l, r| is_pat_equal(l, r))
+    over(left, right, |l, r| is_pat_equal(l, r))
 }
 
 fn is_lifetimedef_equal(left : &LifetimeDef, right : &LifetimeDef)
-               -> bool {
-       is_lifetime_equal(&left.lifetime, &right.lifetime) && 
-               over(&left.bounds, &right.bounds, is_lifetime_equal)
+        -> bool {
+    is_lifetime_equal(&left.lifetime, &right.lifetime) && 
+        over(&left.bounds, &right.bounds, is_lifetime_equal)
 }
 
 fn is_lifetimedefs_equal(left : &[LifetimeDef], right : &[LifetimeDef]) 
-               -> bool {
-       over(left, right, is_lifetimedef_equal)
+        -> bool {
+    over(left, right, is_lifetimedef_equal)
 }
 
 fn is_lifetime_equal(left : &Lifetime, right : &Lifetime) -> bool {
-       left.name == right.name
+    left.name == right.name
 }
 
 fn is_tys_equal(left : &[P<Ty>], right : &[P<Ty>]) -> bool {
-       over(left, right, |l, r| is_ty_equal(l, r))
+    over(left, right, |l, r| is_ty_equal(l, r))
 }
 
 fn over<X, F>(left: &[X], right: &[X], mut eq_fn: F) -> bool 
-               where F: FnMut(&X, &X) -> bool {
+        where F: FnMut(&X, &X) -> bool {
     left.len() == right.len() && left.iter().zip(right).all(|(x, y)| 
-               eq_fn(x, y))
+        eq_fn(x, y))
 }
 
 fn both<X, F>(l: &Option<X>, r: &Option<X>, mut eq_fn : F) -> bool 
-               where F: FnMut(&X, &X) -> bool {
-       l.as_ref().map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false,
-               |y| eq_fn(x, y)))
+        where F: FnMut(&X, &X) -> bool {
+    l.as_ref().map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false,
+        |y| eq_fn(x, y)))
 }
 
 fn is_cmp_or_bit(op : &BinOp) -> bool {
index b89eef8c8bb3dd97ae2fc7f5fa85509fa239a531..18011c618316b8918d1861739624d95bee038d01 100644 (file)
@@ -3,6 +3,8 @@
 use syntax::codemap::{Span, Spanned};
 use syntax::print::pprust::expr_to_string;
 
+use utils::span_lint;
+
 
 #[allow(missing_copy_implementations)]
 pub struct EtaPass;
@@ -18,7 +20,7 @@ fn get_lints(&self) -> LintArray {
 
     fn check_expr(&mut self, cx: &Context, expr: &Expr) {
         if let ExprClosure(_, ref decl, ref blk) = expr.node {
-            if blk.stmts.len() != 0 {
+            if !blk.stmts.is_empty() {
                 // || {foo(); bar()}; can't be reduced here
                 return;
             }
@@ -48,7 +50,7 @@ fn check_expr(&mut self, cx: &Context, expr: &Expr) {
                             return
                         }
                     }
-                    cx.span_lint(REDUNDANT_CLOSURE, expr.span,
+                    span_lint(cx, REDUNDANT_CLOSURE, expr.span,
                                  &format!("Redundant closure found, consider using `{}` in its place",
                                           expr_to_string(caller))[..])
                 }
index 25697199dc39b809f0e686daa87c50ea63f0c794..b3fb3e05447926e15c6ec19a3123624e7c7f0770 100644 (file)
@@ -7,6 +7,8 @@
 use syntax::ptr::P;
 use syntax::codemap::Span;
 
+use utils::{span_lint, snippet};
+
 declare_lint! { pub IDENTITY_OP, Warn,
     "Warn on identity operations, e.g. '_ + 0'"}
     
@@ -46,10 +48,9 @@ fn check_expr(&mut self, cx: &Context, e: &Expr) {
 
 fn check(cx: &Context, e: &Expr, m: i8, span: Span, arg: Span) {
     if have_lit(cx, e, m) {
-        let map = cx.sess().codemap();
-        cx.span_lint(IDENTITY_OP, span, &format!(
+        span_lint(cx, IDENTITY_OP, span, &format!(
             "The operation is ineffective. Consider reducing it to '{}'", 
-            &*map.span_to_snippet(arg).unwrap_or("..".to_string())));
+           snippet(cx, arg, "..")));
     }
 }
 
index 2ed0a6a9fabfb783a01539c0e5ff766adb093dbc..37683bbfa53ae8d355e6f9f497680649fdd57934 100644 (file)
@@ -1,11 +1,16 @@
 extern crate rustc_typeck as typeck;
 
+use std::rc::Rc;
+use std::cell::RefCell;
 use syntax::ptr::P;
-use syntax::ast::*;
 use rustc::lint::{Context, LintPass, LintArray, Lint};
-use rustc::middle::ty::{self, node_id_to_type, sty, ty_ptr, ty_rptr, mt, MethodTraitItemId};
+use rustc::util::nodemap::DefIdMap;
+use rustc::middle::ty::{self, TypeVariants, TypeAndMut, MethodTraitItemId, ImplOrTraitItemId};
 use rustc::middle::def::{DefTy, DefStruct, DefTrait};
 use syntax::codemap::{Span, Spanned};
+use syntax::ast::*;
+use misc::walk_ty;
+use utils::span_lint;
 
 declare_lint!(pub LEN_ZERO, Warn,
               "Warn when .is_empty() could be used instead of checking .len()");
@@ -45,17 +50,18 @@ fn check_expr(&mut self, cx: &Context, expr: &Expr) {
 
 fn check_trait_items(cx: &Context, item: &Item, trait_items: &[P<TraitItem>]) {
        fn is_named_self(item: &TraitItem, name: &str) -> bool {
-               item.ident.as_str() == name && item.attrs.len() == 0
+               item.ident.name == name && if let MethodTraitItem(ref sig, _) =
+                       item.node { is_self_sig(sig) } else { false }
        }
 
        if !trait_items.iter().any(|i| is_named_self(i, "is_empty")) {
-               //cx.span_lint(LEN_WITHOUT_IS_EMPTY, item.span, &format!("trait {}", item.ident.as_str()));
+               //span_lint(cx, LEN_WITHOUT_IS_EMPTY, item.span, &format!("trait {}", item.ident.as_str()));
                for i in trait_items {
                        if is_named_self(i, "len") {
-                               cx.span_lint(LEN_WITHOUT_IS_EMPTY, i.span,
-                                       &format!("Trait '{}' has a '.len()' method, but no \
-                                               '.is_empty()' method. Consider adding one.", 
-                                               item.ident.as_str()));
+                               span_lint(cx, LEN_WITHOUT_IS_EMPTY, i.span,
+                                       &format!("Trait '{}' has a '.len(_: &Self)' method, but no \
+                                               '.is_empty(_: &Self)' method. Consider adding one.", 
+                                               item.ident.name));
                        }
                };
        }
@@ -63,22 +69,30 @@ fn is_named_self(item: &TraitItem, name: &str) -> bool {
 
 fn check_impl_items(cx: &Context, item: &Item, impl_items: &[P<ImplItem>]) {
        fn is_named_self(item: &ImplItem, name: &str) -> bool {
-               item.ident.as_str() == name && item.attrs.len() == 0
+               item.ident.name == name && if let MethodImplItem(ref sig, _) = 
+                               item.node { is_self_sig(sig) } else { false }
        }
 
        if !impl_items.iter().any(|i| is_named_self(i, "is_empty")) {
                for i in impl_items {
                        if is_named_self(i, "len") {
-                               cx.span_lint(LEN_WITHOUT_IS_EMPTY, i.span,
-                                       &format!("Item '{}' has a '.len()' method, but no \
-                                               '.is_empty()' method. Consider adding one.", 
-                                               item.ident.as_str()));
+                               let s = i.span;
+                               span_lint(cx, LEN_WITHOUT_IS_EMPTY, 
+                                       Span{ lo: s.lo, hi: s.lo, expn_id: s.expn_id },
+                                       &format!("Item '{}' has a '.len(_: &Self)' method, but no \
+                                               '.is_empty(_: &Self)' method. Consider adding one.", 
+                                               item.ident.name));
                                return;
                        }
                }
        }
 }
 
+fn is_self_sig(sig: &MethodSig) -> bool {
+       if let SelfStatic = sig.explicit_self.node { 
+               false } else { sig.decl.inputs.len() == 1 }
+}
+
 fn check_cmp(cx: &Context, span: Span, left: &Expr, right: &Expr, empty: &str) {
        match (&left.node, &right.node) {
                (&ExprLit(ref lit), &ExprMethodCall(ref method, _, ref args)) => 
@@ -92,10 +106,46 @@ fn check_cmp(cx: &Context, span: Span, left: &Expr, right: &Expr, empty: &str) {
 fn check_len_zero(cx: &Context, span: Span, method: &SpannedIdent, 
                args: &[P<Expr>], lit: &Lit, empty: &str) {
        if let &Spanned{node: LitInt(0, _), ..} = lit {
-               if method.node.as_str() == "len" && args.len() == 1 {
-                       cx.span_lint(LEN_ZERO, span, &format!(
-                               "Consider replacing the len comparison with '{}_.is_empty()' if available",
+               if method.node.name == "len" && args.len() == 1 &&
+                       has_is_empty(cx, &*args[0]) {
+                       span_lint(cx, LEN_ZERO, span, &format!(
+                               "Consider replacing the len comparison with '{}_.is_empty()'",
                                        empty))
                }
        }
 }
+
+/// check if this type has an is_empty method
+fn has_is_empty(cx: &Context, expr: &Expr) -> bool {
+       /// get a ImplOrTraitItem and return true if it matches is_empty(self)
+       fn is_is_empty(cx: &Context, id: &ImplOrTraitItemId) -> bool {
+               if let &MethodTraitItemId(def_id) = id {
+                       if let ty::MethodTraitItem(ref method) = 
+                                       cx.tcx.impl_or_trait_item(def_id) {
+                               method.name.as_str() == "is_empty"
+                                       && method.fty.sig.skip_binder().inputs.len() == 1 
+                       } else { false }
+               } else { false }
+       }
+       
+       /// check the inherent impl's items for an is_empty(self) method
+       fn has_is_empty_impl(cx: &Context, id: &DefId) -> bool {
+               let impl_items = cx.tcx.impl_items.borrow();
+               cx.tcx.inherent_impls.borrow().get(id).map_or(false, 
+                       |ids| ids.iter().any(|iid| impl_items.get(iid).map_or(false, 
+                               |iids| iids.iter().any(|i| is_is_empty(cx, i)))))
+       }
+       
+       let ty = &walk_ty(&cx.tcx.expr_ty(expr));
+       match ty.sty {
+               ty::TyTrait(_) => cx.tcx.trait_item_def_ids.borrow().get(
+                       &ty.ty_to_def_id().expect("trait impl not found")).map_or(false, 
+                       |ids| ids.iter().any(|i| is_is_empty(cx, i))),
+               ty::TyProjection(_) => ty.ty_to_def_id().map_or(false, 
+                       |id| has_is_empty_impl(cx, &id)),
+               ty::TyEnum(ref id, _) | ty::TyStruct(ref id, _) => 
+                       has_is_empty_impl(cx, &id.did),
+               ty::TyArray(..) => true,
+               _ => false,
+       }
+}
old mode 100644 (file)
new mode 100755 (executable)
index 1d760bd..80ba040
@@ -1,6 +1,5 @@
 #![feature(plugin_registrar, box_syntax)]
 #![feature(rustc_private, collections)]
-
 #![allow(unused_imports)]
 
 #[macro_use]
 pub mod identity_op;
 pub mod mut_mut;
 pub mod len_zero;
+pub mod attrs;
 pub mod collapsible_if;
+pub mod unicode;
+pub mod utils;
+pub mod strings;
 
 #[plugin_registrar]
 pub fn plugin_registrar(reg: &mut Registry) {
@@ -46,13 +49,17 @@ pub fn plugin_registrar(reg: &mut Registry) {
     reg.register_lint_pass(box mut_mut::MutMut as LintPassObject);
     reg.register_lint_pass(box len_zero::LenZero as LintPassObject);
     reg.register_lint_pass(box misc::CmpOwned as LintPassObject);
+    reg.register_lint_pass(box attrs::AttrPass as LintPassObject);
     reg.register_lint_pass(box collapsible_if::CollapsibleIf as LintPassObject);
     reg.register_lint_pass(box misc::ModuloOne as LintPassObject);
-    
+    reg.register_lint_pass(box unicode::Unicode as LintPassObject);
+    reg.register_lint_pass(box strings::StringAdd as LintPassObject);
+    reg.register_lint_pass(box misc::NeedlessReturn as LintPassObject);
+
     reg.register_lint_group("clippy", vec![types::BOX_VEC, types::LINKEDLIST,
                                            misc::SINGLE_MATCH, misc::STR_TO_STRING,
                                            misc::TOPLEVEL_REF_ARG, eq_op::EQ_OP,
-                                           bit_mask::BAD_BIT_MASK, 
+                                           bit_mask::BAD_BIT_MASK,
                                            bit_mask::INEFFECTIVE_BIT_MASK,
                                            ptr_arg::PTR_ARG,
                                            needless_bool::NEEDLESS_BOOL,
@@ -64,7 +71,11 @@ pub fn plugin_registrar(reg: &mut Registry) {
                                            mut_mut::MUT_MUT,
                                            len_zero::LEN_ZERO,
                                            len_zero::LEN_WITHOUT_IS_EMPTY,
+                                           attrs::INLINE_ALWAYS,
                                            collapsible_if::COLLAPSIBLE_IF,
+                                           unicode::ZERO_WIDTH_SPACE,
+                                           strings::STRING_ADD_ASSIGN,
+                                           misc::NEEDLESS_RETURN,
                                            misc::MODULO_ONE,
                                            ]);
 }
index 590ebbc3f9fafd2ddf593dd92ce3191f64e0fb4b..53bf73de35a7d45ac81c0aa3a276d44a0ca51f9e 100644 (file)
@@ -4,17 +4,16 @@
 use syntax::ast_util::{is_comparison_binop, binop_to_string};
 use syntax::visit::{FnKind};
 use rustc::lint::{Context, LintPass, LintArray, Lint, Level};
-use rustc::middle::ty::{self, expr_ty, ty_str, ty_ptr, ty_rptr, ty_float};
+use rustc::middle::ty;
 use syntax::codemap::{Span, Spanned};
 
+use utils::{match_path, snippet, span_lint, span_help_and_lint};
 
-use types::span_note_and_lint;
-
-fn walk_ty<'t>(ty: ty::Ty<'t>) -> ty::Ty<'t> {
-       match ty.sty {
-               ty_ptr(ref tm) | ty_rptr(_, ref tm) => walk_ty(tm.ty),
-               _ => ty
-       }
+pub fn walk_ty<'t>(ty: ty::Ty<'t>) -> ty::Ty<'t> {
+    match ty.sty {
+        ty::TyRef(_, ref tm) | ty::TyRawPtr(ref tm) => walk_ty(tm.ty),
+        _ => ty
+    }
 }
 
 /// Handles uncategorized lints
@@ -36,19 +35,21 @@ fn check_expr(&mut self, cx: &Context, expr: &Expr) {
             if arms.len() == 2 {
                 if arms[0].guard.is_none() && arms[1].pats.len() == 1 {
                     match arms[1].body.node {
-                        ExprTup(ref v) if v.len() == 0 && arms[1].guard.is_none() => (),
-                        ExprBlock(ref b) if b.stmts.len() == 0 && arms[1].guard.is_none() => (),
+                        ExprTup(ref v) if v.is_empty() && arms[1].guard.is_none() => (),
+                        ExprBlock(ref b) if b.stmts.is_empty() && arms[1].guard.is_none() => (),
                          _ => return
                     }
                     // In some cases, an exhaustive match is preferred to catch situations when
                     // an enum is extended. So we only consider cases where a `_` wildcard is used
-                    if arms[1].pats[0].node == PatWild(PatWildSingle) && arms[0].pats.len() == 1 {
-                        let map = cx.sess().codemap();
-                        span_note_and_lint(cx, SINGLE_MATCH, expr.span,
-                              "You seem to be trying to use match for destructuring a single type. Did you mean to use `if let`?",
+                    if arms[1].pats[0].node == PatWild(PatWildSingle) &&
+                            arms[0].pats.len() == 1 {
+                        span_help_and_lint(cx, SINGLE_MATCH, expr.span,
+                              "You seem to be trying to use match for \
+                              destructuring a single type. Did you mean to \
+                              use `if let`?",
                               &*format!("Try if let {} = {} {{ ... }}",
-                                      &*map.span_to_snippet(arms[0].pats[0].span).unwrap_or("..".to_string()),
-                                      &*map.span_to_snippet(ex.span).unwrap_or("..".to_string()))
+                                      snippet(cx, arms[0].pats[0].span, ".."),
+                                      snippet(cx, ex.span, ".."))
                         );
                     }
                 }
@@ -71,16 +72,16 @@ fn get_lints(&self) -> LintArray {
     fn check_expr(&mut self, cx: &Context, expr: &ast::Expr) {
         match expr.node {
             ast::ExprMethodCall(ref method, _, ref args)
-                if method.node.as_str() == "to_string"
+                if method.node.name == "to_string"
                 && is_str(cx, &*args[0]) => {
-                cx.span_lint(STR_TO_STRING, expr.span, "str.to_owned() is faster");
+                span_lint(cx, STR_TO_STRING, expr.span, "str.to_owned() is faster");
             },
             _ => ()
         }
 
         fn is_str(cx: &Context, expr: &ast::Expr) -> bool {
-            match walk_ty(expr_ty(cx.tcx, expr)).sty {
-                ty_str => true,
+            match walk_ty(cx.tcx.expr_ty(expr)).sty {
+                ty::TyStr => true,
                 _ => false
             }
         }
@@ -101,7 +102,7 @@ fn get_lints(&self) -> LintArray {
     fn check_fn(&mut self, cx: &Context, _: FnKind, decl: &FnDecl, _: &Block, _: Span, _: NodeId) {
         for ref arg in decl.inputs.iter() {
             if let PatIdent(BindByRef(_), _, _) = arg.pat.node {
-                cx.span_lint(
+                span_lint(cx,
                     TOPLEVEL_REF_ARG,
                     arg.pat.span,
                     "`ref` directly on a function argument is ignored. Have you considered using a reference type instead?"
@@ -117,154 +118,224 @@ fn check_fn(&mut self, cx: &Context, _: FnKind, decl: &FnDecl, _: &Block, _: Spa
 pub struct CmpNan;
 
 impl LintPass for CmpNan {
-       fn get_lints(&self) -> LintArray {
+    fn get_lints(&self) -> LintArray {
         lint_array!(CMP_NAN)
-       }
-       
-       fn check_expr(&mut self, cx: &Context, expr: &Expr) {
-               if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
-                       if is_comparison_binop(cmp.node) {
-                               if let &ExprPath(_, ref path) = &left.node {
-                                       check_nan(cx, path, expr.span);
-                               }
-                               if let &ExprPath(_, ref path) = &right.node {
-                                       check_nan(cx, path, expr.span);
-                               }
-                       }
-               }
-       }
+    }
+
+    fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+        if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
+            if is_comparison_binop(cmp.node) {
+                if let &ExprPath(_, ref path) = &left.node {
+                    check_nan(cx, path, expr.span);
+                }
+                if let &ExprPath(_, ref path) = &right.node {
+                    check_nan(cx, path, expr.span);
+                }
+            }
+        }
+    }
 }
 
 fn check_nan(cx: &Context, path: &Path, span: Span) {
-       path.segments.last().map(|seg| if seg.identifier.as_str() == "NAN" {
-               cx.span_lint(CMP_NAN, span, "Doomed comparison with NAN, use std::{f32,f64}::is_nan instead");
-       });
+    path.segments.last().map(|seg| if seg.identifier.name == "NAN" {
+        span_lint(cx, CMP_NAN, span,
+                  "Doomed comparison with NAN, use std::{f32,f64}::is_nan instead");
+    });
 }
 
 declare_lint!(pub FLOAT_CMP, Warn,
-                         "Warn on ==/!= comparison of floaty values");
-                         
+              "Warn on ==/!= comparison of floaty values");
+
 #[derive(Copy,Clone)]
 pub struct FloatCmp;
 
 impl LintPass for FloatCmp {
-       fn get_lints(&self) -> LintArray {
+    fn get_lints(&self) -> LintArray {
         lint_array!(FLOAT_CMP)
-       }
-       
-       fn check_expr(&mut self, cx: &Context, expr: &Expr) {
-               if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
-                       let op = cmp.node;
-                       if (op == BiEq || op == BiNe) && (is_float(cx, left) || is_float(cx, right)) {
-                               let map = cx.sess().codemap();
-                               cx.span_lint(FLOAT_CMP, expr.span, &format!(
-                                       "{}-Comparison of f32 or f64 detected. You may want to change this to 'abs({} - {}) < epsilon' for some suitable value of epsilon",
-                                       binop_to_string(op), &*map.span_to_snippet(left.span).unwrap_or("..".to_string()), 
-                                       &*map.span_to_snippet(right.span).unwrap_or("..".to_string())));
-                       }
-               }
-       }
+    }
+
+    fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+        if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
+            let op = cmp.node;
+            if (op == BiEq || op == BiNe) && (is_float(cx, left) || is_float(cx, right)) {
+                span_lint(cx, FLOAT_CMP, expr.span, &format!(
+                    "{}-Comparison of f32 or f64 detected. You may want to change this to 'abs({} - {}) < epsilon' for some suitable value of epsilon",
+                    binop_to_string(op), snippet(cx, left.span, ".."),
+                    snippet(cx, right.span, "..")));
+            }
+        }
+    }
 }
 
 fn is_float(cx: &Context, expr: &Expr) -> bool {
-       if let ty_float(_) = walk_ty(expr_ty(cx.tcx, expr)).sty { true } else { false }
+    if let ty::TyFloat(_) = walk_ty(cx.tcx.expr_ty(expr)).sty {
+        true
+    } else {
+        false
+    }
 }
 
 declare_lint!(pub PRECEDENCE, Warn,
-                         "Warn on mixing bit ops with integer arithmetic without parenthesis");
-                         
+              "Warn on mixing bit ops with integer arithmetic without parenthesis");
+
 #[derive(Copy,Clone)]
 pub struct Precedence;
 
 impl LintPass for Precedence {
-       fn get_lints(&self) -> LintArray {
+    fn get_lints(&self) -> LintArray {
         lint_array!(PRECEDENCE)
-       }
-       
-       fn check_expr(&mut self, cx: &Context, expr: &Expr) {
-               if let ExprBinary(Spanned { node: op, ..}, ref left, ref right) = expr.node {
-                       if is_bit_op(op) && (is_arith_expr(left) || is_arith_expr(right)) {
-                               cx.span_lint(PRECEDENCE, expr.span, 
-                                       "Operator precedence can trip the unwary. Consider adding parenthesis to the subexpression.");
-                       }
-               }
-       }
+    }
+
+    fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+        if let ExprBinary(Spanned { node: op, ..}, ref left, ref right) = expr.node {
+            if is_bit_op(op) && (is_arith_expr(left) || is_arith_expr(right)) {
+                span_lint(cx, PRECEDENCE, expr.span,
+                    "Operator precedence can trip the unwary. Consider adding parenthesis to the subexpression.");
+            }
+        }
+    }
 }
 
 fn is_arith_expr(expr : &Expr) -> bool {
-       match expr.node {
-               ExprBinary(Spanned { node: op, ..}, _, _) => is_arith_op(op),
-               _ => false
-       }
+    match expr.node {
+        ExprBinary(Spanned { node: op, ..}, _, _) => is_arith_op(op),
+        _ => false
+    }
 }
 
 fn is_bit_op(op : BinOp_) -> bool {
-       match op {
-               BiBitXor | BiBitAnd | BiBitOr | BiShl | BiShr => true,
-               _ => false
-       }
+    match op {
+        BiBitXor | BiBitAnd | BiBitOr | BiShl | BiShr => true,
+        _ => false
+    }
 }
 
 fn is_arith_op(op : BinOp_) -> bool {
-       match op {
-               BiAdd | BiSub | BiMul | BiDiv | BiRem => true,
-               _ => false
-       }
+    match op {
+        BiAdd | BiSub | BiMul | BiDiv | BiRem => true,
+        _ => false
+    }
 }
 
 declare_lint!(pub CMP_OWNED, Warn,
-                         "Warn on creating an owned string just for comparison");
-                         
+              "Warn on creating an owned string just for comparison");
+
 #[derive(Copy,Clone)]
 pub struct CmpOwned;
 
 impl LintPass for CmpOwned {
-       fn get_lints(&self) -> LintArray {
+    fn get_lints(&self) -> LintArray {
         lint_array!(CMP_OWNED)
-       }
-       
-       fn check_expr(&mut self, cx: &Context, expr: &Expr) {
-               if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
-                       if is_comparison_binop(cmp.node) {
-                               check_to_owned(cx, left, right.span);
-                               check_to_owned(cx, right, left.span)
-                       }
-               }
-       }
+    }
+
+    fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+        if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
+            if is_comparison_binop(cmp.node) {
+                check_to_owned(cx, left, right.span);
+                check_to_owned(cx, right, left.span)
+            }
+        }
+    }
 }
 
 fn check_to_owned(cx: &Context, expr: &Expr, other_span: Span) {
-       match &expr.node {
-               &ExprMethodCall(Spanned{node: ref ident, ..}, _, ref args) => {
-                       let name = ident.as_str();
-                       if name == "to_string" || 
-                          name == "to_owned" && is_str_arg(cx, args) {
-                               cx.span_lint(CMP_OWNED, expr.span, &format!(
-                                       "this creates an owned instance just for comparison. \
-                                       Consider using {}.as_slice() to compare without allocation",
-                                       cx.sess().codemap().span_to_snippet(other_span).unwrap_or(
-                                               "..".to_string())))
-                       }
-               },
-               &ExprCall(ref path, _) => {
-                       if let &ExprPath(None, ref path) = &path.node {
-                               if path.segments.iter().zip(["String", "from_str"].iter()).all(
-                                               |(seg, name)| &seg.identifier.as_str() == name) {
-                                       cx.span_lint(CMP_OWNED, expr.span, &format!(
-                                       "this creates an owned instance just for comparison. \
-                                       Consider using {}.as_slice() to compare without allocation",
-                                       cx.sess().codemap().span_to_snippet(other_span).unwrap_or(
-                                               "..".to_string())))
-                               }
-                       }
-               },
-               _ => ()
-       }
+    match &expr.node {
+        &ExprMethodCall(Spanned{node: ref ident, ..}, _, ref args) => {
+            let name = ident.name;
+            if name == "to_string" ||
+                name == "to_owned" && is_str_arg(cx, args) {
+                    span_lint(cx, CMP_OWNED, expr.span, &format!(
+                        "this creates an owned instance just for comparison. \
+                         Consider using {}.as_slice() to compare without allocation",
+                        snippet(cx, other_span, "..")))
+                }
+        },
+        &ExprCall(ref path, _) => {
+            if let &ExprPath(None, ref path) = &path.node {
+                if match_path(path, &["String", "from_str"]) ||
+                    match_path(path, &["String", "from"]) {
+                        span_lint(cx, CMP_OWNED, expr.span, &format!(
+                            "this creates an owned instance just for comparison. \
+                             Consider using {}.as_slice() to compare without allocation",
+                            snippet(cx, other_span, "..")))
+                    }
+            }
+        },
+        _ => ()
+    }
 }
 
 fn is_str_arg(cx: &Context, args: &[P<Expr>]) -> bool {
-       args.len() == 1 && if let ty_str = 
-               walk_ty(expr_ty(cx.tcx, &*args[0])).sty { true } else { false }
+    args.len() == 1 && if let ty::TyStr =
+        walk_ty(cx.tcx.expr_ty(&*args[0])).sty { true } else { false }
+}
+
+declare_lint!(pub NEEDLESS_RETURN, Warn,
+              "Warn on using a return statement where an expression would be enough");
+
+#[derive(Copy,Clone)]
+pub struct NeedlessReturn;
+
+impl NeedlessReturn {
+    // Check the final stmt or expr in a block for unnecessary return.
+    fn check_block_return(&mut self, cx: &Context, block: &Block) {
+        if let Some(ref expr) = block.expr {
+            self.check_final_expr(cx, expr);
+        } else if let Some(stmt) = block.stmts.last() {
+            if let StmtSemi(ref expr, _) = stmt.node {
+                if let ExprRet(Some(ref inner)) = expr.node {
+                    self.emit_lint(cx, (expr.span, inner.span));
+                }
+            }
+        }
+    }
+
+    // Check a the final expression in a block if it's a return.
+    fn check_final_expr(&mut self, cx: &Context, expr: &Expr) {
+        match expr.node {
+            // simple return is always "bad"
+            ExprRet(Some(ref inner)) => {
+                self.emit_lint(cx, (expr.span, inner.span));
+            }
+            // a whole block? check it!
+            ExprBlock(ref block) => {
+                self.check_block_return(cx, block);
+            }
+            // an if/if let expr, check both exprs
+            // note, if without else is going to be a type checking error anyways
+            // (except for unit type functions) so we don't match it
+            ExprIf(_, ref ifblock, Some(ref elsexpr)) |
+            ExprIfLet(_, _, ref ifblock, Some(ref elsexpr)) => {
+                self.check_block_return(cx, ifblock);
+                self.check_final_expr(cx, elsexpr);
+            }
+            // a match expr, check all arms
+            ExprMatch(_, ref arms, _) => {
+                for arm in arms {
+                    self.check_final_expr(cx, &*arm.body);
+                }
+            }
+            _ => { }
+        }
+    }
+
+    fn emit_lint(&mut self, cx: &Context, spans: (Span, Span)) {
+        span_lint(cx, NEEDLESS_RETURN, spans.0, &format!(
+            "unneeded return statement. Consider using {} \
+             without the trailing semicolon",
+            snippet(cx, spans.1, "..")))
+    }
+}
+
+impl LintPass for NeedlessReturn {
+    fn get_lints(&self) -> LintArray {
+        lint_array!(NEEDLESS_RETURN)
+    }
+
+    fn check_fn(&mut self, cx: &Context, _: FnKind, _: &FnDecl,
+                block: &Block, _: Span, _: ast::NodeId) {
+        self.check_block_return(cx, block);
+    }
 }
 
 
index b1e21def574678b3d6232d0cbaa694b633c590d1..73e97ae31f42896296a4453d3c99eaee5d9c4b6c 100644 (file)
@@ -1,8 +1,9 @@
 use syntax::ptr::P;
 use syntax::ast::*;
 use rustc::lint::{Context, LintPass, LintArray, Lint};
-use rustc::middle::ty::{expr_ty, sty, ty_ptr, ty_rptr, mt};
-use syntax::codemap::ExpnInfo;
+use rustc::middle::ty::{TypeVariants, TypeAndMut, TyRef};
+use syntax::codemap::{BytePos, ExpnInfo, Span};
+use utils::{in_macro, span_lint};
 
 declare_lint!(pub MUT_MUT, Warn,
               "Warn on usage of double-mut refs, e.g. '&mut &mut ...'");
@@ -21,13 +22,13 @@ fn check_expr(&mut self, cx: &Context, expr: &Expr) {
        }
        
        fn check_ty(&mut self, cx: &Context, ty: &Ty) {
-               unwrap_mut(ty).and_then(unwrap_mut).map_or((), |_| cx.span_lint(MUT_MUT, 
+               unwrap_mut(ty).and_then(unwrap_mut).map_or((), |_| span_lint(cx, MUT_MUT, 
                        ty.span, "Generally you want to avoid &mut &mut _ if possible."))
        }
 }
 
 fn check_expr_expd(cx: &Context, expr: &Expr, info: Option<&ExpnInfo>) {
-       if in_external_macro(info) { return; }
+       if in_macro(cx, info) { return; }
 
        fn unwrap_addr(expr : &Expr) -> Option<&Expr> {
                match expr.node {
@@ -38,12 +39,12 @@ fn unwrap_addr(expr : &Expr) -> Option<&Expr> {
        
        unwrap_addr(expr).map_or((), |e| {
                unwrap_addr(e).map(|_| {
-                       cx.span_lint(MUT_MUT, expr.span, 
+                       span_lint(cx, MUT_MUT, expr.span, 
                                "Generally you want to avoid &mut &mut _ if possible.")
                }).unwrap_or_else(|| {
-                       if let ty_rptr(_, mt{ty: _, mutbl: MutMutable}) = 
-                                       expr_ty(cx.tcx, e).sty {
-                               cx.span_lint(MUT_MUT, expr.span,
+                       if let TyRef(_, TypeAndMut{ty: _, mutbl: MutMutable}) = 
+                                       cx.tcx.expr_ty(e).sty {
+                               span_lint(cx, MUT_MUT, expr.span,
                                        "This expression mutably borrows a mutable reference. \
                                        Consider reborrowing")
                        }
@@ -51,10 +52,6 @@ fn unwrap_addr(expr : &Expr) -> Option<&Expr> {
        })
 }
 
-fn in_external_macro(info: Option<&ExpnInfo>) -> bool {
-       info.map_or(false, |i| i.callee.span.is_some())
-}
-
 fn unwrap_mut(ty : &Ty) -> Option<&Ty> {
        match ty.node {
                TyPtr(MutTy{ ty: ref pty, mutbl: MutMutable }) => Option::Some(pty),
index fe35e6ee3bd460414d8ef2243a343eaf48fbd590..35d921e8fa1f1cb6e29cdadbf35f3e2daf1065be 100644 (file)
@@ -10,6 +10,7 @@
 use syntax::ast_util::{is_comparison_binop, binop_to_string};
 use syntax::ptr::P;
 use syntax::codemap::Span;
+use utils::{de_p, span_lint};
 
 declare_lint! {
     pub NEEDLESS_BOOL,
@@ -28,10 +29,18 @@ fn get_lints(&self) -> LintArray {
     fn check_expr(&mut self, cx: &Context, e: &Expr) {
         if let ExprIf(_, ref then_block, Option::Some(ref else_expr)) = e.node {
                        match (fetch_bool_block(then_block), fetch_bool_expr(else_expr)) {
-                               (Option::Some(true), Option::Some(true)) => { cx.span_lint(NEEDLESS_BOOL, e.span, "your if-then-else expression will always return true"); },
-                               (Option::Some(true), Option::Some(false)) => { cx.span_lint(NEEDLESS_BOOL, e.span, "you can reduce your if-statement to its predicate"); },
-                               (Option::Some(false), Option::Some(true)) => { cx.span_lint(NEEDLESS_BOOL, e.span, "you can reduce your if-statement to '!' + your predicate"); },
-                               (Option::Some(false), Option::Some(false)) => { cx.span_lint(NEEDLESS_BOOL, e.span, "your if-then-else expression will always return false"); },
+                               (Option::Some(true), Option::Some(true)) => { 
+                                       span_lint(cx, NEEDLESS_BOOL, e.span, 
+                                               "your if-then-else expression will always return true"); },
+                               (Option::Some(true), Option::Some(false)) => { 
+                                       span_lint(cx, NEEDLESS_BOOL, e.span, 
+                                               "you can reduce your if-statement to its predicate"); },
+                               (Option::Some(false), Option::Some(true)) => { 
+                                       span_lint(cx, NEEDLESS_BOOL, e.span, 
+                                               "you can reduce your if-statement to '!' + your predicate"); },
+                               (Option::Some(false), Option::Some(false)) => { 
+                                       span_lint(cx, NEEDLESS_BOOL, e.span, 
+                                               "your if-then-else expression will always return false"); },
                                _ => ()
                        }
                }
@@ -39,7 +48,9 @@ fn check_expr(&mut self, cx: &Context, e: &Expr) {
 }
 
 fn fetch_bool_block(block: &Block) -> Option<bool> {
-       if block.stmts.is_empty() { block.expr.as_ref().and_then(|e| fetch_bool_expr(e)) } else { Option::None }
+       if block.stmts.is_empty() { 
+               block.expr.as_ref().map(de_p).and_then(fetch_bool_expr)
+       } else { Option::None }
 }
        
 fn fetch_bool_expr(expr: &Expr) -> Option<bool> {
index 64c3c84c7b6e9eea5b86d3c98272b206f0015d48..dad4e48c832d0e8451d3f5f98a0cd871c779022d 100644 (file)
@@ -11,6 +11,7 @@
 use syntax::ptr::P;
 use syntax::codemap::Span;
 use types::match_ty_unwrap;
+use utils::span_lint;
 
 declare_lint! {
     pub PTR_ARG,
@@ -58,10 +59,10 @@ fn check_fn(cx: &Context, decl: &FnDecl) {
 fn check_ptr_subtype(cx: &Context, span: Span, ty: &Ty) {
        match_ty_unwrap(ty, &["Vec"]).map_or_else(|| match_ty_unwrap(ty, 
                        &["String"]).map_or((), |_| {
-               cx.span_lint(PTR_ARG, span,
+               span_lint(cx, PTR_ARG, span,
                        "Writing '&String' instead of '&str' involves a new Object \
                        where a slices will do. Consider changing the type to &str")
-       }), |_| cx.span_lint(PTR_ARG, span, "Writing '&Vec<_>' instead of \
+       }), |_| span_lint(cx, PTR_ARG, span, "Writing '&Vec<_>' instead of \
                        '&[_]' involves one more reference and cannot be used with \
                        non-vec-based slices. Consider changing the type to &[...]")
        )
diff --git a/src/strings.rs b/src/strings.rs
new file mode 100644 (file)
index 0000000..511d123
--- /dev/null
@@ -0,0 +1,55 @@
+//! This LintPass catches both string addition and string addition + assignment
+//! 
+//! Note that since we have two lints where one subsumes the other, we try to
+//! disable the subsumed lint unless it has a higher level
+
+use rustc::lint::*;
+use rustc::middle::ty::TypeVariants::TyStruct;
+use syntax::ast::*;
+use syntax::codemap::{Span, Spanned};
+use eq_op::is_exp_equal;
+use misc::walk_ty;
+use types::match_ty_unwrap;
+use utils::{match_def_path, span_lint};
+
+declare_lint! {
+    pub STRING_ADD_ASSIGN,
+    Warn,
+    "Warn on `x = x + ..` where x is a `String`"
+}
+
+#[derive(Copy,Clone)]
+pub struct StringAdd;
+
+impl LintPass for StringAdd {
+    fn get_lints(&self) -> LintArray {
+        lint_array!(STRING_ADD_ASSIGN)
+    }
+    
+    fn check_expr(&mut self, cx: &Context, e: &Expr) {
+        if let &ExprAssign(ref target, ref  src) = &e.node {
+            if is_string(cx, target) && is_add(src, target) { 
+                span_lint(cx, STRING_ADD_ASSIGN, e.span, 
+                    "You assign the result of adding something to this string. \
+                    Consider using `String::push_str(..) instead.")
+            }
+        }
+    }
+}
+
+fn is_string(cx: &Context, e: &Expr) -> bool {
+    if let TyStruct(did, _) = walk_ty(cx.tcx.expr_ty(e)).sty {
+        match_def_path(cx, did.did, &["std", "string", "String"])
+    } else { false }
+}
+
+fn is_add(src: &Expr, target: &Expr) -> bool {
+    match &src.node {
+        &ExprBinary(Spanned{ node: BiAdd, .. }, ref left, _) =>
+            is_exp_equal(target, left),
+        &ExprBlock(ref block) => block.stmts.is_empty() && 
+            block.expr.as_ref().map_or(false, |expr| is_add(&*expr, target)),
+        &ExprParen(ref expr) => is_add(&*expr, target),
+        _ => false
+    }
+}
index f0c91dc18b5349002dfe660898bb9ad15a55815a..d138239b5a76aa7f67d043f3a8ec24fae50b80dc 100644 (file)
@@ -6,6 +6,8 @@
 use rustc::lint::{Context, LintPass, LintArray, Lint, Level};
 use syntax::codemap::Span;
 
+use utils::span_lint;
+
 /// Handles all the linting of funky types
 #[allow(missing_copy_implementations)]
 pub struct TypePass;
@@ -23,7 +25,7 @@ pub fn match_ty_unwrap<'a>(ty: &'a Ty, segments: &[&str]) -> Option<&'a [P<Ty>]>
             // I could muck around with the maps and find the full path
             // however the more efficient way is to simply reverse the iterators and zip them
             // which will compare them in reverse until one of them runs out of segments
-            if seg.iter().rev().zip(segments.iter().rev()).all(|(a,b)| a.identifier.as_str() == *b) {
+            if seg.iter().rev().zip(segments.iter().rev()).all(|(a,b)| a.identifier.name == b) {
                 match seg[..].last() {
                     Some(&PathSegment {parameters: AngleBracketedParameters(ref a), ..}) => {
                         Some(&a.types[..])
@@ -40,7 +42,7 @@ pub fn match_ty_unwrap<'a>(ty: &'a Ty, segments: &[&str]) -> Option<&'a [P<Ty>]>
 
 /// Lets me span a note only if the lint is shown
 pub fn span_note_and_lint(cx: &Context, lint: &'static Lint, span: Span, msg: &str, note: &str) {
-    cx.span_lint(lint, span, msg);
+    span_lint(cx, lint, span, msg);
     if cx.current_level(lint) != Level::Allow {
         cx.sess().span_note(span, note);
     }
diff --git a/src/unicode.rs b/src/unicode.rs
new file mode 100644 (file)
index 0000000..9b908c3
--- /dev/null
@@ -0,0 +1,46 @@
+use rustc::lint::*;
+use syntax::ast::*;
+use syntax::codemap::{BytePos, Span};
+use utils::span_lint;
+
+declare_lint!{ pub ZERO_WIDTH_SPACE, Deny, "Zero-width space is confusing" }
+
+#[derive(Copy, Clone)]
+pub struct Unicode;
+
+impl LintPass for Unicode {
+       fn get_lints(&self) -> LintArray {
+        lint_array!(ZERO_WIDTH_SPACE)
+    }
+    
+    fn check_expr(&mut self, cx: &Context, expr: &Expr) {
+               if let ExprLit(ref lit) = expr.node {
+                       if let LitStr(ref string, _) = lit.node {
+                               check_str(cx, string, lit.span)
+                       }
+               }
+       }
+}
+
+fn check_str(cx: &Context, string: &str, span: Span) {
+       let mut start: Option<usize> = None;
+       for (i, c) in string.char_indices() {
+               if c == '\u{200B}' {
+                       if start.is_none() { start = Some(i); }
+               } else {
+                       lint_zero_width(cx, span, start);
+                       start = None;
+               }
+       }
+       lint_zero_width(cx, span, start);
+}
+
+fn lint_zero_width(cx: &Context, span: Span, start: Option<usize>) {
+       start.map(|index| {
+               span_lint(cx, ZERO_WIDTH_SPACE, Span {
+                       lo: span.lo + BytePos(index as u32),
+                       hi: span.lo + BytePos(index as u32),
+                       expn_id: span.expn_id,
+               }, "Zero-width space detected. Consider using \\u{200B}")
+       });
+}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644 (file)
index 0000000..a231171
--- /dev/null
@@ -0,0 +1,86 @@
+use rustc::lint::{Context, Lint, Level};
+use syntax::ast::{DefId, Expr, Name, NodeId, Path};
+use syntax::codemap::{ExpnInfo, Span};
+use syntax::ptr::P;
+use rustc::ast_map::Node::NodeExpr;
+use rustc::middle::ty;
+use std::borrow::{Cow, IntoCow};
+use std::convert::From;
+
+/// returns true if the macro that expanded the crate was outside of
+/// the current crate or was a compiler plugin
+pub fn in_macro(cx: &Context, opt_info: Option<&ExpnInfo>) -> bool {
+    // no ExpnInfo = no macro
+    opt_info.map_or(false, |info| {
+        // no span for the callee = external macro
+        info.callee.span.map_or(true, |span| {
+            // no snippet = external macro or compiler-builtin expansion
+            cx.sess().codemap().span_to_snippet(span).ok().map_or(true, |code| 
+                // macro doesn't start with "macro_rules"
+                // = compiler plugin
+                !code.starts_with("macro_rules")
+            )
+        })
+    })
+}
+
+/// invokes in_macro with the expansion info of the given span
+pub fn in_external_macro(cx: &Context, span: Span) -> bool {
+    cx.sess().codemap().with_expn_info(span.expn_id, 
+            |info| in_macro(cx, info))
+}
+
+/// check if a DefId's path matches the given absolute type path
+/// usage e.g. with
+/// `match_def_path(cx, id, &["core", "option", "Option"])`
+pub fn match_def_path(cx: &Context, def_id: DefId, path: &[&str]) -> bool {
+    cx.tcx.with_path(def_id, |iter| iter.map(|elem| elem.name())
+        .zip(path.iter()).all(|(nm, p)| &nm.as_str() == p))
+}
+
+/// match a Path against a slice of segment string literals, e.g.
+/// `match_path(path, &["std", "rt", "begin_unwind"])`
+pub fn match_path(path: &Path, segments: &[&str]) -> bool {
+    path.segments.iter().rev().zip(segments.iter().rev()).all(
+        |(a,b)| &a.identifier.name.as_str() == b)
+}
+
+/// convert a span to a code snippet if available, otherwise use default, e.g.
+/// `snippet(cx, expr.span, "..")`
+pub fn snippet<'a>(cx: &Context, span: Span, default: &'a str) -> Cow<'a, str> {
+    cx.sess().codemap().span_to_snippet(span).map(From::from).unwrap_or(Cow::Borrowed(default))
+}
+
+/// get a parent expr if any – this is useful to constrain a lint
+pub fn get_parent_expr<'c>(cx: &'c Context, e: &Expr) -> Option<&'c Expr> {
+    let map = &cx.tcx.map;
+    let node_id : NodeId = e.id;
+    let parent_id : NodeId = map.get_parent_node(node_id);
+    if node_id == parent_id { return None; }
+    map.find(parent_id).and_then(|node| 
+        if let NodeExpr(parent) = node { Some(parent) } else { None } )
+}
+
+/// dereference a P<T> and return a ref on the result
+pub fn de_p<T>(p: &P<T>) -> &T { &*p }
+
+#[cfg(not(feature="structured_logging"))]
+pub fn span_lint(cx: &Context, lint: &'static Lint, sp: Span, msg: &str) {
+    cx.span_lint(lint, sp, msg);
+}
+
+#[cfg(feature="structured_logging")]
+pub fn span_lint(cx: &Context, lint: &'static Lint, sp: Span, msg: &str) {
+    // lint.name / lint.desc is can give details of the lint
+    // cx.sess().codemap() has all these nice functions for line/column/snippet details
+    // http://doc.rust-lang.org/syntax/codemap/struct.CodeMap.html#method.span_to_string
+    cx.span_lint(lint, sp, msg);
+}
+
+pub fn span_help_and_lint(cx: &Context, lint: &'static Lint, span: Span, 
+        msg: &str, help: &str) {
+    span_lint(cx, lint, span, msg);
+    if cx.current_level(lint) != Level::Allow {
+        cx.sess().span_help(span, help);
+    }
+}
diff --git a/tests/compile-fail/attrs.rs b/tests/compile-fail/attrs.rs
new file mode 100644 (file)
index 0000000..9b648a5
--- /dev/null
@@ -0,0 +1,33 @@
+#![feature(plugin)]
+#![plugin(clippy)]
+
+#![deny(inline_always)]
+
+#[inline(always)] //~ERROR You have declared #[inline(always)] on test_attr_lint.
+fn test_attr_lint() {
+       assert!(true)
+}
+
+#[inline(always)]
+fn false_positive_expr() {
+       unreachable!()
+}
+
+#[inline(always)]
+fn false_positive_stmt() {
+       unreachable!();
+}
+
+#[inline(always)]
+fn empty_and_false_positive_stmt() {
+       ;
+       unreachable!();
+}
+
+
+fn main() {
+       test_attr_lint();
+       if false { false_positive_expr() }
+       if false { false_positive_stmt() }
+       if false { empty_and_false_positive_stmt() }
+}
index e45b789800e8c042d8d34c4fd3c9a7d72268f9b2..e6b89b98564fe60f99f8cfe3f6d4797a84466390 100644 (file)
@@ -11,6 +11,7 @@ fn main() {
        
        x & 0 == 0; //~ERROR &-masking with zero
        x & 1 == 1; //ok, distinguishes bit 0
+       x & 1 == 0; //ok, compared with zero
        x & 2 == 1; //~ERROR
        x | 0 == 0; //ok, equals x == 0 (maybe warn?)
        x | 1 == 3; //ok, equals x == 2 || x == 3
index a8b0cb32f6f60bdd349bbe672892e1699675dd0b..d7399e6d3aad16ebd8617454edf6e2f7849f31ec 100644 (file)
@@ -13,5 +13,11 @@ fn with_to_string(x : &str) {
        
        x != "foo".to_owned(); //~ERROR this creates an owned instance
        
-       x != String::from_str("foo"); //~ERROR this creates an owned instance
+       #[allow(deprecated)] // for from_str
+       fn old_timey(x : &str) {
+               x != String::from_str("foo"); //~ERROR this creates an owned instance
+       }
+       old_timey(x);
+       
+       x != String::from("foo"); //~ERROR this creates an owned instance
 }
index 3aa86c893c60ce6c7bb146de2aeba2a1a57dd74f..7b7ff13f24badea9479aaa8816270b96c1155713 100644 (file)
@@ -34,4 +34,10 @@ fn main() {
         }
     }
 
+       if x == "hello" {
+               print!("Hello ");
+               if y == "world" {
+                       println!("world!")
+               }
+       }
 }
index 14f2506ec8b4ad203902523d855bf5fd2be03ec9..e64010d334d0fe3d9ddf7410c2edf33725e149bd 100644 (file)
@@ -5,14 +5,14 @@
 
 #[deny(len_without_is_empty)]
 impl One {
-       fn len(self: &Self) -> isize { //~ERROR Item 'One' has a '.len()' method
+       fn len(self: &Self) -> isize { //~ERROR Item 'One' has a '.len(_: &Self)'
                1
        }
 }
 
 #[deny(len_without_is_empty)]
 trait TraitsToo {
-       fn len(self: &Self) -> isize; //~ERROR Trait 'TraitsToo' has a '.len()' method,
+       fn len(self: &Self) -> isize; //~ERROR Trait 'TraitsToo' has a '.len(_:
 }
 
 impl TraitsToo for One {
@@ -21,17 +21,47 @@ fn len(self: &Self) -> isize {
        }
 }
 
-#[allow(dead_code)]
 struct HasIsEmpty;
 
 #[deny(len_without_is_empty)]
-#[allow(dead_code)]
 impl HasIsEmpty {
        fn len(self: &Self) -> isize {
                1
        }
+
+       fn is_empty(self: &Self) -> bool {
+               false
+       }
+}
+
+struct Wither;
+
+#[deny(len_without_is_empty)]
+trait WithIsEmpty {
+       fn len(self: &Self) -> isize;
+       fn is_empty(self: &Self) -> bool;
+}
+
+impl WithIsEmpty for Wither {
+       fn len(self: &Self) -> isize {
+               1
+       }
+
+       fn is_empty(self: &Self) -> bool {
+               false
+       }
+}
+
+struct HasWrongIsEmpty;
+
+#[deny(len_without_is_empty)]
+impl HasWrongIsEmpty {
+       fn len(self: &Self) -> isize { //~ERROR Item 'HasWrongIsEmpty' has a '.len(_: &Self)'
+               1
+       }
        
-       fn is_empty() -> bool {
+       #[allow(dead_code, unused)]
+       fn is_empty(self: &Self, x : u32) -> bool {
                false
        }
 }
@@ -44,13 +74,29 @@ fn main() {
        }
        
        let y = One;
-       // false positives here
-       if y.len()  == 0 { //~ERROR Consider replacing the len comparison
+       if y.len()  == 0 { //no error because One does not have .is_empty()
                println!("This should not happen either!");
        }
        
        let z : &TraitsToo = &y;
-       if z.len() > 0 { //~ERROR Consider replacing the len comparison
+       if z.len() > 0 { //no error, because TraitsToo has no .is_empty() method
                println!("Nor should this!");
        }
+       
+       let hie = HasIsEmpty;
+       if hie.len() == 0 { //~ERROR Consider replacing the len comparison
+               println!("Or this!");
+       }
+       assert!(!hie.is_empty());
+       
+       let wie : &WithIsEmpty = &Wither;
+       if wie.len() == 0 { //~ERROR Consider replacing the len comparison
+               println!("Or this!");
+       }
+       assert!(!wie.is_empty());
+       
+       let hwie = HasWrongIsEmpty;
+       if hwie.len() == 0 { //no error as HasWrongIsEmpty does not have .is_empty()
+               println!("Or this!");
+       }
 }
index b03c6e1140af5edb0e3afe97c6e92134d72a65ad..f1864f9d7f759c086c0dfc3da908e700169a6d78 100644 (file)
@@ -6,7 +6,7 @@
 fn main(){
     let x = Some(1u8);
     match x {  //~ ERROR You seem to be trying to use match
-               //~^ NOTE Try if let Some(y) = x { ... }
+               //~^ HELP Try if let Some(y) = x { ... }
         Some(y) => println!("{:?}", y),
         _ => ()
     }
@@ -17,7 +17,7 @@ fn main(){
     }
     let z = (1u8,1u8);
     match z { //~ ERROR You seem to be trying to use match
-              //~^ NOTE Try if let (2...3, 7...9) = z { ... }
+              //~^ HELP Try if let (2...3, 7...9) = z { ... }
         (2...3, 7...9) => println!("{:?}", z),
         _ => {}
     }
index 65e3762e2c4025577c1a60b4e18aac2e6581ad3a..d7adc067740f4c7abddf6429e164c18df3309d56 100644 (file)
@@ -1,11 +1,18 @@
 #![feature(plugin)]
 #![plugin(clippy)]
 
+//#![plugin(regex_macros)]
+//extern crate regex;
+
 #[deny(mut_mut)]
 fn fun(x : &mut &mut u32) -> bool { //~ERROR
        **x > 0
 }
 
+macro_rules! mut_ptr {
+       ($p:expr) => { &mut $p }
+} 
+
 #[deny(mut_mut)]
 #[allow(unused_mut, unused_variables)]
 fn main() {
@@ -22,4 +29,6 @@ fn main() {
                                                                           //~^^^^ ERROR
                ***y + **x;
        }
+       
+       let mut z = mut_ptr!(&mut 3u32); //~ERROR
 }
diff --git a/tests/compile-fail/needless_return.rs b/tests/compile-fail/needless_return.rs
new file mode 100755 (executable)
index 0000000..34d5712
--- /dev/null
@@ -0,0 +1,49 @@
+#![feature(plugin)]
+#![plugin(clippy)]
+
+#![deny(needless_return)]
+
+fn test_end_of_fn() -> bool {
+    if true {
+        // no error!
+        return true;
+    }
+    return true;           //~ERROR
+}
+
+fn test_no_semicolon() -> bool {
+    return true            //~ERROR
+}
+
+fn test_if_block() -> bool {
+    if true {
+        return true;       //~ERROR
+    } else {
+        return false;      //~ERROR
+    }
+}
+
+fn test_match(x: bool) -> bool {
+    match x {
+        true => {
+            return false;  //~ERROR
+        }
+        false => {
+            return true    //~ERROR
+        }
+    }
+}
+
+fn test_closure() {
+    let _ = || {
+        return true;       //~ERROR
+    };
+}
+
+fn main() {
+    let _ = test_end_of_fn();
+    let _ = test_no_semicolon();
+    let _ = test_if_block();
+    let _ = test_match(true);
+    test_closure();
+}
diff --git a/tests/compile-fail/strings.rs b/tests/compile-fail/strings.rs
new file mode 100644 (file)
index 0000000..2b200f1
--- /dev/null
@@ -0,0 +1,12 @@
+#![feature(plugin)]
+#![plugin(clippy)]
+
+#![deny(string_add_assign)]
+
+fn main() {
+       let x = "".to_owned();
+       
+       for i in (1..3) {
+               x = x + "."; //~ERROR
+       }
+}
diff --git a/tests/compile-fail/unicode.rs b/tests/compile-fail/unicode.rs
new file mode 100644 (file)
index 0000000..0385f45
--- /dev/null
@@ -0,0 +1,25 @@
+#![feature(plugin)]
+#![plugin(clippy)]
+
+#[deny(zero_width_space)]
+fn zero() {
+       print!("Here >​< is a ZWS, and ​another"); 
+                               //~^ ERROR Zero-width space detected. Consider using \u{200B}
+                                                               //~^^ ERROR Zero-width space detected. Consider using \u{200B}
+}
+
+//#[deny(unicode_canon)]
+fn canon() {
+       print!("̀ah?"); //not yet ~ERROR Non-canonical unicode sequence detected. Consider using à
+}
+
+//#[deny(ascii_only)]
+fn uni() {
+       println!("Üben!"); //not yet ~ERROR Unicode literal detected. Consider using \u{FC}
+}
+
+fn main() {
+       zero();
+       uni();
+       canon();
+}
index 04f3fc16b1b69e8ca8a7054852ebd3b46fef420e..4af4ea7673a25eaf2c55d84725ff79a89ef745bb 100644 (file)
@@ -1,11 +1,17 @@
 extern crate compiletest_rs as compiletest;
 
 use std::path::PathBuf;
+use std::env::var;
 
 fn run_mode(mode: &'static str) {
     let mut config = compiletest::default_config();
+    
     let cfg_mode = mode.parse().ok().expect("Invalid mode");
-    config.target_rustcflags = Some("-L target/debug/".to_string());
+    config.target_rustcflags = Some("-L target/debug/".to_owned());
+       if let Ok(name) = var::<&str>("TESTNAME") {
+               let s : String = name.to_owned();
+               config.filter = Some(s)
+       }
 
     config.mode = cfg_mode;
     config.src_base = PathBuf::from(format!("tests/{}", mode));
diff --git a/tests/mut_mut_macro.rs b/tests/mut_mut_macro.rs
new file mode 100644 (file)
index 0000000..32fdea3
--- /dev/null
@@ -0,0 +1,31 @@
+#![feature(plugin)]
+#![plugin(clippy, regex_macros)]
+
+#[macro_use]
+extern crate lazy_static;
+extern crate regex;
+
+use std::collections::HashMap;
+
+#[test]
+#[deny(mut_mut)]
+fn test_regex() {
+       let pattern = regex!(r"^(?P<level>[#]+)\s(?P<title>.+)$");
+       assert!(pattern.is_match("# headline"));
+}
+
+#[test]
+#[deny(mut_mut)]
+#[allow(unused_variables, unused_mut)]
+fn test_lazy_static() {
+       lazy_static! {
+               static ref MUT_MAP : HashMap<usize, &'static str> = {
+                       let mut m = HashMap::new();
+                       let mut zero = &mut &mut "zero";
+                       m.insert(0, "zero");
+                       m
+               };
+               static ref MUT_COUNT : usize = MUT_MAP.len();
+       }
+       assert!(*MUT_COUNT == 1);
+}