Skip to main content

rustc_builtin_macros/deriving/
partial_eq.rs

1use rustc_ast::{BinOpKind, BorrowKind, Expr, ExprKind, Mutability};
2use rustc_expand::base::ExtCtxt;
3use rustc_span::{Span, kw, sym};
4use thin_vec::thin_vec;
5
6use crate::deriving::generic::*;
7use crate::deriving::{call_discriminant_value, path_std};
8
9/// 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) {
18    let structural_trait_def = TraitDef {
19        span,
20        path: cx.std_path(span, { &[sym::marker, sym::StructuralPartialEq] })path_std!(cx, span, marker::StructuralPartialEq),
21        skip_path_as_bound: true, // crucial!
22        needs_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(cx.std_path(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(),
                    [cx.std_path(span, { &[sym::cmp, sym::PartialEq] })])))
    }
}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.
29        supports_unions: true,
30        methods: SmallVec::new(),
31        is_const: false,
32        ..
33    };
34    structural_trait_def.expand(cx, item, push);
35
36    // No need to generate `ne`, the default suffices, and not generating it is
37    // faster.
38    let 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_name(span, sym::bool),
                attributes: {
                    let len = [()].len();
                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                    vec.push(cx.generated_attr(span,
                            ast::GeneratedAttr::Inline));
                    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_name(span, sym::bool),
                                attributes: {
                                    let len = [()].len();
                                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                                    vec.push(cx.generated_attr(span,
                                            ast::GeneratedAttr::Inline));
                                    vec
                                },
                                fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
                                combine_substructure: combine_substructure(get_substructure_equality_expr),
                            }])))
    }
}smallvec![MethodDef {
39        name: sym::eq,
40        generics: cx.empty_generics(span),
41        explicit_self: true,
42        nonself_args: smallvec![(cx.ty_self_ref(span), sym::other)],
43        has_other_selflike_arg: true,
44        ret_ty: cx.ty_name(span, sym::bool),
45        attributes: thin_vec![cx.generated_attr(span, ast::GeneratedAttr::Inline)],
46        fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
47        combine_substructure: combine_substructure(get_substructure_equality_expr),
48    }];
49
50    let trait_def = TraitDef {
51        span,
52        path: cx.std_path(span, { &[sym::cmp, sym::PartialEq] })path_std!(cx, span, cmp::PartialEq),
53        needs_copy_as_bound_if_packed: true,
54        additional_bounds: SmallVec::new(),
55        supports_unions: false,
56        methods,
57        is_const,
58        ..
59    };
60    trait_def.expand(cx, item, push)
61}
62
63/// Generates the equality expression for a struct or enum variant when deriving
64/// `PartialEq`.
65///
66/// This function generates an expression that checks if all fields of a struct
67/// or enum variant are equal.
68/// - Scalar fields are compared first for efficiency, followed by compound
69///   fields.
70/// - If there are no fields, returns `true` (fieldless types are always equal).
71///
72/// Whether a field is considered "scalar" is determined by comparing the symbol
73/// of its type to a set of known scalar type symbols (e.g., `i32`, `u8`, etc).
74/// This check is based on the type's symbol.
75///
76/// ### Example 1
77/// ```
78/// #[derive(PartialEq)]
79/// struct i32;
80///
81/// // Here, `field_2` is of type `i32`, but since it's a user-defined type (not
82/// // the primitive), it will not be treated as scalar. The function will still
83/// // check equality of `field_2` first because the symbol matches `i32`.
84/// #[derive(PartialEq)]
85/// struct Struct {
86///     field_1: &'static str,
87///     field_2: i32,
88/// }
89/// ```
90///
91/// ### Example 2
92/// ```
93/// mod ty {
94///     pub type i32 = i32;
95/// }
96///
97/// // Here, `field_2` is of type `ty::i32`, which is a type alias for `i32`.
98/// // However, the function will not reorder the fields because the symbol for
99/// // `ty::i32` does not match the symbol for the primitive `i32`
100/// // ("ty::i32" != "i32").
101/// #[derive(PartialEq)]
102/// struct Struct {
103///     field_1: &'static str,
104///     field_2: ty::i32,
105/// }
106/// ```
107///
108/// For enums, the discriminant is compared first, then the rest of the fields.
109///
110/// # Panics
111///
112/// If called on static or all-fieldless enums/structs, which should not occur
113/// during derive expansion.
114fn get_substructure_equality_expr(
115    cx: &ExtCtxt<'_>,
116    span: Span,
117    substructure: Substructure<'_>,
118) -> BlockOrExpr {
119    BlockOrExpr::new_expr(match substructure {
120        EnumMatching(.., fields) | Struct(.., fields) => {
121            let combine = move |acc, field: &FieldInfo| {
122                let rhs = field
123                    .other_selflike_expr
124                    .as_ref()
125                    .expect("not exactly 2 arguments in `derive(PartialEq)`");
126
127                let rhs = cx.expr_binary(
128                    field.span,
129                    BinOpKind::Eq,
130                    wrap_block_expr(cx, peel_refs(&field.self_expr)),
131                    wrap_block_expr(cx, peel_refs(rhs)),
132                );
133                match acc {
134                    // Combine the previous comparison with the current field
135                    // using logical AND.
136                    Some(lhs) => Some(cx.expr_binary(field.span, BinOpKind::And, lhs, rhs)),
137                    // Start the chain with the first field's comparison.
138                    None => Some(rhs),
139                }
140            };
141
142            // First compare scalar fields, then compound fields, combining all
143            // with logical AND.
144            fields
145                .iter()
146                .filter(|field| field.maybe_scalar)
147                .chain(fields.iter().filter(|field| !field.maybe_scalar))
148                .fold(None, combine)
149                // If there are no fields, treat as always equal.
150                .unwrap_or_else(|| cx.expr_bool(span, true))
151        }
152        EnumDiscr(match_expr) => {
153            let self_expr = call_discriminant_value(cx, span, kw::SelfLower);
154            let other_selflike_expr = call_discriminant_value(cx, span, sym::other);
155            let lhs = cx.expr_binary(span, BinOpKind::Eq, self_expr, other_selflike_expr);
156            let Some(match_expr) = match_expr else {
157                return BlockOrExpr::new_expr(lhs);
158            };
159            // Compare the discriminant first (cheaper), then the rest of the
160            // fields.
161            cx.expr_binary(span, BinOpKind::And, lhs, match_expr)
162        }
163        _ => cx.dcx().span_bug(span, "unexpected substructure in `derive(PartialEq)`"),
164    })
165}
166
167/// Removes all leading immutable references from an expression.
168///
169/// This is used to strip away any number of leading `&` from an expression
170/// (e.g., `&&&T` becomes `T`). Only removes immutable references; mutable
171/// references are preserved.
172fn peel_refs(mut expr: &Box<Expr>) -> Box<Expr> {
173    while let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = &expr.kind {
174        expr = inner;
175    }
176    expr.clone()
177}
178
179/// Wraps a block expression in parentheses to ensure valid AST in macro
180/// expansion output.
181///
182/// If the given expression is a block, it is wrapped in parentheses; otherwise,
183/// it is returned unchanged.
184fn wrap_block_expr(cx: &ExtCtxt<'_>, expr: Box<Expr>) -> Box<Expr> {
185    if #[allow(non_exhaustive_omitted_patterns)] match &expr.kind {
    ExprKind::Block(..) => true,
    _ => false,
}matches!(&expr.kind, ExprKind::Block(..)) {
186        return cx.expr_paren(expr.span, expr);
187    }
188    expr
189}