Skip to content

Commit bff3748

Browse files
committed
libsyntax: short-circuit on non-matching variants in deriving code.
Allow a deriving instance using the generic code to short-circuit for any non-matching enum variants (grouping them all into a _ match), reducing the number of arms required. Use this to speed up the Eq & TotalEq implementations.
1 parent 9949279 commit bff3748

11 files changed

+349
-156
lines changed

src/libsyntax/ext/deriving/clone.rs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub fn expand_deriving_clone(cx: @ext_ctxt,
2929
name: ~"clone",
3030
nargs: 0,
3131
output_type: None, // return Self
32+
const_nonmatching: false,
3233
combine_substructure: cs_clone
3334
}
3435
]

src/libsyntax/ext/deriving/cmp/eq.rs

+13-13
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,24 @@ pub fn expand_deriving_eq(cx: @ext_ctxt,
3131
cs_or(|cx, span, _| build::mk_bool(cx, span, true),
3232
cx, span, substr)
3333
}
34-
34+
macro_rules! md (
35+
($name:expr, $f:ident) => {
36+
MethodDef {
37+
name: $name,
38+
output_type: Some(~[~"bool"]),
39+
nargs: 1,
40+
const_nonmatching: true,
41+
combine_substructure: $f
42+
},
43+
}
44+
)
3545

3646
let trait_def = TraitDef {
3747
path: ~[~"core", ~"cmp", ~"Eq"],
3848
additional_bounds: ~[],
3949
methods: ~[
40-
MethodDef {
41-
name: ~"ne",
42-
output_type: Some(~[~"bool"]),
43-
nargs: 1,
44-
combine_substructure: cs_ne
45-
},
46-
MethodDef {
47-
name: ~"eq",
48-
output_type: Some(~[~"bool"]),
49-
nargs: 1,
50-
combine_substructure: cs_eq
51-
}
50+
md!(~"eq", cs_eq),
51+
md!(~"ne", cs_ne)
5252
]
5353
};
5454

src/libsyntax/ext/deriving/cmp/ord.rs

+14-28
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@ use ext::build;
1616
use ext::deriving::generic::*;
1717
use core::option::Some;
1818

19-
macro_rules! mk_cso {
20-
($less:expr, $equal:expr) => {
21-
|cx, span, substr|
22-
cs_ord($less, $equal, cx, span, substr)
19+
macro_rules! md {
20+
($name:expr, $less:expr, $equal:expr) => {
21+
MethodDef {
22+
name: $name,
23+
output_type: Some(~[~"bool"]),
24+
nargs: 1,
25+
const_nonmatching: false,
26+
combine_substructure: |cx, span, substr|
27+
cs_ord($less, $equal, cx, span, substr)
28+
}
2329
}
2430
}
2531
@@ -32,30 +38,10 @@ pub fn expand_deriving_ord(cx: @ext_ctxt,
3238
// XXX: Ord doesn't imply Eq yet
3339
additional_bounds: ~[~[~"core", ~"cmp", ~"Eq"]],
3440
methods: ~[
35-
MethodDef {
36-
name: ~"lt",
37-
output_type: Some(~[~"bool"]),
38-
nargs: 1,
39-
combine_substructure: mk_cso!(true, false)
40-
},
41-
MethodDef {
42-
name: ~"le",
43-
output_type: Some(~[~"bool"]),
44-
nargs: 1,
45-
combine_substructure: mk_cso!(true, true)
46-
},
47-
MethodDef {
48-
name: ~"gt",
49-
output_type: Some(~[~"bool"]),
50-
nargs: 1,
51-
combine_substructure: mk_cso!(false, false)
52-
},
53-
MethodDef {
54-
name: ~"ge",
55-
output_type: Some(~[~"bool"]),
56-
nargs: 1,
57-
combine_substructure: mk_cso!(false, true)
58-
},
41+
md!(~"lt", true, false),
42+
md!(~"le", true, true),
43+
md!(~"gt", false, false),
44+
md!(~"ge", false, true)
5945
]
6046
};
6147

src/libsyntax/ext/deriving/cmp/totaleq.rs

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub fn expand_deriving_totaleq(cx: @ext_ctxt,
3535
name: ~"equals",
3636
output_type: Some(~[~"bool"]),
3737
nargs: 1,
38+
const_nonmatching: true,
3839
combine_substructure: cs_equals
3940
}
4041
]

src/libsyntax/ext/deriving/cmp/totalord.rs

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub fn expand_deriving_totalord(cx: @ext_ctxt,
2828
name: ~"cmp",
2929
output_type: Some(~[~"core", ~"cmp", ~"Ordering"]),
3030
nargs: 1,
31+
const_nonmatching: false,
3132
combine_substructure: cs_cmp
3233
}
3334
]

src/libsyntax/ext/deriving/generic.rs

