about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/copies.rs305
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/literal_representation.rs40
-rw-r--r--clippy_lints/src/methods/manual_saturating_arithmetic.rs48
-rw-r--r--clippy_utils/src/hir_utils.rs9
-rw-r--r--tests/ui/checked_unwrap/complex_conditionals.rs2
-rw-r--r--tests/ui/if_same_then_else.rs3
-rw-r--r--tests/ui/if_same_then_else.stderr88
-rw-r--r--tests/ui/if_same_then_else2.rs3
-rw-r--r--tests/ui/if_same_then_else2.stderr80
-rw-r--r--tests/ui/let_if_seq.rs3
-rw-r--r--tests/ui/let_if_seq.stderr8
-rw-r--r--tests/ui/shared_code_in_if_blocks/shared_at_bot.rs47
-rw-r--r--tests/ui/shared_code_in_if_blocks/shared_at_top.rs83
-rw-r--r--tests/ui/shared_code_in_if_blocks/shared_at_top_and_bot.rs22
-rw-r--r--tests/ui/shared_code_in_if_blocks/valid_if_blocks.rs157
17 files changed, 737 insertions, 164 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb4a0b500f2..790bca4ac18 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2455,6 +2455,7 @@ Released 2018-09-13
 [`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse
 [`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same
 [`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated
+[`shared_code_in_if_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#shared_code_in_if_blocks
 [`short_circuit_statement`]: https://rust-lang.github.io/rust-clippy/master/index.html#short_circuit_statement
 [`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq
 [`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs
index 46093a16571..29ff1970705 100644
--- a/clippy_lints/src/copies.rs
+++ b/clippy_lints/src/copies.rs
@@ -3,7 +3,10 @@ use clippy_utils::{eq_expr_value, in_macro, search_same, SpanlessEq, SpanlessHas
 use clippy_utils::{get_parent_expr, if_sequence};
 use rustc_hir::{Block, Expr, ExprKind};
 use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
 use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use std::borrow::Cow;
 
 declare_clippy_lint! {
     /// **What it does:** Checks for consecutive `if`s with the same condition.
@@ -104,7 +107,45 @@ declare_clippy_lint! {
     "`if` with the same `then` and `else` blocks"
 }
 
-declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE]);
+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
+]);
 
 impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
@@ -121,30 +162,256 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
             }
 
             let (conds, blocks) = if_sequence(expr);
-            lint_same_then_else(cx, &blocks);
+            // Conditions
             lint_same_cond(cx, &conds);
             lint_same_fns_in_if_cond(cx, &conds);
+            // Block duplication
+            lint_same_then_else(cx, &blocks, conds.len() != blocks.len(), expr);
         }
     }
 }
 
-/// Implementation of `IF_SAME_THEN_ELSE`.
-fn lint_same_then_else(cx: &LateContext<'_>, blocks: &[&Block<'_>]) {
-    let eq: &dyn Fn(&&Block<'_>, &&Block<'_>) -> bool =
-        &|&lhs, &rhs| -> bool { SpanlessEq::new(cx).eq_block(lhs, rhs) };
+/// 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;
+    }
 
-    if let Some((i, j)) = search_same_sequenced(blocks, eq) {
-        span_lint_and_note(
+    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(
             cx,
-            IF_SAME_THEN_ELSE,
-            j.span,
-            "this `if` has identical blocks",
-            Some(i.span),
-            "same as this",
+            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);
+                }
+            },
         );
     }
 }
 
+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);
+                }
+            }
+        }
+    }
+}
+
 /// Implementation of `IFS_SAME_COND`.
 fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
     let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 {
@@ -198,15 +465,3 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
         );
     }
 }
-
-fn search_same_sequenced<T, Eq>(exprs: &[T], eq: Eq) -> Option<(&T, &T)>
-where
-    Eq: Fn(&T, &T) -> bool,
-{
-    for win in exprs.windows(2) {
-        if eq(&win[0], &win[1]) {
-            return Some((&win[0], &win[1]));
-        }
-    }
-    None
-}
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index b93e6b5b2e8..ba98a2a2607 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -616,6 +616,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         &copies::IFS_SAME_COND,
         &copies::IF_SAME_THEN_ELSE,
         &copies::SAME_FUNCTIONS_IN_IF_CONDITION,
