O(n*k) code-size deriving on enums (better than previous O(n^k)).

In the above formulas, `n` is the number of variants, and `k` is the
number of self-args fed into deriving.  In the particular case of
interest (namely `PartialOrd` and `Ord`), `k` is always 2, so we are
basically comparing `O(n)` versus `O(n^2)`.

Also, the stage is set for having *all* enum deriving codes go through
`build_enum_match_tuple` and getting rid of `build_enum_match`.

Also, seriously attempted to clean up the code itself.  Added a bunch
of comments attempting to document what I learned as I worked through
the original code and adapted it to this new strategy.
This commit is contained in:
Felix S. Klock II 2014-07-06 21:19:12 +02:00
parent 5d1bdc320b
commit c8ae44682d
8 changed files with 455 additions and 43 deletions

View File

@ -69,7 +69,7 @@ fn cs_clone(
ctor_ident = variant.node.name; ctor_ident = variant.node.name;
all_fields = af; all_fields = af;
}, },
EnumNonMatching(..) => { EnumNonMatching(..) | EnumNonMatchingCollapsed (..) => {
cx.span_bug(trait_span, cx.span_bug(trait_span,
format!("non-matching enum variants in \ format!("non-matching enum variants in \
`deriving({})`", `deriving({})`",

View File

@ -27,10 +27,12 @@ pub fn expand_deriving_eq(cx: &mut ExtCtxt,
// any fields are not equal or if the enum variants are different // any fields are not equal or if the enum variants are different
fn cs_eq(cx: &mut ExtCtxt, span: Span, substr: &Substructure) -> Gc<Expr> { fn cs_eq(cx: &mut ExtCtxt, span: Span, substr: &Substructure) -> Gc<Expr> {
cs_and(|cx, span, _, _| cx.expr_bool(span, false), cs_and(|cx, span, _, _| cx.expr_bool(span, false),
cx, span, substr) |cx, span, _, _| cx.expr_bool(span, false),
cx, span, substr)
} }
fn cs_ne(cx: &mut ExtCtxt, span: Span, substr: &Substructure) -> Gc<Expr> { fn cs_ne(cx: &mut ExtCtxt, span: Span, substr: &Substructure) -> Gc<Expr> {
cs_or(|cx, span, _, _| cx.expr_bool(span, true), cs_or(|cx, span, _, _| cx.expr_bool(span, true),
|cx, span, _, _| cx.expr_bool(span, true),
cx, span, substr) cx, span, substr)
} }

View File

@ -35,7 +35,7 @@ pub fn expand_deriving_ord(cx: &mut ExtCtxt,
args: vec!(borrowed_self()), args: vec!(borrowed_self()),
ret_ty: Literal(Path::new(vec!("bool"))), ret_ty: Literal(Path::new(vec!("bool"))),
attributes: attrs, attributes: attrs,
on_nonmatching: NonMatchesExplode, on_nonmatching: NonMatchesCollapseWithTags,
combine_substructure: combine_substructure(|cx, span, substr| { combine_substructure: combine_substructure(|cx, span, substr| {
cs_op($op, $equal, cx, span, substr) cs_op($op, $equal, cx, span, substr)
}) })
@ -59,7 +59,7 @@ pub fn expand_deriving_ord(cx: &mut ExtCtxt,
args: vec![borrowed_self()], args: vec![borrowed_self()],
ret_ty: ret_ty, ret_ty: ret_ty,
attributes: attrs, attributes: attrs,
on_nonmatching: NonMatchesExplode, on_nonmatching: NonMatchesCollapseWithTags,
combine_substructure: combine_substructure(|cx, span, substr| { combine_substructure: combine_substructure(|cx, span, substr| {
cs_partial_cmp(cx, span, substr) cs_partial_cmp(cx, span, substr)
}) })
@ -96,6 +96,24 @@ pub fn some_ordering_const(cx: &mut ExtCtxt, span: Span, cnst: Ordering) -> Gc<a
cx.expr_some(span, ordering) cx.expr_some(span, ordering)
} }
pub enum OrderingOp {
PartialCmpOp, LtOp, LeOp, GtOp, GeOp,
}
pub fn some_ordering_collapsed(cx: &mut ExtCtxt,
span: Span,
op: OrderingOp,
self_arg_tags: &[ast::Ident]) -> Gc<ast::Expr> {
let lft = cx.expr_ident(span, self_arg_tags[0]);
let rgt = cx.expr_addr_of(span, cx.expr_ident(span, self_arg_tags[1]));
let op_str = match op {
PartialCmpOp => "partial_cmp",
LtOp => "lt", LeOp => "le",
GtOp => "gt", GeOp => "ge",
};
cx.expr_method_call(span, lft, cx.ident_of(op_str), vec![rgt])
}
pub fn cs_partial_cmp(cx: &mut ExtCtxt, span: Span, pub fn cs_partial_cmp(cx: &mut ExtCtxt, span: Span,
substr: &Substructure) -> Gc<Expr> { substr: &Substructure) -> Gc<Expr> {
let test_id = cx.ident_of("__test"); let test_id = cx.ident_of("__test");
@ -147,7 +165,14 @@ pub fn cs_partial_cmp(cx: &mut ExtCtxt, span: Span,
// later one. // later one.
[(self_var, _, _), (other_var, _, _)] => [(self_var, _, _), (other_var, _, _)] =>
some_ordering_const(cx, span, self_var.cmp(&other_var)), some_ordering_const(cx, span, self_var.cmp(&other_var)),
_ => cx.span_bug(span, "not exactly 2 arguments in `deriving(Ord)`") _ => cx.span_bug(span, "not exactly 2 arguments in `deriving(Ord)`"),
}
},
|cx, span, (self_args, tag_tuple), _non_self_args| {
if self_args.len() != 2 {
cx.span_bug(span, "not exactly 2 arguments in `deriving(Ord)`")
} else {
some_ordering_collapsed(cx, span, PartialCmpOp, tag_tuple)
} }
}, },
cx, span, substr) cx, span, substr)
@ -206,5 +231,16 @@ fn cs_op(less: bool, equal: bool, cx: &mut ExtCtxt, span: Span,
_ => cx.span_bug(span, "not exactly 2 arguments in `deriving(Ord)`") _ => cx.span_bug(span, "not exactly 2 arguments in `deriving(Ord)`")
} }
}, },
|cx, span, (self_args, tag_tuple), _non_self_args| {
if self_args.len() != 2 {
cx.span_bug(span, "not exactly 2 arguments in `deriving(Ord)`")
} else {
let op = match (less, equal) {
(true, true) => LeOp, (true, false) => LtOp,
(false, true) => GeOp, (false, false) => GtOp,
};
some_ordering_collapsed(cx, span, op, tag_tuple)
}
},
cx, span, substr) cx, span, substr)
} }

