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