Merge pull request #647 from mcarton/copies2

[WIP] Lint equal match arms
This commit is contained in:
llogiq 2016-02-12 19:49:16 +01:00
commit 13a0d5cd9f
16 changed files with 907 additions and 357 deletions

View File

@ -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 119 lints included in this crate:
There are 120 lints included in this crate:
name | default | meaning
---------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -65,6 +65,7 @@ name
[match_bool](https://github.com/Manishearth/rust-clippy/wiki#match_bool) | warn | a match on boolean expression; recommends `if..else` block instead
[match_overlapping_arm](https://github.com/Manishearth/rust-clippy/wiki#match_overlapping_arm) | warn | a match has overlapping arms
[match_ref_pats](https://github.com/Manishearth/rust-clippy/wiki#match_ref_pats) | warn | a match or `if let` has all arms prefixed with `&`; the match expression can be dereferenced instead
[match_same_arms](https://github.com/Manishearth/rust-clippy/wiki#match_same_arms) | warn | `match` with identical arm bodies
[min_max](https://github.com/Manishearth/rust-clippy/wiki#min_max) | warn | `min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant
[modulo_one](https://github.com/Manishearth/rust-clippy/wiki#modulo_one) | warn | taking a number modulo 1, which always returns 0
[mut_mut](https://github.com/Manishearth/rust-clippy/wiki#mut_mut) | allow | usage of double-mut refs, e.g. `&mut &mut ...` (either copy'n'paste error, or shows a fundamental misunderstanding of references)

View File

@ -2,21 +2,18 @@
use rustc::lint::LateContext;
use rustc::middle::const_eval::lookup_const_by_id;
use rustc::middle::def::PathResolution;
use rustc::middle::def::Def;
use rustc::middle::def::{Def, PathResolution};
use rustc_front::hir::*;
use syntax::ptr::P;
use std::cmp::PartialOrd;
use std::cmp::Ordering::{self, Greater, Less, Equal};
use std::rc::Rc;
use std::cmp::PartialOrd;
use std::hash::{Hash, Hasher};
use std::mem;
use std::ops::Deref;
use std::rc::Rc;
use syntax::ast::{FloatTy, LitIntType, LitKind, StrStyle, UintTy};
use syntax::ptr::P;
use syntax::ast::LitKind;
use syntax::ast::LitIntType;
use syntax::ast::{UintTy, FloatTy, StrStyle};
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone)]
pub enum FloatWidth {
Fw32,
Fw64,
@ -32,14 +29,14 @@ impl From<FloatTy> for FloatWidth {
}
}
#[derive(Copy, Eq, Debug, Clone, PartialEq)]
#[derive(Copy, Eq, Debug, Clone, PartialEq, Hash)]
pub enum Sign {
Plus,
Minus,
}
/// a Lit_-like enum to fold constant `Expr`s into
#[derive(Eq, Debug, Clone)]
#[derive(Debug, Clone)]
pub enum Constant {
/// a String "abc"
Str(String, StrStyle),
@ -101,18 +98,12 @@ impl PartialEq for Constant {
(&Constant::Int(lv, _, lneg), &Constant::Int(rv, _, rneg)) => {
lv == rv && lneg == rneg
}
(&Constant::Float(ref ls, lw), &Constant::Float(ref rs, rw)) => {
use self::FloatWidth::*;
if match (lw, rw) {
(FwAny, _) | (_, FwAny) | (Fw32, Fw32) | (Fw64, Fw64) => true,
(&Constant::Float(ref ls, _), &Constant::Float(ref rs, _)) => {
// we want `Fw32 == FwAny` and `FwAny == Fw64`, by transitivity we must have
// `Fw32 == Fw64` so dont compare them
match (ls.parse::<f64>(), rs.parse::<f64>()) {
(Ok(l), Ok(r)) => l.eq(&r),
_ => false,
} {
match (ls.parse::<f64>(), rs.parse::<f64>()) {
(Ok(l), Ok(r)) => l.eq(&r),
_ => false,
}
} else {
false
}
}
(&Constant::Bool(l), &Constant::Bool(r)) => l == r,
@ -124,6 +115,46 @@ impl PartialEq for Constant {
}
}
impl Hash for Constant {
fn hash<H>(&self, state: &mut H) where H: Hasher {
match *self {
Constant::Str(ref s, ref k) => {
s.hash(state);
k.hash(state);
}
Constant::Binary(ref b) => {
b.hash(state);
}
Constant::Byte(u) => {
u.hash(state);
}
Constant::Char(c) => {
c.hash(state);
}
Constant::Int(u, _, t) => {
u.hash(state);
t.hash(state);
}
Constant::Float(ref f, _) => {
// dont use the width here because of PartialEq implementation
if let Ok(f) = f.parse::<f64>() {
unsafe { mem::transmute::<f64, u64>(f) }.hash(state);
}
}
Constant::Bool(b) => {
b.hash(state);
}
Constant::Vec(ref v) | Constant::Tuple(ref v)=> {
v.hash(state);
}
Constant::Repeat(ref c, l) => {
c.hash(state);
l.hash(state);
}
}
}
}
impl PartialOrd for Constant {
fn partial_cmp(&self, other: &Constant) -> Option<Ordering> {
match (self, other) {
@ -141,18 +172,10 @@ impl PartialOrd for Constant {
(&Constant::Int(ref lv, _, Sign::Minus), &Constant::Int(ref rv, _, Sign::Minus)) => Some(rv.cmp(lv)),
(&Constant::Int(_, _, Sign::Minus), &Constant::Int(_, _, Sign::Plus)) => Some(Less),
(&Constant::Int(_, _, Sign::Plus), &Constant::Int(_, _, Sign::Minus)) => Some(Greater),
(&Constant::Float(ref ls, lw), &Constant::Float(ref rs, rw)) => {
use self::FloatWidth::*;
if match (lw, rw) {
(FwAny, _) | (_, FwAny) | (Fw32, Fw32) | (Fw64, Fw64) => true,
_ => false,
} {
match (ls.parse::<f64>(), rs.parse::<f64>()) {
(Ok(ref l), Ok(ref r)) => l.partial_cmp(r),
_ => None,
}
} else {
None
(&Constant::Float(ref ls, _), &Constant::Float(ref rs, _)) => {
match (ls.parse::<f64>(), rs.parse::<f64>()) {
(Ok(ref l), Ok(ref r)) => l.partial_cmp(r),
_ => None,
}
}
(&Constant::Bool(ref l), &Constant::Bool(ref r)) => Some(l.cmp(r)),
@ -187,8 +210,7 @@ fn constant_not(o: Constant) -> Option<Constant> {
use self::Constant::*;
match o {
Bool(b) => Some(Bool(!b)),
Int(::std::u64::MAX, LitIntType::Signed(_), Sign::Plus) => None,
Int(value, LitIntType::Signed(ity), Sign::Plus) => Some(Int(value + 1, LitIntType::Signed(ity), Sign::Minus)),
Int(value, LitIntType::Signed(ity), Sign::Plus) if value != ::std::u64::MAX => Some(Int(value + 1, LitIntType::Signed(ity), Sign::Minus)),
Int(0, LitIntType::Signed(ity), Sign::Minus) => Some(Int(1, LitIntType::Signed(ity), Sign::Minus)),
Int(value, LitIntType::Signed(ity), Sign::Minus) => Some(Int(value - 1, LitIntType::Signed(ity), Sign::Plus)),
Int(value, LitIntType::Unsigned(ity), Sign::Plus) => {

View File

@ -1,6 +1,12 @@
use rustc::lint::*;
use rustc::middle::ty;
use rustc_front::hir::*;
use utils::{get_parent_expr, in_macro, is_block_equal, is_exp_equal, span_lint, span_note_and_lint};
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use syntax::parse::token::InternedString;
use syntax::util::small_vector::SmallVector;
use utils::{SpanlessEq, SpanlessHash};
use utils::{get_parent_expr, in_macro, span_note_and_lint};
/// **What it does:** This lint checks for consecutive `ifs` with the same condition. This lint is
/// `Warn` by default.
@ -30,6 +36,25 @@ declare_lint! {
"if with the same *then* and *else* blocks"
}
/// **What it does:** This lint checks for `match` with identical arm bodies.
///
/// **Why is this bad?** This is probably a copy & paste error.
///
/// **Known problems:** Hopefully none.
///
/// **Example:**
/// ```rust,ignore
/// match foo {
/// Bar => bar(),
/// Quz => quz(),
/// Baz => bar(), // <= oups
/// ```
declare_lint! {
pub MATCH_SAME_ARMS,
Warn,
"`match` with identical arm bodies"
}
#[derive(Copy, Clone, Debug)]
pub struct CopyAndPaste;
@ -37,7 +62,8 @@ impl LintPass for CopyAndPaste {
fn get_lints(&self) -> LintArray {
lint_array![
IFS_SAME_COND,
IF_SAME_THEN_ELSE
IF_SAME_THEN_ELSE,
MATCH_SAME_ARMS
]
}
}
@ -45,50 +71,86 @@ impl LintPass for CopyAndPaste {
impl LateLintPass for CopyAndPaste {
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
if !in_macro(cx, expr.span) {
lint_same_then_else(cx, expr);
lint_same_cond(cx, expr);
// skip ifs directly in else, it will be checked in the parent if
if let Some(&Expr{node: ExprIf(_, _, Some(ref else_expr)), ..}) = get_parent_expr(cx, expr) {
if else_expr.id == expr.id {
return;
}
}
let (conds, blocks) = if_sequence(expr);
lint_same_then_else(cx, blocks.as_slice());
lint_same_cond(cx, conds.as_slice());
lint_match_arms(cx, expr);
}
}
}
/// Implementation of `IF_SAME_THEN_ELSE`.
fn lint_same_then_else(cx: &LateContext, expr: &Expr) {
if let ExprIf(_, ref then_block, Some(ref else_expr)) = expr.node {
if let ExprBlock(ref else_block) = else_expr.node {
if is_block_equal(cx, &then_block, &else_block, false) {
span_lint(cx, IF_SAME_THEN_ELSE, expr.span, "this if has the same then and else blocks");
}
}
fn lint_same_then_else(cx: &LateContext, blocks: &[&Block]) {
let hash : &Fn(&&Block) -> u64 = &|block| -> u64 {
let mut h = SpanlessHash::new(cx);
h.hash_block(block);
h.finish()
};
let eq : &Fn(&&Block, &&Block) -> bool = &|&lhs, &rhs| -> bool {
SpanlessEq::new(cx).eq_block(lhs, rhs)
};
if let Some((i, j)) = search_same(blocks, hash, eq) {
span_note_and_lint(cx, IF_SAME_THEN_ELSE, j.span, "this `if` has identical blocks", i.span, "same as this");
}
}
/// Implementation of `IFS_SAME_COND`.
fn lint_same_cond(cx: &LateContext, expr: &Expr) {
// skip ifs directly in else, it will be checked in the parent if
if let Some(&Expr{node: ExprIf(_, _, Some(ref else_expr)), ..}) = get_parent_expr(cx, expr) {
if else_expr.id == expr.id {
return;
}
fn lint_same_cond(cx: &LateContext, conds: &[&Expr]) {
let hash : &Fn(&&Expr) -> u64 = &|expr| -> u64 {
let mut h = SpanlessHash::new(cx);
h.hash_expr(expr);
h.finish()
};
let eq : &Fn(&&Expr, &&Expr) -> bool = &|&lhs, &rhs| -> bool {
SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs)
};
if let Some((i, j)) = search_same(conds, hash, eq) {
span_note_and_lint(cx, IFS_SAME_COND, j.span, "this `if` has the same condition as a previous if", i.span, "same as this");
}
}
let conds = condition_sequence(expr);
/// Implementation if `MATCH_SAME_ARMS`.
fn lint_match_arms(cx: &LateContext, expr: &Expr) {
let hash = |arm: &Arm| -> u64 {
let mut h = SpanlessHash::new(cx);
h.hash_expr(&arm.body);
h.finish()
};
for (n, i) in conds.iter().enumerate() {
for j in conds.iter().skip(n+1) {
if is_exp_equal(cx, i, j, true) {
span_note_and_lint(cx, IFS_SAME_COND, j.span, "this if has the same condition as a previous if", i.span, "same as this");
}
let eq = |lhs: &Arm, rhs: &Arm| -> bool {
SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) &&
// all patterns should have the same bindings
bindings(cx, &lhs.pats[0]) == bindings(cx, &rhs.pats[0])
};
if let ExprMatch(_, ref arms, MatchSource::Normal) = expr.node {
if let Some((i, j)) = search_same(&**arms, hash, eq) {
span_note_and_lint(cx, MATCH_SAME_ARMS, j.body.span, "this `match` has identical arm bodies", i.body.span, "same as this");
}
}
}
/// Return the list of condition expressions in a sequence of `if/else`.
/// Eg. would return `[a, b]` for the expression `if a {..} else if b {..}`.
fn condition_sequence(mut expr: &Expr) -> Vec<&Expr> {
let mut result = vec![];
/// Return the list of condition expressions and the list of blocks in a sequence of `if/else`.
/// Eg. would return `([a, b], [c, d, e])` for the expression
/// `if a { c } else if b { d } else { e }`.
fn if_sequence(mut expr: &Expr) -> (SmallVector<&Expr>, SmallVector<&Block>) {
let mut conds = SmallVector::zero();
let mut blocks = SmallVector::zero();
while let ExprIf(ref cond, _, ref else_expr) = expr.node {
result.push(&**cond);
while let ExprIf(ref cond, ref then_block, ref else_expr) = expr.node {
conds.push(&**cond);
blocks.push(&**then_block);
if let Some(ref else_expr) = *else_expr {
expr = else_expr;
@ -98,5 +160,96 @@ fn condition_sequence(mut expr: &Expr) -> Vec<&Expr> {
}
}
// final `else {..}`
if !blocks.is_empty() {
if let ExprBlock(ref block) = expr.node {
blocks.push(&**block);
}
}
(conds, blocks)
}
/// Return the list of bindings in a pattern.
fn bindings<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat) -> HashMap<InternedString, ty::Ty<'tcx>> {
fn bindings_impl<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat, map: &mut HashMap<InternedString, ty::Ty<'tcx>>) {
match pat.node {
PatBox(ref pat) | PatRegion(ref pat, _) => bindings_impl(cx, pat, map),
PatEnum(_, Some(ref pats)) => {
for pat in pats {
bindings_impl(cx, pat, map);
}
}
PatIdent(_, ref ident, ref as_pat) => {
if let Entry::Vacant(v) = map.entry(ident.node.name.as_str()) {
v.insert(cx.tcx.pat_ty(pat));
}
if let Some(ref as_pat) = *as_pat {
bindings_impl(cx, as_pat, map);
}
},
PatStruct(_, ref fields, _) => {
for pat in fields {
bindings_impl(cx, &pat.node.pat, map);
}
}
PatTup(ref fields) => {
for pat in fields {
bindings_impl(cx, pat, map);
}
}
PatVec(ref lhs, ref mid, ref rhs) => {
for pat in lhs {
bindings_impl(cx, pat, map);
}
if let Some(ref mid) = *mid {
bindings_impl(cx, mid, map);
}
for pat in rhs {
bindings_impl(cx, pat, map);
}
}
PatEnum(..) | PatLit(..) | PatQPath(..) | PatRange(..) | PatWild => (),
}
}
let mut result = HashMap::new();
bindings_impl(cx, pat, &mut result);
result
}
fn search_same<T, Hash, Eq>(exprs: &[T],
hash: Hash,
eq: Eq) -> Option<(&T, &T)>
where Hash: Fn(&T) -> u64,
Eq: Fn(&T, &T) -> bool {
// common cases
if exprs.len() < 2 {
return None;
}
else if exprs.len() == 2 {
return if eq(&exprs[0], &exprs[1]) {
Some((&exprs[0], &exprs[1]))
}
else {
None
}
}
let mut map : HashMap<_, Vec<&_>> = HashMap::with_capacity(exprs.len());
for expr in exprs {
match map.entry(hash(expr)) {
Entry::Occupied(o) => {
for o in o.get() {
if eq(&o, expr) {
return Some((&o, expr))
}
}
}
Entry::Vacant(v) => { v.insert(vec![expr]); }
}
}
None
}

View File

@ -1,8 +1,9 @@
use rustc::lint::*;
use rustc_front::hir::*;
use syntax::codemap::Span;
use utils::{get_item_name, is_exp_equal, match_type, snippet, span_lint_and_then, walk_ptrs_ty};
use utils::SpanlessEq;
use utils::{BTREEMAP_PATH, HASHMAP_PATH};
use utils::{get_item_name, match_type, snippet, span_lint_and_then, walk_ptrs_ty};
/// **What it does:** This lint checks for uses of `contains_key` + `insert` on `HashMap` or
/// `BTreeMap`.
@ -89,7 +90,7 @@ fn check_for_insert(cx: &LateContext, span: Span, map: &Expr, key: &Expr, expr:
params.len() == 3,
name.node.as_str() == "insert",
get_item_name(cx, map) == get_item_name(cx, &*params[0]),
is_exp_equal(cx, key, &params[1], false)
SpanlessEq::new(cx).eq_expr(key, &params[1])
], {
let help = if sole_expr {
format!("{}.entry({}).or_insert({})",

View File

@ -2,7 +2,7 @@ use rustc::lint::*;
use rustc_front::hir::*;
use rustc_front::util as ast_util;
use utils::{is_exp_equal, span_lint};
use utils::{SpanlessEq, span_lint};
/// **What it does:** This lint checks for equal operands to comparison, logical and bitwise,
/// difference and division binary operators (`==`, `>`, etc., `&&`, `||`, `&`, `|`, `^`, `-` and
@ -31,7 +31,7 @@ impl LintPass for EqOp {
impl LateLintPass for EqOp {
fn check_expr(&mut self, cx: &LateContext, e: &Expr) {
if let ExprBinary(ref op, ref left, ref right) = e.node {
if is_valid_operator(op) && is_exp_equal(cx, left, right, true) {
if is_valid_operator(op) && SpanlessEq::new(cx).ignore_fn().eq_expr(left, right) {
span_lint(cx,
EQ_OP,
e.span,

View File

@ -140,9 +140,7 @@ fn check_cmp(cx: &LateContext, span: Span, left: &Expr, right: &Expr, op: &str)
}
}
match (&left.node, &right.node) {
(&ExprLit(ref lit), &ExprMethodCall(ref method, _, ref args)) => {
check_len_zero(cx, span, &method.node, args, lit, op)
}
(&ExprLit(ref lit), &ExprMethodCall(ref method, _, ref args)) |
(&ExprMethodCall(ref method, _, ref args), &ExprLit(ref lit)) => {
check_len_zero(cx, span, &method.node, args, lit, op)
}

View File

@ -196,6 +196,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
collapsible_if::COLLAPSIBLE_IF,
copies::IF_SAME_THEN_ELSE,
copies::IFS_SAME_COND,
copies::MATCH_SAME_ARMS,
cyclomatic_complexity::CYCLOMATIC_COMPLEXITY,
derive::DERIVE_HASH_NOT_EQ,
derive::EXPL_IMPL_CLONE_ON_COPY,

View File

@ -865,12 +865,11 @@ enum SelfKind {
impl SelfKind {
fn matches(&self, slf: &ExplicitSelf_, allow_value_for_ref: bool) -> bool {
match (self, slf) {
(&SelfKind::Value, &SelfValue(_)) => true,
(&SelfKind::Ref, &SelfRegion(_, Mutability::MutImmutable, _)) => true,
(&SelfKind::RefMut, &SelfRegion(_, Mutability::MutMutable, _)) => true,
(&SelfKind::Ref, &SelfValue(_)) => allow_value_for_ref,
(&SelfKind::RefMut, &SelfValue(_)) => allow_value_for_ref,
(&SelfKind::No, &SelfStatic) => true,
(&SelfKind::Value, &SelfValue(_)) |
(&SelfKind::Ref, &SelfRegion(_, Mutability::MutImmutable, _)) |
(&SelfKind::RefMut, &SelfRegion(_, Mutability::MutMutable, _)) |
(&SelfKind::No, &SelfStatic) => true,
(&SelfKind::Ref, &SelfValue(_)) | (&SelfKind::RefMut, &SelfValue(_)) => allow_value_for_ref,
(_, &SelfExplicit(ref ty, _)) => self.matches_explicit_type(ty, allow_value_for_ref),
_ => false,
}
@ -878,11 +877,11 @@ impl SelfKind {
fn matches_explicit_type(&self, ty: &Ty, allow_value_for_ref: bool) -> bool {
match (self, &ty.node) {
(&SelfKind::Value, &TyPath(..)) => true,
(&SelfKind::Ref, &TyRptr(_, MutTy { mutbl: Mutability::MutImmutable, .. })) => true,
(&SelfKind::RefMut, &TyRptr(_, MutTy { mutbl: Mutability::MutMutable, .. })) => true,
(&SelfKind::Ref, &TyPath(..)) => allow_value_for_ref,
(&SelfKind::RefMut, &TyPath(..)) => allow_value_for_ref,
(&SelfKind::Value, &TyPath(..)) |
(&SelfKind::Ref, &TyRptr(_, MutTy { mutbl: Mutability::MutImmutable, .. })) |
(&SelfKind::RefMut, &TyRptr(_, MutTy { mutbl: Mutability::MutMutable, .. })) => true,
(&SelfKind::Ref, &TyPath(..)) |
(&SelfKind::RefMut, &TyPath(..)) => allow_value_for_ref,
_ => false,
}
}

View File

@ -421,8 +421,7 @@ impl LateLintPass for UsedUnderscoreBinding {
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,
ExprAssign(_, ref rhs) | ExprAssignOp(_, _, ref rhs) => **rhs == *expr,
_ => is_used(cx, &parent),
}
} else {

View File

@ -7,7 +7,8 @@ use rustc::lint::*;
use rustc_front::hir::*;
use syntax::codemap::Spanned;
use utils::{is_exp_equal, match_type, span_lint, walk_ptrs_ty, get_parent_expr};
use utils::{match_type, span_lint, walk_ptrs_ty, get_parent_expr};
use utils::SpanlessEq;
use utils::STRING_PATH;
/// **What it does:** This lint matches code of the form `x = x + y` (without `let`!).
@ -84,7 +85,7 @@ impl LateLintPass for StringAdd {
if let Some(ref p) = parent {
if let ExprAssign(ref target, _) = p.node {
// avoid duplicate matches
if is_exp_equal(cx, target, left, false) {
if SpanlessEq::new(cx).eq_expr(target, left) {
return;
}
}
@ -113,7 +114,7 @@ fn is_string(cx: &LateContext, e: &Expr) -> bool {
fn is_add(cx: &LateContext, src: &Expr, target: &Expr) -> bool {
match src.node {
ExprBinary(Spanned{ node: BiAdd, .. }, ref left, _) => is_exp_equal(cx, target, left, false),
ExprBinary(Spanned{ node: BiAdd, .. }, ref left, _) => SpanlessEq::new(cx).eq_expr(target, left),
ExprBlock(ref block) => {
block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| is_add(cx, expr, target))
}

539
src/utils/hir.rs Normal file
View File

@ -0,0 +1,539 @@
use consts::constant;
use rustc::lint::*;
use rustc_front::hir::*;
use std::hash::{Hash, Hasher, SipHasher};
use syntax::ast::Name;
use syntax::ptr::P;
/// Type used to check whether two ast are the same. This is different from the operator
/// `==` on ast types as this operator would compare true equality with ID and span.
///
/// Note that some expressions kinds are not considered but could be added.
pub struct SpanlessEq<'a, 'tcx: 'a> {
/// Context used to evaluate constant expressions.
cx: &'a LateContext<'a, 'tcx>,
/// If is true, never consider as equal expressions containing fonction calls.
ignore_fn: bool,
}
impl<'a, 'tcx: 'a> SpanlessEq<'a, 'tcx> {
pub fn new(cx: &'a LateContext<'a, 'tcx>) -> Self {
SpanlessEq { cx: cx, ignore_fn: false }
}
pub fn ignore_fn(self) -> Self {
SpanlessEq { cx: self.cx, ignore_fn: true }
}
/// Check whether two statements are the same.
pub fn eq_stmt(&self, left: &Stmt, right: &Stmt) -> bool {
match (&left.node, &right.node) {
(&StmtDecl(ref l, _), &StmtDecl(ref r, _)) => {
if let (&DeclLocal(ref l), &DeclLocal(ref r)) = (&l.node, &r.node) {
// TODO: tys
l.ty.is_none() && r.ty.is_none() &&
both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
}
else {
false
}
}
(&StmtExpr(ref l, _), &StmtExpr(ref r, _)) |
(&StmtSemi(ref l, _), &StmtSemi(ref r, _)) => self.eq_expr(l, r),
_ => false,
}
}
/// Check whether two blocks are the same.
pub fn eq_block(&self, left: &Block, right: &Block) -> bool {
over(&left.stmts, &right.stmts, |l, r| self.eq_stmt(l, r)) &&
both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
}
// ok, its a big function, but mostly one big match with simples cases
#[allow(cyclomatic_complexity)]
pub fn eq_expr(&self, left: &Expr, right: &Expr) -> bool {
if let (Some(l), Some(r)) = (constant(self.cx, left), constant(self.cx, right)) {
if l == r {
return true;
}
}
match (&left.node, &right.node) {
(&ExprAddrOf(lmut, ref le), &ExprAddrOf(rmut, ref re)) => {
lmut == rmut && self.eq_expr(le, re)
}
(&ExprAgain(li), &ExprAgain(ri)) => {
both(&li, &ri, |l, r| l.node.name.as_str() == r.node.name.as_str())
}
(&ExprAssign(ref ll, ref lr), &ExprAssign(ref rl, ref rr)) => {
self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
}
(&ExprAssignOp(ref lo, ref ll, ref lr), &ExprAssignOp(ref ro, ref rl, ref rr)) => {
lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
}
(&ExprBlock(ref l), &ExprBlock(ref r)) => {
self.eq_block(l, r)
}
(&ExprBinary(lop, ref ll, ref lr), &ExprBinary(rop, ref rl, ref rr)) => {
lop.node == rop.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
}
(&ExprBreak(li), &ExprBreak(ri)) => {
both(&li, &ri, |l, r| l.node.name.as_str() == r.node.name.as_str())
}
(&ExprBox(ref l), &ExprBox(ref r)) => {
self.eq_expr(l, r)
}
(&ExprCall(ref lfun, ref largs), &ExprCall(ref rfun, ref rargs)) => {
!self.ignore_fn &&
self.eq_expr(lfun, rfun) &&
self.eq_exprs(largs, rargs)
}
(&ExprCast(ref lx, ref lt), &ExprCast(ref rx, ref rt)) => {
self.eq_expr(lx, rx) && self.eq_ty(lt, rt)
}
(&ExprField(ref lfexp, ref lfident), &ExprField(ref rfexp, ref rfident)) => {
lfident.node == rfident.node && self.eq_expr(lfexp, rfexp)
}
(&ExprIndex(ref la, ref li), &ExprIndex(ref ra, ref ri)) => {
self.eq_expr(la, ra) && self.eq_expr(li, ri)
}
(&ExprIf(ref lc, ref lt, ref le), &ExprIf(ref rc, ref rt, ref re)) => {
self.eq_expr(lc, rc) &&
self.eq_block(lt, rt) &&
both(le, re, |l, r| self.eq_expr(l, r))
}
(&ExprLit(ref l), &ExprLit(ref r)) => l.node == r.node,
(&ExprLoop(ref lb, ref ll), &ExprLoop(ref rb, ref rl)) => {
self.eq_block(lb, rb) &&
both(ll, rl, |l, r| l.name.as_str() == r.name.as_str())
}
(&ExprMatch(ref le, ref la, ref ls), &ExprMatch(ref re, ref ra, ref rs)) => {
ls == rs &&
self.eq_expr(le, re) &&
over(la, ra, |l, r| {
self.eq_expr(&l.body, &r.body) &&
both(&l.guard, &r.guard, |l, r| self.eq_expr(l, r)) &&
over(&l.pats, &r.pats, |l, r| self.eq_pat(l, r))
})
}
(&ExprMethodCall(ref lname, ref ltys, ref largs), &ExprMethodCall(ref rname, ref rtys, ref rargs)) => {
// TODO: tys
!self.ignore_fn &&
lname.node == rname.node &&
ltys.is_empty() &&
rtys.is_empty() &&
self.eq_exprs(largs, rargs)
}
(&ExprRange(ref lb, ref le), &ExprRange(ref rb, ref re)) => {
both(lb, rb, |l, r| self.eq_expr(l, r)) &&
both(le, re, |l, r| self.eq_expr(l, r))
}
(&ExprRepeat(ref le, ref ll), &ExprRepeat(ref re, ref rl)) => {
self.eq_expr(le, re) && self.eq_expr(ll, rl)
}
(&ExprRet(ref l), &ExprRet(ref r)) => {
both(l, r, |l, r| self.eq_expr(l, r))
}
(&ExprPath(ref lqself, ref lsubpath), &ExprPath(ref rqself, ref rsubpath)) => {
both(lqself, rqself, |l, r| self.eq_qself(l, r)) && self.eq_path(lsubpath, rsubpath)
}
(&ExprTup(ref ltup), &ExprTup(ref rtup)) => self.eq_exprs(ltup, rtup),
(&ExprTupField(ref le, li), &ExprTupField(ref re, ri)) => {
li.node == ri.node && self.eq_expr(le, re)
}
(&ExprUnary(lop, ref le), &ExprUnary(rop, ref re)) => {
lop == rop && self.eq_expr(le, re)
}
(&ExprVec(ref l), &ExprVec(ref r)) => self.eq_exprs(l, r),
(&ExprWhile(ref lc, ref lb, ref ll), &ExprWhile(ref rc, ref rb, ref rl)) => {
self.eq_expr(lc, rc) &&
self.eq_block(lb, rb) &&
both(ll, rl, |l, r| l.name.as_str() == r.name.as_str())
}
_ => false,
}
}
fn eq_exprs(&self, left: &[P<Expr>], right: &[P<Expr>]) -> bool {
over(left, right, |l, r| self.eq_expr(l, r))
}
/// Check whether two patterns are the same.
pub fn eq_pat(&self, left: &Pat, right: &Pat) -> bool {
match (&left.node, &right.node) {
(&PatBox(ref l), &PatBox(ref r)) => {
self.eq_pat(l, r)
}
(&PatEnum(ref lp, ref la), &PatEnum(ref rp, ref ra)) => {
self.eq_path(lp, rp) &&
both(la, ra, |l, r| {
over(l, r, |l, r| self.eq_pat(l, r))
})
}
(&PatIdent(ref lb, ref li, ref lp), &PatIdent(ref rb, ref ri, ref rp)) => {
lb == rb && li.node.name.as_str() == ri.node.name.as_str() &&
both(lp, rp, |l, r| self.eq_pat(l, r))
}
(&PatLit(ref l), &PatLit(ref r)) => {
self.eq_expr(l, r)
}
(&PatQPath(ref ls, ref lp), &PatQPath(ref rs, ref rp)) => {
self.eq_qself(ls, rs) && self.eq_path(lp, rp)
}
(&PatTup(ref l), &PatTup(ref r)) => {
over(l, r, |l, r| self.eq_pat(l, r))
}
(&PatRange(ref ls, ref le), &PatRange(ref rs, ref re)) => {
self.eq_expr(ls, rs) &&
self.eq_expr(le, re)
}
(&PatRegion(ref le, ref lm), &PatRegion(ref re, ref rm)) => {
lm == rm && self.eq_pat(le, re)
}
(&PatVec(ref ls, ref li, ref le), &PatVec(ref rs, ref ri, ref re)) => {
over(ls, rs, |l, r| self.eq_pat(l, r)) &&
over(le, re, |l, r| self.eq_pat(l, r)) &&
both(li, ri, |l, r| self.eq_pat(l, r))
}
(&PatWild, &PatWild) => true,
_ => false,
}
}
fn eq_path(&self, left: &Path, right: &Path) -> bool {
// The == of idents doesn't work with different contexts,
// we have to be explicit about hygiene
left.global == right.global &&
over(&left.segments,
&right.segments,
|l, r| l.identifier.name.as_str() == r.identifier.name.as_str() && l.parameters == r.parameters)
}
fn eq_qself(&self, left: &QSelf, right: &QSelf) -> bool {
left.ty.node == right.ty.node && left.position == right.position
}
fn eq_ty(&self, left: &Ty, right: &Ty) -> bool {
match (&left.node, &right.node) {
(&TyVec(ref lvec), &TyVec(ref rvec)) => self.eq_ty(lvec, rvec),
(&TyFixedLengthVec(ref lt, ref ll), &TyFixedLengthVec(ref rt, ref rl)) => {
self.eq_ty(lt, rt) && self.eq_expr(ll, rl)
}
(&TyPtr(ref lmut), &TyPtr(ref rmut)) => lmut.mutbl == rmut.mutbl && self.eq_ty(&*lmut.ty, &*rmut.ty),
(&TyRptr(_, ref lrmut), &TyRptr(_, ref rrmut)) => {
lrmut.mutbl == rrmut.mutbl && self.eq_ty(&*lrmut.ty, &*rrmut.ty)
}
(&TyPath(ref lq, ref lpath), &TyPath(ref rq, ref rpath)) => {
both(lq, rq, |l, r| self.eq_qself(l, r)) && self.eq_path(lpath, rpath)
}
(&TyTup(ref l), &TyTup(ref r)) => over(l, r, |l, r| self.eq_ty(l, r)),
(&TyInfer, &TyInfer) => true,
_ => false,
}
}
}
/// Check if the two `Option`s are both `None` or some equal values as per `eq_fn`.
fn both<X, F>(l: &Option<X>, r: &Option<X>, mut eq_fn: F) -> bool
where F: FnMut(&X, &X) -> bool
{
l.as_ref().map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
}
/// Check if two slices are equal as per `eq_fn`.
fn over<X, F>(left: &[X], right: &[X], mut eq_fn: F) -> bool
where F: FnMut(&X, &X) -> bool
{
left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
}
/// Type used to hash an ast element. This is different from the `Hash` trait on ast types as this
/// trait would consider IDs and spans.
///
/// All expressions kind are hashed, but some might have a weaker hash.
pub struct SpanlessHash<'a, 'tcx: 'a> {
/// Context used to evaluate constant expressions.
cx: &'a LateContext<'a, 'tcx>,
s: SipHasher,
}
impl<'a, 'tcx: 'a> SpanlessHash<'a, 'tcx> {
pub fn new(cx: &'a LateContext<'a, 'tcx>) -> Self {
SpanlessHash { cx: cx, s: SipHasher::new() }
}
pub fn finish(&self) -> u64 {
self.s.finish()
}
pub fn hash_block(&mut self, b: &Block) {
for s in &b.stmts {
self.hash_stmt(s);
}
if let Some(ref e) = b.expr {
self.hash_expr(e);
}
b.rules.hash(&mut self.s);
}
pub fn hash_expr(&mut self, e: &Expr) {
if let Some(e) = constant(self.cx, e) {
return e.hash(&mut self.s);
}
match e.node {
ExprAddrOf(m, ref e) => {
let c: fn(_, _) -> _ = ExprAddrOf;
c.hash(&mut self.s);
m.hash(&mut self.s);
self.hash_expr(e);
}
ExprAgain(i) => {
let c: fn(_) -> _ = ExprAgain;
c.hash(&mut self.s);
if let Some(i) = i {
self.hash_name(&i.node.name);
}
}
ExprAssign(ref l, ref r) => {
let c: fn(_, _) -> _ = ExprAssign;
c.hash(&mut self.s);
self.hash_expr(l);
self.hash_expr(r);
}
ExprAssignOp(ref o, ref l, ref r) => {
let c: fn(_, _, _) -> _ = ExprAssignOp;
c.hash(&mut self.s);
o.hash(&mut self.s);
self.hash_expr(l);
self.hash_expr(r);
}
ExprBlock(ref b) => {
let c: fn(_) -> _ = ExprBlock;
c.hash(&mut self.s);
self.hash_block(b);
}
ExprBinary(op, ref l, ref r) => {
let c: fn(_, _, _) -> _ = ExprBinary;
c.hash(&mut self.s);
op.node.hash(&mut self.s);
self.hash_expr(l);
self.hash_expr(r);
}
ExprBreak(i) => {
let c: fn(_) -> _ = ExprBreak;
c.hash(&mut self.s);
if let Some(i) = i {
self.hash_name(&i.node.name);
}
}
ExprBox(ref e) => {
let c: fn(_) -> _ = ExprBox;
c.hash(&mut self.s);
self.hash_expr(e);
}
ExprCall(ref fun, ref args) => {
let c: fn(_, _) -> _ = ExprCall;
c.hash(&mut self.s);
self.hash_expr(fun);
self.hash_exprs(args);
}
ExprCast(ref e, ref _ty) => {
let c: fn(_, _) -> _ = ExprCast;
c.hash(&mut self.s);
self.hash_expr(e);
// TODO: _ty
}
ExprClosure(cap, _, ref b) => {
let c: fn(_, _, _) -> _ = ExprClosure;
c.hash(&mut self.s);
cap.hash(&mut self.s);
self.hash_block(b);
}
ExprField(ref e, ref f) => {
let c: fn(_, _) -> _ = ExprField;
c.hash(&mut self.s);
self.hash_expr(e);
self.hash_name(&f.node);
}
ExprIndex(ref a, ref i) => {
let c: fn(_, _) -> _ = ExprIndex;
c.hash(&mut self.s);
self.hash_expr(a);
self.hash_expr(i);
}
ExprInlineAsm(_) => {
let c: fn(_) -> _ = ExprInlineAsm;
c.hash(&mut self.s);
}
ExprIf(ref cond, ref t, ref e) => {
let c: fn(_, _, _) -> _ = ExprIf;
c.hash(&mut self.s);
self.hash_expr(cond);
self.hash_block(t);
if let Some(ref e) = *e {
self.hash_expr(e);
}
}
ExprLit(ref l) => {
let c: fn(_) -> _ = ExprLit;
c.hash(&mut self.s);
l.hash(&mut self.s);
},
ExprLoop(ref b, ref i) => {
let c: fn(_, _) -> _ = ExprLoop;
c.hash(&mut self.s);
self.hash_block(b);
if let Some(i) = *i {
self.hash_name(&i.name);
}
}
ExprMatch(ref e, ref arms, ref s) => {
let c: fn(_, _, _) -> _ = ExprMatch;
c.hash(&mut self.s);
self.hash_expr(e);
for arm in arms {
// TODO: arm.pat?
if let Some(ref e) = arm.guard {
self.hash_expr(e);
}
self.hash_expr(&arm.body);
}
s.hash(&mut self.s);
}
ExprMethodCall(ref name, ref _tys, ref args) => {
let c: fn(_, _, _) -> _ = ExprMethodCall;
c.hash(&mut self.s);
self.hash_name(&name.node);
self.hash_exprs(args);
}
ExprRange(ref b, ref e) => {
let c: fn(_, _) -> _ = ExprRange;
c.hash(&mut self.s);
if let Some(ref b) = *b {
self.hash_expr(b);
}
if let Some(ref e) = *e {
self.hash_expr(e);
}
}
ExprRepeat(ref e, ref l) => {
let c: fn(_, _) -> _ = ExprRepeat;
c.hash(&mut self.s);
self.hash_expr(e);
self.hash_expr(l);
}
ExprRet(ref e) => {
let c: fn(_) -> _ = ExprRet;
c.hash(&mut self.s);
if let Some(ref e) = *e {
self.hash_expr(e);
}
}
ExprPath(ref _qself, ref subpath) => {
let c: fn(_, _) -> _ = ExprPath;
c.hash(&mut self.s);
self.hash_path(subpath);
}
ExprStruct(ref path, ref fields, ref expr) => {
let c: fn(_, _, _) -> _ = ExprStruct;
c.hash(&mut self.s);
self.hash_path(path);
for f in fields {
self.hash_name(&f.name.node);
self.hash_expr(&f.expr);
}
if let Some(ref e) = *expr {
self.hash_expr(e);
}
}
ExprTup(ref tup) => {
let c: fn(_) -> _ = ExprTup;
c.hash(&mut self.s);
self.hash_exprs(tup);
},
ExprTupField(ref le, li) => {
let c: fn(_, _) -> _ = ExprTupField;
c.hash(&mut self.s);
self.hash_expr(le);
li.node.hash(&mut self.s);
}
ExprType(_, _) => {
let c: fn(_, _) -> _ = ExprType;
c.hash(&mut self.s);
// whats an ExprType anyway?
}
ExprUnary(lop, ref le) => {
let c: fn(_, _) -> _ = ExprUnary;
c.hash(&mut self.s);
lop.hash(&mut self.s);
self.hash_expr(le);
}
ExprVec(ref v) => {
let c: fn(_) -> _ = ExprVec;
c.hash(&mut self.s);
self.hash_exprs(v);
},
ExprWhile(ref cond, ref b, l) => {
let c: fn(_, _, _) -> _ = ExprWhile;
c.hash(&mut self.s);
self.hash_expr(cond);
self.hash_block(b);
if let Some(l) = l {
self.hash_name(&l.name);
}
}
}
}
pub fn hash_exprs(&mut self, e: &[P<Expr>]) {
for e in e {
self.hash_expr(e);
}
}
pub fn hash_name(&mut self, n: &Name) {
n.as_str().hash(&mut self.s);
}
pub fn hash_path(&mut self, p: &Path) {
p.global.hash(&mut self.s);
for p in &p.segments {
self.hash_name(&p.identifier.name);
}
}
pub fn hash_stmt(&mut self, b: &Stmt) {
match b.node {
StmtDecl(ref _decl, _) => {
let c: fn(_, _) -> _ = StmtDecl;
c.hash(&mut self.s);
// TODO: decl
}
StmtExpr(ref expr, _) => {
let c: fn(_, _) -> _ = StmtExpr;
c.hash(&mut self.s);
self.hash_expr(expr);
}
StmtSemi(ref expr, _) => {
let c: fn(_, _) -> _ = StmtSemi;
c.hash(&mut self.s);
self.hash_expr(expr);
}
}
}
}

View File

@ -1,4 +1,3 @@
use consts::constant;
use reexport::*;
use rustc::front::map::Node;
use rustc::lint::{LintContext, LateContext, Level, Lint};
@ -15,6 +14,8 @@ use syntax::codemap::{ExpnInfo, Span, ExpnFormat};
use syntax::errors::DiagnosticBuilder;
use syntax::ptr::P;
mod hir;
pub use self::hir::{SpanlessEq, SpanlessHash};
pub type MethodArgs = HirVec<P<Expr>>;
// module DefPaths for certain structs/enums we check for
@ -590,227 +591,6 @@ fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'
}
}
/// Check whether two statements are the same.
/// See also `is_exp_equal`.
pub fn is_stmt_equal(cx: &LateContext, left: &Stmt, right: &Stmt, ignore_fn: bool) -> bool {
match (&left.node, &right.node) {
(&StmtDecl(ref l, _), &StmtDecl(ref r, _)) => {
if let (&DeclLocal(ref l), &DeclLocal(ref r)) = (&l.node, &r.node) {
// TODO: tys
l.ty.is_none() && r.ty.is_none() &&
both(&l.init, &r.init, |l, r| is_exp_equal(cx, l, r, ignore_fn))
}
else {
false
}
}
(&StmtExpr(ref l, _), &StmtExpr(ref r, _)) => is_exp_equal(cx, l, r, ignore_fn),
(&StmtSemi(ref l, _), &StmtSemi(ref r, _)) => is_exp_equal(cx, l, r, ignore_fn),
_ => false,
}
}
/// Check whether two blocks are the same.
/// See also `is_exp_equal`.
pub fn is_block_equal(cx: &LateContext, left: &Block, right: &Block, ignore_fn: bool) -> bool {
over(&left.stmts, &right.stmts, |l, r| is_stmt_equal(cx, l, r, ignore_fn)) &&
both(&left.expr, &right.expr, |l, r| is_exp_equal(cx, l, r, ignore_fn))
}
/// Check whether two pattern are the same.
/// See also `is_exp_equal`.
pub fn is_pat_equal(cx: &LateContext, left: &Pat, right: &Pat, ignore_fn: bool) -> bool {
match (&left.node, &right.node) {
(&PatBox(ref l), &PatBox(ref r)) => {
is_pat_equal(cx, l, r, ignore_fn)
}
(&PatEnum(ref lp, ref la), &PatEnum(ref rp, ref ra)) => {
is_path_equal(lp, rp) &&
both(la, ra, |l, r| {
over(l, r, |l, r| is_pat_equal(cx, l, r, ignore_fn))
})
}
(&PatIdent(ref lb, ref li, ref lp), &PatIdent(ref rb, ref ri, ref rp)) => {
lb == rb && li.node.name.as_str() == ri.node.name.as_str() &&
both(lp, rp, |l, r| is_pat_equal(cx, l, r, ignore_fn))
}
(&PatLit(ref l), &PatLit(ref r)) => {
is_exp_equal(cx, l, r, ignore_fn)
}
(&PatQPath(ref ls, ref lp), &PatQPath(ref rs, ref rp)) => {
is_qself_equal(ls, rs) && is_path_equal(lp, rp)
}
(&PatTup(ref l), &PatTup(ref r)) => {
over(l, r, |l, r| is_pat_equal(cx, l, r, ignore_fn))
}
(&PatRange(ref ls, ref le), &PatRange(ref rs, ref re)) => {
is_exp_equal(cx, ls, rs, ignore_fn) &&
is_exp_equal(cx, le, re, ignore_fn)
}
(&PatRegion(ref le, ref lm), &PatRegion(ref re, ref rm)) => {
lm == rm && is_pat_equal(cx, le, re, ignore_fn)
}
(&PatVec(ref ls, ref li, ref le), &PatVec(ref rs, ref ri, ref re)) => {
over(ls, rs, |l, r| is_pat_equal(cx, l, r, ignore_fn)) &&
over(le, re, |l, r| is_pat_equal(cx, l, r, ignore_fn)) &&
both(li, ri, |l, r| is_pat_equal(cx, l, r, ignore_fn))
}
(&PatWild, &PatWild) => true,
_ => false,
}
}
/// Check whether two expressions are the same. This is different from the operator `==` on
/// expression as this operator would compare true equality with ID and span.
/// If `ignore_fn` is true, never consider as equal fonction calls.
///
/// Note that some expression kinds are not considered but could be added.
#[allow(cyclomatic_complexity)] // ok, its a big function, but mostly one big match with simples cases
pub fn is_exp_equal(cx: &LateContext, left: &Expr, right: &Expr, ignore_fn: bool) -> bool {
if let (Some(l), Some(r)) = (constant(cx, left), constant(cx, right)) {
if l == r {
return true;
}
}
match (&left.node, &right.node) {
(&ExprAddrOf(ref lmut, ref le), &ExprAddrOf(ref rmut, ref re)) => {
lmut == rmut && is_exp_equal(cx, le, re, ignore_fn)
}
(&ExprAgain(li), &ExprAgain(ri)) => {
both(&li, &ri, |l, r| l.node.name.as_str() == r.node.name.as_str())
}
(&ExprAssign(ref ll, ref lr), &ExprAssign(ref rl, ref rr)) => {
is_exp_equal(cx, ll, rl, ignore_fn) && is_exp_equal(cx, lr, rr, ignore_fn)
}
(&ExprAssignOp(ref lo, ref ll, ref lr), &ExprAssignOp(ref ro, ref rl, ref rr)) => {
lo.node == ro.node && is_exp_equal(cx, ll, rl, ignore_fn) && is_exp_equal(cx, lr, rr, ignore_fn)
}
(&ExprBlock(ref l), &ExprBlock(ref r)) => {
is_block_equal(cx, l, r, ignore_fn)
}
(&ExprBinary(lop, ref ll, ref lr), &ExprBinary(rop, ref rl, ref rr)) => {
lop.node == rop.node && is_exp_equal(cx, ll, rl, ignore_fn) && is_exp_equal(cx, lr, rr, ignore_fn)
}
(&ExprBreak(li), &ExprBreak(ri)) => {
both(&li, &ri, |l, r| l.node.name.as_str() == r.node.name.as_str())
}
(&ExprBox(ref l), &ExprBox(ref r)) => {
is_exp_equal(cx, l, r, ignore_fn)
}
(&ExprCall(ref lfun, ref largs), &ExprCall(ref rfun, ref rargs)) => {
!ignore_fn &&
is_exp_equal(cx, lfun, rfun, ignore_fn) &&
is_exps_equal(cx, largs, rargs, ignore_fn)
}
(&ExprCast(ref lx, ref lt), &ExprCast(ref rx, ref rt)) => {
is_exp_equal(cx, lx, rx, ignore_fn) && is_cast_ty_equal(lt, rt)
}
(&ExprField(ref lfexp, ref lfident), &ExprField(ref rfexp, ref rfident)) => {
lfident.node == rfident.node && is_exp_equal(cx, lfexp, rfexp, ignore_fn)
}
(&ExprIndex(ref la, ref li), &ExprIndex(ref ra, ref ri)) => {
is_exp_equal(cx, la, ra, ignore_fn) && is_exp_equal(cx, li, ri, ignore_fn)
}
(&ExprIf(ref lc, ref lt, ref le), &ExprIf(ref rc, ref rt, ref re)) => {
is_exp_equal(cx, lc, rc, ignore_fn) &&
is_block_equal(cx, lt, rt, ignore_fn) &&
both(le, re, |l, r| is_exp_equal(cx, l, r, ignore_fn))
}
(&ExprLit(ref l), &ExprLit(ref r)) => l.node == r.node,
(&ExprMatch(ref le, ref la, ref ls), &ExprMatch(ref re, ref ra, ref rs)) => {
ls == rs &&
is_exp_equal(cx, le, re, ignore_fn) &&
over(la, ra, |l, r| {
is_exp_equal(cx, &l.body, &r.body, ignore_fn) &&
both(&l.guard, &r.guard, |l, r| is_exp_equal(cx, l, r, ignore_fn)) &&
over(&l.pats, &r.pats, |l, r| is_pat_equal(cx, l, r, ignore_fn))
})
}
(&ExprMethodCall(ref lname, ref ltys, ref largs), &ExprMethodCall(ref rname, ref rtys, ref rargs)) => {
// TODO: tys
!ignore_fn &&
lname.node == rname.node &&
ltys.is_empty() &&
rtys.is_empty() &&
is_exps_equal(cx, largs, rargs, ignore_fn)
}
(&ExprRange(ref lb, ref le), &ExprRange(ref rb, ref re)) => {
both(lb, rb, |l, r| is_exp_equal(cx, l, r, ignore_fn)) &&
both(le, re, |l, r| is_exp_equal(cx, l, r, ignore_fn))
}
(&ExprRepeat(ref le, ref ll), &ExprRepeat(ref re, ref rl)) => {
is_exp_equal(cx, le, re, ignore_fn) && is_exp_equal(cx, ll, rl, ignore_fn)
}
(&ExprRet(ref l), &ExprRet(ref r)) => {
both(l, r, |l, r| is_exp_equal(cx, l, r, ignore_fn))
}
(&ExprPath(ref lqself, ref lsubpath), &ExprPath(ref rqself, ref rsubpath)) => {
both(lqself, rqself, is_qself_equal) && is_path_equal(lsubpath, rsubpath)
}
(&ExprTup(ref ltup), &ExprTup(ref rtup)) => is_exps_equal(cx, ltup, rtup, ignore_fn),
(&ExprTupField(ref le, li), &ExprTupField(ref re, ri)) => {
li.node == ri.node && is_exp_equal(cx, le, re, ignore_fn)
}
(&ExprUnary(lop, ref le), &ExprUnary(rop, ref re)) => {
lop == rop && is_exp_equal(cx, le, re, ignore_fn)
}
(&ExprVec(ref l), &ExprVec(ref r)) => is_exps_equal(cx, l, r, ignore_fn),
(&ExprWhile(ref lc, ref lb, ref ll), &ExprWhile(ref rc, ref rb, ref rl)) => {
is_exp_equal(cx, lc, rc, ignore_fn) &&
is_block_equal(cx, lb, rb, ignore_fn) &&
both(ll, rl, |l, r| l.name.as_str() == r.name.as_str())
}
_ => false,
}
}
fn is_exps_equal(cx: &LateContext, left: &[P<Expr>], right: &[P<Expr>], ignore_fn: bool) -> bool {
over(left, right, |l, r| is_exp_equal(cx, l, r, ignore_fn))
}
fn is_path_equal(left: &Path, right: &Path) -> bool {
// The == of idents doesn't work with different contexts,
// we have to be explicit about hygiene
left.global == right.global &&
over(&left.segments,
&right.segments,
|l, r| l.identifier.name.as_str() == r.identifier.name.as_str() && l.parameters == r.parameters)
}
fn is_qself_equal(left: &QSelf, right: &QSelf) -> bool {
left.ty.node == right.ty.node && left.position == right.position
}
/// Check if two slices are equal as per `eq_fn`.
pub fn over<X, F>(left: &[X], right: &[X], mut eq_fn: F) -> bool
where F: FnMut(&X, &X) -> bool
{
left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
}
/// Check if the two `Option`s are both `None` or some equal values as per `eq_fn`.
pub fn both<X, F>(l: &Option<X>, r: &Option<X>, mut eq_fn: F) -> bool
where F: FnMut(&X, &X) -> bool
{
l.as_ref().map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
}
fn is_cast_ty_equal(left: &Ty, right: &Ty) -> bool {
match (&left.node, &right.node) {
(&TyVec(ref lvec), &TyVec(ref rvec)) => is_cast_ty_equal(lvec, rvec),
(&TyPtr(ref lmut), &TyPtr(ref rmut)) => lmut.mutbl == rmut.mutbl && is_cast_ty_equal(&*lmut.ty, &*rmut.ty),
(&TyRptr(_, ref lrmut), &TyRptr(_, ref rrmut)) => {
lrmut.mutbl == rrmut.mutbl && is_cast_ty_equal(&*lrmut.ty, &*rrmut.ty)
}
(&TyPath(ref lq, ref lpath), &TyPath(ref rq, ref rpath)) => {
both(lq, rq, is_qself_equal) && is_path_equal(lpath, rpath)
}
(&TyInfer, &TyInfer) => true,
_ => false,
}
}
/// Return the pre-expansion span if is this comes from an expansion of the macro `name`.
pub fn is_expn_of(cx: &LateContext, mut span: Span, name: &str) -> Option<Span> {
loop {

View File

@ -5,16 +5,18 @@
#![allow(let_and_return)]
#![allow(needless_return)]
#![allow(unused_variables)]
#![deny(if_same_then_else)]
#![deny(ifs_same_cond)]
#![allow(cyclomatic_complexity)]
fn bar<T>(_: T) {}
fn foo() -> bool { unimplemented!() }
#[deny(if_same_then_else)]
#[deny(match_same_arms)]
fn if_same_then_else() -> &'static str {
if true { //~ERROR this if has the same then and else blocks
if true {
foo();
}
else {
else { //~ERROR this `if` has identical blocks
foo();
}
@ -26,11 +28,11 @@ fn if_same_then_else() -> &'static str {
foo();
}
let _ = if true { //~ERROR this if has the same then and else blocks
let _ = if true {
foo();
42
}
else {
else { //~ERROR this `if` has identical blocks
foo();
42
};
@ -39,14 +41,14 @@ fn if_same_then_else() -> &'static str {
foo();
}
let _ = if true { //~ERROR this if has the same then and else blocks
let _ = if true {
42
}
else {
else { //~ERROR this `if` has identical blocks
42
};
if true { //~ERROR this if has the same then and else blocks
if true {
let bar = if true {
42
}
@ -57,7 +59,7 @@ fn if_same_then_else() -> &'static str {
while foo() { break; }
bar + 1;
}
else {
else { //~ERROR this `if` has identical blocks
let bar = if true {
42
}
@ -69,53 +71,99 @@ fn if_same_then_else() -> &'static str {
bar + 1;
}
if true { //~ERROR this if has the same then and else blocks
match 42 {
42 => (),
a if a > 0 => (),
10...15 => (),
_ => (),
}
if true {
let _ = match 42 {
42 => 1,
a if a > 0 => 2,
10...15 => 3,
_ => 4,
};
}
else {
match 42 {
42 => (),
a if a > 0 => (),
10...15 => (),
_ => (),
}
else if false {
foo();
}
else if foo() { //~ERROR this `if` has identical blocks
let _ = match 42 {
42 => 1,
a if a > 0 => 2,
10...15 => 3,
_ => 4,
};
}
if true { //~ERROR this if has the same then and else blocks
if true {
if let Some(a) = Some(42) {}
}
else {
else { //~ERROR this `if` has identical blocks
if let Some(a) = Some(42) {}
}
if true { //~ERROR this if has the same then and else blocks
if true {
if let Some(a) = Some(42) {}
}
else {
if let Some(a) = Some(43) {}
}
let _ = match 42 {
42 => foo(),
51 => foo(), //~ERROR this `match` has identical arm bodies
_ => true,
};
let _ = match Some(42) {
Some(42) => 24,
Some(a) => 24, // bindings are different
None => 0,
};
match (Some(42), Some(42)) {
(Some(a), None) => bar(a),
(None, Some(a)) => bar(a), //~ERROR this `match` has identical arm bodies
_ => (),
}
match (Some(42), Some("")) {
(Some(a), None) => bar(a),
(None, Some(a)) => bar(a), // bindings have different types
_ => (),
}
if true {
let foo = "";
return &foo[0..];
}
else {
else if false {
let foo = "bar";
return &foo[0..];
}
else { //~ERROR this `if` has identical blocks
let foo = "";
return &foo[0..];
}
}
#[deny(ifs_same_cond)]
#[allow(if_same_then_else)] // all empty blocks
fn ifs_same_cond() {
let a = 0;
let b = false;
if b {
}
else if b { //~ERROR this `if` has the same condition as a previous if
}
if a == 1 {
}
else if a == 1 { //~ERROR this if has the same condition as a previous if
else if a == 1 { //~ERROR this `if` has the same condition as a previous if
}
if 2*a == 1 {
}
else if 2*a == 2 {
}
else if 2*a == 1 { //~ERROR this if has the same condition as a previous if
else if 2*a == 1 { //~ERROR this `if` has the same condition as a previous if
}
else if a == 1 {
}

View File

@ -138,15 +138,15 @@ fn bloo() {
#[cyclomatic_complexity = "0"]
fn baa() { //~ ERROR: the function has a cyclomatic complexity of 2
let x = || match 99 {
0 => true,
1 => false,
2 => true,
4 => true,
6 => true,
9 => true,
_ => false,
0 => 0,
1 => 1,
2 => 2,
4 => 4,
6 => 6,
9 => 9,
_ => 42,
};
if x() {
if x() == 42 {
println!("x");
} else {
println!("not x");

View File

@ -101,8 +101,8 @@ fn match_bool() {
let test: bool = true;
match test { //~ ERROR you seem to be trying to match on a boolean expression
true => (),
false => (),
true => 0,
false => 42,
};
let option = 1;
@ -128,9 +128,9 @@ fn match_bool() {
// Not linted
match option {
1 ... 10 => (),
11 ... 20 => (),
_ => (),
1 ... 10 => 1,
11 ... 20 => 2,
_ => 3,
};
}

View File

@ -15,7 +15,7 @@ use syntax::ast::LitKind;
use syntax::ast::LitIntType;
use syntax::ast::StrStyle;
use clippy::consts::{constant_simple, Constant, Sign};
use clippy::consts::{constant_simple, Constant, FloatWidth, Sign};
fn spanned<T>(t: T) -> Spanned<T> {
Spanned{ node: t, span: COMMAND_LINE_SP }
@ -76,4 +76,12 @@ fn test_ops() {
check(ONE, &binop(BiSub, litone.clone(), litzero.clone()));
check(ONE, &binop(BiMul, litone.clone(), litone.clone()));
check(ONE, &binop(BiDiv, litone.clone(), litone.clone()));
let half_any = Constant::Float("0.5".into(), FloatWidth::FwAny);
let half32 = Constant::Float("0.5".into(), FloatWidth::Fw32);
let half64 = Constant::Float("0.5".into(), FloatWidth::Fw64);
assert_eq!(half_any, half32);
assert_eq!(half_any, half64);
assert_eq!(half32, half64); // for transitivity
}