]> git.lizzy.rs Git - rust.git/blob - src/libsyntax_ext/deriving/cmp/partial_ord.rs
Rollup merge of #65540 - guanqun:extend-t-macro, r=nikomatsakis
[rust.git] / src / libsyntax_ext / deriving / cmp / partial_ord.rs
1 pub use OrderingOp::*;
2
3 use crate::deriving::{path_local, pathvec_std, path_std};
4 use crate::deriving::generic::*;
5 use crate::deriving::generic::ty::*;
6
7 use syntax::ast::{self, BinOpKind, Expr, MetaItem};
8 use syntax_expand::base::{Annotatable, ExtCtxt};
9 use syntax::ptr::P;
10 use syntax::symbol::{sym, Symbol};
11 use syntax_pos::Span;
12
13 pub fn expand_deriving_partial_ord(cx: &mut ExtCtxt<'_>,
14                                    span: Span,
15                                    mitem: &MetaItem,
16                                    item: &Annotatable,
17                                    push: &mut dyn FnMut(Annotatable)) {
18     macro_rules! md {
19         ($name:expr, $op:expr, $equal:expr) => { {
20             let inline = cx.meta_word(span, sym::inline);
21             let attrs = vec![cx.attribute(inline)];
22             MethodDef {
23                 name: $name,
24                 generics: LifetimeBounds::empty(),
25                 explicit_self: borrowed_explicit_self(),
26                 args: vec![(borrowed_self(), "other")],
27                 ret_ty: Literal(path_local!(bool)),
28                 attributes: attrs,
29                 is_unsafe: false,
30                 unify_fieldless_variants: true,
31                 combine_substructure: combine_substructure(Box::new(|cx, span, substr| {
32                     cs_op($op, $equal, cx, span, substr)
33                 }))
34             }
35         } }
36     }
37
38     let ordering_ty = Literal(path_std!(cx, cmp::Ordering));
39     let ret_ty = Literal(Path::new_(pathvec_std!(cx, option::Option),
40                                     None,
41                                     vec![Box::new(ordering_ty)],
42                                     PathKind::Std));
43
44     let inline = cx.meta_word(span, sym::inline);
45     let attrs = vec![cx.attribute(inline)];
46
47     let partial_cmp_def = MethodDef {
48         name: "partial_cmp",
49         generics: LifetimeBounds::empty(),
50         explicit_self: borrowed_explicit_self(),
51         args: vec![(borrowed_self(), "other")],
52         ret_ty,
53         attributes: attrs,
54         is_unsafe: false,
55         unify_fieldless_variants: true,
56         combine_substructure: combine_substructure(Box::new(|cx, span, substr| {
57             cs_partial_cmp(cx, span, substr)
58         })),
59     };
60
61     // avoid defining extra methods if we can
62     // c-like enums, enums without any fields and structs without fields
63     // can safely define only `partial_cmp`.
64     let methods = if is_type_without_fields(item) {
65         vec![partial_cmp_def]
66     } else {
67         vec![partial_cmp_def,
68              md!("lt", true, false),
69              md!("le", true, true),
70              md!("gt", false, false),
71              md!("ge", false, true)]
72     };
73
74     let trait_def = TraitDef {
75         span,
76         attributes: vec![],
77         path: path_std!(cx, cmp::PartialOrd),
78         additional_bounds: vec![],
79         generics: LifetimeBounds::empty(),
80         is_unsafe: false,
81         supports_unions: false,
82         methods,
83         associated_types: Vec::new(),
84     };
85     trait_def.expand(cx, mitem, item, push)
86 }
87
88 #[derive(Copy, Clone)]
89 pub enum OrderingOp {
90     PartialCmpOp,
91     LtOp,
92     LeOp,
93     GtOp,
94     GeOp,
95 }
96
97 pub fn some_ordering_collapsed(
98     cx: &mut ExtCtxt<'_>,
99     span: Span,
100     op: OrderingOp,
101     self_arg_tags: &[ast::Ident],
102 ) -> P<ast::Expr> {
103     let lft = cx.expr_ident(span, self_arg_tags[0]);
104     let rgt = cx.expr_addr_of(span, cx.expr_ident(span, self_arg_tags[1]));
105     let op_str = match op {
106         PartialCmpOp => "partial_cmp",
107         LtOp => "lt",
108         LeOp => "le",
109         GtOp => "gt",
110         GeOp => "ge",
111     };
112     cx.expr_method_call(span, lft, cx.ident_of(op_str, span), vec![rgt])
113 }
114
115 pub fn cs_partial_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> P<Expr> {
116     let test_id = ast::Ident::new(sym::cmp, span);
117     let ordering = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal]));
118     let ordering_expr = cx.expr_path(ordering.clone());
119     let equals_expr = cx.expr_some(span, ordering_expr);
120
121     let partial_cmp_path = cx.std_path(&[sym::cmp, sym::PartialOrd, sym::partial_cmp]);
122
123     // Builds:
124     //
125     // match ::std::cmp::PartialOrd::partial_cmp(&self_field1, &other_field1) {
126     // ::std::option::Option::Some(::std::cmp::Ordering::Equal) =>
127     // match ::std::cmp::PartialOrd::partial_cmp(&self_field2, &other_field2) {
128     // ::std::option::Option::Some(::std::cmp::Ordering::Equal) => {
129     // ...
130     // }
131     // cmp => cmp
132     // },
133     // cmp => cmp
134     // }
135     //
136     cs_fold(// foldr nests the if-elses correctly, leaving the first field
137             // as the outermost one, and the last as the innermost.
138             false,
139             |cx, span, old, self_f, other_fs| {
140                 // match new {
141                 //     Some(::std::cmp::Ordering::Equal) => old,
142                 //     cmp => cmp
143                 // }
144
145                 let new = {
146                     let other_f = match other_fs {
147                         [o_f] => o_f,
148                                 _ => {
149                                     cx.span_bug(span,
150                                         "not exactly 2 arguments in `derive(PartialOrd)`")
151                                 }
152                     };
153
154                     let args = vec![
155                             cx.expr_addr_of(span, self_f),
156                             cx.expr_addr_of(span, other_f.clone()),
157                         ];
158
159                     cx.expr_call_global(span, partial_cmp_path.clone(), args)
160                 };
161
162                 let eq_arm = cx.arm(span,
163                                     cx.pat_some(span, cx.pat_path(span, ordering.clone())),
164                                     old);
165                 let neq_arm = cx.arm(span,
166                                     cx.pat_ident(span, test_id),
167                                     cx.expr_ident(span, test_id));
168
169                 cx.expr_match(span, new, vec![eq_arm, neq_arm])
170             },
171             equals_expr,
172             Box::new(|cx, span, (self_args, tag_tuple), _non_self_args| {
173         if self_args.len() != 2 {
174             cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`")
175         } else {
176             some_ordering_collapsed(cx, span, PartialCmpOp, tag_tuple)
177         }
178     }),
179             cx,
180             span,
181             substr)
182 }
183
184 /// Strict inequality.
185 fn cs_op(less: bool,
186          inclusive: bool,
187          cx: &mut ExtCtxt<'_>,
188          span: Span,
189          substr: &Substructure<'_>) -> P<Expr> {
190     let ordering_path = |cx: &mut ExtCtxt<'_>, name: &str| {
191         cx.expr_path(cx.path_global(
192             span, cx.std_path(&[sym::cmp, sym::Ordering, Symbol::intern(name)])))
193     };
194
195     let par_cmp = |cx: &mut ExtCtxt<'_>, span, self_f: P<Expr>, other_fs: &[P<Expr>], default| {
196         let other_f = match other_fs {
197             [o_f] => o_f,
198             _ => cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`"),
199         };
200
201         // `PartialOrd::partial_cmp(self.fi, other.fi)`
202         let cmp_path = cx.expr_path(cx.path_global(span, cx.std_path(&[sym::cmp,
203                                                                        sym::PartialOrd,
204                                                                        sym::partial_cmp])));
205         let cmp = cx.expr_call(span,
206                                cmp_path,
207                                vec![cx.expr_addr_of(span, self_f),
208                                     cx.expr_addr_of(span, other_f.clone())]);
209
210         let default = ordering_path(cx, default);
211         // `Option::unwrap_or(_, Ordering::Equal)`
212         let unwrap_path = cx.expr_path(cx.path_global(span, cx.std_path(&[sym::option,
213                                                                           sym::Option,
214                                                                           sym::unwrap_or])));
215         cx.expr_call(span, unwrap_path, vec![cmp, default])
216     };
217
218     let fold = cs_fold1(false, // need foldr
219         |cx, span, subexpr, self_f, other_fs| {
220             // build up a series of `partial_cmp`s from the inside
221             // out (hence foldr) to get lexical ordering, i.e., for op ==
222             // `ast::lt`
223             //
224             // ```
225             // Ordering::then_with(
226             //    Option::unwrap_or(
227             //        PartialOrd::partial_cmp(self.f1, other.f1), Ordering::Equal)
228             //    ),
229             //    Option::unwrap_or(
230             //        PartialOrd::partial_cmp(self.f2, other.f2), Ordering::Greater)
231             //    )
232             // )
233             // == Ordering::Less
234             // ```
235             //
236             // and for op ==
237             // `ast::le`
238             //
239             // ```
240             // Ordering::then_with(
241             //    Option::unwrap_or(
242             //        PartialOrd::partial_cmp(self.f1, other.f1), Ordering::Equal)
243             //    ),
244             //    Option::unwrap_or(
245             //        PartialOrd::partial_cmp(self.f2, other.f2), Ordering::Greater)
246             //    )
247             // )
248             // != Ordering::Greater
249             // ```
250             //
251             // The optimiser should remove the redundancy. We explicitly
252             // get use the binops to avoid auto-deref dereferencing too many
253             // layers of pointers, if the type includes pointers.
254
255             // `Option::unwrap_or(PartialOrd::partial_cmp(self.fi, other.fi), Ordering::Equal)`
256             let par_cmp = par_cmp(cx, span, self_f, other_fs, "Equal");
257
258             // `Ordering::then_with(Option::unwrap_or(..), ..)`
259             let then_with_path = cx.expr_path(cx.path_global(span,
260                                                              cx.std_path(&[sym::cmp,
261                                                                            sym::Ordering,
262                                                                            sym::then_with])));
263             cx.expr_call(span, then_with_path, vec![par_cmp, cx.lambda0(span, subexpr)])
264         },
265         |cx, args| {
266             match args {
267                 Some((span, self_f, other_fs)) => {
268                     let opposite = if less { "Greater" } else { "Less" };
269                     par_cmp(cx, span, self_f, other_fs, opposite)
270                 },
271                 None => cx.expr_bool(span, inclusive)
272             }
273         },
274         Box::new(|cx, span, (self_args, tag_tuple), _non_self_args| {
275             if self_args.len() != 2 {
276                 cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`")
277             } else {
278                 let op = match (less, inclusive) {
279                     (false, false) => GtOp,
280                     (false, true) => GeOp,
281                     (true, false) => LtOp,
282                     (true, true) => LeOp,
283                 };
284                 some_ordering_collapsed(cx, span, op, tag_tuple)
285             }
286         }),
287         cx,
288         span,
289         substr);
290
291     match *substr.fields {
292         EnumMatching(.., ref all_fields) |
293         Struct(.., ref all_fields) if !all_fields.is_empty() => {
294             let ordering = ordering_path(cx, if less ^ inclusive { "Less" } else { "Greater" });
295             let comp_op = if inclusive { BinOpKind::Ne } else { BinOpKind::Eq };
296
297             cx.expr_binary(span, comp_op, fold, ordering)
298         }
299         _ => fold
300     }
301 }