+121-40
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ arguments:
4040
- `EnumMatching`, when `Self` is an enum and all the arguments are the
4141
same variant of the enum (e.g. `Some(1)`, `Some(3)` and `Some(4)`)
4242
- `EnumNonMatching` when `Self` is an enum and the arguments are not
43-
the same variant (e.g. `None`, `Some(1)` and `None`)
43+
the same variant (e.g. `None`, `Some(1)` and `None`). If
44+
`const_nonmatching` is true, this will contain an empty list.
4445
4546
In the first two cases, the values from the corresponding fields in
4647
all the arguments are grouped together. In the `EnumNonMatching` case
@@ -129,9 +130,11 @@ use core::prelude::*;
129130
use ast;
130131

131132
use ast::{
133+
132134
and, binop, deref, enum_def, expr, expr_match, ident, impure_fn,
133-
item, Generics, m_imm, meta_item, method, named_field, or, public,
134-
struct_def, sty_region, ty_rptr, ty_path, variant};
135+
item, Generics, m_imm, meta_item, method, named_field, or,
136+
pat_wild, public, struct_def, sty_region, ty_rptr, ty_path,
137+
variant};
135138

136139
use ast_util;
137140
use ext::base::ext_ctxt;
@@ -177,6 +180,10 @@ pub struct MethodDef<'self> {
177180
/// Number of arguments other than `self` (all of type `&Self`)
178181
nargs: uint,
179182

183+
/// if the value of the nonmatching enums is independent of the
184+
/// actual enums, i.e. can use _ => .. match.
185+
const_nonmatching: bool,
186+
180187
combine_substructure: CombineSubstructureFunc<'self>
181188
}
182189

@@ -555,12 +562,13 @@ impl<'self> MethodDef<'self> {
555562
enum_def: &enum_def,
556563
type_ident: ident)
557564
-> @expr {
558-
self.build_enum_match(cx, span, enum_def, type_ident, ~[])
565+
self.build_enum_match(cx, span, enum_def, type_ident,
566+
None, ~[], 0)
559567
}
560568

561569