View File

@ -32,6 +32,7 @@ pub fn expand_deriving_totaleq(cx: &mut ExtCtxt,
let block = cx.block(span, stmts, None); let block = cx.block(span, stmts, None);
cx.expr_block(block) cx.expr_block(block)
}, },
|cx, sp, _, _| cx.span_bug(sp, "non matching enums in deriving(Eq)?"),
|cx, sp, _, _| cx.span_bug(sp, "non matching enums in deriving(Eq)?"), |cx, sp, _, _| cx.span_bug(sp, "non matching enums in deriving(Eq)?"),
cx, cx,
span, span,

View File

@ -41,7 +41,7 @@ pub fn expand_deriving_totalord(cx: &mut ExtCtxt,
args: vec!(borrowed_self()), args: vec!(borrowed_self()),
ret_ty: Literal(Path::new(vec!("std", "cmp", "Ordering"))), ret_ty: Literal(Path::new(vec!("std", "cmp", "Ordering"))),
attributes: attrs, attributes: attrs,
on_nonmatching: NonMatchesExplode, on_nonmatching: NonMatchesCollapseWithTags,
combine_substructure: combine_substructure(|a, b, c| { combine_substructure: combine_substructure(|a, b, c| {
cs_cmp(a, b, c) cs_cmp(a, b, c)
}), }),
@ -65,6 +65,14 @@ pub fn ordering_const(cx: &mut ExtCtxt, span: Span, cnst: Ordering) -> ast::Path
cx.ident_of(cnst))) cx.ident_of(cnst)))
} }
pub fn ordering_collapsed(cx: &mut ExtCtxt,
span: Span,
self_arg_tags: &[ast::Ident]) -> Gc<ast::Expr> {
let lft = cx.expr_ident(span, self_arg_tags[0]);
let rgt = cx.expr_addr_of(span, cx.expr_ident(span, self_arg_tags[1]));
cx.expr_method_call(span, lft, cx.ident_of("cmp"), vec![rgt])
}
pub fn cs_cmp(cx: &mut ExtCtxt, span: Span, pub fn cs_cmp(cx: &mut ExtCtxt, span: Span,
substr: &Substructure) -> Gc<Expr> { substr: &Substructure) -> Gc<Expr> {
let test_id = cx.ident_of("__test"); let test_id = cx.ident_of("__test");
@ -122,5 +130,12 @@ pub fn cs_cmp(cx: &mut ExtCtxt, span: Span,
_ => cx.span_bug(span, "not exactly 2 arguments in `deriving(Ord)`") _ => cx.span_bug(span, "not exactly 2 arguments in `deriving(Ord)`")
} }
}, },
|cx, span, (self_args, tag_tuple), _non_self_args| {
if self_args.len() != 2 {
cx.span_bug(span, "not exactly 2 arguments in `deriving(TotalOrd)`")
} else {
ordering_collapsed(cx, span, tag_tuple)
}
},
cx, span, substr) cx, span, substr)
} }