+        &copies::SHARED_CODE_IN_IF_BLOCKS,
         &copy_iterator::COPY_ITERATOR,
         &create_dir::CREATE_DIR,
         &dbg_macro::DBG_MACRO,
@@ -1365,6 +1366,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&casts::PTR_AS_PTR),
         LintId::of(&checked_conversions::CHECKED_CONVERSIONS),
         LintId::of(&copies::SAME_FUNCTIONS_IN_IF_CONDITION),
+        LintId::of(&copies::SHARED_CODE_IN_IF_BLOCKS),
         LintId::of(&copy_iterator::COPY_ITERATOR),
         LintId::of(&default::DEFAULT_TRAIT_ACCESS),
         LintId::of(&dereference::EXPLICIT_DEREF_METHODS),
diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs
index 7fd55151226..b736eeff6bf 100644
--- a/clippy_lints/src/literal_representation.rs
+++ b/clippy_lints/src/literal_representation.rs
@@ -464,32 +464,28 @@ impl DecimalLiteralRepresentation {
             {
                 return Err(WarningType::DecimalRepresentation);
             }
-        } else if digits.len() < 4 {
-            // Lint for Literals with a hex-representation of 2 or 3 digits
-            let f = &digits[0..1]; // first digit
-            let s = &digits[1..]; // suffix
-
-            // Powers of 2
-            if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0'))
-                // Powers of 2 minus 1
-                || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F'))
-            {
-                return Err(WarningType::DecimalRepresentation);
-            }
         } else {
-            // Lint for Literals with a hex-representation of 4 digits or more
             let f = &digits[0..1]; // first digit
             let m = &digits[1..digits.len() - 1]; // middle digits, except last
             let s = &digits[1..]; // suffix
-
-            // Powers of 2 with a margin of +15/-16
-            if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0'))
-                || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F'))
-                // Lint for representations with only 0s and Fs, while allowing 7 as the first
-                // digit
-                || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
-            {
-                return Err(WarningType::DecimalRepresentation);
+            if digits.len() < 4 {
+                // Powers of 2
+                if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0'))
+                    // Powers of 2 minus 1
+                    || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F'))
+                {
+                    return Err(WarningType::DecimalRepresentation);
+                }
+            } else {
+                // Powers of 2 with a margin of +15/-16
+                if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0'))
+                    || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F'))
+                    // Lint for representations with only 0s and Fs, while allowing 7 as the first
+                    // digit
+                    || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
+                {
+                    return Err(WarningType::DecimalRepresentation);
+                }
             }
         }
 
diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints/src/methods/manual_saturating_arithmetic.rs
index 6c5a842a912..ecb8b72ef46 100644
--- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs
+++ b/clippy_lints/src/methods/manual_saturating_arithmetic.rs
@@ -44,44 +44,28 @@ pub fn check(
             // "mul" is omitted because lhs can be negative.
             _ => return,
         }
-
-        let mut applicability = Applicability::MachineApplicable;
-        span_lint_and_sugg(
-            cx,
-            super::MANUAL_SATURATING_ARITHMETIC,
-            expr.span,
-            "manual saturating arithmetic",
-            &format!("try using `saturating_{}`", arith),
-            format!(
-                "{}.saturating_{}({})",
-                snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
-                arith,
-                snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
-            ),
-            applicability,
-        );
     } else {
         match (mm, arith) {
             (MinMax::Max, "add" | "mul") | (MinMax::Min, "sub") => (),
             _ => return,
         }
-
-        let mut applicability = Applicability::MachineApplicable;
-        span_lint_and_sugg(
-            cx,
-            super::MANUAL_SATURATING_ARITHMETIC,
-            expr.span,
-            "manual saturating arithmetic",
-            &format!("try using `saturating_{}`", arith),
-            format!(
-                "{}.saturating_{}({})",
-                snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
-                arith,
-                snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
-            ),
-            applicability,
-        );
     }
