extending while_let to warn for more statements

This commit is contained in:
Ravi Shankar
2015-09-27 13:09:42 +05:30
parent b749d832cc
commit 185da55263
4 changed files with 69 additions and 28 deletions

View File

@@ -6,6 +6,7 @@ use rustc::middle::ty;
use rustc::middle::def::DefLocal; use rustc::middle::def::DefLocal;
use consts::{constant_simple, Constant}; use consts::{constant_simple, Constant};
use rustc::front::map::Node::{NodeBlock}; use rustc::front::map::Node::{NodeBlock};
use std::borrow::Cow;
use std::collections::{HashSet,HashMap}; use std::collections::{HashSet,HashMap};
use syntax::ast::Lit_::*; use syntax::ast::Lit_::*;
@@ -159,10 +160,27 @@ impl LateLintPass for LoopsPass {
} }
} }
// check for `loop { if let {} else break }` that could be `while let` // check for `loop { if let {} else break }` that could be `while let`
// (also matches explicit "match" instead of "if let") // (also matches an explicit "match" instead of "if let")
// (even if the "match" or "if let" is used for declaration)
if let ExprLoop(ref block, _) = expr.node { if let ExprLoop(ref block, _) = expr.node {
// extract the first statement (if any) in a block
let inner_stmt = extract_expr_from_first_stmt(block);
// extract a single expression // extract a single expression
if let Some(inner) = extract_single_expr(block) { let inner_expr = extract_first_expr(block);
let extracted = match inner_stmt {
Some(_) => inner_stmt,
None => inner_expr,
};
if let Some(inner) = extracted {
// collect remaining expressions below the match
let other_stuff = block.stmts
.iter()
.skip(1)
.map(|stmt| {
format!("{}", snippet(cx, stmt.span, ".."))
}).collect::<Vec<String>>();
if let ExprMatch(ref matchexpr, ref arms, ref source) = inner.node { if let ExprMatch(ref matchexpr, ref arms, ref source) = inner.node {
// ensure "if let" compatible match structure // ensure "if let" compatible match structure
match *source { match *source {
@@ -174,12 +192,19 @@ impl LateLintPass for LoopsPass {
is_break_expr(&arms[1].body) is_break_expr(&arms[1].body)
{ {
if in_external_macro(cx, expr.span) { return; } if in_external_macro(cx, expr.span) { return; }
let loop_body = match inner_stmt {
// FIXME: should probably be an ellipsis
// tabbing and newline is probably a bad idea, especially for large blocks
Some(_) => Cow::Owned(format!("{{\n {}\n}}", other_stuff.join("\n "))),
None => expr_block(cx, &arms[0].body,
Some(other_stuff.join("\n ")), ".."),
};
span_help_and_lint(cx, WHILE_LET_LOOP, expr.span, span_help_and_lint(cx, WHILE_LET_LOOP, expr.span,
"this loop could be written as a `while let` loop", "this loop could be written as a `while let` loop",
&format!("try\nwhile let {} = {} {}", &format!("try\nwhile let {} = {} {}",
snippet(cx, arms[0].pats[0].span, ".."), snippet(cx, arms[0].pats[0].span, ".."),
snippet(cx, matchexpr.span, ".."), snippet(cx, matchexpr.span, ".."),
expr_block(cx, &arms[0].body, ".."))); loop_body));
}, },
_ => () _ => ()
} }
@@ -276,23 +301,38 @@ fn is_ref_iterable_type(cx: &LateContext, e: &Expr) -> bool {
} }
fn is_iterable_array(ty: ty::Ty) -> bool { fn is_iterable_array(ty: ty::Ty) -> bool {
//IntoIterator is currently only implemented for array sizes <= 32 in rustc // IntoIterator is currently only implemented for array sizes <= 32 in rustc
match ty.sty { match ty.sty {
ty::TyArray(_, 0...32) => true, ty::TyArray(_, 0...32) => true,
_ => false _ => false
} }
} }
/// If block consists of a single expression (with or without semicolon), return it. /// If a block begins with a statement (possibly a `let` binding) and has an expression, return it.
fn extract_single_expr(block: &Block) -> Option<&Expr> { fn extract_expr_from_first_stmt(block: &Block) -> Option<&Expr> {
match (&block.stmts.len(), &block.expr) { match block.expr {
(&1, &None) => match block.stmts[0].node { Some(_) => None,
StmtExpr(ref expr, _) | None => match block.stmts[0].node {
StmtSemi(ref expr, _) => Some(expr), StmtDecl(ref decl, _) => match decl.node {
DeclLocal(ref local) => match local.init {
Some(ref expr) => Some(expr),
None => None,
},
_ => None,
},
_ => None,
},
}
}
/// If a block begins with an expression (with or without semicolon), return it.
fn extract_first_expr(block: &Block) -> Option<&Expr> {
match block.expr {
Some(ref expr) => Some(expr),
None => match block.stmts[0].node {
StmtExpr(ref expr, _) | StmtSemi(ref expr, _) => Some(expr),
_ => None, _ => None,
}, },
(&0, &Some(ref expr)) => Some(expr),
_ => None
} }
} }
@@ -300,7 +340,8 @@ fn extract_single_expr(block: &Block) -> Option<&Expr> {
fn is_break_expr(expr: &Expr) -> bool { fn is_break_expr(expr: &Expr) -> bool {
match expr.node { match expr.node {
ExprBreak(None) => true, ExprBreak(None) => true,
ExprBlock(ref b) => match extract_single_expr(b) { // there won't be a `let <pat> = break` and so we can safely ignore the StmtDecl case
ExprBlock(ref b) => match extract_first_expr(b) {
Some(ref subexpr) => is_break_expr(subexpr), Some(ref subexpr) => is_break_expr(subexpr),
None => false, None => false,
}, },

View File

@@ -43,7 +43,7 @@ impl LateLintPass for MatchPass {
&format!("try\nif let {} = {} {}", &format!("try\nif let {} = {} {}",
snippet(cx, arms[0].pats[0].span, ".."), snippet(cx, arms[0].pats[0].span, ".."),
snippet(cx, ex.span, ".."), snippet(cx, ex.span, ".."),
expr_block(cx, &arms[0].body, ".."))); expr_block(cx, &arms[0].body, None, "..")));
} }
// check preconditions for MATCH_REF_PATS // check preconditions for MATCH_REF_PATS

View File

@@ -130,12 +130,18 @@ pub fn snippet_block<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -
} }
/// Like snippet_block, but add braces if the expr is not an ExprBlock /// Like snippet_block, but add braces if the expr is not an ExprBlock
pub fn expr_block<'a, T: LintContext>(cx: &T, expr: &Expr, default: &'a str) -> Cow<'a, str> { /// Also takes an Option<String> which can be put inside the braces
pub fn expr_block<'a, T: LintContext>(cx: &T, expr: &Expr,
option: Option<String>,
default: &'a str) -> Cow<'a, str> {
let code = snippet_block(cx, expr.span, default); let code = snippet_block(cx, expr.span, default);
let string = option.map_or("".to_owned(), |s| s);
if let ExprBlock(_) = expr.node { if let ExprBlock(_) = expr.node {
code Cow::Owned(format!("{}{}", code, string))
} else { } else if string.is_empty() {
Cow::Owned(format!("{{ {} }}", code)) Cow::Owned(format!("{{ {} }}", code))
} else {
Cow::Owned(format!("{{\n{};\n{}\n}}", code, string))
} }
} }

View File

@@ -4,13 +4,6 @@
#[deny(while_let_loop)] #[deny(while_let_loop)]
fn main() { fn main() {
let y = Some(true); let y = Some(true);
loop { //~ERROR
if let Some(_x) = y {
let _v = 1;
} else {
break;
}
}
loop { //~ERROR loop { //~ERROR
if let Some(_x) = y { if let Some(_x) = y {
let _v = 1; let _v = 1;
@@ -30,12 +23,13 @@ fn main() {
None => break None => break
}; };
} }
loop { // no error, match is not the only statement loop { //~ERROR
match y { let x = match y {
Some(_x) => true, Some(x) => x,
None => break None => break
}; };
let _x = 1; let _x = x;
let _str = "foo";
} }
loop { // no error, else branch does something other than break loop { // no error, else branch does something other than break
match y { match y {