562570
/**
563-
Creates the nested matches for an enum definition, i.e.
571+
Creates the nested matches for an enum definition recursively, i.e.
564572
565573
```
566574
match self {
@@ -575,14 +583,20 @@ impl<'self> MethodDef<'self> {
575583
the tree are the same. Hopefully the optimisers get rid of any
576584
repetition, otherwise derived methods with many Self arguments will be
577585
exponentially large.
586+
587+
`matching` is Some(n) if all branches in the tree above the
588+
current position are variant `n`, `None` otherwise (including on
589+
the first call).
578590
*/
579591
fn build_enum_match(&self,
580592
cx: @ext_ctxt, span: span,
581593
enum_def: &enum_def,
582594
type_ident: ident,
595+
matching: Option<uint>,
583596
matches_so_far: ~[(uint, variant,
584-
~[(Option<ident>, @expr)])]) -> @expr {
585-
if matches_so_far.len() == self.nargs + 1 {
597+
~[(Option<ident>, @expr)])],
598+
match_count: uint) -> @expr {
599+
if match_count == self.nargs + 1 {
586600
// we've matched against all arguments, so make the final
587601
// expression at the bottom of the match tree
588602
match matches_so_far {
@@ -594,41 +608,44 @@ impl<'self> MethodDef<'self> {
594608
// vec of tuples, where each tuple represents a
595609
// field.
596610

597-
// `ref` inside let matches is buggy. Causes havoc wih rusc.
598-
// let (variant_index, ref self_vec) = matches_so_far[0];
599-
let (variant_index, variant, self_vec) = match matches_so_far[0] {
600-
(i, v, ref s) => (i, v, s)
601-
};
602-
603611
let substructure;
604612

605613
// most arms don't have matching variants, so do a
606614
// quick check to see if they match (even though
607615
// this means iterating twice) instead of being
608616
// optimistic and doing a pile of allocations etc.
609-
if matches_so_far.all(|&(v_i, _, _)| v_i == variant_index) {
610-
let mut enum_matching_fields = vec::from_elem(self_vec.len(), ~[]);
611-
612-
for matches_so_far.tail().each |&(_, _, other_fields)| {
613-
for other_fields.eachi |i, &(_, other_field)| {
614-
enum_matching_fields[i].push(other_field);
617+
match matching {
618+
Some(variant_index) => {
619+
// `ref` inside let matches is buggy. Causes havoc wih rusc.
620+
// let (variant_index, ref self_vec) = matches_so_far[0];
621+
let (variant, self_vec) = match matches_so_far[0] {
622+
(_, v, ref s) => (v, s)
623+
};
624+
625+
let mut enum_matching_fields = vec::from_elem(self_vec.len(), ~[]);
626+
627+
for matches_so_far.tail().each |&(_, _, other_fields)| {
628+
for other_fields.eachi |i, &(_, other_field)| {
629+
enum_matching_fields[i].push(other_field);
630+
}
615631
}
632+
let field_tuples =
633+
do vec::map2(*self_vec,
634+
enum_matching_fields) |&(id, self_f), &other| {
635+
(id, self_f, other)
636+
};
637+
substructure = EnumMatching(variant_index, variant, field_tuples);
638+
}
639+
None => {
640+
substructure = EnumNonMatching(matches_so_far);
616641
}
617-
let field_tuples =
618-
do vec::map2(*self_vec,
619-
enum_matching_fields) |&(id, self_f), &other| {
620-
(id, self_f, other)
621-
};
622-
substructure = EnumMatching(variant_index, variant, field_tuples);
623-
} else {
624-
substructure = EnumNonMatching(matches_so_far);
625642
}
626643
self.call_substructure_method(cx, span, type_ident, &substructure)
627644
}
628645
}
629646

630647
} else { // there are still matches to create
631-
let (current_match_ident, current_match_str) = if matches_so_far.is_empty() {
648+
let (current_match_ident, current_match_str) = if match_count == 0 {
632649
(cx.ident_of(~"self"), ~"__self")
633650
} else {
634651
let s = fmt!("__other_%u", matches_so_far.len() - 1);
@@ -640,8 +657,32 @@ impl<'self> MethodDef<'self> {
640657
// this is used as a stack
641658
let mut matches_so_far = matches_so_far;
642659

643-
// create an arm matching on each variant
644-
for enum_def.variants.eachi |index, variant| {
660+
macro_rules! mk_arm(
661+
($pat:expr, $expr:expr) => {
662+
{
663+
let blk = build::mk_simple_block(cx, span, $expr);
664+
let arm = ast::arm {
665+
pats: ~[$ pat ],
666+
guard: None,
667+
body: blk
668+
};
669+
arm
670+
}
671+
}
672+
)
673+
674+
// the code for nonmatching variants only matters when
675+
// we've seen at least one other variant already
676+
if self.const_nonmatching && match_count > 0 {
677+
// make a matching-variant match, and a _ match.
678+
let index = match matching {
679+
Some(i) => i,
680+
None => cx.span_bug(span, ~"Non-matching variants when required to\
681+
be matching in `deriving_generic`")
682+
};
683+
684+
// matching-variant match
685+
let variant = &enum_def.variants[index];
645686
let pattern = create_enum_variant_pattern(cx, span,
646687
variant,
647688
current_match_str);
@@ -653,23 +694,63 @@ impl<'self> MethodDef<'self> {
653694
}
654695
};
655696

656-
657697
matches_so_far.push((index, *variant, idents));
658698
let arm_expr = self.build_enum_match(cx, span,
659699
enum_def,
660700
type_ident,
661-
matches_so_far);
701+
matching,
702+
matches_so_far,
703+
match_count + 1);
662704
matches_so_far.pop();
663-
664-
let arm_block = build::mk_simple_block(cx, span, arm_expr);
665-
let arm = ast::arm {
666-
pats: ~[ pattern ],
667-
guard: None,
668-
body: arm_block
669-
};
705+
let arm = mk_arm!(pattern, arm_expr);
670706
arms.push(arm);
671-
}
672707

708+
if enum_def.variants.len() > 1 {
709+
// _ match, if necessary
710+
let wild_pat = @ast::pat {
711+
id: cx.next_id(),
712+
node: pat_wild,
713+
span: span
714+
};
715+
716+
let wild_expr = self.call_substructure_method(cx, span, type_ident,
717+
&EnumNonMatching(~[]));
718+
let wild_arm = mk_arm!(wild_pat, wild_expr);
719+
arms.push(wild_arm);
720+
}
721+
} else {
722+
// create an arm matching on each variant
723+
for enum_def.variants.eachi |index, variant| {
724+
let pattern = create_enum_variant_pattern(cx, span,
725+
variant,
726+
current_match_str);
727+
728+
let idents = do vec::build |push| {
729+
for each_variant_arg_ident(cx, span, variant) |i, field_id| {
730+
let id = cx.ident_of(fmt!("%s_%u", current_match_str, i));
731+
push((field_id, build::mk_path(cx, span, ~[ id ])));
732+
}
733+
};
734+
735+
matches_so_far.push((index, *variant, idents));
736+
let new_matching =
737+
match matching {
738+
_ if match_count == 0 => Some(index),
739+
Some(i) if index == i => Some(i),
740+
_ => None
741+
};
742+
let arm_expr = self.build_enum_match(cx, span,
743+
enum_def,
744+
type_ident,
745+
new_matching,
746+
matches_so_far,
747+
match_count + 1);
748+
matches_so_far.pop();
749+
750+
let arm = mk_arm!(pattern, arm_expr);
751+
arms.push(arm);
752+
}
753+
}
673754
let deref_expr = build::mk_unary(cx, span, deref,
674755
build::mk_path(cx, span,
675756
~[ current_match_ident ]));

0 commit comments

Comments
 (0)