+/// This function can also trigger the `IF_SAME_THEN_ELSE` in which case it'll return `None` to
+/// abort any further processing and avoid duplicate lint triggers.
+fn scan_block_for_eq(cx: &LateContext<'tcx>, blocks: &[&Block<'tcx>]) -> Option<BlockEqual> {
+ let mut start_eq = usize::MAX;
+ let mut end_eq = usize::MAX;
+ let mut expr_eq = true;
+ for win in blocks.windows(2) {
+ let l_stmts = win[0].stmts;
+ let r_stmts = win[1].stmts;
+
+ // `SpanlessEq` now keeps track of the locals and is therefore context sensitive clippy#6752.
+ // The comparison therefore needs to be done in a way that builds the correct context.
+ let mut evaluator = SpanlessEq::new(cx);
+ let mut evaluator = evaluator.inter_expr();
+
+ 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 = {
+ // We skip the middle statements which can't be equal
+ let end_comparison_count = l_stmts.len().min(r_stmts.len()) - current_start_eq;
+ let it1 = l_stmts.iter().skip(l_stmts.len() - end_comparison_count);
+ let it2 = r_stmts.iter().skip(r_stmts.len() - end_comparison_count);
+ it1.zip(it2)
+ .fold(0, |acc, (l, r)| if evaluator.eq_stmt(l, r) { acc + 1 } else { 0 })
+ };
+ let block_expr_eq = both(&win[0].expr, &win[1].expr, |l, r| evaluator.eq_expr(l, r));
+
+ // IF_SAME_THEN_ELSE
+ if_chain! {
+ if block_expr_eq;
+ if l_stmts.len() == r_stmts.len();
+ if l_stmts.len() == current_start_eq;
+ if !is_lint_allowed(cx, IF_SAME_THEN_ELSE, win[0].hir_id);
+ if !is_lint_allowed(cx, IF_SAME_THEN_ELSE, win[1].hir_id);
+ then {
+ 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 None;
+ }
+ }
+
+ start_eq = start_eq.min(current_start_eq);
+ end_eq = end_eq.min(current_end_eq);
+ expr_eq &= block_expr_eq;
+ }
+
+ if !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;
+ }
+
+ Some(BlockEqual {
+ start_eq,
+ end_eq,
+ expr_eq,
+ })
+}
+
+fn check_for_warn_of_moved_symbol(
+ cx: &LateContext<'tcx>,
+ symbols: &FxHashSet<Symbol>,
+ if_expr: &'tcx Expr<'_>,
+) -> bool {
+ get_enclosing_block(cx, if_expr.hir_id).map_or(false, |block| {
+ let ignore_span = block.span.shrink_to_lo().to(if_expr.span);
+
+ symbols
+ .iter()
+ .filter(|sym| !sym.as_str().starts_with('_'))
+ .any(move |sym| {
+ let mut walker = ContainsName {
+ name: *sym,
+ result: false,
+ };
+
+ // Scan block
+ block
+ .stmts
+ .iter()
+ .filter(|stmt| !ignore_span.overlaps(stmt.span))
+ .for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
+
+ if let Some(expr) = block.expr {
+ intravisit::walk_expr(&mut walker, expr);
+ }
+
+ walker.result
+ })
+ })
+}
+
+fn emit_branches_sharing_code_lint(
+ cx: &LateContext<'tcx>,
+ start_stmts: usize,
+ end_stmts: usize,
+ lint_end: bool,
+ warn_about_moved_symbol: 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![];
+ let mut add_expr_note = false;
+
+ // Construct suggestions
+ 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", 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 = block
+ .expr
+ .map_or_else(|| block.stmts[block.stmts.len() - 1].span, |expr| expr.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 mut span = moved_start.to(span_end);
+ // Improve formatting if the inner block has indention (i.e. normal Rust formatting)
+ let test_span = Span::new(span.lo() - BytePos(4), span.lo(), span.ctxt());
+ if snippet_opt(cx, test_span)
+ .map(|snip| snip == " ")
+ .unwrap_or_default()
+ {
+ span = span.with_lo(test_span.lo());
+ }
+
+ suggestions.push(("end", span, suggestion.to_string()));
+ add_expr_note = !cx.typeck_results().expr_ty(if_expr).is_unit();
+ }
+
+ let add_optional_msgs = |diag: &mut DiagnosticBuilder<'_>| {
+ if add_expr_note {
+ diag.note("The end suggestion probably needs some adjustments to use the expression result correctly");
+ }
+
+ if warn_about_moved_symbol {
+ diag.warn("Some moved values might need to be renamed to avoid wrong references");
+ }
+ };
+
+ // Emit lint
+ if suggestions.len() == 1 {
+ let (place_str, span, sugg) = suggestions.pop().unwrap();
+ let msg = format!("all if blocks contain the same code at the {}", place_str);
+ let help = format!("consider moving the {} statements out like this", place_str);
+ span_lint_and_then(cx, BRANCHES_SHARING_CODE, span, msg.as_str(), |diag| {
+ diag.span_suggestion(span, help.as_str(), sugg, Applicability::Unspecified);
+
+ add_optional_msgs(diag);
+ });
+ } else if suggestions.len() == 2 {
+ let (_, end_span, end_sugg) = suggestions.pop().unwrap();
+ let (_, start_span, start_sugg) = suggestions.pop().unwrap();
+ span_lint_and_then(