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