Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add used_underscore_binding lint #499

Merged
merged 14 commits into from
Dec 19, 2015
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
[Jump to usage instructions](#usage)

##Lints
There are 83 lints included in this crate:
There are 84 lints included in this crate:

name | default | meaning
---------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -86,6 +86,7 @@ name
[unstable_as_slice](https://github.com/Manishearth/rust-clippy/wiki#unstable_as_slice) | warn | as_slice is not stable and can be replaced by & v[..]see https://github.com/rust-lang/rust/issues/27729
[unused_collect](https://github.com/Manishearth/rust-clippy/wiki#unused_collect) | warn | `collect()`ing an iterator without using the result; this is usually better written as a for loop
[unused_lifetimes](https://github.com/Manishearth/rust-clippy/wiki#unused_lifetimes) | warn | unused lifetimes in function definitions
[used_underscore_binding](https://github.com/Manishearth/rust-clippy/wiki#used_underscore_binding) | warn | using a binding which is prefixed with an underscore
[useless_transmute](https://github.com/Manishearth/rust-clippy/wiki#useless_transmute) | warn | transmutes that have the same to and from types
[while_let_loop](https://github.com/Manishearth/rust-clippy/wiki#while_let_loop) | warn | `loop { if let { ... } else break }` can be written as a `while let` loop
[while_let_on_iterator](https://github.com/Manishearth/rust-clippy/wiki#while_let_on_iterator) | warn | using a while-let loop instead of a for loop on an iterator
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
reg.register_late_lint_pass(box cyclomatic_complexity::CyclomaticComplexity::new(25));
reg.register_late_lint_pass(box escape::EscapePass);
reg.register_early_lint_pass(box misc_early::MiscEarly);
reg.register_late_lint_pass(box misc::UsedUnderscoreBinding);

reg.register_lint_group("clippy_pedantic", vec![
methods::OPTION_UNWRAP_USED,
Expand Down Expand Up @@ -184,6 +185,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
misc::MODULO_ONE,
misc::REDUNDANT_PATTERN,
misc::TOPLEVEL_REF_ARG,
misc::USED_UNDERSCORE_BINDING,
misc_early::UNNEEDED_FIELD_PATTERN,
mut_reference::UNNECESSARY_MUT_PASSED,
mutex_atomic::MUTEX_ATOMIC,
Expand Down
69 changes: 68 additions & 1 deletion src/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use rustc::middle::const_eval::ConstVal::Float;
use rustc::middle::const_eval::eval_const_expr_partial;
use rustc::middle::const_eval::EvalHint::ExprTypeChecked;

use utils::{get_item_name, match_path, snippet, span_lint, walk_ptrs_ty, is_integer_literal};
use utils::{get_item_name, match_path, snippet, get_parent_expr, span_lint, walk_ptrs_ty,
is_integer_literal};
use utils::span_help_and_lint;

/// **What it does:** This lint checks for function arguments and let bindings denoted as `ref`. It is `Warn` by default.
Expand Down Expand Up @@ -316,3 +317,69 @@ impl LateLintPass for PatternPass {
}
}
}


/// **What it does:** This lint checks for the use of bindings with a single leading underscore
///
/// **Why is this bad?** A single leading underscore is usually used to indicate that a binding
/// will not be used. Using such a binding breaks this expectation.
///
/// **Known problems:** None
///
/// **Example**:
/// ```
/// let _x = 0;
/// let y = _x + 1; // Here we are using `_x`, even though it has a leading underscore.
/// // We should rename `_x` to `x`
/// ```
declare_lint!(pub USED_UNDERSCORE_BINDING, Warn,
"using a binding which is prefixed with an underscore");

#[derive(Copy, Clone)]
pub struct UsedUnderscoreBinding;

impl LintPass for UsedUnderscoreBinding {
fn get_lints(&self) -> LintArray {
lint_array!(USED_UNDERSCORE_BINDING)
}
}

impl LateLintPass for UsedUnderscoreBinding {
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
let needs_lint = match expr.node {
ExprPath(_, ref path) => {
let ident = path.segments.last()
.expect("path should always have at least one segment")
.identifier;
ident.name.as_str().chars().next() == Some('_') //starts with '_'
&& ident.name.as_str().chars().skip(1).next() != Some('_') //doesn't start with "__"
&& ident.name != ident.unhygienic_name //not in macro
&& is_used(cx, expr)
},
ExprField(_, spanned) => {
let name = spanned.node.as_str();
name.chars().next() == Some('_')
&& name.chars().skip(1).next() != Some('_')
},
_ => false
};
if needs_lint {
cx.span_lint(USED_UNDERSCORE_BINDING, expr.span,
"used binding which is prefixed with an underscore. A leading underscore \
signals that a binding will not be used.");
}
}
}

fn is_used(cx: &LateContext, expr: &Expr) -> bool {
if let Some(ref parent) = get_parent_expr(cx, expr) {
match parent.node {
ExprAssign(_, ref rhs) => **rhs == *expr,
ExprAssignOp(_, _, ref rhs) => **rhs == *expr,
_ => is_used(cx, &parent)
Copy link
Member

Choose a reason for hiding this comment

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

Oh I like this way of calculating is_used; smart 😄 !

}
}
else {
true
}
}
4 changes: 2 additions & 2 deletions tests/compile-fail/for_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ fn main() {
if false { _index = 0 };
for _v in &vec { _index += 1 }

let mut _index = 0;
{ let mut _x = &mut _index; }
let mut index = 0;
{ let mut _x = &mut index; }
for _v in &vec { _index += 1 }

let mut index = 0;
Expand Down
8 changes: 4 additions & 4 deletions tests/compile-fail/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ fn main() {
let y = NotARange;
y.step_by(0);

let _v1 = vec![1,2,3];
let _v2 = vec![4,5];
let _x = _v1.iter().zip(0.._v1.len()); //~ERROR It is more idiomatic to use _v1.iter().enumerate()
let _y = _v1.iter().zip(0.._v2.len()); // No error
let v1 = vec![1,2,3];
let v2 = vec![4,5];
let _x = v1.iter().zip(0..v1.len()); //~ERROR It is more idiomatic to use v1.iter().enumerate()
let _y = v1.iter().zip(0..v2.len()); // No error
}
81 changes: 81 additions & 0 deletions tests/compile-fail/used_underscore_binding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#![feature(plugin)]
#![plugin(clippy)]
#![deny(clippy)]

/// Test that we lint if we use a binding with a single leading underscore
fn prefix_underscore(_foo: u32) -> u32 {
_foo + 1 //~ ERROR used binding which is prefixed with an underscore
}

/// Test that we lint even if the use is within a macro expansion
fn in_macro(_foo: u32) {
println!("{}", _foo); //~ ERROR used binding which is prefixed with an underscore
Copy link
Member

Choose a reason for hiding this comment

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

\o/

}

// Struct for testing use of fields prefixed with an underscore
struct StructFieldTest {
_underscore_field: u32,
}

/// Test that we lint the use of a struct field which is prefixed with an underscore
fn in_struct_field() {
let mut s = StructFieldTest { _underscore_field: 0 };
s._underscore_field += 1; //~ Error used binding which is prefixed with an underscore
}

/// Test that we do not lint if the underscore is not a prefix
fn non_prefix_underscore(some_foo: u32) -> u32 {
some_foo + 1
}

/// Test that we do not lint if we do not use the binding (simple case)
fn unused_underscore_simple(_foo: u32) -> u32 {
1
}

/// Test that we do not lint if we do not use the binding (complex case). This checks for
/// compatibility with the built-in `unused_variables` lint.
fn unused_underscore_complex(mut _foo: u32) -> u32 {
_foo += 1;
_foo = 2;
1
}

///Test that we do not lint for multiple underscores
fn multiple_underscores(__foo: u32) -> u32 {
__foo + 1
}

// Non-variable bindings with preceding underscore
fn _fn_test() {}
struct _StructTest;
enum _EnumTest {
_FieldA,
_FieldB(_StructTest)
}

/// Test that we do not lint for non-variable bindings
fn non_variables() {
_fn_test();
let _s = _StructTest;
let _e = match _EnumTest::_FieldB(_StructTest) {
_EnumTest::_FieldA => 0,
_EnumTest::_FieldB(_st) => 1,
};
let f = _fn_test;
f();
}

fn main() {
let foo = 0u32;
// tests of unused_underscore lint
let _ = prefix_underscore(foo);
in_macro(foo);
in_struct_field();
// possible false positives
let _ = non_prefix_underscore(foo);
let _ = unused_underscore_simple(foo);
let _ = unused_underscore_complex(foo);
let _ = multiple_underscores(foo);
non_variables();
}