From 6a8b3ae22fbf514d6dc920abebe478e95c38e3ad Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Thu, 6 Feb 2014 23:02:28 +1100 Subject: [PATCH] Implement `#[deriving(Show)]`. --- src/libsyntax/ext/deriving/mod.rs | 2 + src/libsyntax/ext/deriving/show.rs | 138 +++++++++++++++++++++++++++++ src/test/run-pass/deriving-show.rs | 42 +++++++++ 3 files changed, 182 insertions(+) create mode 100644 src/libsyntax/ext/deriving/show.rs create mode 100644 src/test/run-pass/deriving-show.rs diff --git a/src/libsyntax/ext/deriving/mod.rs b/src/libsyntax/ext/deriving/mod.rs index 4430f4abdbf..01e31fc5724 100644 --- a/src/libsyntax/ext/deriving/mod.rs +++ b/src/libsyntax/ext/deriving/mod.rs @@ -28,6 +28,7 @@ pub mod encodable; pub mod decodable; pub mod rand; pub mod to_str; +pub mod show; pub mod zero; pub mod default; pub mod primitive; @@ -83,6 +84,7 @@ pub fn expand_meta_deriving(cx: &mut ExtCtxt, "Rand" => expand!(rand::expand_deriving_rand), "ToStr" => expand!(to_str::expand_deriving_to_str), + "Show" => expand!(show::expand_deriving_show), "Zero" => expand!(zero::expand_deriving_zero), "Default" => expand!(default::expand_deriving_default), diff --git a/src/libsyntax/ext/deriving/show.rs b/src/libsyntax/ext/deriving/show.rs new file mode 100644 index 00000000000..67cfd151f62 --- /dev/null +++ b/src/libsyntax/ext/deriving/show.rs @@ -0,0 +1,138 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// 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::format; +use ext::base::ExtCtxt; +use ext::build::AstBuilder; +use ext::deriving::generic::*; + +use parse::token; + +use std::hashmap::HashMap; + +pub fn expand_deriving_show(cx: &mut ExtCtxt, + span: Span, + mitem: @MetaItem, + in_items: ~[@Item]) + -> ~[@Item] { + // &mut ::std::fmt::Formatter + let fmtr = Ptr(~Literal(Path::new(~["std", "fmt", "Formatter"])), + Borrowed(None, ast::MutMutable)); + + let trait_def = TraitDef { + cx: cx, span: span, + + path: Path::new(~["std", "fmt", "Show"]), + additional_bounds: ~[], + generics: LifetimeBounds::empty(), + methods: ~[ + MethodDef { + name: "fmt", + generics: LifetimeBounds::empty(), + explicit_self: borrowed_explicit_self(), + args: ~[fmtr], + ret_ty: Literal(Path::new(~["std", "fmt", "Result"])), + inline: false, + const_nonmatching: false, + combine_substructure: show_substructure + } + ] + }; + trait_def.expand(mitem, in_items) +} + +// we construct a format string and then defer to std::fmt, since that +// knows what's up with formatting at so on. +fn show_substructure(cx: &mut ExtCtxt, span: Span, + substr: &Substructure) -> @Expr { + // build ``, `({}, {}, ...)` or ` { : {}, + // : {}, ... }` based on the "shape". + // + // Easy start: they all start with the name. + let name = match *substr.fields { + Struct(_) => substr.type_ident, + EnumMatching(_, v, _) => v.node.name, + + EnumNonMatching(..) | StaticStruct(..) | StaticEnum(..) => { + cx.span_bug(span, "nonsensical .fields in `#[deriving(Show)]`") + } + }; + + let mut format_string = token::get_ident(name.name).get().to_owned(); + // the internal fields we're actually formatting + let mut exprs = ~[]; + + // Getting harder... making the format string: + match *substr.fields { + // unit struct/nullary variant: no work necessary! + Struct([]) | EnumMatching(_, _, []) => {} + + Struct(ref fields) | EnumMatching(_, _, ref fields) => { + if fields[0].name.is_none() { + // tuple struct/"normal" variant + + format_string.push_str("("); + + for (i, field) in fields.iter().enumerate() { + if i != 0 { format_string.push_str(", "); } + + format_string.push_str("{}"); + + exprs.push(field.self_); + } + + format_string.push_str(")"); + } else { + // normal struct/struct variant + + format_string.push_str(" \\{"); + + for (i, field) in fields.iter().enumerate() { + if i != 0 { format_string.push_str(","); } + + let name = token::get_ident(field.name.unwrap().name); + format_string.push_str(" "); + format_string.push_str(name.get()); + format_string.push_str(": {}"); + + exprs.push(field.self_); + } + + format_string.push_str(" \\}"); + } + } + _ => unreachable!() + } + + // AST construction! + // we're basically calling + // + // format_arg!(|__args| ::std::fmt::write(fmt.buf, __args), "", exprs...) + // + // but doing it directly via ext::format. + let formatter = substr.nonself_args[0]; + let buf = cx.expr_field_access(span, formatter, cx.ident_of("buf")); + + let std_write = ~[cx.ident_of("std"), cx.ident_of("fmt"), cx.ident_of("write")]; + let args = cx.ident_of("__args"); + let write_call = cx.expr_call_global(span, std_write, ~[buf, cx.expr_ident(span, args)]); + let format_closure = cx.lambda_expr(span, ~[args], write_call); + + let s = token::intern_and_get_ident(format_string); + let format_string = cx.expr_str(span, s); + + // phew, not our responsibility any more! + format::expand_preparsed_format_args(cx, span, + format_closure, + format_string, exprs, HashMap::new()) +} diff --git a/src/test/run-pass/deriving-show.rs b/src/test/run-pass/deriving-show.rs new file mode 100644 index 00000000000..40965615506 --- /dev/null +++ b/src/test/run-pass/deriving-show.rs @@ -0,0 +1,42 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[feature(struct_variant, macro_rules)]; + +#[deriving(Show)] +struct Unit; + +#[deriving(Show)] +struct Tuple(int, uint); + +#[deriving(Show)] +struct Struct { x: int, y: uint } + +#[deriving(Show)] +enum Enum { + Nullary, + Variant(int, uint), + StructVariant { x: int, y : uint } +} + +macro_rules! t { + ($x:expr, $expected:expr) => { + assert_eq!(format!("{}", $x), $expected.to_owned()) + } +} + +pub fn main() { + t!(Unit, "Unit"); + t!(Tuple(1, 2), "Tuple(1, 2)"); + t!(Struct { x: 1, y: 2 }, "Struct { x: 1, y: 2 }"); + t!(Nullary, "Nullary"); + t!(Variant(1, 2), "Variant(1, 2)"); + t!(StructVariant { x: 1, y: 2 }, "StructVariant { x: 1, y: 2 }"); +}