Skip to content

Commit d711dc9

Browse files
committed
Auto merge of rust-lang#50011 - varkor:partialord-opt-ii, r=Manishearth
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, addressing rust-lang#49650 (comment). Closes rust-lang#49505. r? @Manishearth (sorry for changing it again so soon!) Close rust-lang#50755
2 parents 308b7b0 + 6805e5a commit d711dc9

7 files changed

+102
-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'], 1),
126126
('Eq', ['PartialEq'], 1),
127127
('Ord', ['Eq', 'PartialOrd', 'PartialEq'], 1),
128128
('Debug', [], 1),

src/libsyntax_ext/deriving/cmp/partial_ord.rs

+101-66
Original file line numberDiff line numberDiff line change
@@ -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,99 @@ 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, self_f: P<Expr>, other_fs: &[P<Expr>], default| {
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+
// `PartialOrd::partial_cmp(self.fi, other.fi)`
211+
let cmp_path = cx.expr_path(cx.path_global(span, cx.std_path(&["cmp",
212+
"PartialOrd",
213+
"partial_cmp"])));
214+
let cmp = cx.expr_call(span,
215+
cmp_path,
216+
vec![cx.expr_addr_of(span, self_f),
217+
cx.expr_addr_of(span, other_f.clone())]);
218+
219+
let default = ordering_path(cx, default);
220+
// `Option::unwrap_or(_, Ordering::Equal)`
221+
let unwrap_path = cx.expr_path(cx.path_global(span, cx.std_path(&["option",
222+
"Option",
223+
"unwrap_or"])));
224+
cx.expr_call(span, unwrap_path, vec![cmp, default])
225+
};
226+
227+
let fold = cs_fold1(false, // need foldr
195228
|cx, span, subexpr, self_f, other_fs| {
196-
// build up a series of chain ||'s and &&'s from the inside
229+
// build up a series of `partial_cmp`s from the inside
197230
// out (hence foldr) to get lexical ordering, i.e. for op ==
198231
// `ast::lt`
199232
//
200233
// ```
201-
// self.f1 < other.f1 || (!(other.f1 < self.f1) &&
202-
// self.f2 < other.f2
234+
// Ordering::then_with(
235+
// Option::unwrap_or(
236+
// PartialOrd::partial_cmp(self.f1, other.f1), Ordering::Equal)
237+
// ),
238+
// Option::unwrap_or(
239+
// PartialOrd::partial_cmp(self.f2, other.f2), Ordering::Greater)
240+
// )
203241
// )
242+
// == Ordering::Less
204243
// ```
205244
//
206245
// and for op ==
207246
// `ast::le`
208247
//
209248
// ```
210-
// self.f1 < other.f1 || (self.f1 == other.f1 &&
211-
// self.f2 <= other.f2
249+
// Ordering::then_with(
250+
// Option::unwrap_or(
251+
// PartialOrd::partial_cmp(self.f1, other.f1), Ordering::Equal)
252+
// ),
253+
// Option::unwrap_or(
254+
// PartialOrd::partial_cmp(self.f2, other.f2), Ordering::Greater)
255+
// )
212256
// )
257+
// != Ordering::Greater
213258
// ```
214259
//
215260
// The optimiser should remove the redundancy. We explicitly
216261
// get use the binops to avoid auto-deref dereferencing too many
217262
// 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-
};
223263

224-
let strict_ineq = cx.expr_binary(span, strict_op, self_f.clone(), other_f.clone());
264+
// `Option::unwrap_or(PartialOrd::partial_cmp(self.fi, other.fi), Ordering::Equal)`
265+
let par_cmp = par_cmp(cx, span, self_f, other_fs, "Equal");
225266

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)
267+
// `Ordering::then_with(Option::unwrap_or(..), ..)`
268+
let then_with_path = cx.expr_path(cx.path_global(span,
269+
cx.std_path(&["cmp",
270+
"Ordering",
271+
"then_with"])));
272+
cx.expr_call(span, then_with_path, vec![par_cmp, cx.lambda0(span, subexpr)])
236273
},
237274
|cx, args| {
238275
match args {
239276
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)
277+
let opposite = if less { "Greater" } else { "Less" };
278+
par_cmp(cx, span, self_f, other_fs, opposite)
279+
},
280+
None => cx.expr_bool(span, inclusive)
257281
}
258282
},
259283
Box::new(|cx, span, (self_args, tag_tuple), _non_self_args| {
260284
if self_args.len() != 2 {
261285
cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`")
262286
} else {
263-
let op = match (less, equal) {
287+
let op = match (less, inclusive) {
264288
(false, false) => GtOp,
265289
(false, true) => GeOp,
266290
(true, false) => LtOp,
@@ -271,5 +295,16 @@ fn cs_op(less: bool, equal: bool, cx: &mut ExtCtxt, span: Span, substr: &Substru
271295
}),
272296
cx,
273297
span,
274-
substr)
298+
substr);
299+
300+
match *substr.fields {
301+
EnumMatching(.., ref all_fields) |
302+
Struct(.., ref all_fields) if !all_fields.is_empty() => {
303+
let ordering = ordering_path(cx, if less ^ inclusive { "Less" } else { "Greater" });
304+
let comp_op = if inclusive { BinOpKind::Ne } else { BinOpKind::Eq };
305+
306+
cx.expr_binary(span, comp_op, fold, ordering)
307+
}
308+
_ => fold
309+
}
275310
}

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

-4
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ struct Error;
1717
enum Enum {
1818
A {
1919
x: Error //~ ERROR
20-
//~^ ERROR
21-
//~^^ ERROR
22-
//~^^^ ERROR
23-
//~^^^^ ERROR
2420
}
2521
}
2622

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

-4
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ struct Error;
1717
enum Enum {
1818
A(
1919
Error //~ ERROR
20-
//~^ ERROR
21-
//~^^ ERROR
22-
//~^^^ ERROR
23-
//~^^^^ ERROR
2420
)
2521
}
2622

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

-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ struct Error;
1616
#[derive(PartialOrd,PartialEq)]
1717
struct Struct {
1818
x: Error //~ ERROR
19-
//~^ ERROR
20-
//~^^ ERROR
21-
//~^^^ ERROR
22-
//~^^^^ ERROR
2319
}
2420

2521
fn main() {}

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

-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ struct Error;
1616
#[derive(PartialOrd,PartialEq)]
1717
struct Struct(
1818
Error //~ ERROR
19-
//~^ ERROR
20-
//~^^ ERROR
21-
//~^^^ ERROR
22-
//~^^^^ ERROR
2319
);
2420

2521
fn main() {}

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

-14
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,21 @@ 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
2018
b: RangeTo<usize>,
2119
//~^ ERROR PartialOrd
2220
//~^^ ERROR Ord
23-
//~^^^ ERROR binary operation `<` cannot be applied to type
24-
//~^^^^ ERROR binary operation `>` cannot be applied to type
2521
c: RangeFrom<usize>,
2622
//~^ ERROR PartialOrd
2723
//~^^ ERROR Ord
28-
//~^^^ ERROR binary operation `<` cannot be applied to type
29-
//~^^^^ ERROR binary operation `>` cannot be applied to type
3024
d: RangeFull,
3125
//~^ ERROR PartialOrd
3226
//~^^ ERROR Ord
33-
//~^^^ ERROR binary operation `<` cannot be applied to type
34-
//~^^^^ ERROR binary operation `>` cannot be applied to type
3527
e: RangeInclusive<usize>,
3628
//~^ ERROR PartialOrd
3729
//~^^ ERROR Ord
38-
//~^^^ ERROR binary operation `<` cannot be applied to type
39-
//~^^^^ ERROR binary operation `>` cannot be applied to type
4030
f: RangeToInclusive<usize>,
4131
//~^ ERROR PartialOrd
4232
//~^^ 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
4733
}
4834

4935
fn main() {}

0 commit comments

Comments
 (0)