Skip to main content

rustc_builtin_macros/deriving/
partial_ord.rs

1use rustc_ast::{ExprKind, ItemKind, PatKind, Safety, ast};
2use rustc_expand::base::ExtCtxt;
3use rustc_span::{Ident, Span, sym};
4use thin_vec::thin_vec;
5
6use crate::deriving::generic::ty::*;
7use crate::deriving::generic::*;
8use crate::deriving::{path_std, pathvec};
9
10pub(crate) fn expand_deriving_partial_ord(
11    cx: &ExtCtxt<'_>,
12    span: Span,
13    item: &ast::Item,
14    push: &mut dyn FnMut(Box<ast::Item>),
15    is_const: bool,
16) {
17    let ordering_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::Ordering]))
    })path_std!(cmp::Ordering));
18    let ret_ty =
19        Path(Path::new_({
    ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
            [sym::option, sym::Option]))
}pathvec!(option::Option), ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Box::new(ordering_ty)]))vec![Box::new(ordering_ty)], PathKind::Std));
20
21    // Order in which to perform matching
22    let discr_then_data = if let ItemKind::Enum(_, _, def) = &item.kind {
23        let dataful: Vec<bool> = def.variants.iter().map(|v| !v.data.fields().is_empty()).collect();
24        match dataful.iter().filter(|&&b| b).count() {
25            // No data, placing the discriminant check first makes codegen simpler
26            0 => true,
27            1..=2 => false,
28            _ => (0..dataful.len() - 1).any(|i| {
29                if dataful[i]
30                    && let Some(idx) = dataful[i + 1..].iter().position(|v| *v)
31                {
32                    idx >= 2
33                } else {
34                    false
35                }
36            }),
37        }
38    } else {
39        true
40    };
41
42    let container_id = cx.current_expansion.id.expn_data().parent.expect_local();
43    let has_derive_ord = cx.resolver.has_derive_ord(container_id);
44    let default_substructure =
45        combine_substructure(|cx, span, substr| cs_partial_cmp(cx, span, substr, discr_then_data));
46    let simple_substructure = combine_substructure(|cx, span, _| {
47        cs_partial_cmp_simple(cx, span, cx.expr_ident(span, Ident::new(sym::other, span)))
48    });
49    let is_simple = match &item.kind {
50        // For unit structs/zero-variant enums, the default generated code is better.
51        ItemKind::Struct(.., ast::VariantData::Unit(..)) => false,
52        // Also for single fieldless variant enum
53        ItemKind::Enum(.., enum_def) if enum_def.variants.is_empty() => false,
54        ItemKind::Enum(.., enum_def)
55            if enum_def.variants.len() == 1
56                && #[allow(non_exhaustive_omitted_patterns)] match enum_def.variants[0].data {
    ast::VariantData::Unit(..) => true,
    _ => false,
}matches!(enum_def.variants[0].data, ast::VariantData::Unit(..)) =>
57        {
58            false
59        }
60        ItemKind::Struct(_, ast::Generics { params, .. }, _)
61        | ItemKind::Enum(_, ast::Generics { params, .. }, _)
62            if has_derive_ord
63                && !params
64                    .iter()
65                    .any(|param| #[allow(non_exhaustive_omitted_patterns)] match param.kind {
    ast::GenericParamKind::Type { .. } => true,
    _ => false,
}matches!(param.kind, ast::GenericParamKind::Type { .. })) =>
66        {
67            true
68        }
69        _ => false,
70    };
71
72    let partial_cmp_def = MethodDef {
73        name: sym::partial_cmp,
74        generics: cx.empty_generics(span),
75        explicit_self: true,
76        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)])))
    }
}smallvec![(self_ref(), sym::other)],
77        ret_ty,
78        attributes: {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(cx.attr_word(sym::inline, span));
    vec
}thin_vec![cx.attr_word(sym::inline, span)],
79        fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
80        combine_substructure: if is_simple { simple_substructure } else { default_substructure },
81    };
82
83    let trait_def = TraitDef {
84        span,
85        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::PartialOrd]))
    })path_std!(cmp::PartialOrd),
