Skip to content

Commit 9ad6e26

Browse files
committed
Fix match_same_arms with SpanlessEq changes
1 parent 742922a commit 9ad6e26

File tree

1 file changed

+50
-63
lines changed

1 file changed

+50
-63
lines changed

clippy_lints/src/matches.rs

Lines changed: 50 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@ use crate::utils::sugg::Sugg;
33
use crate::utils::visitors::LocalUsedVisitor;
44
use crate::utils::{
55
expr_block, get_parent_expr, implements_trait, in_macro, indent_of, is_allowed, is_expn_of, is_refutable,
6-
is_type_diagnostic_item, is_wild, match_qpath, match_type, meets_msrv, multispan_sugg, path_to_local_id,
7-
peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block, snippet_opt,
8-
snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
9-
strip_pat_refs,
6+
is_type_diagnostic_item, is_wild, match_qpath, match_type, meets_msrv, multispan_sugg, path_to_local,
7+
path_to_local_id, peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block,
8+
snippet_opt, snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg,
9+
span_lint_and_then, strip_pat_refs,
1010
};
1111
use crate::utils::{paths, search_same, SpanlessEq, SpanlessHash};
1212
use if_chain::if_chain;
1313
use rustc_ast::ast::LitKind;
14-
use rustc_data_structures::fx::FxHashMap;
14+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1515
use rustc_errors::Applicability;
1616
use rustc_hir::def::CtorKind;
1717
use rustc_hir::{
18-
Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, Local, MatchSource, Mutability, Node, Pat,
18+
Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource, Mutability, Node, Pat,
1919
PatKind, QPath, RangeEnd,
2020
};
2121
use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -24,7 +24,7 @@ use rustc_middle::ty::{self, Ty, TyS};
2424
use rustc_semver::RustcVersion;
2525
use rustc_session::{declare_tool_lint, impl_lint_pass};
2626
use rustc_span::source_map::{Span, Spanned};
27-
use rustc_span::{sym, Symbol};
27+
use rustc_span::sym;
2828
use std::cmp::Ordering;
2929
use std::collections::hash_map::Entry;
3030
use std::collections::Bound;
@@ -1873,13 +1873,6 @@ fn test_overlapping() {
18731873

18741874
/// Implementation of `MATCH_SAME_ARMS`.
18751875
fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
1876-
fn same_bindings<'tcx>(lhs: &FxHashMap<Symbol, Ty<'tcx>>, rhs: &FxHashMap<Symbol, Ty<'tcx>>) -> bool {
1877-
lhs.len() == rhs.len()
1878-
&& lhs
1879-
.iter()
1880-
.all(|(name, l_ty)| rhs.get(name).map_or(false, |r_ty| TyS::same_type(l_ty, r_ty)))
1881-
}
1882-
18831876
if let ExprKind::Match(_, ref arms, MatchSource::Normal) = expr.kind {
18841877
let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
18851878
let mut h = SpanlessHash::new(cx);
@@ -1891,12 +1884,38 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
18911884
let min_index = usize::min(lindex, rindex);
18921885
let max_index = usize::max(lindex, rindex);
18931886

1887+
let mut local_map: FxHashMap<HirId, HirId> = FxHashMap::default();
1888+
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
1889+
if_chain! {
1890+
if let Some(a_id) = path_to_local(a);
1891+
if let Some(b_id) = path_to_local(b);
1892+
let entry = match local_map.entry(a_id) {
1893+
Entry::Vacant(entry) => entry,
1894+
// check if using the same bindings as before
1895+
Entry::Occupied(entry) => return *entry.get() == b_id,
1896+
};
1897+
// the names technically don't have to match; this makes the lint more conservative
1898+
if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id);
1899+
if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b));
1900+
if pat_contains_local(lhs.pat, a_id);
1901+
if pat_contains_local(rhs.pat, b_id);
1902+
then {
1903+
entry.insert(b_id);
1904+
true
1905+
} else {
1906+
false
1907+
}
1908+
}
1909+
};
18941910
// Arms with a guard are ignored, those can’t always be merged together
18951911
// This is also the case for arms in-between each there is an arm with a guard
1896-
(min_index..=max_index).all(|index| arms[index].guard.is_none()) &&
1897-
SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) &&
1898-
// all patterns should have the same bindings
1899-
same_bindings(&bindings(cx, &lhs.pat), &bindings(cx, &rhs.pat))
1912+
(min_index..=max_index).all(|index| arms[index].guard.is_none())
1913+
&& SpanlessEq::new(cx)
1914+
.expr_fallback(eq_fallback)
1915+
.eq_expr(&lhs.body, &rhs.body)
1916+
// these checks could be removed to allow unused bindings
1917+
&& bindings_eq(lhs.pat, local_map.keys().copied().collect())
1918+
&& bindings_eq(rhs.pat, local_map.values().copied().collect())
19001919
};
19011920

19021921
let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
@@ -1939,50 +1958,18 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
19391958
}
19401959
}
19411960

1942-
/// Returns the list of bindings in a pattern.
1943-
fn bindings<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>) -> FxHashMap<Symbol, Ty<'tcx>> {
1944-
fn bindings_impl<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, map: &mut FxHashMap<Symbol, Ty<'tcx>>) {
1945-
match pat.kind {
1946-
PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => bindings_impl(cx, pat, map),
1947-
PatKind::TupleStruct(_, pats, _) => {
1948-
for pat in pats {
1949-
bindings_impl(cx, pat, map);
1950-
}
1951-
},
1952-
PatKind::Binding(.., ident, ref as_pat) => {
1953-
if let Entry::Vacant(v) = map.entry(ident.name) {
1954-
v.insert(cx.typeck_results().pat_ty(pat));
1955-
}
1956-
if let Some(ref as_pat) = *as_pat {
1957-
bindings_impl(cx, as_pat, map);
1958-
}
1959-
},
1960-
PatKind::Or(fields) | PatKind::Tuple(fields, _) => {
1961-
for pat in fields {
1962-
bindings_impl(cx, pat, map);
1963-
}
1964-
},
1965-
PatKind::Struct(_, fields, _) => {
1966-
for pat in fields {
1967-
bindings_impl(cx, &pat.pat, map);
1968-
}
1969-
},
1970-
PatKind::Slice(lhs, ref mid, rhs) => {
1971-
for pat in lhs {
1972-
bindings_impl(cx, pat, map);
1973-
}
1974-
if let Some(ref mid) = *mid {
1975-
bindings_impl(cx, mid, map);
1976-
}
1977-
for pat in rhs {
1978-
bindings_impl(cx, pat, map);
1979-
}
1980-
},
1981-
PatKind::Lit(..) | PatKind::Range(..) | PatKind::Wild | PatKind::Path(..) => (),
1982-
}
1983-
}
1984-
1985-
let mut result = FxHashMap::default();
1986-
bindings_impl(cx, pat, &mut result);
1961+
fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool {
1962+
let mut result = false;
1963+
pat.walk_short(|p| {
1964+
result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id);
1965+
!result
1966+
});
19871967
result
19881968
}
1969+
1970+
/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa
1971+
fn bindings_eq(pat: &Pat<'_>, mut ids: FxHashSet<HirId>) -> bool {
1972+
let mut result = true;
1973+
pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id));
1974+
result && ids.is_empty()
1975+
}

0 commit comments

Comments
 (0)