From 30d755957a0f2cc3be3b355671da79cdf34fd50a Mon Sep 17 00:00:00 2001
From: Alex Crichton <alex@alexcrichton.com>
Date: Mon, 17 Jun 2013 17:23:18 -0700
Subject: [PATCH] Expand the deriving(ToStr) implementation

---
 doc/rust.md                          |  4 +-
 src/libsyntax/ext/deriving/to_str.rs | 75 ++++++++++++++++++++++++----
 src/test/run-pass/deriving-to-str.rs | 68 +++++++++++++------------
 3 files changed, 102 insertions(+), 45 deletions(-)

diff --git a/doc/rust.md b/doc/rust.md
index 9edbc44d6c2..4eedcb9a952 100644
--- a/doc/rust.md
+++ b/doc/rust.md
@@ -1562,7 +1562,9 @@ Supported traits for `deriving` are:
 * `IterBytes`, to iterate over the bytes in a data type.
 * `Rand`, to create a random instance of a data type.
 * `ToStr`, to convert to a string. For a type with this instance,
-  `obj.to_str()` has the same output as `fmt!("%?", obj)`.
+  `obj.to_str()` has similar output as `fmt!("%?", obj)`, but it differs in that
+  each constituent field of the type must also implement `ToStr` and will have
+  `field.to_str()` invoked to build up the result.
 
 # Statements and expressions
 
diff --git a/src/libsyntax/ext/deriving/to_str.rs b/src/libsyntax/ext/deriving/to_str.rs
index 41be3a775c1..4cd168b12c0 100644
--- a/src/libsyntax/ext/deriving/to_str.rs
+++ b/src/libsyntax/ext/deriving/to_str.rs
@@ -10,6 +10,7 @@
 
 use core::prelude::*;
 
+use ast;
 use ast::{meta_item, item, expr};
 use codemap::span;
 use ext::base::ExtCtxt;
@@ -40,16 +41,68 @@ pub fn expand_deriving_to_str(cx: @ExtCtxt,
     trait_def.expand(cx, span, mitem, in_items)
 }
 