86        skip_path_as_bound: false,
87        needs_copy_as_bound_if_packed: true,
88        additional_bounds: ::smallvec::SmallVec::new()smallvec![],
89        supports_unions: false,
90        methods: {
    let count = 0usize + 1usize;
    let mut vec = ::smallvec::SmallVec::new();
    if count <= vec.inline_size() {
        vec.push(partial_cmp_def);
        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(),
                    [partial_cmp_def])))
    }
}smallvec![partial_cmp_def],
91        associated_types: SmallVec::new(),
92        is_const,
93        safety: Safety::Default,
94        document: true,
95    };
96    trait_def.expand_ext(cx, item, push, is_simple)
97}
98
99// Special case for the type deriving both `PartialOrd` and `Ord`. Builds:
100// ```
101// Some(::core::cmp::Ord::cmp(self, other))
102// ```
103fn cs_partial_cmp_simple(cx: &ExtCtxt<'_>, span: Span, other_expr: Box<ast::Expr>) -> BlockOrExpr {
104    let ord_cmp_path = cx.std_path(&[sym::cmp, sym::Ord, sym::cmp]);
105    let cmp_expr =
106        cx.expr_call_global(span, ord_cmp_path, {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(cx.expr_self(span));
    vec.push(other_expr);
    vec
}thin_vec![cx.expr_self(span), other_expr]);
107    BlockOrExpr::new_expr(cx.expr_some(span, cmp_expr))
108}
109
110fn cs_partial_cmp(
111    cx: &ExtCtxt<'_>,
112    span: Span,
113    substr: Substructure<'_>,
114    discr_then_data: bool,
115) -> BlockOrExpr {
116    let test_id = Ident::new(sym::cmp, span);
117    let equal_path = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal]));
118    let partial_cmp_path = cx.std_path(&[sym::cmp, sym::PartialOrd, sym::partial_cmp]);
119
120    // Builds:
121    //
122    // match ::core::cmp::PartialOrd::partial_cmp(&self.x, &other.x) {
123    //     ::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
124    //         ::core::cmp::PartialOrd::partial_cmp(&self.y, &other.y),
125    //     cmp => cmp,
126    // }
127    let expr = cs_fold(
128        // foldr nests the if-elses correctly, leaving the first field
129        // as the outermost one, and the last as the innermost.
130        false,
131        cx,
132        span,
133        substr,
134        |cx, fold| match fold {
135            CsFold::Single(field) => {
136                let [other_expr] = &field.other_selflike_exprs[..] else {
137                    cx.dcx()
138                        .span_bug(field.span, "not exactly 2 arguments in `derive(PartialOrd)`");
139                };
140                let args = {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(field.self_expr.clone());
    vec.push(other_expr.clone());
    vec
}thin_vec![field.self_expr.clone(), other_expr.clone()];
141                cx.expr_call_global(field.span, partial_cmp_path.clone(), args)
142            }
143            CsFold::Combine(span, mut expr1, expr2) => {
144                // When the item is an enum, this expands to
145                // ```
146                // match (expr2) {
147                //     Some(Ordering::Equal) => expr1,
148                //     cmp => cmp
149                // }
150                // ```
151                // where `expr2` is `partial_cmp(self_discr, other_discr)`, and `expr1` is a `match`
152                // against the enum variants. This means that we begin by comparing the enum discriminants,
153                // before either inspecting their contents (if they match), or returning
154                // the `cmp::Ordering` of comparing the enum discriminants.
155                // ```
156                // match partial_cmp(self_discr, other_discr) {
157                //     Some(Ordering::Equal) => match (self, other)  {
158                //         (Self::A(self_0), Self::A(other_0)) => partial_cmp(self_0, other_0),
159                //         (Self::B(self_0), Self::B(other_0)) => partial_cmp(self_0, other_0),
160                //         _ => Some(Ordering::Equal)
161                //     }
162                //     cmp => cmp
163                // }
164                // ```
165                // If we have any certain enum layouts, flipping this results in better codegen
166                // ```
167                // match (self, other) {
168                //     (Self::A(self_0), Self::A(other_0)) => partial_cmp(self_0, other_0),
169                //     _ => partial_cmp(self_discr, other_discr)
170                // }
171                // ```
172                // Reference: https://github.com/rust-lang/rust/pull/103659#issuecomment-1328126354
173
174                if !discr_then_data
175                    && let ExprKind::Match(_, arms, _) = &mut expr1.kind
176                    && let Some(last) = arms.last_mut()
177                    && let PatKind::Wild = last.pat.kind
178                {
179                    last.body = Some(expr2);
180                    expr1
181                } else {
182                    let eq_arm = cx.arm(
183                        span,
184                        cx.pat_some(span, cx.pat_path(span, equal_path.clone())),
185                        expr1,
186                    );
187                    let neq_arm =
188                        cx.arm(span, cx.pat_ident(span, test_id), cx.expr_ident(span, test_id));
189                    cx.expr_match(span, expr2, {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(eq_arm);
    vec.push(neq_arm);
    vec
}thin_vec![eq_arm, neq_arm])
190                }
191            }
192            CsFold::Fieldless => cx.expr_some(span, cx.expr_path(equal_path.clone())),
193        },
194    );
195    BlockOrExpr::new_expr(expr)
196}