+
+    let mut applicability = Applicability::MachineApplicable;
+    span_lint_and_sugg(
+        cx,
+        super::MANUAL_SATURATING_ARITHMETIC,
+        expr.span,
+        "manual saturating arithmetic",
+        &format!("try using `saturating_{}`", arith),
+        format!(
+            "{}.saturating_{}({})",
+            snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
+            arith,
+            snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
+        ),
+        applicability,
+    );
 }
 
 #[derive(PartialEq, Eq)]
diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs
index 0c3b8b89171..8b90fedbafe 100644
--- a/clippy_utils/src/hir_utils.rs
+++ b/clippy_utils/src/hir_utils.rs
@@ -483,6 +483,15 @@ pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -
     left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
 }
 
+/// Counts how many elements of the slices are equal as per `eq_fn`.
+pub fn count_eq<X: Sized>(
+    left: &mut dyn Iterator<Item = X>,
+    right: &mut dyn Iterator<Item = X>,
+    mut eq_fn: impl FnMut(&X, &X) -> bool,
+) -> usize {
+    left.zip(right).take_while(|(l, r)| eq_fn(l, r)).count()
+}
+
 /// Checks if two expressions evaluate to the same value, and don't contain any side effects.
 pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool {
     SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right)
diff --git a/tests/ui/checked_unwrap/complex_conditionals.rs b/tests/ui/checked_unwrap/complex_conditionals.rs
index c986c992a07..bb5b6f5ec04 100644
--- a/tests/ui/checked_unwrap/complex_conditionals.rs
+++ b/tests/ui/checked_unwrap/complex_conditionals.rs
@@ -1,5 +1,5 @@
 #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)]
-#![allow(clippy::if_same_then_else)]
+#![allow(clippy::if_same_then_else, clippy::shared_code_in_if_blocks)]
 
 fn test_complex_conditions() {
     let x: Result<(), ()> = Ok(());
diff --git a/tests/ui/if_same_then_else.rs b/tests/ui/if_same_then_else.rs
index 9c5fe02f751..35a2e139c11 100644
--- a/tests/ui/if_same_then_else.rs
+++ b/tests/ui/if_same_then_else.rs
@@ -5,7 +5,8 @@
     clippy::never_loop,
     clippy::no_effect,
     clippy::unused_unit,
-    clippy::zero_divided_by_zero
+    clippy::zero_divided_by_zero,
+    clippy::shared_code_in_if_blocks
 )]
 
 struct Foo {
diff --git a/tests/ui/if_same_then_else.stderr b/tests/ui/if_same_then_else.stderr
index d9fdf06fa8b..2f38052fc20 100644
--- a/tests/ui/if_same_then_else.stderr
+++ b/tests/ui/if_same_then_else.stderr
@@ -1,111 +1,111 @@
 error: this `if` has identical blocks
-  --> $DIR/if_same_then_else.rs:28:12
+  --> $DIR/if_same_then_else.rs:21:13
    |
-LL |       } else {
-   |  ____________^
-LL | |         //~ ERROR same body as `if` block
+LL |       if true {
+   |  _____________^
 LL | |         Foo { bar: 42 };
 LL | |         0..10;
+LL | |         ..;
 ...  |
 LL | |         foo();
-LL | |     }
+LL | |     } else {
    | |_____^
    |
    = note: `-D clippy::if-same-then-else` implied by `-D warnings`
 note: same as this
-  --> $DIR/if_same_then_else.rs:20:13
+  --> $DIR/if_same_then_else.rs:29:12
    |
-LL |       if true {
-   |  _____________^
+LL |       } else {
+   |  ____________^
+LL | |         //~ ERROR same body as `if` block
 LL | |         Foo { bar: 42 };
 LL | |         0..10;
-LL | |         ..;
 ...  |
 LL | |         foo();
-LL | |     } else {
+LL | |     }
    | |_____^
 
 error: this `if` has identical blocks
-  --> $DIR/if_same_then_else.rs:66:12
-   |
-LL |       } else {
-   |  ____________^
-LL | |         //~ ERROR same body as `if` block
-LL | |         0.0
-LL | |     };
-   | |_____^
-   |
-note: same as this
-  --> $DIR/if_same_then_else.rs:64:21
+  --> $DIR/if_same_then_else.rs:65:21
    |
 LL |       let _ = if true {
    |  _____________________^
 LL | |         0.0
 LL | |     } else {
    | |_____^
-
-error: this `if` has identical blocks
-  --> $DIR/if_same_then_else.rs:73:12
+   |
+note: same as this
+  --> $DIR/if_same_then_else.rs:67:12
    |
 LL |       } else {
    |  ____________^
 LL | |         //~ ERROR same body as `if` block
-LL | |         -0.0
+LL | |         0.0
 LL | |     };
    | |_____^
