syntax: make #[deriving(TotalOrd)] lazy.

Previously it would call:

  f(sf1.cmp(&of1), f(sf2.cmp(&of2), ...))

(where s/of1 = 'self/other field 1', and f was
std::cmp::lexical_ordering)

This meant that every .cmp subcall got evaluated when calling a derived
TotalOrd.cmp.

This corrects this to use

   let test = sf1.cmp(&of1);
   if test == Equal {
      let test = sf2.cmp(&of2);
      if test == Equal {
        // ...
      } else {
        test
      }
   } else {
     test
   }

This gives a lexical ordering by short-circuiting on the first comparison
that is not Equal.
This commit is contained in:
Huon Wilson 2013-07-28 01:16:30 +10:00
parent 44acdad5f8
commit 8407ec9fed
2 changed files with 47 additions and 17 deletions

View File

@ -153,7 +153,6 @@ pub fn cmp2<A:TotalOrd,B:TotalOrd>(
Return `o1` if it is not `Equal`, otherwise `o2`. Simulates the
lexical ordering on a type `(int, int)`.
*/
// used in deriving code in libsyntax
#[inline]
pub fn lexical_ordering(o1: Ordering, o2: Ordering) -> Ordering {
match o1 {

View File

@ -8,6 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use ast;
use ast::{MetaItem, item, expr};
use codemap::span;
use ext::base::ExtCtxt;
@ -40,40 +41,70 @@ pub fn expand_deriving_totalord(cx: @ExtCtxt,
}
pub fn ordering_const(cx: @ExtCtxt, span: span, cnst: Ordering) -> @expr {
pub fn ordering_const(cx: @ExtCtxt, span: span, cnst: Ordering) -> ast::Path {
let cnst = match cnst {
Less => "Less",
Equal => "Equal",
Greater => "Greater"
};
cx.expr_path(
cx.path_global(span,
~[cx.ident_of("std"),
cx.ident_of("cmp"),
cx.ident_of(cnst)]))
cx.path_global(span,
~[cx.ident_of("std"),
cx.ident_of("cmp"),
cx.ident_of(cnst)])
}
pub fn cs_cmp(cx: @ExtCtxt, span: span,
substr: &Substructure) -> @expr {
let test_id = cx.ident_of("__test");
let equals_path = ordering_const(cx, span, Equal);
/*
Builds:
let __test = self_field1.cmp(&other_field2);
if other == ::std::cmp::Equal {
let __test = self_field2.cmp(&other_field2);
if __test == ::std::cmp::Equal {
...
} else {
__test
}
} else {
__test
}
FIXME #6449: These `if`s could/should be `match`es.
*/
cs_same_method_fold(
// foldr (possibly) nests the matches in lexical_ordering better
// foldr nests the if-elses correctly, leaving the first field
// as the outermost one, and the last as the innermost.
false,
|cx, span, old, new| {
cx.expr_call_global(span,
~[cx.ident_of("std"),
cx.ident_of("cmp"),
cx.ident_of("lexical_ordering")],
~[old, new])
// let __test = new;
// if __test == ::std::cmp::Equal {
// old
// } else {
// __test
// }
let assign = cx.stmt_let(span, false, test_id, new);
let cond = cx.expr_binary(span, ast::eq,
cx.expr_ident(span, test_id),
cx.expr_path(equals_path.clone()));
let if_ = cx.expr_if(span,
cond,
old, Some(cx.expr_ident(span, test_id)));
cx.expr_block(cx.block(span, ~[assign], Some(if_)))
},
ordering_const(cx, span, Equal),
cx.expr_path(equals_path.clone()),
|cx, span, list, _| {
match list {
// an earlier nonmatching variant is Less than a
// later one
// later one.
[(self_var, _, _),
(other_var, _, _)] => ordering_const(cx, span,
self_var.cmp(&other_var)),
(other_var, _, _)] => cx.expr_path(ordering_const(cx, span,
self_var.cmp(&other_var))),
_ => cx.span_bug(span, "Not exactly 2 arguments in `deriving(TotalOrd)`")
}
},