Skip to main content

rustc_builtin_macros/deriving/cmp/
partial_eq.rs

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