Add lint for redundant pattern matching in iflet for Result/Option

This commit is contained in:
d-dorazio 2016-10-29 18:56:12 +02:00
parent 502416fa78
commit d213040381
13 changed files with 339 additions and 186 deletions

View File

@ -257,6 +257,7 @@ All notable changes to this project will be documented in this file.
[`for_loop_over_option`]: https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_option
[`for_loop_over_result`]: https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_result
[`identity_op`]: https://github.com/Manishearth/rust-clippy/wiki#identity_op
[`if_let_redundant_pattern_matching`]: https://github.com/Manishearth/rust-clippy/wiki#if_let_redundant_pattern_matching
[`if_let_some_result`]: https://github.com/Manishearth/rust-clippy/wiki#if_let_some_result
[`if_not_else`]: https://github.com/Manishearth/rust-clippy/wiki#if_not_else
[`if_same_then_else`]: https://github.com/Manishearth/rust-clippy/wiki#if_same_then_else

View File

@ -174,10 +174,10 @@ You can check out this great service at [clippy.bashy.io](https://clippy.bashy.i
## Lints
There are 174 lints included in this crate:
There are 175 lints included in this crate:
name | default | triggers on
---------------------------------------------------------------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------
[absurd_extreme_comparisons](https://github.com/Manishearth/rust-clippy/wiki#absurd_extreme_comparisons) | warn | a comparison with a maximum or minimum value that is always true or false
[almost_swapped](https://github.com/Manishearth/rust-clippy/wiki#almost_swapped) | warn | `foo = bar; bar = foo` sequence
[approx_constant](https://github.com/Manishearth/rust-clippy/wiki#approx_constant) | warn | the approximate of a known float constant (in `std::fXX::consts`)
@ -231,6 +231,7 @@ name
[for_loop_over_option](https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_option) | warn | for-looping over an `Option`, which is more clearly expressed as an `if let`
[for_loop_over_result](https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_result) | warn | for-looping over a `Result`, which is more clearly expressed as an `if let`
[identity_op](https://github.com/Manishearth/rust-clippy/wiki#identity_op) | warn | using identity operations, e.g. `x + 0` or `y / 1`
[if_let_redundant_pattern_matching](https://github.com/Manishearth/rust-clippy/wiki#if_let_redundant_pattern_matching) | warn | use the proper utility function avoiding an `if let`
[if_let_some_result](https://github.com/Manishearth/rust-clippy/wiki#if_let_some_result) | warn | usage of `ok()` in `if let Some(pat)` statements is unnecessary, match on `Ok(pat)` instead
[if_not_else](https://github.com/Manishearth/rust-clippy/wiki#if_not_else) | allow | `if` branches that could be swapped so no negation operation is necessary on the condition
[if_same_then_else](https://github.com/Manishearth/rust-clippy/wiki#if_same_then_else) | warn | if with the same *then* and *else* blocks

View File

@ -76,7 +76,7 @@ fn check_lit(cx: &LateContext, lit: &Lit, e: &Expr) {
}
fn check_known_consts(cx: &LateContext, e: &Expr, s: &str, module: &str) {
if let Ok(_) = s.parse::<f64>() {
if s.parse::<f64>().is_ok() {
for &(constant, name, min_digits) in KNOWN_CONSTS {
if is_approx_const(constant, s, min_digits) {
span_lint(cx,

View File

@ -49,7 +49,7 @@ impl LintPass for Arithmetic {
impl LateLintPass for Arithmetic {
fn check_expr(&mut self, cx: &LateContext, expr: &hir::Expr) {
if let Some(_) = self.span {
if self.span.is_some() {
return;
}
match expr.node {

View File

@ -322,7 +322,7 @@ fn check_doc(cx: &EarlyContext, valid_idents: &[String], docs: &[(&str, Span)])
if new_line {
let mut lookup_parser = parser.clone();
if let Some(_) = lookup_parser.find(|&(_, c)| c == ']') {
if lookup_parser.any(|(_, c)| c == ']') {
if let Some((_, ':')) = lookup_parser.next() {
lookup_parser.next_line();
parser = lookup_parser;

View File

@ -0,0 +1,91 @@
use rustc::lint::*;
use rustc::hir::*;
use syntax::codemap::Span;
use utils::{paths, span_lint_and_then, match_path, snippet};
/// **What it does:*** Lint for redundant pattern matching over `Result` or `Option`
///
/// **Why is this bad?** It's more concise and clear to just use the proper utility function
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// if let Ok(_) = Ok::<i32, i32>(42) {}
/// if let Err(_) = Err::<i32, i32>(42) {}
/// if let None = None::<()> {}
/// if let Some(_) = Some(42) {}
/// ```
///
/// The more idiomatic use would be:
///
/// ```rust
/// if Ok::<i32, i32>(42).is_ok() {}
/// if Err::<i32, i32>(42).is_err() {}
/// if None::<()>.is_none() {}
/// if Some(42).is_some() {}
/// ```
///
declare_lint! {
pub IF_LET_REDUNDANT_PATTERN_MATCHING,
Warn,
"use the proper utility function avoiding an `if let`"
}
#[derive(Copy, Clone)]
pub struct Pass;
impl LintPass for Pass {
fn get_lints(&self) -> LintArray {
lint_array!(IF_LET_REDUNDANT_PATTERN_MATCHING)
}
}
impl LateLintPass for Pass {
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
if let ExprMatch(ref op, ref arms, MatchSource::IfLetDesugar{..}) = expr.node {
if arms[0].pats.len() == 1 {
let good_method = match arms[0].pats[0].node {
PatKind::TupleStruct(ref path, ref pats, _) if pats.len() == 1 && pats[0].node == PatKind::Wild => {
if match_path(path, &paths::RESULT_OK) {
"is_ok()"
} else if match_path(path, &paths::RESULT_ERR) {
"is_err()"
} else if match_path(path, &paths::OPTION_SOME) {
"is_some()"
} else {
return
}
}
PatKind::Path(_, ref path) if match_path(path, &paths::OPTION_NONE) => {
"is_none()"
}
_ => return
};
span_lint_and_then(cx,
IF_LET_REDUNDANT_PATTERN_MATCHING,
arms[0].pats[0].span,
&format!("redundant pattern matching, consider using `{}`", good_method),
|db| {
let span = Span {
lo: expr.span.lo,
hi: op.span.hi,
expn_id: expr.span.expn_id,
};
db.span_suggestion(span,
"try this",
format!("if {}.{}", snippet(cx, op.span, "_"), good_method));
});
}
}
}
}

View File

@ -82,6 +82,7 @@ pub mod format;
pub mod formatting;
pub mod functions;
pub mod identity_op;
pub mod if_let_redundant_pattern_matching;
pub mod if_not_else;
pub mod items_after_statements;
pub mod len_zero;
@ -257,6 +258,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
reg.register_late_lint_pass(box eval_order_dependence::EvalOrderDependence);
reg.register_late_lint_pass(box missing_doc::MissingDoc::new());
reg.register_late_lint_pass(box ok_if_let::Pass);
reg.register_late_lint_pass(box if_let_redundant_pattern_matching::Pass);
reg.register_lint_group("clippy_restrictions", vec![
arithmetic::FLOAT_ARITHMETIC,
@ -344,6 +346,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
functions::NOT_UNSAFE_PTR_ARG_DEREF,
functions::TOO_MANY_ARGUMENTS,
identity_op::IDENTITY_OP,
if_let_redundant_pattern_matching::IF_LET_REDUNDANT_PATTERN_MATCHING,
len_zero::LEN_WITHOUT_IS_EMPTY,
len_zero::LEN_ZERO,
let_if_seq::USELESS_LET_IF_SEQ,

View File

@ -532,7 +532,7 @@ impl LateLintPass for Pass {
lint_iter_nth(cx, expr, arglists[0], false);
} else if let Some(arglists) = method_chain_args(expr, &["iter_mut", "nth"]) {
lint_iter_nth(cx, expr, arglists[0], true);
} else if let Some(_) = method_chain_args(expr, &["skip", "next"]) {
} else if method_chain_args(expr, &["skip", "next"]).is_some() {
lint_iter_skip_next(cx, expr);
}
@ -796,7 +796,7 @@ fn lint_cstring_as_ptr(cx: &LateContext, expr: &hir::Expr, new: &hir::Expr, unwr
// Type of MethodArgs is potentially a Vec
fn lint_iter_nth(cx: &LateContext, expr: &hir::Expr, iter_args: &MethodArgs, is_mut: bool){
let mut_str = if is_mut { "_mut" } else {""};
let caller_type = if let Some(_) = derefs_to_slice(cx, &iter_args[0], cx.tcx.expr_ty(&iter_args[0])) {
let caller_type = if derefs_to_slice(cx, &iter_args[0], cx.tcx.expr_ty(&iter_args[0])).is_some() {
"slice"
}
else if match_type(cx, cx.tcx.expr_ty(&iter_args[0]), &paths::VEC) {

View File

@ -85,7 +85,7 @@ fn fetch_const(args: &[P<Expr>], m: MinMax) -> Option<(MinMax, Constant, &Expr)>
return None;
}
if let Some(c) = constant_simple(&args[0]) {
if let None = constant_simple(&args[1]) {
if constant_simple(&args[1]).is_none() {
// otherwise ignore
Some((m, c, &args[1]))
} else {

View File

@ -38,7 +38,7 @@ impl LateLintPass for Pass {
let MatchSource::IfLetDesugar { .. } = *source, //test if it is an If Let
let ExprMethodCall(_, _, ref result_types) = op.node, //check is expr.ok() has type Result<T,E>.ok()
let PatKind::TupleStruct(ref x, ref y, _) = body[0].pats[0].node, //get operation
let Some(_) = method_chain_args(op, &["ok"]) //test to see if using ok() methoduse std::marker::Sized;
method_chain_args(op, &["ok"]).is_some() //test to see if using ok() methoduse std::marker::Sized;
], {
let is_result_type = match_type(cx, cx.tcx.expr_ty(&result_types[0]), &paths::RESULT);

View File

@ -34,6 +34,8 @@ pub const MUTEX: [&'static str; 4] = ["std", "sync", "mutex", "Mutex"];
pub const OPEN_OPTIONS: [&'static str; 3] = ["std", "fs", "OpenOptions"];
pub const OPS_MODULE: [&'static str; 2] = ["core", "ops"];
pub const OPTION: [&'static str; 3] = ["core", "option", "Option"];
pub const OPTION_NONE: [&'static str; 4] = ["core", "option", "Option", "None"];
pub const OPTION_SOME: [&'static str; 4] = ["core", "option", "Option", "Some"];
pub const PTR_NULL: [&'static str; 2] = ["ptr", "null"];
pub const PTR_NULL_MUT: [&'static str; 2] = ["ptr", "null_mut"];
pub const RANGE: [&'static str; 3] = ["core", "ops", "Range"];
@ -59,6 +61,8 @@ pub const REGEX_BYTES_SET_NEW: [&'static str; 5] = ["regex", "re_set", "bytes",
pub const REGEX_NEW: [&'static str; 4] = ["regex", "re_unicode", "Regex", "new"];
pub const REGEX_SET_NEW: [&'static str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
pub const RESULT: [&'static str; 3] = ["core", "result", "Result"];
pub const RESULT_ERR: [&'static str; 4] = ["core", "result", "Result", "Err"];
pub const RESULT_OK: [&'static str; 4] = ["core", "result", "Result", "Ok"];
pub const SERDE_DE_VISITOR: [&'static str; 3] = ["serde", "de", "Visitor"];
pub const STRING: [&'static str; 3] = ["collections", "string", "String"];
pub const TRANSMUTE: [&'static str; 4] = ["core", "intrinsics", "", "transmute"];

View File

@ -0,0 +1,53 @@
#![feature(plugin)]
#![plugin(clippy)]
#![deny(clippy)]
#![deny(if_let_redundant_pattern_matching)]
fn main() {
if let Ok(_) = Ok::<i32, i32>(42) {}
//~^ERROR redundant pattern matching, consider using `is_ok()`
//~| HELP try this
//~| SUGGESTION if Ok::<i32, i32>(42).is_ok() {
if let Err(_) = Err::<i32, i32>(42) {
//~^ERROR redundant pattern matching, consider using `is_err()`
//~| HELP try this
//~| SUGGESTION if Err::<i32, i32>(42).is_err() {
}
if let None = None::<()> {
//~^ERROR redundant pattern matching, consider using `is_none()`
//~| HELP try this
//~| SUGGESTION if None::<()>.is_none() {
}
if let Some(_) = Some(42) {
//~^ERROR redundant pattern matching, consider using `is_some()`
//~| HELP try this
//~| SUGGESTION if Some(42).is_some() {
}
if Ok::<i32, i32>(42).is_ok() {
}
if Err::<i32, i32>(42).is_err() {
}
if None::<i32>.is_none() {
}
if Some(42).is_some() {
}
if let Ok(x) = Ok::<i32,i32>(42) {
println!("{}", x);
}
}

View File

@ -2,7 +2,7 @@
#![plugin(clippy)]
#![deny(clippy)]
#![allow(unused)]
#![allow(unused, if_let_redundant_pattern_matching)]
#![deny(single_match_else)]
use std::borrow::Cow;