-   |
-note: same as this
-  --> $DIR/if_same_then_else.rs:71:21
+
+error: this `if` has identical blocks
+  --> $DIR/if_same_then_else.rs:72:21
    |
 LL |       let _ = if true {
    |  _____________________^
 LL | |         -0.0
 LL | |     } else {
    | |_____^
-
-error: this `if` has identical blocks
-  --> $DIR/if_same_then_else.rs:89:12
+   |
+note: same as this
+  --> $DIR/if_same_then_else.rs:74:12
    |
 LL |       } else {
    |  ____________^
 LL | |         //~ ERROR same body as `if` block
-LL | |         42
+LL | |         -0.0
 LL | |     };
    | |_____^
-   |
-note: same as this
-  --> $DIR/if_same_then_else.rs:87:21
+
+error: this `if` has identical blocks
+  --> $DIR/if_same_then_else.rs:88:21
    |
 LL |       let _ = if true {
    |  _____________________^
 LL | |         42
 LL | |     } else {
    | |_____^
-
-error: this `if` has identical blocks
-  --> $DIR/if_same_then_else.rs:101:12
+   |
+note: same as this
+  --> $DIR/if_same_then_else.rs:90:12
    |
 LL |       } else {
    |  ____________^
 LL | |         //~ ERROR same body as `if` block
+LL | |         42
+LL | |     };
+   | |_____^
+
+error: this `if` has identical blocks
+  --> $DIR/if_same_then_else.rs:95:13
+   |
+LL |       if true {
+   |  _____________^
 LL | |         let bar = if true { 42 } else { 43 };
 LL | |
+LL | |         while foo() {
 ...  |
 LL | |         bar + 1;
-LL | |     }
+LL | |     } else {
    | |_____^
    |
 note: same as this
-  --> $DIR/if_same_then_else.rs:94:13
+  --> $DIR/if_same_then_else.rs:102:12
    |
-LL |       if true {
-   |  _____________^
+LL |       } else {
+   |  ____________^
+LL | |         //~ ERROR same body as `if` block
 LL | |         let bar = if true { 42 } else { 43 };
 LL | |
-LL | |         while foo() {
 ...  |
 LL | |         bar + 1;
-LL | |     } else {
+LL | |     }
    | |_____^
 
 error: aborting due to 5 previous errors
diff --git a/tests/ui/if_same_then_else2.rs b/tests/ui/if_same_then_else2.rs
index a2ff1b741ca..8164b125570 100644
--- a/tests/ui/if_same_then_else2.rs
+++ b/tests/ui/if_same_then_else2.rs
@@ -5,7 +5,8 @@
     clippy::collapsible_if,
     clippy::ifs_same_cond,
     clippy::needless_return,
-    clippy::single_element_loop
+    clippy::single_element_loop,
+    clippy::shared_code_in_if_blocks
 )]
 
 fn if_same_then_else2() -> Result<&'static str, ()> {