-fn to_str_substructure(cx: @ExtCtxt, span: span, substr: &Substructure) -> @expr {
-    match substr.self_args {
-        [self_obj] => {
-            let self_addr = cx.expr_addr_of(span, self_obj);
-            cx.expr_call_global(span,
-                                ~[cx.ident_of("std"),
-                                  cx.ident_of("sys"),
-                                  cx.ident_of("log_str")],
-                                ~[self_addr])
+// It used to be the case that this deriving implementation invoked
+// std::sys::log_str, but this isn't sufficient because it doesn't invoke the
+// to_str() method on each field. Hence we mirror the logic of the log_str()
+// method, but with tweaks to call to_str() on sub-fields.
+fn to_str_substructure(cx: @ExtCtxt, span: span,
+                       substr: &Substructure) -> @expr {
+    let to_str = cx.ident_of("to_str");
+
+    let doit = |start: &str, end: @str, name: ast::ident,
+                fields: &[(Option<ast::ident>, @expr, ~[@expr])]| {
+        if fields.len() == 0 {
+            cx.expr_str_uniq(span, cx.str_of(name))
+        } else {
+            let buf = cx.ident_of("buf");
+            let start = cx.str_of(name) + start;
+            let init = cx.expr_str_uniq(span, start.to_managed());
+            let mut stmts = ~[cx.stmt_let(span, true, buf, init)];
+            let push_str = cx.ident_of("push_str");
+
+            let push = |s: @expr| {
+                let ebuf = cx.expr_ident(span, buf);
+                let call = cx.expr_method_call(span, ebuf, push_str, ~[s]);
+                stmts.push(cx.stmt_expr(call));
+            };
+
+            for fields.iter().enumerate().advance |(i, &(name, e, _))| {
+                if i > 0 {
+                    push(cx.expr_str(span, @", "));
+                }
+                match name {
+                    None => {}
+                    Some(id) => {
+                        let name = cx.str_of(id) + ": ";
+                        push(cx.expr_str(span, name.to_managed()));
+                    }
+                }
+                push(cx.expr_method_call(span, e, to_str, ~[]));
+            }
+            push(cx.expr_str(span, end));
+
+            cx.expr_blk(cx.blk(span, stmts, Some(cx.expr_ident(span, buf))))
         }
-        _ => cx.span_bug(span, "Invalid number of arguments in `deriving(ToStr)`")
-    }
+    };
+
+    return match *substr.fields {
+        Struct(ref fields) => {
+            if fields.len() == 0 || fields[0].n0_ref().is_none() {
+                doit("(", @")", substr.type_ident, *fields)
+            } else {
+                doit("{", @"}", substr.type_ident, *fields)
+            }
+        }
+
+        EnumMatching(_, variant, ref fields) => {
+            match variant.node.kind {
+                ast::tuple_variant_kind(*) =>
+                    doit("(", @")", variant.node.name, *fields),
+                ast::struct_variant_kind(*) =>
+                    doit("{", @"}", variant.node.name, *fields),
+            }
+        }
+
+        _ => cx.bug("expected Struct or EnumMatching in deriving(ToStr)")
+    };
 }
diff --git a/src/test/run-pass/deriving-to-str.rs b/src/test/run-pass/deriving-to-str.rs
index fcf0a009d9b..1fc1d6815f5 100644
--- a/src/test/run-pass/deriving-to-str.rs
+++ b/src/test/run-pass/deriving-to-str.rs
@@ -1,5 +1,4 @@
-// xfail-fast #6330
-// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
 // file at the top-level directory of this distribution and at
 // http://rust-lang.org/COPYRIGHT.
 //
@@ -9,39 +8,42 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use std::rand;
+#[deriving(ToStr)]
+enum A {}
+#[deriving(ToStr)]
+enum B { B1, B2, B3 }
+#[deriving(ToStr)]
+enum C { C1(int), C2(B), C3(~str) }
+#[deriving(ToStr)]
+enum D { D1{ a: int } }
+#[deriving(ToStr)]
+struct E;
+#[deriving(ToStr)]
+struct F(int);
+#[deriving(ToStr)]
+struct G(int, int);
+#[deriving(ToStr)]
+struct H { a: int }
+#[deriving(ToStr)]
+struct I { a: int, b: int }
+#[deriving(ToStr)]
+struct J(Custom);
 
-#[deriving(Rand,ToStr)]
-struct A;
-
-#[deriving(Rand,ToStr)]
-struct B(int, int);
-
-#[deriving(Rand,ToStr)]
-struct C {
-    x: f64,
-    y: (u8, u8)
-}
-
-#[deriving(Rand,ToStr)]
-enum D {
-    D0,
-    D1(uint),
-    D2 { x: (), y: () }
+struct Custom;
+impl ToStr for Custom {
+    fn to_str(&self) -> ~str { ~"yay" }
 }
 
 fn main() {
-    macro_rules! t(
-        ($ty:ty) => {{
-            let x =rand::random::<$ty>();
-            assert_eq!(x.to_str(), fmt!("%?", x));
-        }}
-    );
-
-    for 20.times {
-        t!(A);
-        t!(B);
-        t!(C);
-        t!(D);
-    }
+    assert_eq!(B1.to_str(), ~"B1");
+    assert_eq!(B2.to_str(), ~"B2");
+    assert_eq!(C1(3).to_str(), ~"C1(3)");
+    assert_eq!(C2(B2).to_str(), ~"C2(B2)");
+    assert_eq!(D1{ a: 2 }.to_str(), ~"D1{a: 2}");
+    assert_eq!(E.to_str(), ~"E");
+    assert_eq!(F(3).to_str(), ~"F(3)");
+    assert_eq!(G(3, 4).to_str(), ~"G(3, 4)");
+    assert_eq!(G(3, 4).to_str(), ~"G(3, 4)");
+    assert_eq!(I{ a: 2, b: 4 }.to_str(), ~"I{a: 2, b: 4}");
+    assert_eq!(J(Custom).to_str(), ~"J(yay)");
 }