1use rustc_ast::{BinOpKind, BorrowKind, Expr, ExprKind, Mutability, Safety};
2use rustc_expand::base::ExtCtxt;
3use rustc_span::{Ident, Span, sym};
4use thin_vec::thin_vec;
56use crate::deriving::generic::ty::*;
7use crate::deriving::generic::*;
8use crate::deriving::path_std;
910/// Expands a `#[derive(PartialEq)]` attribute into an implementation for the
11/// target item.
12pub(crate) fn expand_deriving_partial_eq(
13 cx: &ExtCtxt<'_>,
14 span: Span,
15 item: &ast::Item,
16 push: &mut dyn FnMut(Box<ast::Item>),
17 is_const: bool,
18) {
19let structural_trait_def = TraitDef {
20span,
21 path: generic::ty::new_path(cx, span, { &[sym::marker, sym::StructuralPartialEq] },
&[])path_std!(cx, span, marker::StructuralPartialEq),
22 skip_path_as_bound: true, // crucial!
23needs_copy_as_bound_if_packed: false,
24// The `StructuralPartialEq` impl must have the *same* bounds as the `PartialEq` impl,
25 // or it will apply in situations where it should not, such as in the bug
26 // <https://github.com/rust-lang/rust/issues/147714>.
27 additional_bounds: {
let count = 0usize + 1usize;
let mut vec = ::smallvec::SmallVec::new();
if count <= vec.inline_size() {
vec.push(generic::ty::new_path(cx, span,
{ &[sym::cmp, sym::PartialEq] }, &[]));
vec
} else {
::smallvec::SmallVec::from_vec(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[generic::ty::new_path(cx, span,
{ &[sym::cmp, sym::PartialEq] }, &[])])))
}
}smallvec![path_std!(cx, span, cmp::PartialEq)],
28// We really don't support unions, but that's already checked by the impl generated below;
29 // a second check here would lead to redundant error messages.
30supports_unions: true,
31 methods: SmallVec::new(),
32 associated_types: SmallVec::new(),
33 is_const: false,
34 safety: Safety::Default,
35 document: true,
36 };
37structural_trait_def.expand(cx, item, push);
3839// No need to generate `ne`, the default suffices, and not generating it is
40 // faster.
41let methods = {
let count = 0usize + 1usize;
let mut vec = ::smallvec::SmallVec::new();
if count <= vec.inline_size() {
vec.push(MethodDef {
name: sym::eq,
generics: cx.empty_generics(span),
explicit_self: true,
nonself_args: {
let count = 0usize + 1usize;
let mut vec = ::smallvec::SmallVec::new();
if count <= vec.inline_size() {
vec.push((self_ref(), sym::other));
vec
} else {
::smallvec::SmallVec::from_vec(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[(self_ref(), sym::other)])))
}
},
ret_ty: Path(cx.path_ident(span,
Ident::new(sym::bool, span))),
attributes: {
let len = [()].len();
let mut vec = ::thin_vec::ThinVec::with_capacity(len);
vec.push(cx.attr_word(sym::inline, span));
vec
},
fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
combine_substructure: combine_substructure(get_substructure_equality_expr),
});
vec
} else {
::smallvec::SmallVec::from_vec(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[MethodDef {
name: sym::eq,
generics: cx.empty_generics(span),
explicit_self: true,
nonself_args: {
let count = 0usize + 1usize;
let mut vec = ::smallvec::SmallVec::new();
if count <= vec.inline_size() {
vec.push((self_ref(), sym::other));
vec
} else {
::smallvec::SmallVec::from_vec(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[(self_ref(), sym::other)])))
}
},
ret_ty: Path(cx.path_ident(span,
Ident::new(sym::bool, span))),
attributes: {
let len = [()].len();
let mut vec = ::thin_vec::ThinVec::with_capacity(len);
vec.push(cx.attr_word(sym::inline, span));
vec
},
fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
combine_substructure: combine_substructure(get_substructure_equality_expr),
}])))
}
}smallvec![MethodDef {
42 name: sym::eq,
43 generics: cx.empty_generics(span),
44 explicit_self: true,
45 nonself_args: smallvec![(self_ref(), sym::other)],
46 ret_ty: Path(cx.path_ident(span, Ident::new(sym::bool, span))),
47 attributes: thin_vec![cx.attr_word(sym::inline, span)],
48 fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
49 combine_substructure: combine_substructure(get_substructure_equality_expr),
50 }];
5152let trait_def = TraitDef {
53span,
54 path: generic::ty::new_path(cx, span, { &[sym::cmp, sym::PartialEq] }, &[])path_std!(cx, span, cmp::PartialEq),
55 skip_path_as_bound: false,
56 needs_copy_as_bound_if_packed: true,
57 additional_bounds: SmallVec::new(),
58 supports_unions: false,
59methods,
60 associated_types: SmallVec::new(),
61is_const,
62 safety: Safety::Default,
63 document: true,
64 };
65trait_def.expand(cx, item, push)
66}
6768/// Generates the equality expression for a struct or enum variant when deriving
69/// `PartialEq`.
70///
71/// This function generates an expression that checks if all fields of a struct
72/// or enum variant are equal.
73/// - Scalar fields are compared first for efficiency, followed by compound
74/// fields.
75/// - If there are no fields, returns `true` (fieldless types are always equal).
76///
77/// Whether a field is considered "scalar" is determined by comparing the symbol
78/// of its type to a set of known scalar type symbols (e.g., `i32`, `u8`, etc).
79/// This check is based on the type's symbol.
80///
81/// ### Example 1
82/// ```
83/// #[derive(PartialEq)]
84/// struct i32;
85///
86/// // Here, `field_2` is of type `i32`, but since it's a user-defined type (not
87/// // the primitive), it will not be treated as scalar. The function will still
88/// // check equality of `field_2` first because the symbol matches `i32`.
89/// #[derive(PartialEq)]
90/// struct Struct {
91/// field_1: &'static str,
92/// field_2: i32,
93/// }
94/// ```
95///
96/// ### Example 2
97/// ```
98/// mod ty {
99/// pub type i32 = i32;
100/// }
101///
102/// // Here, `field_2` is of type `ty::i32`, which is a type alias for `i32`.
103/// // However, the function will not reorder the fields because the symbol for
104/// // `ty::i32` does not match the symbol for the primitive `i32`
105/// // ("ty::i32" != "i32").
106/// #[derive(PartialEq)]
107/// struct Struct {
108/// field_1: &'static str,
109/// field_2: ty::i32,
110/// }
111/// ```
112///
113/// For enums, the discriminant is compared first, then the rest of the fields.
114///
115/// # Panics
116///
117/// If called on static or all-fieldless enums/structs, which should not occur
118/// during derive expansion.
119fn get_substructure_equality_expr(
120 cx: &ExtCtxt<'_>,
121 span: Span,
122 substructure: Substructure<'_>,
123) -> BlockOrExpr {
124BlockOrExpr::new_expr(match substructure {
125EnumMatching(.., fields) | Struct(.., fields) => {
126let combine = move |acc, field| {
127let rhs = get_field_equality_expr(cx, field);
128match acc {
129// Combine the previous comparison with the current field
130 // using logical AND.
131Some(lhs) => Some(cx.expr_binary(field.span, BinOpKind::And, lhs, rhs)),
132// Start the chain with the first field's comparison.
133None => Some(rhs),
134 }
135 };
136137// First compare scalar fields, then compound fields, combining all
138 // with logical AND.
139fields140 .iter()
141 .filter(|field| field.maybe_scalar)
142 .chain(fields.iter().filter(|field| !field.maybe_scalar))
143 .fold(None, combine)
144// If there are no fields, treat as always equal.
145.unwrap_or_else(|| cx.expr_bool(span, true))
146 }
147EnumDiscr(disc, match_expr) => {
148let lhs = get_field_equality_expr(cx, &disc);
149let Some(match_expr) = match_exprelse {
150return BlockOrExpr::new_expr(lhs);
151 };
152// Compare the discriminant first (cheaper), then the rest of the
153 // fields.
154cx.expr_binary(disc.span, BinOpKind::And, lhs, match_expr.clone())
155 }
156_ => cx.dcx().span_bug(span, "unexpected substructure in `derive(PartialEq)`"),
157 })
158}
159160/// Generates an equality comparison expression for a single struct or enum
161/// field.
162///
163/// This function produces an AST expression that compares the `self` and
164/// `other` values for a field using `==`. It removes any leading references
165/// from both sides for readability. If the field is a block expression, it is
166/// wrapped in parentheses to ensure valid syntax.
167///
168/// # Panics
169///
170/// Panics if there are not exactly two arguments to compare (should be `self`
171/// and `other`).
172fn get_field_equality_expr(cx: &ExtCtxt<'_>, field: &FieldInfo) -> Box<Expr> {
173let rhs =
174field.other_selflike_expr.as_ref().expect("not exactly 2 arguments in `derive(PartialEq)`");
175176cx.expr_binary(
177field.span,
178 BinOpKind::Eq,
179wrap_block_expr(cx, peel_refs(&field.self_expr)),
180wrap_block_expr(cx, peel_refs(rhs)),
181 )
182}
183184/// Removes all leading immutable references from an expression.
185///
186/// This is used to strip away any number of leading `&` from an expression
187/// (e.g., `&&&T` becomes `T`). Only removes immutable references; mutable
188/// references are preserved.
189fn peel_refs(mut expr: &Box<Expr>) -> Box<Expr> {
190while let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = &expr.kind {
191 expr = inner;
192 }
193expr.clone()
194}
195196/// Wraps a block expression in parentheses to ensure valid AST in macro
197/// expansion output.
198///
199/// If the given expression is a block, it is wrapped in parentheses; otherwise,
200/// it is returned unchanged.
201fn wrap_block_expr(cx: &ExtCtxt<'_>, expr: Box<Expr>) -> Box<Expr> {
202if #[allow(non_exhaustive_omitted_patterns)] match &expr.kind {
ExprKind::Block(..) => true,
_ => false,
}matches!(&expr.kind, ExprKind::Block(..)) {
203return cx.expr_paren(expr.span, expr);
204 }
205expr206}