diff --git a/tests/ui/if_same_then_else2.stderr b/tests/ui/if_same_then_else2.stderr
index 454322d8aac..941b92f23f3 100644
--- a/tests/ui/if_same_then_else2.stderr
+++ b/tests/ui/if_same_then_else2.stderr
@@ -24,18 +24,22 @@ LL | |             if foo.is_some() {
 LL | |         }
 LL | |     } else {
    | |_____^
-
-error: this `if` has identical blocks
-  --> $DIR/if_same_then_else2.rs:35:12
+   |
+   = note: `-D clippy::if-same-then-else` implied by `-D warnings`
+note: same as this
+  --> $DIR/if_same_then_else2.rs:21:12
    |
 LL |       } else {
    |  ____________^
 LL | |         //~ ERROR same body as `if` block
-LL | |         if let Some(a) = Some(42) {}
+LL | |         for _ in &[42] {
+LL | |             let foo: &Option<_> = &Some::<u8>(42);
+...  |
+LL | |         }
 LL | |     }
    | |_____^
-   |
-note: same as this
+
+error: this `if` has identical blocks
   --> $DIR/if_same_then_else2.rs:33:13
    |
 LL |       if true {
@@ -43,18 +47,18 @@ LL |       if true {
 LL | |         if let Some(a) = Some(42) {}
 LL | |     } else {
    | |_____^
-
-error: this `if` has identical blocks
-  --> $DIR/if_same_then_else2.rs:42:12
+   |
+note: same as this
+  --> $DIR/if_same_then_else2.rs:35:12
    |
 LL |       } else {
    |  ____________^
 LL | |         //~ ERROR same body as `if` block
-LL | |         if let (1, .., 3) = (1, 2, 3) {}
+LL | |         if let Some(a) = Some(42) {}
 LL | |     }
    | |_____^
-   |
-note: same as this
+
+error: this `if` has identical blocks
   --> $DIR/if_same_then_else2.rs:40:13
    |
 LL |       if true {
@@ -62,18 +66,18 @@ LL |       if true {
 LL | |         if let (1, .., 3) = (1, 2, 3) {}
 LL | |     } else {
    | |_____^
-
-error: this `if` has identical blocks
-  --> $DIR/if_same_then_else2.rs:92:12
+   |
+note: same as this
+  --> $DIR/if_same_then_else2.rs:42:12
    |
 LL |       } else {
    |  ____________^
 LL | |         //~ ERROR same body as `if` block
-LL | |         f32::NAN
-LL | |     };
+LL | |         if let (1, .., 3) = (1, 2, 3) {}
+LL | |     }
    | |_____^
-   |
-note: same as this
+
+error: this `if` has identical blocks
   --> $DIR/if_same_then_else2.rs:90:21
    |
 LL |       let _ = if true {
@@ -81,18 +85,18 @@ LL |       let _ = if true {
 LL | |         f32::NAN
 LL | |     } else {
    | |_____^
-
-error: this `if` has identical blocks
-  --> $DIR/if_same_then_else2.rs:99:12
+   |
+note: same as this
+  --> $DIR/if_same_then_else2.rs:92:12
    |
 LL |       } else {
    |  ____________^
 LL | |         //~ ERROR same body as `if` block
-LL | |         Ok("foo")?;
-LL | |     }
+LL | |         f32::NAN
+LL | |     };
    | |_____^
-   |
-note: same as this
+
+error: this `if` has identical blocks
   --> $DIR/if_same_then_else2.rs:97:13
    |
 LL |       if true {
@@ -100,18 +104,18 @@ LL |       if true {
 LL | |         Ok("foo")?;
 LL | |     } else {
    | |_____^
-
-error: this `if` has identical blocks
-  --> $DIR/if_same_then_else2.rs:124:12
+   |
+note: same as this
+  --> $DIR/if_same_then_else2.rs:99:12
    |
 LL |       } else {
    |  ____________^
-LL | |         let foo = "";
-LL | |         return Ok(&foo[0..]);
+LL | |         //~ ERROR same body as `if` block
+LL | |         Ok("foo")?;
 LL | |     }
    | |_____^
-   |
-note: same as this
+
+error: this `if` has identical blocks
   --> $DIR/if_same_then_else2.rs:121:20
    |
 LL |       } else if true {
@@ -120,6 +124,16 @@ LL | |         let foo = "";
 LL | |         return Ok(&foo[0..]);
 LL | |     } else {
    | |_____^
+   |
+note: same as this
+  --> $DIR/if_same_then_else2.rs:124:12
+   |
+LL |       } else {
+   |  ____________^
+LL | |         let foo = "";
+LL | |         return Ok(&foo[0..]);
+LL | |     }
+   | |_____^
 
 error: aborting due to 6 previous errors
 
diff --git a/tests/ui/let_if_seq.rs b/tests/ui/let_if_seq.rs
index 32a67f181df..9fd3f875a5f 100644
--- a/tests/ui/let_if_seq.rs
+++ b/tests/ui/let_if_seq.rs
@@ -2,7 +2,8 @@
     unused_variables,
     unused_assignments,
     clippy::similar_names,
-    clippy::blacklisted_name
+    clippy::blacklisted_name,
+    clippy::shared_code_in_if_blocks
 )]
 #![warn(clippy::useless_let_if_seq)]
 
