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