]> git.lizzy.rs Git - rust.git/commitdiff
`O(n*k)` code-size deriving on enums (better than previous `O(n^k)`).
authorFelix S. Klock II <pnkfelix@pnkfx.org>
Sun, 6 Jul 2014 19:19:12 +0000 (21:19 +0200)
committerFelix S. Klock II <pnkfelix@pnkfx.org>
Fri, 11 Jul 2014 15:32:18 +0000 (17:32 +0200)
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.

src/libsyntax/ext/deriving/clone.rs
src/libsyntax/ext/deriving/cmp/eq.rs
src/libsyntax/ext/deriving/cmp/ord.rs
src/libsyntax/ext/deriving/cmp/totaleq.rs
src/libsyntax/ext/deriving/cmp/totalord.rs
src/libsyntax/ext/deriving/generic/mod.rs
src/libsyntax/ext/deriving/show.rs
src/libsyntax/ext/deriving/zero.rs

index 1a296906cc2f90218b1c8987b817a459a08078d7..3164aaf98f2caa55a6a0bb0d02df9db24ad322d6 100644 (file)
@@ -69,7 +69,7 @@ fn cs_clone(
             ctor_ident = variant.node.name;
             all_fields = af;
         },
-        EnumNonMatching(..) => {
+        EnumNonMatching(..) | EnumNonMatchingCollapsed (..) => {
             cx.span_bug(trait_span,
                         format!("non-matching enum variants in \
                                  `deriving({})`",
index 2eaeb0df7fbb702014652620d7ba0ec2090a882d..3c7f82a315acb87cfdeaaf0e6283275eab38d073 100644 (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
     fn cs_eq(cx: &mut ExtCtxt, span: Span, substr: &Substructure) -> Gc<Expr> {
         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> {
         cs_or(|cx, span, _, _| cx.expr_bool(span, true),
+              |cx, span, _, _| cx.expr_bool(span, true),
               cx, span, substr)
     }
 
index c8edf5c4157afa15a88b2c4b67c45ce3398b5b41..52a180deffb3f64f9fe870474f2d132499a99b7e 100644 (file)
@@ -35,7 +35,7 @@ macro_rules! md (
                 args: vec!(borrowed_self()),
                 ret_ty: Literal(Path::new(vec!("bool"))),
                 attributes: attrs,
-                on_nonmatching: NonMatchesExplode,
+                on_nonmatching: NonMatchesCollapseWithTags,
                 combine_substructure: combine_substructure(|cx, span, substr| {
                     cs_op($op, $equal, cx, span, substr)
                 })
@@ -59,7 +59,7 @@ macro_rules! md (
         args: vec![borrowed_self()],
         ret_ty: ret_ty,
         attributes: attrs,
-        on_nonmatching: NonMatchesExplode,
+        on_nonmatching: NonMatchesCollapseWithTags,
         combine_substructure: combine_substructure(|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)
 }
 
+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,
               substr: &Substructure) -> Gc<Expr> {
     let test_id = cx.ident_of("__test");
@@ -147,7 +165,14 @@ pub fn cs_partial_cmp(cx: &mut ExtCtxt, span: Span,
                 // later one.
                 [(self_var, _, _), (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)
@@ -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, (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)
 }
index 09aa24f9bfb5d608d0eb38800e3a0e4e9979f2ef..3a6b1fa218aee8d4510e611b6fc046888fe1ce25 100644 (file)
@@ -32,6 +32,7 @@ fn cs_total_eq_assert(cx: &mut ExtCtxt, span: Span,
             let block = cx.block(span, stmts, None);
             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,
                        span,
index 24785a026d18eafb9386b380e27044bdfa90ee9c..0e4b786857bac78d1455201dceaa17bbcb8e6b25 100644 (file)
@@ -41,7 +41,7 @@ pub fn expand_deriving_totalord(cx: &mut ExtCtxt,
                 args: vec!(borrowed_self()),
                 ret_ty: Literal(Path::new(vec!("std", "cmp", "Ordering"))),
                 attributes: attrs,
-                on_nonmatching: NonMatchesExplode,
+                on_nonmatching: NonMatchesCollapseWithTags,
                 combine_substructure: combine_substructure(|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)))
 }
 
+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,
               substr: &Substructure) -> Gc<Expr> {
     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, (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)
 }
index eebf55033b374209ec46a871bf1384cbcb36c178..039e4e2fb5723c02320d14a1fc0f369d2741325c 100644 (file)
 //!               }])
 //! ~~~
 //!
-//! For `C1 {x}` and `C1 {x}` ,
+//! For `C1 {x}` and `C1 {x}`,
 //!
 //! ~~~text
 //! EnumMatching(1, <ast::Variant for C1>,
 //! For `C0(a)` and `C1 {x}` ,
 //!
 //! ~~~text
-//! EnumNonMatching(~[(0, <ast::Variant for B0>,
-//!                    ~[(<span of int>, None, <expr for &a>)]),
-//!                   (1, <ast::Variant for B1>,
-//!                    ~[(<span of x>, Some(<ident of x>),
-//!                       <expr for &other.x>)])])
+//! EnumNonMatchingCollapsed(
+//!     ~[<ident of self>, <ident of __arg_1>],
+//!     &[<ast::Variant for C0>, <ast::Variant for C1>],
+//!     &[<ident for self index value>, <ident of __arg_1 index value>])
 //! ~~~
 //!
-//! (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
 //!
 //!                                   (<ident of C1>, <span of C1>,
 //!                                    Named(~[(<ident of x>, <span of x>)]))])
 //! ~~~
-//!
 
 use std::cell::RefCell;
 use std::gc::{Gc, GC};
@@ -215,9 +220,18 @@ pub struct TraitDef<'a> {
 
 #[deriving(PartialEq, Eq)]
 pub enum HandleNonMatchingEnums {
-    NonMatchesCollapse, // handle all non-matches via one `_ => ..` clause
-    NonMatchesExplode, // handle via n^k cases for n variants and k self-args
-    NonMatchHandlingIrrelevant, // cannot encounter two enums of Self type
+    /// handle all non-matches via one `_ => ..` clause
+    NonMatchesCollapse,
+
+    /// 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> {
@@ -308,6 +322,16 @@ pub enum SubstructureFields<'a> {
     EnumNonMatching(&'a [(uint, P<ast::Variant>,
                           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.
     StaticStruct(&'a ast::StructDef, StaticFields),
     /// A static method where Self is an enum.
@@ -324,14 +348,16 @@ pub enum SubstructureFields<'a> {
     |&mut ExtCtxt, Span, &Substructure|: 'a -> Gc<Expr>;
 
 /**
-Deal with non-matching enum variants, the arguments are a list
-representing each variant: (variant index, ast::Variant instance,
-[variant fields]), and a list of the nonself args of the type
+Deal with non-matching enum variants.  The tuple is a list of
+identifiers (one for each Self argument, which could be any of the
+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,
            Span,
-           &[(uint, P<ast::Variant>, Vec<(Span, Option<Ident>, Gc<Expr>)>)],
+           (&[Ident], &[Ident]),
            &[Gc<Expr>]|: 'a
            -> Gc<Expr>;
 
@@ -531,6 +557,15 @@ fn expand_enum_def(&self,
     }
 }
 
+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> {
     fn call_substructure_method(&self,
                                 cx: &mut ExtCtxt,
@@ -769,27 +804,32 @@ fn expand_static_struct_method_body(&self,
    ~~~
     #[deriving(PartialEq)]
     enum A {
-        A1
+        A1,
         A2(int)
     }
 
-    // is equivalent to (with on_nonmatching == NonMatchesExplode)
+    // is equivalent to
 
     impl PartialEq for A {
-        fn eq(&self, __arg_1: &A) {
-            match *self {
-                A1 => match *__arg_1 {
-                    A1 => true
-                    A2(ref __arg_1_1) => false
-                },
-                A2(self_1) => match *__arg_1 {
-                    A1 => false,
-                    A2(ref __arg_1_1) => self_1.eq(__arg_1_1)
+        fn eq(&self, __arg_1: &A) -> ::bool {
+            match (&*self, &*__arg_1) {
+                (&A1, &A1) => true,
+                (&A2(ref __self_0),
+                 &A2(ref __arg_1_0)) => (*__self_0).eq(&(*__arg_1_0)),
+                _ => {
+                    let __self_vi = match *self { A1(..) => 0u, A2(..) => 1u };
+                    let __arg_1_vi = match *__arg_1 { A1(..) => 0u, A2(..) => 1u };
+                    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,
                                cx: &mut ExtCtxt,
@@ -800,9 +840,15 @@ fn expand_enum_method_body(&self,
                                nonself_args: &[Gc<Expr>])
                                -> Gc<Expr> {
         let mut matches = Vec::new();
-        self.build_enum_match(cx, trait_, enum_def, type_ident,
-                              self_args, nonself_args,
-                              None, &mut matches, 0)
+        match self.on_nonmatching {
+            NonMatchesCollapseWithTags =>
+                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 @@ fn build_enum_match(&self,
 
             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
             // we've seen at least one other variant already
             assert!(match_count == 0 ||
@@ -987,6 +1037,293 @@ fn build_enum_match(&self,
         }
     }
 
+    /**
+    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,
                                       cx: &mut ExtCtxt,
                                       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>,
                base: Gc<Expr>,
                enum_nonmatch_f: EnumNonMatchFunc,
+               enum_nonmatch_g: EnumNonMatchCollapsedFunc,
                cx: &mut ExtCtxt,
                trait_span: Span,
                substructure: &Substructure)
@@ -1210,9 +1548,12 @@ pub fn cs_fold(use_foldl: bool,
                 })
             }
         },
-        EnumNonMatching(ref all_enums) => enum_nonmatch_f(cx, trait_span,
-                                                          *all_enums,
-                                                          substructure.nonself_args),
+        EnumNonMatching(ref all_enums) =>
+            enum_nonmatch_f(cx, trait_span, *all_enums,
+                            substructure.nonself_args),
+        EnumNonMatchingCollapsed(ref all_args, _, tuple) =>
+            enum_nonmatch_g(cx, trait_span, (all_args.as_slice(), tuple),
+                            substructure.nonself_args),
         StaticEnum(..) | StaticStruct(..) => {
             cx.span_bug(trait_span, "static function in `deriving`")
         }
@@ -1232,6 +1573,7 @@ pub fn cs_fold(use_foldl: bool,
 #[inline]
 pub fn cs_same_method(f: |&mut ExtCtxt, Span, Vec<Gc<Expr>>| -> Gc<Expr>,
                       enum_nonmatch_f: EnumNonMatchFunc,
+                      enum_nonmatch_g: EnumNonMatchCollapsedFunc,
                       cx: &mut ExtCtxt,
                       trait_span: Span,
                       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)
         },
-        EnumNonMatching(ref all_enums) => enum_nonmatch_f(cx, trait_span,
-                                                          *all_enums,
-                                                          substructure.nonself_args),
+        EnumNonMatching(ref all_enums) =>
+            enum_nonmatch_f(cx, trait_span, *all_enums,
+                            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(..) => {
             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>,
                            base: Gc<Expr>,
                            enum_nonmatch_f: EnumNonMatchFunc,
+                           enum_nonmatch_g: EnumNonMatchCollapsedFunc,
                            cx: &mut ExtCtxt,
                            trait_span: Span,
                            substructure: &Substructure)
@@ -1286,6 +1632,7 @@ pub fn cs_same_method_fold(use_foldl: bool,
             }
         },
         enum_nonmatch_f,
+        enum_nonmatch_g,
         cx, trait_span, substructure)
 }
 
@@ -1296,6 +1643,7 @@ pub fn cs_same_method_fold(use_foldl: bool,
 #[inline]
 pub fn cs_binop(binop: ast::BinOp, base: Gc<Expr>,
                 enum_nonmatch_f: EnumNonMatchFunc,
+                enum_nonmatch_g: EnumNonMatchCollapsedFunc,
                 cx: &mut ExtCtxt, trait_span: Span,
                 substructure: &Substructure) -> Gc<Expr> {
     cs_same_method_fold(
@@ -1308,25 +1656,30 @@ pub fn cs_binop(binop: ast::BinOp, base: Gc<Expr>,
         },
         base,
         enum_nonmatch_f,
+        enum_nonmatch_g,
         cx, trait_span, substructure)
 }
 
 /// cs_binop with binop == or
 #[inline]
 pub fn cs_or(enum_nonmatch_f: EnumNonMatchFunc,
+             enum_nonmatch_g: EnumNonMatchCollapsedFunc,
              cx: &mut ExtCtxt, span: Span,
              substructure: &Substructure) -> Gc<Expr> {
     cs_binop(ast::BiOr, cx.expr_bool(span, false),
              enum_nonmatch_f,
+             enum_nonmatch_g,
              cx, span, substructure)
 }
 
 /// cs_binop with binop == and
 #[inline]
 pub fn cs_and(enum_nonmatch_f: EnumNonMatchFunc,
+              enum_nonmatch_g: EnumNonMatchCollapsedFunc,
               cx: &mut ExtCtxt, span: Span,
               substructure: &Substructure) -> Gc<Expr> {
     cs_binop(ast::BiAnd, cx.expr_bool(span, true),
              enum_nonmatch_f,
+             enum_nonmatch_g,
              cx, span, substructure)
 }
index 722541d7dae53761488e2eb211b65b487bb5e24f..f01beeb7959c258b51fc71c3aefef261e6129d65 100644 (file)
@@ -67,7 +67,8 @@ fn show_substructure(cx: &mut ExtCtxt, span: Span,
         Struct(_) => substr.type_ident,
         EnumMatching(_, v, _) => v.node.name,
 
-        EnumNonMatching(..) | StaticStruct(..) | StaticEnum(..) => {
+        EnumNonMatching(..) | EnumNonMatchingCollapsed(..) |
+        StaticStruct(..) | StaticEnum(..) => {
             cx.span_bug(span, "nonsensical .fields in `#[deriving(Show)]`")
         }
     };
index 1d4f4c78fc044caf32764c35f33e952407141ab3..59a0d1ed5ce210823d6fd472e671e3272a4ce105 100644 (file)
@@ -57,6 +57,10 @@ pub fn expand_deriving_zero(cx: &mut ExtCtxt,
                                                         "Non-matching enum \
                                                          variant in \
                                                          deriving(Zero)"),
+                           |cx, span, _, _| cx.span_bug(span,
+                                                        "Non-matching enum \
+                                                         variant in \
+                                                         deriving(Zero)"),
                            cx, span, substr)
                 })
             }