@@ -3,19 +3,19 @@ use crate::utils::sugg::Sugg;
3
3
use crate :: utils:: visitors:: LocalUsedVisitor ;
4
4
use crate :: utils:: {
5
5
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,
10
10
} ;
11
11
use crate :: utils:: { paths, search_same, SpanlessEq , SpanlessHash } ;
12
12
use if_chain:: if_chain;
13
13
use rustc_ast:: ast:: LitKind ;
14
- use rustc_data_structures:: fx:: FxHashMap ;
14
+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
15
15
use rustc_errors:: Applicability ;
16
16
use rustc_hir:: def:: CtorKind ;
17
17
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 ,
19
19
PatKind , QPath , RangeEnd ,
20
20
} ;
21
21
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
@@ -24,7 +24,7 @@ use rustc_middle::ty::{self, Ty, TyS};
24
24
use rustc_semver:: RustcVersion ;
25
25
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
26
26
use rustc_span:: source_map:: { Span , Spanned } ;
27
- use rustc_span:: { sym, Symbol } ;
27
+ use rustc_span:: sym;
28
28
use std:: cmp:: Ordering ;
29
29
use std:: collections:: hash_map:: Entry ;
30
30
use std:: collections:: Bound ;
@@ -1873,13 +1873,6 @@ fn test_overlapping() {
1873
1873
1874
1874
/// Implementation of `MATCH_SAME_ARMS`.
1875
1875
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
-
1883
1876
if let ExprKind :: Match ( _, ref arms, MatchSource :: Normal ) = expr. kind {
1884
1877
let hash = |& ( _, arm) : & ( usize , & Arm < ' _ > ) | -> u64 {
1885
1878
let mut h = SpanlessHash :: new ( cx) ;
@@ -1891,12 +1884,38 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
1891
1884
let min_index = usize:: min ( lindex, rindex) ;
1892
1885
let max_index = usize:: max ( lindex, rindex) ;
1893
1886
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
+ } ;
1894
1910
// Arms with a guard are ignored, those can’t always be merged together
1895
1911
// 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 ( ) )
1900
1919
} ;
1901
1920
1902
1921
let indexed_arms: Vec < ( usize , & Arm < ' _ > ) > = arms. iter ( ) . enumerate ( ) . collect ( ) ;
@@ -1939,50 +1958,18 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
1939
1958
}
1940
1959
}
1941
1960
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
+ } ) ;
1987
1967
result
1988
1968
}
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