diff --git a/tests/ui/let_if_seq.stderr b/tests/ui/let_if_seq.stderr
index 7de560c7348..9cf2e10a5ee 100644
--- a/tests/ui/let_if_seq.stderr
+++ b/tests/ui/let_if_seq.stderr
@@ -1,5 +1,5 @@
 error: `if _ { .. } else { .. }` is an expression
-  --> $DIR/let_if_seq.rs:64:5
+  --> $DIR/let_if_seq.rs:65:5
    |
 LL | /     let mut foo = 0;
 LL | |     if f() {
@@ -11,7 +11,7 @@ LL | |     }
    = note: you might not need `mut` at all
 
 error: `if _ { .. } else { .. }` is an expression
-  --> $DIR/let_if_seq.rs:69:5
+  --> $DIR/let_if_seq.rs:70:5
    |
 LL | /     let mut bar = 0;
 LL | |     if f() {
@@ -25,7 +25,7 @@ LL | |     }
    = note: you might not need `mut` at all
 
 error: `if _ { .. } else { .. }` is an expression
-  --> $DIR/let_if_seq.rs:77:5
+  --> $DIR/let_if_seq.rs:78:5
    |
 LL | /     let quz;
 LL | |     if f() {
@@ -36,7 +36,7 @@ LL | |     }
    | |_____^ help: it is more idiomatic to write: `let quz = if f() { 42 } else { 0 };`
 
 error: `if _ { .. } else { .. }` is an expression
