@@ -8,6 +8,7 @@ use crate::deriving::generic::ty::*;
88use  crate :: deriving:: generic:: * ; 
99use  crate :: deriving:: { path_local,  path_std} ; 
1010
11+ /// Expands a `#[derive(PartialEq)]` attribute into an implementation for the target item. 
1112pub ( crate )  fn  expand_deriving_partial_eq ( 
1213    cx :  & ExtCtxt < ' _ > , 
1314    span :  Span , 
@@ -16,62 +17,6 @@ pub(crate) fn expand_deriving_partial_eq(
1617    push :  & mut  dyn  FnMut ( Annotatable ) , 
1718    is_const :  bool , 
1819)  { 
19-     fn  cs_eq ( cx :  & ExtCtxt < ' _ > ,  span :  Span ,  substr :  & Substructure < ' _ > )  -> BlockOrExpr  { 
20-         let  base = true ; 
21-         let  expr = cs_fold ( 
22-             true ,  // use foldl 
23-             cx, 
24-             span, 
25-             substr, 
26-             |cx,  fold| match  fold { 
27-                 CsFold :: Single ( field)  => { 
28-                     let  [ other_expr]  = & field. other_selflike_exprs [ ..]  else  { 
29-                         cx. dcx ( ) 
30-                             . span_bug ( field. span ,  "not exactly 2 arguments in `derive(PartialEq)`" ) ; 
31-                     } ; 
32- 
33-                     // We received arguments of type `&T`. Convert them to type `T` by stripping 
34-                     // any leading `&`. This isn't necessary for type checking, but 
35-                     // it results in better error messages if something goes wrong. 
36-                     // 
37-                     // Note: for arguments that look like `&{ x }`, which occur with packed 
38-                     // structs, this would cause expressions like `{ self.x } == { other.x }`, 
39-                     // which isn't valid Rust syntax. This wouldn't break compilation because these 
40-                     // AST nodes are constructed within the compiler. But it would mean that code 
41-                     // printed by `-Zunpretty=expanded` (or `cargo expand`) would have invalid 
42-                     // syntax, which would be suboptimal. So we wrap these in parens, giving 
43-                     // `({ self.x }) == ({ other.x })`, which is valid syntax. 
44-                     let  convert = |expr :  & P < Expr > | { 
45-                         if  let  ExprKind :: AddrOf ( BorrowKind :: Ref ,  Mutability :: Not ,  inner)  =
46-                             & expr. kind 
47-                         { 
48-                             if  let  ExprKind :: Block ( ..)  = & inner. kind  { 
49-                                 // `&{ x }` form: remove the `&`, add parens. 
50-                                 cx. expr_paren ( field. span ,  inner. clone ( ) ) 
51-                             }  else  { 
52-                                 // `&x` form: remove the `&`. 
53-                                 inner. clone ( ) 
54-                             } 
55-                         }  else  { 
56-                             expr. clone ( ) 
57-                         } 
58-                     } ; 
59-                     cx. expr_binary ( 
60-                         field. span , 
61-                         BinOpKind :: Eq , 
62-                         convert ( & field. self_expr ) , 
63-                         convert ( other_expr) , 
64-                     ) 
65-                 } 
66-                 CsFold :: Combine ( span,  expr1,  expr2)  => { 
67-                     cx. expr_binary ( span,  BinOpKind :: And ,  expr1,  expr2) 
68-                 } 
69-                 CsFold :: Fieldless  => cx. expr_bool ( span,  base) , 
70-             } , 
71-         ) ; 
72-         BlockOrExpr :: new_expr ( expr) 
73-     } 
74- 
7520    let  structural_trait_def = TraitDef  { 
7621        span, 
7722        path :  path_std ! ( marker:: StructuralPartialEq ) , 
@@ -97,7 +42,9 @@ pub(crate) fn expand_deriving_partial_eq(
9742        ret_ty:  Path ( path_local!( bool ) ) , 
9843        attributes:  thin_vec![ cx. attr_word( sym:: inline,  span) ] , 
9944        fieldless_variants_strategy:  FieldlessVariantsStrategy :: Unify , 
100-         combine_substructure:  combine_substructure( Box :: new( |a,  b,  c| cs_eq( a,  b,  c) ) ) , 
45+         combine_substructure:  combine_substructure( Box :: new( |a,  b,  c| { 
46+             BlockOrExpr :: new_expr( get_substructure_equality_expr( a,  b,  c) ) 
47+         } ) ) , 
10148    } ] ; 
10249
10350    let  trait_def = TraitDef  { 
@@ -113,3 +60,107 @@ pub(crate) fn expand_deriving_partial_eq(
11360    } ; 
11461    trait_def. expand ( cx,  mitem,  item,  push) 
11562} 
63+ 
64+ /// Generates the equality expression for a struct or enum variant when deriving `PartialEq`. 
65+ /// 
66+ /// This function generates an expression that checks if all fields of a struct or enum variant are equal. 
67+ /// - Scalar fields are compared first for efficiency, followed by compound fields. 
68+ /// - If there are no fields, returns `true` (fieldless types are always equal). 
69+ /// 
70+ /// For enums, the discriminant is compared first, then the rest of the fields. 
71+ /// 
72+ /// # Panics 
73+ /// 
74+ /// If called on static or all-fieldless enums/structs, which should not occur during derive expansion. 
75+ fn  get_substructure_equality_expr ( 
76+     cx :  & ExtCtxt < ' _ > , 
77+     span :  Span , 
78+     substructure :  & Substructure < ' _ > , 
79+ )  -> P < Expr >  { 
80+     use  SubstructureFields :: * ; 
81+ 
82+     match  substructure. fields  { 
83+         EnumMatching ( ..,  fields)  | Struct ( ..,  fields)  => { 
84+             let  combine = move  |acc,  field| { 
85+                 let  rhs = get_field_equality_expr ( cx,  field) ; 
86+                 if  let  Some ( lhs)  = acc { 
87+                     return  Some ( cx. expr_binary ( field. span ,  BinOpKind :: And ,  lhs,  rhs) ) ; 
88+                 } 
89+                 Some ( rhs) 
90+             } ; 
91+ 
92+             // First compare scalar fields, then compound fields, combining all with logical AND. 
93+             return  fields
94+                 . iter ( ) 
95+                 . filter ( |field| !field. is_scalar ) 
96+                 . fold ( fields. iter ( ) . filter ( |field| field. is_scalar ) . fold ( None ,  combine) ,  combine) 
97+                 . unwrap_or_else ( || { 
98+                     // If there are no fields, treat as always equal. 
99+                     cx. expr_bool ( span,  true ) 
100+                 } ) ; 
101+         } 
102+         EnumDiscr ( disc,  match_expr)  => { 
103+             let  lhs = get_field_equality_expr ( cx,  disc) ; 
104+             let  Some ( match_expr)  = match_expr else  { 
105+                 return  lhs; 
106+             } ; 
107+             // Compare the discriminant first (cheaper), then the rest of the fields. 
108+             return  cx. expr_binary ( disc. span ,  BinOpKind :: And ,  lhs,  match_expr. clone ( ) ) ; 
109+         } 
110+         StaticEnum ( ..)  => cx. dcx ( ) . span_bug ( 
111+             span, 
112+             "unexpected static enum encountered during `derive(PartialEq)` expansion" , 
113+         ) , 
114+         StaticStruct ( ..)  => cx. dcx ( ) . span_bug ( 
115+             span, 
116+             "unexpected static struct encountered during `derive(PartialEq)` expansion" , 
117+         ) , 
118+         AllFieldlessEnum ( ..)  => cx. dcx ( ) . span_bug ( 
119+             span, 
120+             "unexpected all-fieldless enum encountered during `derive(PartialEq)` expansion" , 
121+         ) , 
122+     } 
123+ } 
124+ 
125+ /// Generates an equality comparison expression for a single struct or enum field. 
126+ /// 
127+ /// This function produces an AST expression that compares the `self` and `other` values for a field using `==`. 
128+ /// It removes any leading references from both sides for readability. 
129+ /// If the field is a block expression, it is wrapped in parentheses to ensure valid syntax. 
130+ /// 
131+ /// # Panics 
132+ /// 
133+ /// Panics if there are not exactly two arguments to compare (should be `self` and `other`). 
134+ fn  get_field_equality_expr ( cx :  & ExtCtxt < ' _ > ,  field :  & FieldInfo )  -> P < Expr >  { 
135+     let  [ rhs]  = & field. other_selflike_exprs [ ..]  else  { 
136+         cx. dcx ( ) . span_bug ( field. span ,  "not exactly 2 arguments in `derive(PartialEq)`" ) ; 
137+     } ; 
138+ 
139+     cx. expr_binary ( 
140+         field. span , 
141+         BinOpKind :: Eq , 
142+         wrap_block_expr ( cx,  peel_refs ( & field. self_expr ) ) , 
143+         wrap_block_expr ( cx,  peel_refs ( rhs) ) , 
144+     ) 
145+ } 
146+ 
147+ /// Removes all leading immutable references from an expression. 
148+ /// 
149+ /// This is used to strip away any number of leading `&` from an expression (e.g., `&&&T` becomes `T`). 
150+ /// Only removes immutable references; mutable references are preserved. 
151+ fn  peel_refs ( mut  expr :  & P < Expr > )  -> P < Expr >  { 
152+     while  let  ExprKind :: AddrOf ( BorrowKind :: Ref ,  Mutability :: Not ,  inner)  = & expr. kind  { 
153+         expr = & inner; 
154+     } 
155+     expr. clone ( ) 
156+ } 
157+ 
158+ /// Wraps a block expression in parentheses to ensure valid AST in macro expansion output. 
159+ /// 
160+ /// If the given expression is a block, it is wrapped in parentheses; otherwise, it is returned unchanged. 
161+ fn  wrap_block_expr ( cx :  & ExtCtxt < ' _ > ,  expr :  P < Expr > )  -> P < Expr >  { 
162+     if  matches ! ( & expr. kind,  ExprKind :: Block ( ..) )  { 
163+         return  cx. expr_paren ( expr. span ,  expr) ; 
164+     } 
165+     expr
166+ } 
0 commit comments