Skip to content

Commit 6eb4f0f

Browse files
committed
Ensure derive(PartialOrd) is no longer accidentally exponential
Previously, two comparison operations would be generated for each field, each of which could delegate to another derived PartialOrd. Now we use ordering and optional chaining to ensure each pair of fields is only compared once.
1 parent cc79420 commit 6eb4f0f

7 files changed

+90
-97
lines changed

src/etc/generate-deriving-span-tests.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def write_file(name, string):
122122

123123
for (trait, supers, errs) in [('Clone', [], 1),
124124
('PartialEq', [], 2),
125-
('PartialOrd', ['PartialEq'], 5),
125+
('PartialOrd', ['PartialEq'], 2),
126126
('Eq', ['PartialEq'], 1),
127127
('Ord', ['Eq', 'PartialOrd', 'PartialEq'], 1),
128128
('Debug', [], 1),

src/libsyntax_ext/deriving/cmp/partial_ord.rs

+83-70
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use deriving::{path_local, pathvec_std, path_std};
1414
use deriving::generic::*;
1515
use deriving::generic::ty::*;
1616

17-
use syntax::ast::{self, BinOpKind, Expr, MetaItem};
17+
use syntax::ast::{self, BinOpKind, Expr, MetaItem, Ident};
1818
use syntax::ext::base::{Annotatable, ExtCtxt};
1919
use syntax::ext::build::AstBuilder;
2020
use syntax::ptr::P;
@@ -147,34 +147,37 @@ pub fn cs_partial_cmp(cx: &mut ExtCtxt, span: Span, substr: &Substructure) -> P<
147147
// as the outermost one, and the last as the innermost.
148148
false,
149149
|cx, span, old, self_f, other_fs| {
150-
// match new {
151-
// Some(::std::cmp::Ordering::Equal) => old,
152-
// cmp => cmp
153-
// }
150+
// match new {
151+
// Some(::std::cmp::Ordering::Equal) => old,
152+
// cmp => cmp
153+
// }
154154

155-
let new = {
156-
let other_f = match (other_fs.len(), other_fs.get(0)) {
157-
(1, Some(o_f)) => o_f,
158-
_ => cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`"),
159-
};
155+
let new = {
156+
let other_f = match (other_fs.len(), other_fs.get(0)) {
157+
(1, Some(o_f)) => o_f,
158+
_ => {
159+
cx.span_bug(span,
160+
"not exactly 2 arguments in `derive(PartialOrd)`")
161+
}
162+
};
160163

161-
let args = vec![
162-
cx.expr_addr_of(span, self_f),
163-
cx.expr_addr_of(span, other_f.clone()),
164-
];
164+
let args = vec![
165+
cx.expr_addr_of(span, self_f),
166+
cx.expr_addr_of(span, other_f.clone()),
167+
];
165168

166-
cx.expr_call_global(span, partial_cmp_path.clone(), args)
167-
};
169+
cx.expr_call_global(span, partial_cmp_path.clone(), args)
170+
};
168171

169-
let eq_arm = cx.arm(span,
170-
vec![cx.pat_some(span, cx.pat_path(span, ordering.clone()))],
171-
old);
172-
let neq_arm = cx.arm(span,
173-
vec![cx.pat_ident(span, test_id)],
174-
cx.expr_ident(span, test_id));
172+
let eq_arm = cx.arm(span,
173+
vec![cx.pat_some(span, cx.pat_path(span, ordering.clone()))],
174+
old);
175+
let neq_arm = cx.arm(span,
176+
vec![cx.pat_ident(span, test_id)],
177+
cx.expr_ident(span, test_id));
175178

176-
cx.expr_match(span, new, vec![eq_arm, neq_arm])
177-
},
179+
cx.expr_match(span, new, vec![eq_arm, neq_arm])
180+
},
178181
equals_expr.clone(),
179182
Box::new(|cx, span, (self_args, tag_tuple), _non_self_args| {
180183
if self_args.len() != 2 {
@@ -189,78 +192,77 @@ pub fn cs_partial_cmp(cx: &mut ExtCtxt, span: Span, substr: &Substructure) -> P<
189192
}
190193

191194
/// Strict inequality.
192-
fn cs_op(less: bool, equal: bool, cx: &mut ExtCtxt, span: Span, substr: &Substructure) -> P<Expr> {
193-
let strict_op = if less { BinOpKind::Lt } else { BinOpKind::Gt };
194-
cs_fold1(false, // need foldr,
195+
fn cs_op(less: bool,
196+
inclusive: bool,
197+
cx: &mut ExtCtxt,
198+
span: Span,
199+
substr: &Substructure) -> P<Expr> {
200+
let ordering_path = |cx: &mut ExtCtxt, name: &str| {
201+
cx.expr_path(cx.path_global(span, cx.std_path(&["cmp", "Ordering", name])))
202+
};
203+
204+
let par_cmp = |cx: &mut ExtCtxt, span: Span, self_f: P<Expr>, other_fs: &[P<Expr>]| {
205+
let other_f = match (other_fs.len(), other_fs.get(0)) {
206+
(1, Some(o_f)) => o_f,
207+
_ => cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`"),
208+
};
209+
210+
// `self.fi.partial_cmp(other.fi)`
211+
let cmp = cx.expr_method_call(span,
212+
cx.expr_addr_of(span, self_f),
213+
Ident::from_str("partial_cmp"),
214+
vec![cx.expr_addr_of(span, other_f.clone())]);
215+
216+
let default = ordering_path(cx, if less { "Greater" } else { "Less" });
217+
// `_.unwrap_or(Ordering::Greater/Less)`
218+
cx.expr_method_call(span, cmp, Ident::from_str("unwrap_or"), vec![default])
219+
};
220+
221+
let fold = cs_fold1(false, // need foldr
195222
|cx, span, subexpr, self_f, other_fs| {
196-
// build up a series of chain ||'s and &&'s from the inside
223+
// build up a series of `partial_cmp`s from the inside
197224
// out (hence foldr) to get lexical ordering, i.e. for op ==
198225
// `ast::lt`
199226
//
200227
// ```
201-
// self.f1 < other.f1 || (!(other.f1 < self.f1) &&
202-
// self.f2 < other.f2
203-
// )
228+
// self.f1.partial_cmp(other.f1).unwrap_or(Ordering::Greater)
229+
// .then_with(|| self.f2.partial_cmp(other.f2).unwrap_or(Ordering::Greater))
230+
// == Ordering::Less
204231
// ```
205232
//
206233
// and for op ==
207234
// `ast::le`
208235
//
209236
// ```
210-
// self.f1 < other.f1 || (self.f1 == other.f1 &&
211-
// self.f2 <= other.f2
212-
// )
237+
// self.f1.partial_cmp(other.f1).unwrap_or(Ordering::Greater)
238+
// .then_with(|| self.f2.partial_cmp(other.f2).unwrap_or(Ordering::Greater))
239+
// != Ordering::Greater
213240
// ```
214241
//
215242
// The optimiser should remove the redundancy. We explicitly
216243
// get use the binops to avoid auto-deref dereferencing too many
217244
// layers of pointers, if the type includes pointers.
218-
//
219-
let other_f = match (other_fs.len(), other_fs.get(0)) {
220-
(1, Some(o_f)) => o_f,
221-
_ => cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`"),
222-
};
223245

224-
let strict_ineq = cx.expr_binary(span, strict_op, self_f.clone(), other_f.clone());
246+
// `self.fi.partial_cmp(other.fi).unwrap_or(Ordering::Greater/Less)`
247+
let par_cmp = par_cmp(cx, span, self_f, other_fs);
225248

226-
let deleg_cmp = if !equal {
227-
cx.expr_unary(span,
228-
ast::UnOp::Not,
229-
cx.expr_binary(span, strict_op, other_f.clone(), self_f))
230-
} else {
231-
cx.expr_binary(span, BinOpKind::Eq, self_f, other_f.clone())
232-
};
233-
234-
let and = cx.expr_binary(span, BinOpKind::And, deleg_cmp, subexpr);
235-
cx.expr_binary(span, BinOpKind::Or, strict_ineq, and)
249+
// `self.fi.partial_cmp(other.fi).unwrap_or(Ordering::Greater/Less).then_with(...)`
250+
cx.expr_method_call(span,
251+
par_cmp,
252+
Ident::from_str("then_with"),
253+
vec![cx.lambda0(span, subexpr)])
236254
},
237255
|cx, args| {
238256
match args {
239-
Some((span, self_f, other_fs)) => {
240-
// Special-case the base case to generate cleaner code with
241-
// fewer operations (e.g. `<=` instead of `<` and `==`).
242-
let other_f = match (other_fs.len(), other_fs.get(0)) {
243-
(1, Some(o_f)) => o_f,
244-
_ => cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`"),
245-
};
246-
247-
let op = match (less, equal) {
248-
(false, false) => BinOpKind::Gt,
249-
(false, true) => BinOpKind::Ge,
250-
(true, false) => BinOpKind::Lt,
251-
(true, true) => BinOpKind::Le,
252-
};
253-
254-
cx.expr_binary(span, op, self_f, other_f.clone())
255-
}
256-
None => cx.expr_bool(span, equal)
257+
Some((span, self_f, other_fs)) => par_cmp(cx, span, self_f, other_fs),
258+
None => ordering_path(cx, if less { "Less" } else { "Equal" })
257259
}
258260
},
259261
Box::new(|cx, span, (self_args, tag_tuple), _non_self_args| {
260262
if self_args.len() != 2 {
261263
cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`")
262264
} else {
263-
let op = match (less, equal) {
265+
let op = match (less, inclusive) {
264266
(false, false) => GtOp,
265267
(false, true) => GeOp,
266268
(true, false) => LtOp,
@@ -271,5 +273,16 @@ fn cs_op(less: bool, equal: bool, cx: &mut ExtCtxt, span: Span, substr: &Substru
271273
}),
272274
cx,
273275
span,
274-
substr)
276+
substr);
277+
278+
match *substr.fields {
279+
EnumMatching(..) |
280+
Struct(..) => {
281+
let ordering = ordering_path(cx, if less ^ inclusive { "Less" } else { "Greater" });
282+
let comp_op = if inclusive { BinOpKind::Ne } else { BinOpKind::Eq };
283+
284+
cx.expr_binary(span, comp_op, fold, ordering)
285+
}
286+
_ => fold
287+
}
275288
}

src/test/compile-fail/derives-span-PartialOrd-enum-struct-variant.rs

-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ enum Enum {
1818
A {
1919
x: Error //~ ERROR
2020
//~^ ERROR
21-
//~^^ ERROR
22-
//~^^^ ERROR
23-
//~^^^^ ERROR
2421
}
2522
}
2623

src/test/compile-fail/derives-span-PartialOrd-enum.rs

-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ enum Enum {
1818
A(
1919
Error //~ ERROR
2020
//~^ ERROR
21-
//~^^ ERROR
22-
//~^^^ ERROR
23-
//~^^^^ ERROR
2421
)
2522
}
2623

src/test/compile-fail/derives-span-PartialOrd-struct.rs

-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ struct Error;
1717
struct Struct {
1818
x: Error //~ ERROR
1919
//~^ ERROR
20-
//~^^ ERROR
21-
//~^^^ ERROR
22-
//~^^^^ ERROR
2320
}
2421

2522
fn main() {}

src/test/compile-fail/derives-span-PartialOrd-tuple-struct.rs

-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ struct Error;
1717
struct Struct(
1818
Error //~ ERROR
1919
//~^ ERROR
20-
//~^^ ERROR
21-
//~^^^ ERROR
22-
//~^^^^ ERROR
2320
);
2421

2522
fn main() {}

src/test/compile-fail/range_traits-1.rs

+6-14
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,27 @@ struct AllTheRanges {
1515
a: Range<usize>,
1616
//~^ ERROR PartialOrd
1717
//~^^ ERROR Ord
18-
//~^^^ ERROR binary operation `<` cannot be applied to type
19-
//~^^^^ ERROR binary operation `>` cannot be applied to type
18+
//~^^^ the trait bound
2019
b: RangeTo<usize>,
2120
//~^ ERROR PartialOrd
2221
//~^^ ERROR Ord
23-
//~^^^ ERROR binary operation `<` cannot be applied to type
24-
//~^^^^ ERROR binary operation `>` cannot be applied to type
22+
//~^^^ no method named `partial_cmp`
2523
c: RangeFrom<usize>,
2624
//~^ ERROR PartialOrd
2725
//~^^ ERROR Ord
28-
//~^^^ ERROR binary operation `<` cannot be applied to type
29-
//~^^^^ ERROR binary operation `>` cannot be applied to type
26+
//~^^^ the trait bound
3027
d: RangeFull,
3128
//~^ ERROR PartialOrd
3229
//~^^ ERROR Ord
33-
//~^^^ ERROR binary operation `<` cannot be applied to type
34-
//~^^^^ ERROR binary operation `>` cannot be applied to type
30+
//~^^^ no method named `partial_cmp`
3531
e: RangeInclusive<usize>,
3632
//~^ ERROR PartialOrd
3733
//~^^ ERROR Ord
38-
//~^^^ ERROR binary operation `<` cannot be applied to type
39-
//~^^^^ ERROR binary operation `>` cannot be applied to type
34+
//~^^^ the trait bound
4035
f: RangeToInclusive<usize>,
4136
//~^ ERROR PartialOrd
4237
//~^^ ERROR Ord
43-
//~^^^ ERROR binary operation `<` cannot be applied to type
44-
//~^^^^ ERROR binary operation `>` cannot be applied to type
45-
//~^^^^^ ERROR binary operation `<=` cannot be applied to type
46-
//~^^^^^^ ERROR binary operation `>=` cannot be applied to type
38+
//~^^^ no method named `partial_cmp`
4739
}
4840

4941
fn main() {}

0 commit comments

Comments
 (0)