2021-03-16 00:55:45 +00:00
|
|
|
use clippy_utils::diagnostics::span_lint_and_note;
|
2021-03-16 16:06:34 +00:00
|
|
|
use clippy_utils::{eq_expr_value, in_macro, search_same, SpanlessEq, SpanlessHash};
|
|
|
|
use clippy_utils::{get_parent_expr, if_sequence};
|
2021-01-01 18:38:11 +00:00
|
|
|
use rustc_hir::{Block, Expr, ExprKind};
|
2020-01-12 06:08:41 +00:00
|
|
|
use rustc_lint::{LateContext, LateLintPass};
|
2020-12-11 22:29:53 +00:00
|
|
|
use rustc_middle::hir::map::Map;
|
2020-01-11 11:37:08 +00:00
|
|
|
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
2020-12-11 22:29:53 +00:00
|
|
|
use rustc_span::source_map::Span;
|
|
|
|
use std::borrow::Cow;
|
2016-01-30 17:03:53 +00:00
|
|
|
|
2018-03-28 13:24:26 +00:00
|
|
|
declare_clippy_lint! {
|
2019-03-05 16:50:33 +00:00
|
|
|
/// **What it does:** Checks for consecutive `if`s with the same condition.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** This is probably a copy & paste error.
|
|
|
|
///
|
|
|
|
/// **Known problems:** Hopefully none.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
2019-03-05 22:23:50 +00:00
|
|
|
/// ```ignore
|
2019-03-05 16:50:33 +00:00
|
|
|
/// if a == b {
|
|
|
|
/// …
|
|
|
|
/// } else if a == b {
|
|
|
|
/// …
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Note that this lint ignores all conditions with a function call as it could
|
|
|
|
/// have side effects:
|
|
|
|
///
|
2019-03-05 22:23:50 +00:00
|
|
|
/// ```ignore
|
2019-03-05 16:50:33 +00:00
|
|
|
/// if foo() {
|
|
|
|
/// …
|
|
|
|
/// } else if foo() { // not linted
|
|
|
|
/// …
|
|
|
|
/// }
|
|
|
|
/// ```
|
2016-01-30 17:03:53 +00:00
|
|
|
pub IFS_SAME_COND,
|
2018-03-28 13:24:26 +00:00
|
|
|
correctness,
|
2020-01-06 06:30:43 +00:00
|
|
|
"consecutive `if`s with the same condition"
|
2016-01-30 17:03:53 +00:00
|
|
|
}
|
|
|
|
|
2019-11-14 05:06:34 +00:00
|
|
|
declare_clippy_lint! {
|
|
|
|
/// **What it does:** Checks for consecutive `if`s with the same function call.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** This is probably a copy & paste error.
|
|
|
|
/// Despite the fact that function can have side effects and `if` works as
|
|
|
|
/// intended, such an approach is implicit and can be considered a "code smell".
|
|
|
|
///
|
|
|
|
/// **Known problems:** Hopefully none.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
/// ```ignore
|
|
|
|
/// if foo() == bar {
|
|
|
|
/// …
|
|
|
|
/// } else if foo() == bar {
|
|
|
|
/// …
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// This probably should be:
|
|
|
|
/// ```ignore
|
|
|
|
/// if foo() == bar {
|
|
|
|
/// …
|
|
|
|
/// } else if foo() == baz {
|
|
|
|
/// …
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// or if the original code was not a typo and called function mutates a state,
|
|
|
|
/// consider move the mutation out of the `if` condition to avoid similarity to
|
|
|
|
/// a copy & paste error:
|
|
|
|
///
|
|
|
|
/// ```ignore
|
|
|
|
/// let first = foo();
|
|
|
|
/// if first == bar {
|
|
|
|
/// …
|
|
|
|
/// } else {
|
|
|
|
/// let second = foo();
|
|
|
|
/// if second == bar {
|
|
|
|
/// …
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub SAME_FUNCTIONS_IN_IF_CONDITION,
|
|
|
|
pedantic,
|
2020-01-06 06:30:43 +00:00
|
|
|
"consecutive `if`s with the same function call"
|
2019-11-14 05:06:34 +00:00
|
|
|
}
|
|
|
|
|
2018-03-28 13:24:26 +00:00
|
|
|
declare_clippy_lint! {
|
2019-03-05 16:50:33 +00:00
|
|
|
/// **What it does:** Checks for `if/else` with the same body as the *then* part
|
|
|
|
/// and the *else* part.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** This is probably a copy & paste error.
|
|
|
|
///
|
|
|
|
/// **Known problems:** Hopefully none.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
2019-03-05 22:23:50 +00:00
|
|
|
/// ```ignore
|
2019-03-05 16:50:33 +00:00
|
|
|
/// let foo = if … {
|
|
|
|
/// 42
|
|
|
|
/// } else {
|
|
|
|
/// 42
|
|
|
|
/// };
|
|
|
|
/// ```
|
2016-01-30 18:16:49 +00:00
|
|
|
pub IF_SAME_THEN_ELSE,
|
2018-03-28 13:24:26 +00:00
|
|
|
correctness,
|
2020-01-06 06:30:43 +00:00
|
|
|
"`if` with the same `then` and `else` blocks"
|
2016-01-30 18:16:49 +00:00
|
|
|
}
|
|
|
|
|
2020-12-11 22:29:53 +00:00
|
|
|
declare_clippy_lint! {
|
|
|
|
/// **What it does:** Checks if the `if` and `else` block contain shared code that can be
|
|
|
|
/// moved out of the blocks.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** Duplicate code is less maintainable.
|
|
|
|
///
|
|
|
|
/// **Known problems:** Hopefully none.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
/// ```ignore
|
|
|
|
/// let foo = if … {
|
|
|
|
/// println!("Hello World");
|
|
|
|
/// 13
|
|
|
|
/// } else {
|
|
|
|
/// println!("Hello World");
|
|
|
|
/// 42
|
|
|
|
/// };
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Could be written as:
|
|
|
|
/// ```ignore
|
|
|
|
/// println!("Hello World");
|
|
|
|
/// let foo = if … {
|
|
|
|
/// 13
|
|
|
|
/// } else {
|
|
|
|
/// 42
|
|
|
|
/// };
|
|
|
|
/// ```
|
|
|
|
pub SHARED_CODE_IN_IF_BLOCKS,
|
|
|
|
pedantic,
|
|
|
|
"`if` statement with shared code in all blocks"
|
|
|
|
}
|
|
|
|
|
|
|
|
declare_lint_pass!(CopyAndPaste => [
|
|
|
|
IFS_SAME_COND,
|
|
|
|
SAME_FUNCTIONS_IN_IF_CONDITION,
|
|
|
|
IF_SAME_THEN_ELSE,
|
|
|
|
SHARED_CODE_IN_IF_BLOCKS
|
|
|
|
]);
|
2016-01-30 17:03:53 +00:00
|
|
|
|
2020-06-25 20:41:36 +00:00
|
|
|
impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
|
|
|
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
2019-08-19 16:30:32 +00:00
|
|
|
if !expr.span.from_expansion() {
|
2016-02-09 14:18:27 +00:00
|
|
|
// skip ifs directly in else, it will be checked in the parent if
|
2021-01-01 18:38:11 +00:00
|
|
|
if let Some(&Expr {
|
|
|
|
kind: ExprKind::If(_, _, Some(ref else_expr)),
|
|
|
|
..
|
2021-01-15 09:02:28 +00:00
|
|
|
}) = get_parent_expr(cx, expr)
|
|
|
|
{
|
2021-01-01 18:38:11 +00:00
|
|
|
if else_expr.hir_id == expr.hir_id {
|
|
|
|
return;
|
2016-02-09 14:18:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let (conds, blocks) = if_sequence(expr);
|
2020-12-11 22:29:53 +00:00
|
|
|
// Conditions
|
2016-11-16 20:57:56 +00:00
|
|
|
lint_same_cond(cx, &conds);
|
2019-11-14 05:06:34 +00:00
|
|
|
lint_same_fns_in_if_cond(cx, &conds);
|
2020-12-11 22:29:53 +00:00
|
|
|
// Block duplication
|
|
|
|
lint_same_then_else(cx, &blocks, conds.len() != blocks.len(), expr);
|
2016-01-30 17:03:53 +00:00
|
|
|
}
|
2016-01-30 18:16:49 +00:00
|
|
|
}
|
|
|
|
}
|
2016-01-30 17:03:53 +00:00
|
|
|
|
2020-12-11 22:29:53 +00:00
|
|
|
/// Implementation of `SHARED_CODE_IN_IF_BLOCKS` and `IF_SAME_THEN_ELSE` if the blocks are equal.
|
|
|
|
fn lint_same_then_else<'tcx>(
|
|
|
|
cx: &LateContext<'tcx>,
|
|
|
|
blocks: &[&Block<'tcx>],
|
|
|
|
has_unconditional_else: bool,
|
|
|
|
expr: &'tcx Expr<'_>,
|
|
|
|
) {
|
|
|
|
// We only lint ifs with multiple blocks
|
|
|
|
// TODO xFrednet 2021-01-01: Check if it's an else if block
|
|
|
|
if blocks.len() < 2 {
|
|
|
|
return;
|
|
|
|
}
|
2016-02-09 15:45:47 +00:00
|
|
|
|
2020-12-11 22:29:53 +00:00
|
|
|
let has_expr = blocks[0].expr.is_some();
|
|
|
|
|
|
|
|
// Check if each block has shared code
|
|
|
|
let mut start_eq = usize::MAX;
|
|
|
|
let mut end_eq = usize::MAX;
|
|
|
|
let mut expr_eq = true;
|
|
|
|
for (index, win) in blocks.windows(2).enumerate() {
|
|
|
|
let l_stmts = win[0].stmts;
|
|
|
|
let r_stmts = win[1].stmts;
|
|
|
|
|
|
|
|
let mut evaluator = SpanlessEq::new(cx);
|
|
|
|
let current_start_eq = count_eq(&mut l_stmts.iter(), &mut r_stmts.iter(), |l, r| evaluator.eq_stmt(l, r));
|
|
|
|
let current_end_eq = count_eq(&mut l_stmts.iter().rev(), &mut r_stmts.iter().rev(), |l, r| {
|
|
|
|
evaluator.eq_stmt(l, r)
|
|
|
|
});
|
|
|
|
let block_expr_eq = both(&win[0].expr, &win[1].expr, |l, r| evaluator.eq_expr(l, r));
|
|
|
|
|
|
|
|
// IF_SAME_THEN_ELSE
|
|
|
|
// We only lint the first two blocks (index == 0). Further blocks will be linted when that if
|
|
|
|
// statement is checked
|
|
|
|
if index == 0 && block_expr_eq && l_stmts.len() == r_stmts.len() && l_stmts.len() == current_start_eq {
|
|
|
|
span_lint_and_note(
|
|
|
|
cx,
|
|
|
|
IF_SAME_THEN_ELSE,
|
|
|
|
win[0].span,
|
|
|
|
"this `if` has identical blocks",
|
|
|
|
Some(win[1].span),
|
|
|
|
"same as this",
|
|
|
|
);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
start_eq = start_eq.min(current_start_eq);
|
|
|
|
end_eq = end_eq.min(current_end_eq);
|
|
|
|
expr_eq &= block_expr_eq;
|
|
|
|
|
|
|
|
// We can return if the eq count is 0 from both sides or if it has no unconditional else case
|
|
|
|
if !has_unconditional_else || (start_eq == 0 && end_eq == 0 && (has_expr && !expr_eq)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if has_expr && !expr_eq {
|
|
|
|
end_eq = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the regions are overlapping. Set `end_eq` to prevent the overlap
|
|
|
|
let min_block_size = blocks.iter().map(|x| x.stmts.len()).min().unwrap();
|
|
|
|
if (start_eq + end_eq) > min_block_size {
|
|
|
|
end_eq = min_block_size - start_eq;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only the start is the same
|
|
|
|
if start_eq != 0 && end_eq == 0 && (!has_expr || !expr_eq) {
|
|
|
|
emit_shared_code_in_if_blocks_lint(cx, start_eq, 0, false, blocks, expr);
|
|
|
|
} else if end_eq != 0 && (!has_expr || !expr_eq) {
|
|
|
|
let block = blocks[blocks.len() - 1];
|
|
|
|
let stmts = block.stmts.split_at(start_eq).1;
|
|
|
|
let (block_stmts, moved_stmts) = stmts.split_at(stmts.len() - end_eq);
|
|
|
|
|
|
|
|
// Scan block
|
|
|
|
let mut walker = SymbolFinderVisitor::new(cx);
|
|
|
|
for stmt in block_stmts {
|
|
|
|
intravisit::walk_stmt(&mut walker, stmt);
|
|
|
|
}
|
|
|
|
let mut block_defs = walker.defs;
|
|
|
|
|
|
|
|
// Scan moved stmts
|
|
|
|
let mut moved_start: Option<usize> = None;
|
|
|
|
let mut walker = SymbolFinderVisitor::new(cx);
|
|
|
|
for (index, stmt) in moved_stmts.iter().enumerate() {
|
|
|
|
intravisit::walk_stmt(&mut walker, stmt);
|
|
|
|
|
|
|
|
for value in &walker.uses {
|
|
|
|
// Well we can't move this and all prev statements. So reset
|
|
|
|
if block_defs.contains(&value) {
|
|
|
|
moved_start = Some(index + 1);
|
|
|
|
walker.defs.drain().for_each(|x| {
|
|
|
|
block_defs.insert(x);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
walker.uses.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(moved_start) = moved_start {
|
|
|
|
end_eq -= moved_start;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut end_linable = true;
|
|
|
|
if let Some(expr) = block.expr {
|
|
|
|
intravisit::walk_expr(&mut walker, expr);
|
|
|
|
end_linable = walker.uses.iter().any(|x| !block_defs.contains(x));
|
|
|
|
}
|
|
|
|
|
|
|
|
emit_shared_code_in_if_blocks_lint(cx, start_eq, end_eq, end_linable, blocks, expr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn emit_shared_code_in_if_blocks_lint(
|
|
|
|
cx: &LateContext<'tcx>,
|
|
|
|
start_stmts: usize,
|
|
|
|
end_stmts: usize,
|
|
|
|
lint_end: bool,
|
|
|
|
blocks: &[&Block<'tcx>],
|
|
|
|
if_expr: &'tcx Expr<'_>,
|
|
|
|
) {
|
|
|
|
if start_stmts == 0 && !lint_end {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// (help, span, suggestion)
|
|
|
|
let mut suggestions: Vec<(&str, Span, String)> = vec![];
|
|
|
|
|
|
|
|
if start_stmts > 0 {
|
|
|
|
let block = blocks[0];
|
|
|
|
let span_start = first_line_of_span(cx, if_expr.span).shrink_to_lo();
|
|
|
|
let span_end = block.stmts[start_stmts - 1].span.source_callsite();
|
|
|
|
|
|
|
|
let cond_span = first_line_of_span(cx, if_expr.span).until(block.span);
|
|
|
|
let cond_snippet = reindent_multiline(snippet(cx, cond_span, "_"), false, None);
|
|
|
|
let cond_indent = indent_of(cx, cond_span);
|
|
|
|
let moved_span = block.stmts[0].span.source_callsite().to(span_end);
|
|
|
|
let moved_snippet = reindent_multiline(snippet(cx, moved_span, "_"), true, None);
|
|
|
|
let suggestion = moved_snippet.to_string() + "\n" + &cond_snippet + "{";
|
|
|
|
let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, cond_indent);
|
|
|
|
|
|
|
|
let span = span_start.to(span_end);
|
|
|
|
suggestions.push(("START HELP", span, suggestion.to_string()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if lint_end {
|
|
|
|
let block = blocks[blocks.len() - 1];
|
|
|
|
let span_end = block.span.shrink_to_hi();
|
|
|
|
|
|
|
|
let moved_start = if end_stmts == 0 && block.expr.is_some() {
|
|
|
|
block.expr.unwrap().span
|
|
|
|
} else {
|
|
|
|
block.stmts[block.stmts.len() - end_stmts].span
|
|
|
|
}
|
|
|
|
.source_callsite();
|
|
|
|
let moved_end = if let Some(expr) = block.expr {
|
|
|
|
expr.span
|
|
|
|
} else {
|
|
|
|
block.stmts[block.stmts.len() - 1].span
|
|
|
|
}
|
|
|
|
.source_callsite();
|
|
|
|
|
|
|
|
let moved_span = moved_start.to(moved_end);
|
|
|
|
let moved_snipped = reindent_multiline(snippet(cx, moved_span, "_"), true, None);
|
|
|
|
let indent = indent_of(cx, if_expr.span.shrink_to_hi());
|
|
|
|
let suggestion = "}\n".to_string() + &moved_snipped;
|
|
|
|
let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, indent);
|
|
|
|
|
|
|
|
let span = moved_start.to(span_end);
|
|
|
|
suggestions.push(("END_RANGE", span, suggestion.to_string()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if suggestions.len() == 1 {
|
|
|
|
let (_, span, sugg) = &suggestions[0];
|
|
|
|
span_lint_and_sugg(
|
2017-08-09 07:30:56 +00:00
|
|
|
cx,
|
2020-12-11 22:29:53 +00:00
|
|
|
SHARED_CODE_IN_IF_BLOCKS,
|
|
|
|
*span,
|
|
|
|
"All code blocks contain the same code",
|
|
|
|
"Consider moving the code out like this",
|
|
|
|
sugg.clone(),
|
|
|
|
Applicability::Unspecified,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
SHARED_CODE_IN_IF_BLOCKS,
|
|
|
|
if_expr.span,
|
|
|
|
"All if blocks contain the same code",
|
|
|
|
move |diag| {
|
|
|
|
for (help, span, sugg) in suggestions {
|
|
|
|
diag.span_suggestion(span, help, sugg, Applicability::Unspecified);
|
|
|
|
}
|
|
|
|
},
|
2017-08-09 07:30:56 +00:00
|
|
|
);
|
2016-01-30 18:16:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-11 22:29:53 +00:00
|
|
|
pub struct SymbolFinderVisitor<'a, 'tcx> {
|
|
|
|
cx: &'a LateContext<'tcx>,
|
|
|
|
defs: FxHashSet<HirId>,
|
|
|
|
uses: FxHashSet<HirId>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'tcx> SymbolFinderVisitor<'a, 'tcx> {
|
|
|
|
fn new(cx: &'a LateContext<'tcx>) -> Self {
|
|
|
|
SymbolFinderVisitor {
|
|
|
|
cx,
|
|
|
|
defs: FxHashSet::default(),
|
|
|
|
uses: FxHashSet::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'tcx> Visitor<'tcx> for SymbolFinderVisitor<'a, 'tcx> {
|
|
|
|
type Map = Map<'tcx>;
|
|
|
|
|
|
|
|
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
|
|
|
NestedVisitorMap::All(self.cx.tcx.hir())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visit_local(&mut self, l: &'tcx rustc_hir::Local<'tcx>) {
|
|
|
|
let local_id = l.pat.hir_id;
|
|
|
|
self.defs.insert(local_id);
|
|
|
|
if let Some(expr) = l.init {
|
|
|
|
intravisit::walk_expr(self, expr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visit_qpath(&mut self, qpath: &'tcx rustc_hir::QPath<'tcx>, id: HirId, _span: rustc_span::Span) {
|
|
|
|
if let rustc_hir::QPath::Resolved(_, ref path) = *qpath {
|
|
|
|
if path.segments.len() == 1 {
|
|
|
|
if let rustc_hir::def::Res::Local(var) = self.cx.qpath_res(qpath, id) {
|
|
|
|
self.uses.insert(var);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-30 18:16:49 +00:00
|
|
|
/// Implementation of `IFS_SAME_COND`.
|
2020-06-25 20:41:36 +00:00
|
|
|
fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
|
2019-12-27 07:12:26 +00:00
|
|
|
let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 {
|
2020-06-26 02:55:23 +00:00
|
|
|
let mut h = SpanlessHash::new(cx);
|
2016-02-09 15:45:47 +00:00
|
|
|
h.hash_expr(expr);
|
|
|
|
h.finish()
|
|
|
|
};
|
2016-02-10 00:22:53 +00:00
|
|
|
|
2020-08-28 14:10:16 +00:00
|
|
|
let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { eq_expr_value(cx, lhs, rhs) };
|
2016-02-09 15:45:47 +00:00
|
|
|
|
2019-05-20 08:22:13 +00:00
|
|
|
for (i, j) in search_same(conds, hash, eq) {
|
2020-01-27 02:26:42 +00:00
|
|
|
span_lint_and_note(
|
2017-08-09 07:30:56 +00:00
|
|
|
cx,
|
|
|
|
IFS_SAME_COND,
|
|
|
|
j.span,
|
2020-01-06 06:30:43 +00:00
|
|
|
"this `if` has the same condition as a previous `if`",
|
2020-04-18 10:29:36 +00:00
|
|
|
Some(i.span),
|
2017-08-09 07:30:56 +00:00
|
|
|
"same as this",
|
|
|
|
);
|
2016-02-10 00:22:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-14 05:06:34 +00:00
|
|
|
/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`.
|
2020-06-25 20:41:36 +00:00
|
|
|
fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
|
2019-12-27 07:12:26 +00:00
|
|
|
let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 {
|
2020-06-26 02:55:23 +00:00
|
|
|
let mut h = SpanlessHash::new(cx);
|
2019-11-14 05:06:34 +00:00
|
|
|
h.hash_expr(expr);
|
|
|
|
h.finish()
|
|
|
|
};
|
|
|
|
|
2019-12-27 07:12:26 +00:00
|
|
|
let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool {
|
2020-10-23 20:16:59 +00:00
|
|
|
// Do not lint if any expr originates from a macro
|
|
|
|
if in_macro(lhs.span) || in_macro(rhs.span) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-11-14 05:06:34 +00:00
|
|
|
// Do not spawn warning if `IFS_SAME_COND` already produced it.
|
2020-08-28 14:10:16 +00:00
|
|
|
if eq_expr_value(cx, lhs, rhs) {
|
2019-11-14 05:06:34 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
SpanlessEq::new(cx).eq_expr(lhs, rhs)
|
|
|
|
};
|
|
|
|
|
|
|
|
for (i, j) in search_same(conds, hash, eq) {
|
2020-01-27 02:26:42 +00:00
|
|
|
span_lint_and_note(
|
2019-11-14 05:06:34 +00:00
|
|
|
cx,
|
|
|
|
SAME_FUNCTIONS_IN_IF_CONDITION,
|
|
|
|
j.span,
|
2020-01-06 06:30:43 +00:00
|
|
|
"this `if` has the same function call as a previous `if`",
|
2020-04-18 10:29:36 +00:00
|
|
|
Some(i.span),
|
2019-11-14 05:06:34 +00:00
|
|
|
"same as this",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|