-  --> $DIR/let_if_seq.rs:106:5
+  --> $DIR/let_if_seq.rs:107:5
    |
 LL | /     let mut baz = 0;
 LL | |     if f() {
diff --git a/tests/ui/shared_code_in_if_blocks/shared_at_bot.rs b/tests/ui/shared_code_in_if_blocks/shared_at_bot.rs
new file mode 100644
index 00000000000..22f1fe95f79
--- /dev/null
+++ b/tests/ui/shared_code_in_if_blocks/shared_at_bot.rs
@@ -0,0 +1,47 @@
+#![allow(dead_code)]
+#![deny(clippy::if_same_then_else, clippy::shared_code_in_if_blocks)]
+
+// This tests the shared_code_in_if_blocks lint at the end of blocks
+
+fn simple_examples() {
+    // TODO xFrednet 2021-01-06: Test with const literals at the end
+    let x = 1;
+
+    let _ = if x == 7 {
+        println!("Branch I");
+        let start_value = 0;
+        println!("=^.^=");
+
+        // Same but not moveable due to `start_value`
+        let _ = start_value;
+
+        // The rest is self contained and moveable => Only lint the rest
+        let result = false;
+        println!("Block end!");
+        result
+    } else {
+        println!("Branch II");
+        let start_value = 8;
+        println!("xD");
+
+        // Same but not moveable due to `start_value`
+        let _ = start_value;
+
+        // The rest is self contained and moveable => Only lint the rest
+        let result = false;
+        println!("Block end!");
+        result
+    };
+}
+
+/// Simple examples where the move can cause some problems due to moved values
+fn simple_but_suggestion_is_invalid() {
+    // TODO xFrednet 2021-01-12: This
+}
+
+/// Tests where the blocks are not linted due to the used value scope
+fn not_moveable_due_to_value_scope() {
+    // TODO xFrednet 2021-01-12: This
+}
+
+fn main() {}
diff --git a/tests/ui/shared_code_in_if_blocks/shared_at_top.rs b/tests/ui/shared_code_in_if_blocks/shared_at_top.rs
new file mode 100644
index 00000000000..0a6828127e9
--- /dev/null
+++ b/tests/ui/shared_code_in_if_blocks/shared_at_top.rs
@@ -0,0 +1,83 @@
+#![allow(dead_code, clippy::eval_order_dependence)]
+#![deny(clippy::if_same_then_else, clippy::shared_code_in_if_blocks)]
+
+// This tests the shared_code_in_if_blocks lint at the start of blocks
+
+fn simple_examples() {
+    let x = 0;
+
+    // Simple
+    if true {
+        println!("Hello World!");
+        println!("I'm branch nr: 1");
+    } else {
+        println!("Hello World!");
+        println!("I'm branch nr: 2");
+    }
+
+    // Else if
+    if x == 0 {
+        let y = 9;
+        println!("The value y was set to: `{}`", y);
+        let _z = y;
+
+        println!("I'm the true start index of arrays");
+    } else if x == 1 {
+        let y = 9;
+        println!("The value y was set to: `{}`", y);
+        let _z = y;
+
+        println!("I start counting from 1 so my array starts from `1`");
+    } else {
+        let y = 9;
+        println!("The value y was set to: `{}`", y);
+        let _z = y;
+
+        println!("Ha, Pascal allows you to start the array where you want")
+    }
+
+    // Return a value
+    let _ = if x == 7 {
+        let y = 16;
+        println!("What can I say except: \"you're welcome?\"");
+        let _ = y;
+        x
+    } else {
+        let y = 16;
+        println!("Thank you");
+        y
+    };
+}
+
+/// Simple examples where the move can cause some problems due to moved values
+fn simple_but_suggestion_is_invalid() {
+    let x = 10;
+
+    // Can't be automatically moved because used_value_name is getting used again
+    let used_value_name = 19;
+    if x == 10 {
+        let used_value_name = "Different type";
+        println!("Str: {}", used_value_name);
+        let _ = 1;
+    } else {
+        let used_value_name = "Different type";
+        println!("Str: {}", used_value_name);
+        let _ = 2;
+    }
+    let _ = used_value_name;
+
+    // This can be automatically moved as `can_be_overridden` is not used again
+    let can_be_overridden = 8;
+    let _ = can_be_overridden;
+    if x == 11 {
+        let can_be_overridden = "Move me";
+        println!("I'm also moveable");
+        let _ = 111;
+    } else {
+        let can_be_overridden = "Move me";
+        println!("I'm also moveable");
+        let _ = 222;
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/shared_code_in_if_blocks/shared_at_top_and_bot.rs b/tests/ui/shared_code_in_if_blocks/shared_at_top_and_bot.rs
new file mode 100644
index 00000000000..8de69623e5d
--- /dev/null
+++ b/tests/ui/shared_code_in_if_blocks/shared_at_top_and_bot.rs
@@ -0,0 +1,22 @@
+#![allow(dead_code)]
+#![deny(clippy::if_same_then_else, clippy::shared_code_in_if_blocks)]
+
+// shared_code_in_if_blocks at the top and bottom of the if blocks
+
+fn main() {
+    // TODO xFrednet 2021-01-12: This
+}
+
+// General TODOs By xFrednet:
+
+// 
+// * Make a test with overlapping eq regions (else ifs)
+// * Test if as function parameter, tuple constructor, index, while loop condition
+// * Test where only the expression is the same
+// * Test where the block only has an expression
+// * Test with let on a previous line let _ = \n if...
+// * Tests with unreadable formatting (Inline if, Not indented)
+// * Test multiline condition if x == 9 \n x == 8 {}
+// * Test if for return/break (Only move to front)
+// * Test in inline closures
+// * Test with structs and tuples
diff --git a/tests/ui/shared_code_in_if_blocks/valid_if_blocks.rs b/tests/ui/shared_code_in_if_blocks/valid_if_blocks.rs
new file mode 100644
index 00000000000..1f12e9348d4
--- /dev/null
+++ b/tests/ui/shared_code_in_if_blocks/valid_if_blocks.rs
@@ -0,0 +1,157 @@
+#![allow(dead_code, clippy::eval_order_dependence)]
+#![deny(clippy::if_same_then_else, clippy::shared_code_in_if_blocks)]
+
+// This tests the shared_code_in_if_blocks lint at the start of blocks
+
+// Tests with value references are includes in "shared_code_at_bot.rs"
+
+fn valid_examples() {
+    let x = 2;
+
+    // The edge statements are different
+    if x == 9 {
+        let y = 1 << 5;
+
+        println!("This is the same: vvv");
+        let _z = y;
+        println!("The block expression is different");
+
+        println!("Different end 1");
+    } else {
+        let y = 1 << 7;
+
+        println!("This is the same: vvv");
+        let _z = y;
+        println!("The block expression is different");
+
+        println!("Different end 2");
+    }
+
+    // No else
+    if x == 2 {
+        println!("Hello world!");
+        println!("Hello back, how are you?");
+
+        // This is different vvvv
+        println!("Howdy stranger =^.^=");
+
+        println!("Bye Bye World");
+    } else if x == 9 {
+        println!("Hello world!");
+        println!("Hello back, how are you?");
+
+        // This is different vvvv
+        println!("Hello reviewer :D");
+
+        println!("Bye Bye World");
+    }
+
+    // Overlapping statements only in else if blocks -> Don't lint
+    if x == 0 {
+        println!("I'm important!")
+    } else if x == 17 {
+        println!("I share code in else if");
+
+        println!("x is 17");
+    } else {
+        println!("I share code in else if");
+
+        println!("x is nether x nor 17");
+    }
+
+    // Mutability is different
+    if x == 13 {
+        let mut y = 9;
+        println!("Value y is: {}", y);
+        y += 16;
+        let _z1 = y;
+    } else {
+        let y = 9;
+        println!("Value y is: {}", y);
+        let _z2 = y;
+    }
+
+    // Same blocks but at start and bottom so no `if_same_then_else` lint
+    if x == 418 {
+        let y = 9;
+        let z = 8;
+        let _ = (x, y, z);
+        // Don't tell the programmer, my code is also in the else block
+    } else if x == 419 {
+        println!("+-----------+");
+        println!("|           |");
+        println!("|  O     O  |");
+        println!("|     °     |");
+        println!("|  \\_____/  |");
+        println!("|           |");
+        println!("+-----------+");
+    } else {
+        let y = 9;
+        let z = 8;
+        let _ = (x, y, z);
+        // I'm so much better than the x == 418 block. Trust me
+    }
+}
+
+/// This makes sure that the `if_same_then_else` masks the `shared_code_in_if_blocks` lint
+fn trigger_other_lint() {
+    let x = 0;
+    let y = 1;
+
+    // Same block
+    if x == 0 {
+        let u = 19;
+        println!("How are u today?");
+        let _ = "This is a string";
+    } else {
+        let u = 19;
+        println!("How are u today?");
+        let _ = "This is a string";
+    }
+
+    // More complex same blocks
+    if x == 17 {
+        #[derive(Debug)]
+        struct Duck {
+            num: u64,
+        };
+        let pet = Duck { num: 18 };
+        println!("{:?}", pet);
+    } else {
+        #[derive(Debug)]
+        struct Duck {
+            num: u64,
+        };
+        let pet = Duck { num: 18 };
+        println!("{:?}", pet);
+    }
+
+    // Only same expression
+    let _ = if x == 6 { 7 } else { 7 };
+
+    // Same in else if block
+    let _ = if x == 67 {
+        println!("Well I'm the most important block");
+        "I'm a pretty string"
+    } else if x == 68 {
+        println!("I'm a doppelgänger");
+        // Don't listen to my clone below
+
+        if y == 90 {
+            "=^.^="
+        } else {
+            ":D"
+        }
+    } else {
+        // Don't listen to my clone above
+        println!("I'm a doppelgänger");
+
+        if y == 90 {
+            "=^.^="
+        } else {
+            ":D"
+        }
+    };
+}
+
+fn main() {}