@@ -5,7 +5,7 @@ use crate::tokenize_with_text;
5
5
use rustc_ast:: ast:: InlineAsmTemplatePiece ;
6
6
use rustc_data_structures:: fx:: FxHasher ;
7
7
use rustc_hir:: MatchSource :: TryDesugar ;
8
- use rustc_hir:: def:: Res ;
8
+ use rustc_hir:: def:: { DefKind , Res } ;
9
9
use rustc_hir:: {
10
10
ArrayLen , AssocItemConstraint , BinOpKind , BindingMode , Block , BodyId , Closure , ConstArg , ConstArgKind , Expr ,
11
11
ExprField , ExprKind , FnRetTy , GenericArg , GenericArgs , HirId , HirIdMap , InlineAsmOperand , LetExpr , Lifetime ,
@@ -17,11 +17,33 @@ use rustc_middle::ty::TypeckResults;
17
17
use rustc_span:: { BytePos , ExpnKind , MacroKind , Symbol , SyntaxContext , sym} ;
18
18
use std:: hash:: { Hash , Hasher } ;
19
19
use std:: ops:: Range ;
20
+ use std:: slice;
20
21
21
22
/// Callback that is called when two expressions are not equal in the sense of `SpanlessEq`, but
22
23
/// other conditions would make them equal.
23
24
type SpanlessEqCallback < ' a > = dyn FnMut ( & Expr < ' _ > , & Expr < ' _ > ) -> bool + ' a ;
24
25
26
+ /// Determines how paths are hashed and compared for equality.
27
+ #[ derive( Copy , Clone , Debug , Default ) ]
28
+ pub enum PathCheck {
29
+ /// Paths must match exactly and are hashed by their exact HIR tree.
30
+ ///
31
+ /// Thus, `std::iter::Iterator` and `Iterator` are not considered equal even though they refer
32
+ /// to the same item.
33
+ #[ default]
34
+ Exact ,
35
+ /// Paths are compared and hashed based on their resolution.
36
+ ///
37
+ /// They can appear different in the HIR tree but are still considered equal
38
+ /// and have equal hashes as long as they refer to the same item.
39
+ ///
40
+ /// Note that this is currently only partially implemented specifically for paths that are
41
+ /// resolved before type-checking, i.e. the final segment must have a non-error resolution.
42
+ /// If a path with an error resolution is encountered, it falls back to the default exact
43
+ /// matching behavior.
44
+ Resolution ,
45
+ }
46
+
25
47
/// Type used to check whether two ast are the same. This is different from the
26
48
/// operator `==` on ast types as this operator would compare true equality with
27
49
/// ID and span.
@@ -33,6 +55,7 @@ pub struct SpanlessEq<'a, 'tcx> {
33
55
maybe_typeck_results : Option < ( & ' tcx TypeckResults < ' tcx > , & ' tcx TypeckResults < ' tcx > ) > ,
34
56
allow_side_effects : bool ,
35
57
expr_fallback : Option < Box < SpanlessEqCallback < ' a > > > ,
58
+ path_check : PathCheck ,
36
59
}
37
60
38
61
impl < ' a , ' tcx > SpanlessEq < ' a , ' tcx > {
@@ -42,6 +65,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
42
65
maybe_typeck_results : cx. maybe_typeck_results ( ) . map ( |x| ( x, x) ) ,
43
66
allow_side_effects : true ,
44
67
expr_fallback : None ,
68
+ path_check : PathCheck :: default ( ) ,
45
69
}
46
70
}
47
71
@@ -54,6 +78,16 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
54
78
}
55
79
}
56
80
81
+ /// Check paths by their resolution instead of exact equality. See [`PathCheck`] for more
82
+ /// details.
83
+ #[ must_use]
84
+ pub fn paths_by_resolution ( self ) -> Self {
85
+ Self {
86
+ path_check : PathCheck :: Resolution ,
87
+ ..self
88
+ }
89
+ }
90
+
57
91
#[ must_use]
58
92
pub fn expr_fallback ( self , expr_fallback : impl FnMut ( & Expr < ' _ > , & Expr < ' _ > ) -> bool + ' a ) -> Self {
59
93
Self {
@@ -498,7 +532,7 @@ impl HirEqInterExpr<'_, '_, '_> {
498
532
match ( left. res , right. res ) {
499
533
( Res :: Local ( l) , Res :: Local ( r) ) => l == r || self . locals . get ( & l) == Some ( & r) ,
500
534
( Res :: Local ( _) , _) | ( _, Res :: Local ( _) ) => false ,
501
- _ => over ( left. segments , right. segments , |l , r| self . eq_path_segment ( l , r ) ) ,
535
+ _ => self . eq_path_segments ( left. segments , right. segments ) ,
502
536
}
503
537
}
504
538
@@ -511,17 +545,39 @@ impl HirEqInterExpr<'_, '_, '_> {
511
545
}
512
546
}
513
547
514
- pub fn eq_path_segments ( & mut self , left : & [ PathSegment < ' _ > ] , right : & [ PathSegment < ' _ > ] ) -> bool {
515
- left. len ( ) == right. len ( ) && left. iter ( ) . zip ( right) . all ( |( l, r) | self . eq_path_segment ( l, r) )
548
+ pub fn eq_path_segments < ' tcx > (
549
+ & mut self ,
550
+ mut left : & ' tcx [ PathSegment < ' tcx > ] ,
551
+ mut right : & ' tcx [ PathSegment < ' tcx > ] ,
552
+ ) -> bool {
553
+ if let PathCheck :: Resolution = self . inner . path_check
554
+ && let Some ( left_seg) = generic_path_segments ( left)
555
+ && let Some ( right_seg) = generic_path_segments ( right)
556
+ {
557
+ // If we compare by resolution, then only check the last segments that could possibly have generic
558
+ // arguments
559
+ left = left_seg;
560
+ right = right_seg;
561
+ }
562
+
563
+ over ( left, right, |l, r| self . eq_path_segment ( l, r) )
516
564
}
517
565
518
566
pub fn eq_path_segment ( & mut self , left : & PathSegment < ' _ > , right : & PathSegment < ' _ > ) -> bool {
519
- // The == of idents doesn't work with different contexts,
520
- // we have to be explicit about hygiene
521
- left. ident . name == right. ident . name
522
- && both ( left. args . as_ref ( ) , right. args . as_ref ( ) , |l, r| {
523
- self . eq_path_parameters ( l, r)
524
- } )
567
+ if !self . eq_path_parameters ( left. args ( ) , right. args ( ) ) {
568
+ return false ;
569
+ }
570
+
571
+ if let PathCheck :: Resolution = self . inner . path_check
572
+ && left. res != Res :: Err
573
+ && right. res != Res :: Err
574
+ {
575
+ left. res == right. res
576
+ } else {
577
+ // The == of idents doesn't work with different contexts,
578
+ // we have to be explicit about hygiene
579
+ left. ident . name == right. ident . name
580
+ }
525
581
}
526
582
527
583
pub fn eq_ty ( & mut self , left : & Ty < ' _ > , right : & Ty < ' _ > ) -> bool {
@@ -684,6 +740,21 @@ pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) ->
684
740
SpanlessEq :: new ( cx) . deny_side_effects ( ) . eq_expr ( left, right)
685
741
}
686
742
743
+ /// Returns the segments of a path that might have generic parameters.
744
+ /// Usually just the last segment for free items, except for when the path resolves to an associated
745
+ /// item, in which case it is the last two
746
+ fn generic_path_segments < ' tcx > ( segments : & ' tcx [ PathSegment < ' tcx > ] ) -> Option < & ' tcx [ PathSegment < ' tcx > ] > {
747
+ match segments. last ( ) ?. res {
748
+ Res :: Def ( DefKind :: AssocConst | DefKind :: AssocFn | DefKind :: AssocTy , _) => {
749
+ // <Ty as module::Trait<T>>::assoc::<U>
750
+ // ^^^^^^^^^^^^^^^^ ^^^^^^^^^^ segments: [module, Trait<T>, assoc<U>]
751
+ Some ( & segments[ segments. len ( ) . checked_sub ( 2 ) ?..] )
752
+ } ,
753
+ Res :: Err => None ,
754
+ _ => Some ( slice:: from_ref ( segments. last ( ) ?) ) ,
755
+ }
756
+ }
757
+
687
758
/// Type used to hash an ast element. This is different from the `Hash` trait
688
759
/// on ast types as this
689
760
/// trait would consider IDs and spans.
@@ -694,17 +765,29 @@ pub struct SpanlessHash<'a, 'tcx> {
694
765
cx : & ' a LateContext < ' tcx > ,
695
766
maybe_typeck_results : Option < & ' tcx TypeckResults < ' tcx > > ,
696
767
s : FxHasher ,
768
+ path_check : PathCheck ,
697
769
}
698
770
699
771
impl < ' a , ' tcx > SpanlessHash < ' a , ' tcx > {
700
772
pub fn new ( cx : & ' a LateContext < ' tcx > ) -> Self {
701
773
Self {
702
774
cx,
703
775
maybe_typeck_results : cx. maybe_typeck_results ( ) ,
776
+ path_check : PathCheck :: default ( ) ,
704
777
s : FxHasher :: default ( ) ,
705
778
}
706
779
}
707
780
781
+ /// Check paths by their resolution instead of exact equality. See [`PathCheck`] for more
782
+ /// details.
783
+ #[ must_use]
784
+ pub fn paths_by_resolution ( self ) -> Self {
785
+ Self {
786
+ path_check : PathCheck :: Resolution ,
787
+ ..self
788
+ }
789
+ }
790
+
708
791
pub fn finish ( self ) -> u64 {
709
792
self . s . finish ( )
710
793
}
@@ -1042,9 +1125,19 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
1042
1125
// even though the binding names are different and they have different `HirId`s.
1043
1126
Res :: Local ( _) => 1_usize . hash ( & mut self . s ) ,
1044
1127
_ => {
1045
- for seg in path. segments {
1046
- self . hash_name ( seg. ident . name ) ;
1047
- self . hash_generic_args ( seg. args ( ) . args ) ;
1128
+ if let PathCheck :: Resolution = self . path_check
1129
+ && let [ .., last] = path. segments
1130
+ && let Some ( segments) = generic_path_segments ( path. segments )
1131
+ {
1132
+ for seg in segments {
1133
+ self . hash_generic_args ( seg. args ( ) . args ) ;
1134
+ }
1135
+ last. res . hash ( & mut self . s ) ;
1136
+ } else {
1137
+ for seg in path. segments {
1138
+ self . hash_name ( seg. ident . name ) ;
1139
+ self . hash_generic_args ( seg. args ( ) . args ) ;
1140
+ }
1048
1141
}
1049
1142
} ,
1050
1143
}
0 commit comments