diff --git a/src/misc.rs b/src/misc.rs index 861e4a73dd2..091ea36f2f5 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -6,6 +6,7 @@ use syntax::visit::{FnKind}; use rustc::lint::{Context, LintPass, LintArray, Lint, Level}; use rustc::middle::ty; use syntax::codemap::{Span, Spanned}; +use std::borrow::Cow; use utils::{match_path, snippet, snippet_block, span_lint, span_help_and_lint, walk_ptrs_ty}; @@ -26,39 +27,47 @@ impl LintPass for MiscPass { fn check_expr(&mut self, cx: &Context, expr: &Expr) { if let ExprMatch(ref ex, ref arms, ast::MatchSource::Normal) = expr.node { - 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.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 body_code = snippet_block(cx, arms[0].body.span, ".."); - let suggestion = if let ExprBlock(_) = arms[0].body.node { - body_code.into_owned() - } else { - format!("{{ {} }}", body_code) - }; - span_help_and_lint(cx, SINGLE_MATCH, expr.span, - "you seem to be trying to use match for \ - destructuring a single pattern. Did you mean to \ - use `if let`?", - &*format!("try\nif let {} = {} {}", - snippet(cx, arms[0].pats[0].span, ".."), - snippet(cx, ex.span, ".."), - suggestion) - ); - } - } + // check preconditions: only two arms + if arms.len() == 2 && + // both of the arms have a single pattern and no guard + arms[0].pats.len() == 1 && arms[0].guard.is_none() && + arms[1].pats.len() == 1 && arms[1].guard.is_none() && + // and the second pattern is a `_` wildcard: this is not strictly necessary, + // since the exhaustiveness check will ensure the last one is a catch-all, + // but in some cases, an explicit match is preferred to catch situations + // when an enum is extended, so we don't consider these cases + arms[1].pats[0].node == PatWild(PatWildSingle) && + // finally, we don't want any content in the second arm (unit or empty block) + is_unit_expr(&*arms[1].body) + { + let body_code = snippet_block(cx, arms[0].body.span, ".."); + let body_code = if let ExprBlock(_) = arms[0].body.node { + body_code + } else { + Cow::Owned(format!("{{ {} }}", body_code)) + }; + span_help_and_lint(cx, SINGLE_MATCH, expr.span, + "you seem to be trying to use match for \ + destructuring a single pattern. Did you mean to \ + use `if let`?", + &*format!("try\nif let {} = {} {}", + snippet(cx, arms[0].pats[0].span, ".."), + snippet(cx, ex.span, ".."), + body_code) + ); } } } } +fn is_unit_expr(expr: &Expr) -> bool { + match expr.node { + ExprTup(ref v) if v.is_empty() => true, + ExprBlock(ref b) if b.stmts.is_empty() && b.expr.is_none() => true, + _ => false, + } +} + declare_lint!(pub TOPLEVEL_REF_ARG, Warn, "a function argument is declared `ref` (i.e. `fn foo(ref x: u8)`, but not \ diff --git a/tests/compile-fail/match_if_let.rs b/tests/compile-fail/match_if_let.rs index 01bfe744713..bf2e7e43a52 100755 --- a/tests/compile-fail/match_if_let.rs +++ b/tests/compile-fail/match_if_let.rs @@ -23,4 +23,16 @@ fn main(){ (2...3, 7...9) => println!("{:?}", z), _ => {} } + + // Not linted (pattern guards used) + match x { + Some(y) if y == 0 => println!("{:?}", y), + _ => () + } + + // Not linted (content in the else) + match z { + (2...3, 7...9) => println!("{:?}", z), + _ => println!("nope"), + } }