| use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass}; |
| use rustc::hir::def::Def; |
| use rustc::hir::{Expr, Expr_, Stmt, StmtSemi, BlockCheckMode, UnsafeSource, BiAnd, BiOr}; |
| use utils::{in_macro, span_lint, snippet_opt, span_lint_and_sugg}; |
| use std::ops::Deref; |
| |
| /// **What it does:** Checks for statements which have no effect. |
| /// |
| /// **Why is this bad?** Similar to dead code, these statements are actually |
| /// executed. However, as they have no effect, all they do is make the code less |
| /// readable. |
| /// |
| /// **Known problems:** None. |
| /// |
| /// **Example:** |
| /// ```rust |
| /// 0; |
| /// ``` |
| declare_lint! { |
| pub NO_EFFECT, |
| Warn, |
| "statements with no effect" |
| } |
| |
| /// **What it does:** Checks for expression statements that can be reduced to a |
| /// sub-expression. |
| /// |
| /// **Why is this bad?** Expressions by themselves often have no side-effects. |
| /// Having such expressions reduces readability. |
| /// |
| /// **Known problems:** None. |
| /// |
| /// **Example:** |
| /// ```rust |
| /// compute_array()[0]; |
| /// ``` |
| declare_lint! { |
| pub UNNECESSARY_OPERATION, |
| Warn, |
| "outer expressions with no effect" |
| } |
| |
| fn has_no_effect(cx: &LateContext, expr: &Expr) -> bool { |
| if in_macro(expr.span) { |
| return false; |
| } |
| match expr.node { |
| Expr_::ExprLit(..) | |
| Expr_::ExprClosure(.., _) | |
| Expr_::ExprPath(..) => true, |
| Expr_::ExprIndex(ref a, ref b) | |
| Expr_::ExprBinary(_, ref a, ref b) => has_no_effect(cx, a) && has_no_effect(cx, b), |
| Expr_::ExprArray(ref v) | |
| Expr_::ExprTup(ref v) => v.iter().all(|val| has_no_effect(cx, val)), |
| Expr_::ExprRepeat(ref inner, _) | |
| Expr_::ExprCast(ref inner, _) | |
| Expr_::ExprType(ref inner, _) | |
| Expr_::ExprUnary(_, ref inner) | |
| Expr_::ExprField(ref inner, _) | |
| Expr_::ExprTupField(ref inner, _) | |
| Expr_::ExprAddrOf(_, ref inner) | |
| Expr_::ExprBox(ref inner) => has_no_effect(cx, inner), |
| Expr_::ExprStruct(_, ref fields, ref base) => { |
| fields.iter().all(|field| has_no_effect(cx, &field.expr)) && |
| match *base { |
| Some(ref base) => has_no_effect(cx, base), |
| None => true, |
| } |
| }, |
| Expr_::ExprCall(ref callee, ref args) => { |
| if let Expr_::ExprPath(ref qpath) = callee.node { |
| let def = cx.tables.qpath_def(qpath, callee.hir_id); |
| match def { |
| Def::Struct(..) | |
| Def::Variant(..) | |
| Def::StructCtor(..) | |
| Def::VariantCtor(..) => args.iter().all(|arg| has_no_effect(cx, arg)), |
| _ => false, |
| } |
| } else { |
| false |
| } |
| }, |
| Expr_::ExprBlock(ref block) => { |
| block.stmts.is_empty() && |
| if let Some(ref expr) = block.expr { |
| has_no_effect(cx, expr) |
| } else { |
| false |
| } |
| }, |
| _ => false, |
| } |
| } |
| |
| #[derive(Copy, Clone)] |
| pub struct Pass; |
| |
| impl LintPass for Pass { |
| fn get_lints(&self) -> LintArray { |
| lint_array!(NO_EFFECT, UNNECESSARY_OPERATION) |
| } |
| } |
| |
| impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass { |
| fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt) { |
| if let StmtSemi(ref expr, _) = stmt.node { |
| if has_no_effect(cx, expr) { |
| span_lint(cx, NO_EFFECT, stmt.span, "statement with no effect"); |
| } else if let Some(reduced) = reduce_expression(cx, expr) { |
| let mut snippet = String::new(); |
| for e in reduced { |
| if in_macro(e.span) { |
| return; |
| } |
| if let Some(snip) = snippet_opt(cx, e.span) { |
| snippet.push_str(&snip); |
| snippet.push(';'); |
| } else { |
| return; |
| } |
| } |
| span_lint_and_sugg( |
| cx, |
| UNNECESSARY_OPERATION, |
| stmt.span, |
| "statement can be reduced", |
| "replace it with", |
| snippet, |
| ); |
| } |
| } |
| } |
| } |
| |
| |
| fn reduce_expression<'a>(cx: &LateContext, expr: &'a Expr) -> Option<Vec<&'a Expr>> { |
| if in_macro(expr.span) { |
| return None; |
| } |
| match expr.node { |
| Expr_::ExprIndex(ref a, ref b) => Some(vec![&**a, &**b]), |
| Expr_::ExprBinary(ref binop, ref a, ref b) if binop.node != BiAnd && binop.node != BiOr => { |
| Some(vec![&**a, &**b]) |
| }, |
| Expr_::ExprArray(ref v) | |
| Expr_::ExprTup(ref v) => Some(v.iter().collect()), |
| Expr_::ExprRepeat(ref inner, _) | |
| Expr_::ExprCast(ref inner, _) | |
| Expr_::ExprType(ref inner, _) | |
| Expr_::ExprUnary(_, ref inner) | |
| Expr_::ExprField(ref inner, _) | |
| Expr_::ExprTupField(ref inner, _) | |
| Expr_::ExprAddrOf(_, ref inner) | |
| Expr_::ExprBox(ref inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])), |
| Expr_::ExprStruct(_, ref fields, ref base) => { |
| Some( |
| fields |
| .iter() |
| .map(|f| &f.expr) |
| .chain(base) |
| .map(Deref::deref) |
| .collect(), |
| ) |
| }, |
| Expr_::ExprCall(ref callee, ref args) => { |
| if let Expr_::ExprPath(ref qpath) = callee.node { |
| let def = cx.tables.qpath_def(qpath, callee.hir_id); |
| match def { |
| Def::Struct(..) | |
| Def::Variant(..) | |
| Def::StructCtor(..) | |
| Def::VariantCtor(..) => Some(args.iter().collect()), |
| _ => None, |
| } |
| } else { |
| None |
| } |
| }, |
| Expr_::ExprBlock(ref block) => { |
| if block.stmts.is_empty() { |
| block.expr.as_ref().and_then(|e| { |
| match block.rules { |
| BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None, |
| BlockCheckMode::DefaultBlock => Some(vec![&**e]), |
| // in case of compiler-inserted signaling blocks |
| _ => reduce_expression(cx, e), |
| } |
| }) |
| } else { |
| None |
| } |
| }, |
| _ => None, |
| } |
| } |