View File

@ -135,7 +135,7 @@
//! }]) //! }])
//! ~~~ //! ~~~
//! //!
//! For `C1 {x}` and `C1 {x}` , //! For `C1 {x}` and `C1 {x}`,
//! //!
//! ~~~text //! ~~~text
//! EnumMatching(1, <ast::Variant for C1>, //! EnumMatching(1, <ast::Variant for C1>,
@ -150,14 +150,20 @@
//! For `C0(a)` and `C1 {x}` , //! For `C0(a)` and `C1 {x}` ,
//! //!
//! ~~~text //! ~~~text
//! EnumNonMatching(~[(0, <ast::Variant for B0>, //! EnumNonMatchingCollapsed(
//! ~[(<span of int>, None, <expr for &a>)]), //! ~[<ident of self>, <ident of __arg_1>],
//! (1, <ast::Variant for B1>, //! &[<ast::Variant for C0>, <ast::Variant for C1>],
//! ~[(<span of x>, Some(<ident of x>), //! &[<ident for self index value>, <ident of __arg_1 index value>])
//! <expr for &other.x>)])])
//! ~~~ //! ~~~
//! //!
//! (and vice versa, but with the order of the outermost list flipped.) //! It is the same for when the arguments are flipped to `C1 {x}` and
//! `C0(a)`; the only difference is what the values of the identifiers
//! <ident for self index value> and <ident of __arg_1 index value> will
//! be in the generated code.
//!
//! `EnumNonMatchingCollapsed` deliberately provides far less information
//! than is generally available for a given pair of variants; see #15375
//! for discussion.
//! //!
//! ## Static //! ## Static
//! //!
@ -172,7 +178,6 @@
//! (<ident of C1>, <span of C1>, //! (<ident of C1>, <span of C1>,
//! Named(~[(<ident of x>, <span of x>)]))]) //! Named(~[(<ident of x>, <span of x>)]))])
//! ~~~ //! ~~~
//!
use std::cell::RefCell; use std::cell::RefCell;
use std::gc::{Gc, GC}; use std::gc::{Gc, GC};
@ -215,9 +220,18 @@ pub struct TraitDef<'a> {
#[deriving(PartialEq, Eq)] #[deriving(PartialEq, Eq)]
pub enum HandleNonMatchingEnums { pub enum HandleNonMatchingEnums {
NonMatchesCollapse, // handle all non-matches via one `_ => ..` clause /// handle all non-matches via one `_ => ..` clause
NonMatchesExplode, // handle via n^k cases for n variants and k self-args NonMatchesCollapse,
NonMatchHandlingIrrelevant, // cannot encounter two enums of Self type
/// handle all non-matches via one `_ => ..` clause that has
/// access to a tuple of tags indicating each variant index.
NonMatchesCollapseWithTags,
/// handle via n^k cases for n variants and k self-args
NonMatchesExplode,
/// cannot encounter two enums of Self type
NonMatchHandlingIrrelevant,
} }
pub struct MethodDef<'a> { pub struct MethodDef<'a> {
@ -308,6 +322,16 @@ pub enum SubstructureFields<'a> {
EnumNonMatching(&'a [(uint, P<ast::Variant>, EnumNonMatching(&'a [(uint, P<ast::Variant>,
Vec<(Span, Option<Ident>, Gc<Expr>)>)]), Vec<(Span, Option<Ident>, Gc<Expr>)>)]),
/**
non-matching variants of the enum, but with all state hidden from
the consequent code. The first component holds Idents for all of
the Self arguments; the second component is a slice of all of the
variants for the enum itself, and the third component is a list of
Idents bound to the variant index values for each of the actual
input Self arguments.
*/
EnumNonMatchingCollapsed(Vec<Ident>, &'a [Gc<ast::Variant>], &'a [Ident]),
/// A static method where Self is a struct. /// A static method where Self is a struct.
StaticStruct(&'a ast::StructDef, StaticFields), StaticStruct(&'a ast::StructDef, StaticFields),
/// A static method where Self is an enum. /// A static method where Self is an enum.
@ -324,14 +348,16 @@ pub type CombineSubstructureFunc<'a> =
|&mut ExtCtxt, Span, &Substructure|: 'a -> Gc<Expr>; |&mut ExtCtxt, Span, &Substructure|: 'a -> Gc<Expr>;
/** /**
Deal with non-matching enum variants, the arguments are a list Deal with non-matching enum variants. The tuple is a list of
representing each variant: (variant index, ast::Variant instance, identifiers (one for each Self argument, which could be any of the
[variant fields]), and a list of the nonself args of the type variants since they have been collapsed together) and the identifiers
holding the variant index value for each of the Self arguments. The
last argument is all the non-Self args of the method being derived.
*/ */
pub type EnumNonMatchFunc<'a> = pub type EnumNonMatchCollapsedFunc<'a> =
|&mut ExtCtxt, |&mut ExtCtxt,
Span, Span,
&[(uint, P<ast::Variant>, Vec<(Span, Option<Ident>, Gc<Expr>)>)], (&[Ident], &[Ident]),
&[Gc<Expr>]|: 'a &[Gc<Expr>]|: 'a
-> Gc<Expr>; -> Gc<Expr>;
@ -531,6 +557,15 @@ impl<'a> TraitDef<'a> {
} }
} }
fn variant_to_pat(cx: &mut ExtCtxt, sp: Span, variant: &ast::Variant)
-> Gc<ast::Pat> {
let ident = cx.path_ident(sp, variant.node.name);
cx.pat(sp, match variant.node.kind {
ast::TupleVariantKind(..) => ast::PatEnum(ident, None),
ast::StructVariantKind(..) => ast::PatStruct(ident, Vec::new(), true),
})
}
impl<'a> MethodDef<'a> { impl<'a> MethodDef<'a> {
fn call_substructure_method(&self, fn call_substructure_method(&self,
cx: &mut ExtCtxt, cx: &mut ExtCtxt,
@ -769,27 +804,32 @@ impl<'a> MethodDef<'a> {
~~~ ~~~
#[deriving(PartialEq)] #[deriving(PartialEq)]
enum A { enum A {
A1 A1,
A2(int) A2(int)
} }
// is equivalent to (with on_nonmatching == NonMatchesExplode) // is equivalent to
impl PartialEq for A { impl PartialEq for A {
fn eq(&self, __arg_1: &A) { fn eq(&self, __arg_1: &A) -> ::bool {
match *self { match (&*self, &*__arg_1) {
A1 => match *__arg_1 { (&A1, &A1) => true,
A1 => true (&A2(ref __self_0),
A2(ref __arg_1_1) => false &A2(ref __arg_1_0)) => (*__self_0).eq(&(*__arg_1_0)),
}, _ => {
A2(self_1) => match *__arg_1 { let __self_vi = match *self { A1(..) => 0u, A2(..) => 1u };
A1 => false, let __arg_1_vi = match *__arg_1 { A1(..) => 0u, A2(..) => 1u };
A2(ref __arg_1_1) => self_1.eq(__arg_1_1) false
} }
} }
} }
} }
~~~ ~~~
(Of course `__self_vi` and `__arg_1_vi` are unused for
`PartialEq`, and those subcomputations will hopefully be removed
as their results are unused. The point of `__self_vi` and
`__arg_1_vi` is for `PartialOrd`; see #15503.)
*/ */
fn expand_enum_method_body(&self, fn expand_enum_method_body(&self,
cx: &mut ExtCtxt, cx: &mut ExtCtxt,
@ -800,9 +840,15 @@ impl<'a> MethodDef<'a> {
nonself_args: &[Gc<Expr>]) nonself_args: &[Gc<Expr>])
-> Gc<Expr> { -> Gc<Expr> {
let mut matches = Vec::new(); let mut matches = Vec::new();
self.build_enum_match(cx, trait_, enum_def, type_ident, match self.on_nonmatching {
self_args, nonself_args, NonMatchesCollapseWithTags =>
None, &mut matches, 0) self.build_enum_match_tuple(
cx, trait_, enum_def, type_ident, self_args, nonself_args),
NonMatchesCollapse | NonMatchesExplode | NonMatchHandlingIrrelevant =>
self.build_enum_match(
cx, trait_, enum_def, type_ident, self_args, nonself_args,
None, &mut matches, 0),
}
} }
@ -906,6 +952,10 @@ impl<'a> MethodDef<'a> {
let mut arms = Vec::new(); let mut arms = Vec::new();
assert!(self.on_nonmatching == NonMatchesCollapse ||
self.on_nonmatching == NonMatchesExplode ||
self.on_nonmatching == NonMatchHandlingIrrelevant);
// the code for nonmatching variants only matters when // the code for nonmatching variants only matters when
// we've seen at least one other variant already // we've seen at least one other variant already
assert!(match_count == 0 || assert!(match_count == 0 ||
@ -987,6 +1037,293 @@ impl<'a> MethodDef<'a> {
} }
} }
/**
Creates a match for a tuple of all `self_args`, where either all
variants match, or it falls into a catch-all for when one variant
does not match.
There are N + 1 cases because is a case for each of the N
variants where all of the variants match, and one catch-all for
when one does not match.
The catch-all handler is provided access the variant index values
for each of the self-args, carried in precomputed variables. (Nota
bene: the variant index values are not necessarily the
discriminant values. See issue #15523.)
~~~text
match (this, that, ...) {
(Variant1, Variant1, Variant1) => ... // delegate Matching on Variant1
(Variant2, Variant2, Variant2) => ... // delegate Matching on Variant2
...
_ => {
let __this_vi = match this { Variant1 => 0u, Variant2 => 1u, ... };
let __that_vi = match that { Variant1 => 0u, Variant2 => 1u, ... };
... // catch-all remainder can inspect above variant index values.
}
}
~~~
*/
fn build_enum_match_tuple(
&self,
cx: &mut ExtCtxt,
trait_: &TraitDef,
enum_def: &EnumDef,
type_ident: Ident,
self_args: &[Gc<Expr>],
nonself_args: &[Gc<Expr>]) -> Gc<Expr> {
let sp = trait_.span;
let variants = &enum_def.variants;
let self_arg_names = self_args.iter().enumerate()
.map(|(arg_count, _self_arg)| {
if arg_count == 0 {
"__self".to_string()
} else {
format!("__arg_{}", arg_count)
}
})
.collect::<Vec<String>>();
let self_arg_idents = self_arg_names.iter()
.map(|name|cx.ident_of(name.as_slice()))
.collect::<Vec<ast::Ident>>();
// The `vi_idents` will be bound, solely in the catch-all, to
// a series of let statements mapping each self_arg to a uint
// corresponding to its variant index.
let vi_idents : Vec<ast::Ident> = self_arg_names.iter()
.map(|name| { let vi_suffix = format!("{:s}_vi", name.as_slice());
cx.ident_of(vi_suffix.as_slice()) })
.collect::<Vec<ast::Ident>>();
// Builds, via callback to call_substructure_method, the
// delegated expression that handles the catch-all case,
// using `__variants_tuple` to drive logic if necessary.
let catch_all_substructure = EnumNonMatchingCollapsed(
self_arg_idents, variants.as_slice(), vi_idents.as_slice());
// These arms are of the form:
// (Variant1, Variant1, ...) => Body1
// (Variant2, Variant2, ...) => Body2
// ...
// where each tuple has length = self_args.len()
let mut match_arms : Vec<ast::Arm> = variants.iter().enumerate()
.map(|(index, &variant)| {
// These self_pats have form Variant1, Variant2, ...
let self_pats : Vec<(Gc<ast::Pat>,
Vec<(Span, Option<Ident>, Gc<Expr>)>)>;
self_pats = self_arg_names.iter()
.map(|self_arg_name|
trait_.create_enum_variant_pattern(
cx, &*variant, self_arg_name.as_slice(),
ast::MutImmutable))
.collect();
// A single arm has form (&VariantK, &VariantK, ...) => BodyK
// (see "Final wrinkle" note below for why.)
let subpats = self_pats.iter()
.map(|&(p, ref _idents)| cx.pat(sp, ast::PatRegion(p)))
.collect::<Vec<Gc<ast::Pat>>>();
// Here is the pat = `(&VariantK, &VariantK, ...)`
let single_pat = cx.pat(sp, ast::PatTup(subpats));
// For the BodyK, we need to delegate to our caller,
// passing it an EnumMatching to indicate which case
// we are in.
// All of the Self args have the same variant in these
// cases. So we transpose the info in self_pats to
// gather the getter expressions together, in the form
// that EnumMatching expects.
// The transposition is driven by walking across the
// arg fields of the variant for the first self pat.
let &(_, ref self_arg_fields) = self_pats.get(0);
let field_tuples : Vec<FieldInfo>;
field_tuples = self_arg_fields.iter().enumerate()
// For each arg field of self, pull out its getter expr ...
.map(|(field_index, &(sp, opt_ident, self_getter_expr))| {
// ... but FieldInfo also wants getter expr
// for matching other arguments of Self type;
// so walk across the *other* self_pats and
// pull out getter for same field in each of
// them (using `field_index` tracked above).
// That is the heart of the transposition.
let others = self_pats.tail().iter()
.map(|&(_pat, ref fields)| {
let &(_, _opt_ident, other_getter_expr) =
fields.get(field_index);
// All Self args have same variant, so
// opt_idents are the same. (Assert
// here to make it self-evident that
// it is okay to ignore `_opt_ident`.)
assert!(opt_ident == _opt_ident);
other_getter_expr
}).collect::<Vec<Gc<Expr>>>();
FieldInfo { span: sp,
name: opt_ident,
self_: self_getter_expr,
other: others,
}
}).collect::<Vec<FieldInfo>>();
// Now, for some given VariantK, we have built up
// expressions for referencing every field of every
// Self arg, assuming all are instances of VariantK.
// Build up code associated with such a case.
let substructure = EnumMatching(index, variant, field_tuples);
let arm_expr = self.call_substructure_method(
cx, trait_, type_ident, self_args, nonself_args,
&substructure);
cx.arm(sp, vec![single_pat], arm_expr)
}).collect();
// We will usually need the catch-all after matching the
// tuples `(VariantK, VariantK, ...)` for each VariantK of the
// enum. But:
//
// * when there is only one Self arg, the arms above suffice
// (and the deriving we call back into may not be prepared to
// handle EnumNonMatchCollapsed), and,
//
// * when the enum has only one variant, the single arm that
// is already present always suffices.
//
// * In either of the two cases above, if we *did* add a
// catch-all `_` match, it would trigger the
// unreachable-pattern error.
//
if variants.len() > 1 && self_args.len() > 1 {
let arms : Vec<ast::Arm> = variants.iter().enumerate()
.map(|(index, &variant)| {
let pat = variant_to_pat(cx, sp, &*variant);
let lit = ast::LitUint(index as u64, ast::TyU);
cx.arm(sp, vec![pat], cx.expr_lit(sp, lit))
}).collect();
// Build a series of let statements mapping each self_arg
// to a uint corresponding to its variant index.
// i.e. for `enum E<T> { A, B(1), C(T, T) }`, and a deriving
// with three Self args, builds three statements:
//
// ```
// let __self0_vi = match self {
// A => 0u, B(..) => 1u, C(..) => 2u
// };
// let __self1_vi = match __arg1 {
// A => 0u, B(..) => 1u, C(..) => 2u
// };
// let __self2_vi = match __arg2 {
// A => 0u, B(..) => 1u, C(..) => 2u
// };
// ```
let mut index_let_stmts : Vec<Gc<ast::Stmt>> = Vec::new();
for (&ident, &self_arg) in vi_idents.iter().zip(self_args.iter()) {
let variant_idx = cx.expr_match(sp, self_arg, arms.clone());
let let_stmt = cx.stmt_let(sp, false, ident, variant_idx);
index_let_stmts.push(let_stmt);
}
let arm_expr = self.call_substructure_method(
cx, trait_, type_ident, self_args, nonself_args,
&catch_all_substructure);
// Builds the expression:
// {
// let __self0_vi = ...;
// let __self1_vi = ...;
// ...
// <delegated expression referring to __self0_vi, et al.>
// }
let arm_expr = cx.expr_block(
cx.block_all(sp, Vec::new(), index_let_stmts, Some(arm_expr)));
// Builds arm:
// _ => { let __self0_vi = ...;
// let __self1_vi = ...;
// ...
// <delegated expression as above> }
let catch_all_match_arm =
cx.arm(sp, vec![cx.pat_wild(sp)], arm_expr);
match_arms.push(catch_all_match_arm);
} else if variants.len() == 0 {
// As an additional wrinkle, For a zero-variant enum A,
// currently the compiler
// will accept `fn (a: &Self) { match *a { } }`
// but rejects `fn (a: &Self) { match (&*a,) { } }`
// as well as `fn (a: &Self) { match ( *a,) { } }`
//
// This means that the strategy of building up a tuple of
// all Self arguments fails when Self is a zero variant
// enum: rustc rejects the expanded program, even though
// the actual code tends to be impossible to execute (at
// least safely), according to the type system.
//
// The most expedient fix for this is to just let the
// code fall through to the catch-all. But even this is
// error-prone, since the catch-all as defined above would
// generate code like this:
//
// _ => { let __self0 = match *self { };
// let __self1 = match *__arg_0 { };
// <catch-all-expr> }
//
// Which is yields bindings for variables which type
// inference cannot resolve to unique types.
//
// One option to the above might be to add explicit type
// annotations. But the *only* reason to go down that path
// would be to try to make the expanded output consistent
// with the case when the number of enum variants >= 1.
//
// That just isn't worth it. In fact, trying to generate
// sensible code for *any* deriving on a zero-variant enum
// does not make sense. But at the same time, for now, we
// do not want to cause a compile failure just because the
// user happened to attach a deriving to their
// zero-variant enum.
//
// Instead, just generate a failing expression for the
// zero variant case, skipping matches and also skipping
// delegating back to the end user code entirely.
//
// (See also #4499 and #12609; note that some of the
// discussions there influence what choice we make here;
// e.g. if we feature-gate `match x { ... }` when x refers
// to an uninhabited type (e.g. a zero-variant enum or a
// type holding such an enum), but do not feature-gate
// zero-variant enums themselves, then attempting to
// derive Show on such a type could here generate code
// that needs the feature gate enabled.)
return cx.expr_unreachable(sp);
}
// Final wrinkle: the self_args are expressions that deref
// down to desired l-values, but we cannot actually deref
// them when they are fed as r-values into a tuple
// expression; here add a layer of borrowing, turning
// `(*self, *__arg_0, ...)` into `(&*self, &*__arg_0, ...)`.
let borrowed_self_args = self_args.iter()
.map(|&self_arg| cx.expr_addr_of(sp, self_arg))
.collect::<Vec<Gc<ast::Expr>>>();
let match_arg = cx.expr(sp, ast::ExprTup(borrowed_self_args));
cx.expr_match(sp, match_arg, match_arms)
}
fn expand_static_enum_method_body(&self, fn expand_static_enum_method_body(&self,
cx: &mut ExtCtxt, cx: &mut ExtCtxt,
trait_: &TraitDef, trait_: &TraitDef,
@ -1186,6 +1523,7 @@ pub fn cs_fold(use_foldl: bool,
f: |&mut ExtCtxt, Span, Gc<Expr>, Gc<Expr>, &[Gc<Expr>]| -> Gc<Expr>, f: |&mut ExtCtxt, Span, Gc<Expr>, Gc<Expr>, &[Gc<Expr>]| -> Gc<Expr>,
base: Gc<Expr>, base: Gc<Expr>,
enum_nonmatch_f: EnumNonMatchFunc, enum_nonmatch_f: EnumNonMatchFunc,
enum_nonmatch_g: EnumNonMatchCollapsedFunc,
cx: &mut ExtCtxt, cx: &mut ExtCtxt,
trait_span: Span, trait_span: Span,
substructure: &Substructure) substructure: &Substructure)
@ -1210,9 +1548,12 @@ pub fn cs_fold(use_foldl: bool,
}) })
} }
}, },
EnumNonMatching(ref all_enums) => enum_nonmatch_f(cx, trait_span, EnumNonMatching(ref all_enums) =>
*all_enums, enum_nonmatch_f(cx, trait_span, *all_enums,
substructure.nonself_args), substructure.nonself_args),
EnumNonMatchingCollapsed(ref all_args, _, tuple) =>
enum_nonmatch_g(cx, trait_span, (all_args.as_slice(), tuple),
substructure.nonself_args),
StaticEnum(..) | StaticStruct(..) => { StaticEnum(..) | StaticStruct(..) => {
cx.span_bug(trait_span, "static function in `deriving`") cx.span_bug(trait_span, "static function in `deriving`")
} }
@ -1232,6 +1573,7 @@ f(cx, span, ~[self_1.method(__arg_1_1, __arg_2_1),
#[inline] #[inline]
pub fn cs_same_method(f: |&mut ExtCtxt, Span, Vec<Gc<Expr>>| -> Gc<Expr>, pub fn cs_same_method(f: |&mut ExtCtxt, Span, Vec<Gc<Expr>>| -> Gc<Expr>,
enum_nonmatch_f: EnumNonMatchFunc, enum_nonmatch_f: EnumNonMatchFunc,
enum_nonmatch_g: EnumNonMatchCollapsedFunc,
cx: &mut ExtCtxt, cx: &mut ExtCtxt,
trait_span: Span, trait_span: Span,
substructure: &Substructure) substructure: &Substructure)
@ -1250,9 +1592,12 @@ pub fn cs_same_method(f: |&mut ExtCtxt, Span, Vec<Gc<Expr>>| -> Gc<Expr>,
f(cx, trait_span, called) f(cx, trait_span, called)
}, },
EnumNonMatching(ref all_enums) => enum_nonmatch_f(cx, trait_span, EnumNonMatching(ref all_enums) =>
*all_enums, enum_nonmatch_f(cx, trait_span, *all_enums,
substructure.nonself_args), substructure.nonself_args),
EnumNonMatchingCollapsed(ref all_self_args, _, tuple) =>
enum_nonmatch_g(cx, trait_span, (all_self_args.as_slice(), tuple),
substructure.nonself_args),
StaticEnum(..) | StaticStruct(..) => { StaticEnum(..) | StaticStruct(..) => {
cx.span_bug(trait_span, "static function in `deriving`") cx.span_bug(trait_span, "static function in `deriving`")
} }
@ -1269,6 +1614,7 @@ pub fn cs_same_method_fold(use_foldl: bool,
f: |&mut ExtCtxt, Span, Gc<Expr>, Gc<Expr>| -> Gc<Expr>, f: |&mut ExtCtxt, Span, Gc<Expr>, Gc<Expr>| -> Gc<Expr>,
base: Gc<Expr>, base: Gc<Expr>,
enum_nonmatch_f: EnumNonMatchFunc, enum_nonmatch_f: EnumNonMatchFunc,
enum_nonmatch_g: EnumNonMatchCollapsedFunc,
cx: &mut ExtCtxt, cx: &mut ExtCtxt,
trait_span: Span, trait_span: Span,
substructure: &Substructure) substructure: &Substructure)
@ -1286,6 +1632,7 @@ pub fn cs_same_method_fold(use_foldl: bool,
} }
}, },
enum_nonmatch_f, enum_nonmatch_f,
enum_nonmatch_g,
cx, trait_span, substructure) cx, trait_span, substructure)
} }
@ -1296,6 +1643,7 @@ on all the fields.
#[inline] #[inline]
pub fn cs_binop(binop: ast::BinOp, base: Gc<Expr>, pub fn cs_binop(binop: ast::BinOp, base: Gc<Expr>,
enum_nonmatch_f: EnumNonMatchFunc, enum_nonmatch_f: EnumNonMatchFunc,
enum_nonmatch_g: EnumNonMatchCollapsedFunc,
cx: &mut ExtCtxt, trait_span: Span, cx: &mut ExtCtxt, trait_span: Span,
substructure: &Substructure) -> Gc<Expr> { substructure: &Substructure) -> Gc<Expr> {
cs_same_method_fold( cs_same_method_fold(
@ -1308,25 +1656,30 @@ pub fn cs_binop(binop: ast::BinOp, base: Gc<Expr>,
}, },
base, base,
enum_nonmatch_f, enum_nonmatch_f,
enum_nonmatch_g,
cx, trait_span, substructure) cx, trait_span, substructure)
} }
/// cs_binop with binop == or /// cs_binop with binop == or
#[inline] #[inline]
pub fn cs_or(enum_nonmatch_f: EnumNonMatchFunc, pub fn cs_or(enum_nonmatch_f: EnumNonMatchFunc,
enum_nonmatch_g: EnumNonMatchCollapsedFunc,
cx: &mut ExtCtxt, span: Span, cx: &mut ExtCtxt, span: Span,
substructure: &Substructure) -> Gc<Expr> { substructure: &Substructure) -> Gc<Expr> {
cs_binop(ast::BiOr, cx.expr_bool(span, false), cs_binop(ast::BiOr, cx.expr_bool(span, false),
enum_nonmatch_f, enum_nonmatch_f,
enum_nonmatch_g,
cx, span, substructure) cx, span, substructure)
} }
/// cs_binop with binop == and /// cs_binop with binop == and
#[inline] #[inline]
pub fn cs_and(enum_nonmatch_f: EnumNonMatchFunc, pub fn cs_and(enum_nonmatch_f: EnumNonMatchFunc,
enum_nonmatch_g: EnumNonMatchCollapsedFunc,
cx: &mut ExtCtxt, span: Span, cx: &mut ExtCtxt, span: Span,
substructure: &Substructure) -> Gc<Expr> { substructure: &Substructure) -> Gc<Expr> {
cs_binop(ast::BiAnd, cx.expr_bool(span, true), cs_binop(ast::BiAnd, cx.expr_bool(span, true),
enum_nonmatch_f, enum_nonmatch_f,
enum_nonmatch_g,
cx, span, substructure) cx, span, substructure)
} }

View File

@ -67,7 +67,8 @@ fn show_substructure(cx: &mut ExtCtxt, span: Span,
Struct(_) => substr.type_ident, Struct(_) => substr.type_ident,
EnumMatching(_, v, _) => v.node.name, EnumMatching(_, v, _) => v.node.name,
EnumNonMatching(..) | StaticStruct(..) | StaticEnum(..) => { EnumNonMatching(..) | EnumNonMatchingCollapsed(..) |
StaticStruct(..) | StaticEnum(..) => {
cx.span_bug(span, "nonsensical .fields in `#[deriving(Show)]`") cx.span_bug(span, "nonsensical .fields in `#[deriving(Show)]`")
} }
}; };

View File

@ -57,6 +57,10 @@ pub fn expand_deriving_zero(cx: &mut ExtCtxt,
"Non-matching enum \ "Non-matching enum \
variant in \ variant in \
deriving(Zero)"), deriving(Zero)"),
|cx, span, _, _| cx.span_bug(span,
"Non-matching enum \
variant in \
deriving(Zero)"),
cx, span, substr) cx, span, substr)
}) })
} }