diff --git a/serde_codegen/src/attr.rs b/serde_codegen/src/attr.rs index 376d3444..7c69cdf1 100644 --- a/serde_codegen/src/attr.rs +++ b/serde_codegen/src/attr.rs @@ -1,7 +1,8 @@ use syntax::ast; use syntax::attr; use syntax::ext::base::ExtCtxt; -use syntax::print::pprust::meta_item_to_string; +use syntax::print::pprust::{lit_to_string, meta_item_to_string}; +use syntax::parse; use syntax::ptr::P; use aster::AstBuilder; @@ -167,12 +168,16 @@ pub struct FieldAttrs { skip_serializing_field: bool, skip_serializing_field_if_empty: bool, skip_serializing_field_if_none: bool, - use_default: bool, + default_expr_if_missing: Option>, } impl FieldAttrs { /// Extract out the `#[serde(...)]` attributes from a struct field. - pub fn from_field(cx: &ExtCtxt, field: &ast::StructField) -> Result { + pub fn from_field(cx: &ExtCtxt, + generics: &ast::Generics, + field: &ast::StructField) -> Result { + let builder = AstBuilder::new(); + let field_ident = match field.node.ident() { Some(ident) => ident, None => { cx.span_bug(field.span, "struct field has no name?") } @@ -185,7 +190,7 @@ impl FieldAttrs { skip_serializing_field: false, skip_serializing_field_if_empty: false, skip_serializing_field_if_none: false, - use_default: false, + default_expr_if_missing: None, }; for meta_items in field.node.attrs.iter().filter_map(get_serde_meta_items) { @@ -206,7 +211,20 @@ impl FieldAttrs { // Parse `#[serde(default)]` ast::MetaItemKind::Word(ref name) if name == &"default" => { - field_attrs.use_default = true; + let default_expr = builder.expr().default(); + field_attrs.default_expr_if_missing = Some(default_expr); + } + + // Parse `#[serde(default="...")]` + ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"default" => { + let wrapped_expr = wrap_default( + cx, + &field.node.ty, + generics, + try!(parse_lit_into_expr(cx, name, lit)), + ); + + field_attrs.default_expr_if_missing = Some(wrapped_expr); } // Parse `#[serde(skip_serializing)]` @@ -261,8 +279,18 @@ impl FieldAttrs { } /// Predicate for using a field's default value - pub fn use_default(&self) -> bool { - self.use_default + pub fn expr_is_missing(&self) -> P { + match self.default_expr_if_missing { + Some(ref expr) => expr.clone(), + None => { + let name = self.ident_expr(); + AstBuilder::new().expr() + .try() + .method_call("missing_field").id("visitor") + .with_arg(name) + .build() + } + } } /// Predicate for ignoring a field when serializing a value @@ -281,9 +309,10 @@ impl FieldAttrs { /// Extract out the `#[serde(...)]` attributes from a struct field. pub fn get_struct_field_attrs(cx: &ExtCtxt, + generics: &ast::Generics, fields: &[ast::StructField]) -> Result, Error> { fields.iter() - .map(|field| FieldAttrs::from_field(cx, field)) + .map(|field| FieldAttrs::from_field(cx, generics, field)) .collect() } @@ -325,3 +354,50 @@ fn get_serde_meta_items(attr: &ast::Attribute) -> Option<&[P]> { _ => None } } + +fn parse_lit_into_expr(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Result, Error> { + let s: &str = match lit.node { + ast::LitKind::Str(ref s, ast::StrStyle::Cooked) => &s, + _ => { + cx.span_err( + lit.span, + &format!("{} literal `{}` must be a string", + name, + lit_to_string(lit))); + + return Err(Error); + } + }; + + let expr = parse::parse_expr_from_source_str("".to_string(), + s.to_owned(), + cx.cfg(), + cx.parse_sess()); + + Ok(expr) +} + +/// This function wraps the expression in `#[serde(default="...")]` in a function to prevent it +/// from accessing the internal `Deserialize` state. +fn wrap_default(cx: &ExtCtxt, + field_ty: &P, + generics: &ast::Generics, + expr: P) -> P { + let builder = AstBuilder::new(); + + // Quasi-quoting doesn't do a great job of expanding generics into paths, so manually build it. + let fn_path = builder.path() + .segment("__serde_default") + .with_generics(generics.clone()) + .build() + .build(); + + let where_clause = &generics.where_clause; + + quote_expr!(cx, { + fn __serde_default $generics() -> $field_ty $where_clause { + $expr + } + $fn_path() + }) +} diff --git a/serde_codegen/src/de.rs b/serde_codegen/src/de.rs index 298c856d..458913e0 100644 --- a/serde_codegen/src/de.rs +++ b/serde_codegen/src/de.rs @@ -503,6 +503,7 @@ fn deserialize_struct( cx, builder, type_path.clone(), + impl_generics, fields, container_attrs )); @@ -756,6 +757,7 @@ fn deserialize_struct_variant( cx, builder, type_path, + generics, fields, container_attrs, )); @@ -918,20 +920,21 @@ fn deserialize_struct_visitor( cx: &ExtCtxt, builder: &aster::AstBuilder, struct_path: ast::Path, + generics: &ast::Generics, fields: &[ast::StructField], container_attrs: &attr::ContainerAttrs, ) -> Result<(Vec>, ast::Stmt, P), Error> { + let field_exprs = fields.iter() + .map(|field| { + let field_attrs = try!(attr::FieldAttrs::from_field(cx, generics, field)); + Ok(field_attrs.deserialize_name_expr()) + }) + .collect(); + let field_visitor = deserialize_field_visitor( cx, builder, - try!( - fields.iter() - .map(|field| { - let attrs = try!(attr::FieldAttrs::from_field(cx, field)); - Ok(attrs.deserialize_name_expr()) - }) - .collect() - ), + try!(field_exprs), container_attrs ); @@ -939,6 +942,7 @@ fn deserialize_struct_visitor( cx, builder, struct_path, + generics, fields, container_attrs, )); @@ -968,6 +972,7 @@ fn deserialize_map( cx: &ExtCtxt, builder: &aster::AstBuilder, struct_path: ast::Path, + generics: &ast::Generics, fields: &[ast::StructField], container_attrs: &attr::ContainerAttrs, ) -> Result, Error> { @@ -1003,26 +1008,22 @@ fn deserialize_map( .chain(ignored_arm.into_iter()) .collect(); - let field_attrs = try!(attr::get_struct_field_attrs(cx, fields)); + let extract_values = fields.iter() + .zip(field_names.iter()) + .map(|(field, field_name)| { + let field_attr = try!(attr::FieldAttrs::from_field(cx, generics, field)); + let missing_expr = field_attr.expr_is_missing(); - let extract_values = field_names.iter() - .zip(field_attrs.iter()) - .map(|(field_name, field_attr)| { - let missing_expr = if field_attr.use_default() { - quote_expr!(cx, ::std::default::Default::default()) - } else { - let name = field_attr.ident_expr(); - quote_expr!(cx, try!(visitor.missing_field($name))) - }; - - quote_stmt!(cx, + Ok(quote_stmt!(cx, let $field_name = match $field_name { Some($field_name) => $field_name, None => $missing_expr }; - ).unwrap() + ).unwrap()) }) - .collect::>(); + .collect::, _>>(); + + let extract_values = try!(extract_values); let result = builder.expr().struct_path(struct_path) .with_id_exprs( diff --git a/serde_codegen/src/ser.rs b/serde_codegen/src/ser.rs index ab59071a..7aef3de1 100644 --- a/serde_codegen/src/ser.rs +++ b/serde_codegen/src/ser.rs @@ -605,7 +605,7 @@ fn serialize_struct_visitor( { let value_exprs = value_exprs.collect::>(); - let field_attrs = try!(attr::get_struct_field_attrs(cx, fields)); + let field_attrs = try!(attr::get_struct_field_attrs(cx, generics, fields)); let arms: Vec = field_attrs.iter() .zip(value_exprs.iter()) diff --git a/serde_tests/tests/test_annotations.rs b/serde_tests/tests/test_annotations.rs index c5ff6e38..7f525746 100644 --- a/serde_tests/tests/test_annotations.rs +++ b/serde_tests/tests/test_annotations.rs @@ -7,19 +7,29 @@ use token::{ assert_de_tokens_error }; +trait Trait { + fn my_default() -> Self; +} + +impl Trait for i32 { + fn my_default() -> Self { 123 } +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] -struct Default { - a1: i32, +struct DefaultStruct where C: Trait { + a1: A, #[serde(default)] - a2: i32, + a2: B, + #[serde(default="Trait::my_default()")] + a3: C, } #[test] -fn test_default() { +fn test_default_struct() { assert_de_tokens( - &Default { a1: 1, a2: 2 }, + &DefaultStruct { a1: 1, a2: 2, a3: 3 }, vec![ - Token::StructStart("Default", Some(2)), + Token::StructStart("DefaultStruct", Some(3)), Token::MapSep, Token::Str("a1"), @@ -29,14 +39,66 @@ fn test_default() { Token::Str("a2"), Token::I32(2), + Token::MapSep, + Token::Str("a3"), + Token::I32(3), + Token::MapEnd, ] ); assert_de_tokens( - &Default { a1: 1, a2: 0 }, + &DefaultStruct { a1: 1, a2: 0, a3: 123 }, vec![ - Token::StructStart("Default", Some(1)), + Token::StructStart("DefaultStruct", Some(1)), + + Token::MapSep, + Token::Str("a1"), + Token::I32(1), + + Token::MapEnd, + ] + ); +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +enum DefaultEnum where C: Trait { + Struct { + a1: A, + #[serde(default)] + a2: B, + #[serde(default="Trait::my_default()")] + a3: C, + } +} + +#[test] +fn test_default_enum() { + assert_de_tokens( + &DefaultEnum::Struct { a1: 1, a2: 2, a3: 3 }, + vec![ + Token::EnumMapStart("DefaultEnum", "Struct", Some(3)), + + Token::MapSep, + Token::Str("a1"), + Token::I32(1), + + Token::MapSep, + Token::Str("a2"), + Token::I32(2), + + Token::MapSep, + Token::Str("a3"), + Token::I32(3), + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &DefaultEnum::Struct { a1: 1, a2: 0, a3: 123 }, + vec![ + Token::EnumMapStart("DefaultEnum", "Struct", Some(3)), Token::MapSep, Token::Str("a1"), @@ -57,9 +119,9 @@ struct DenyUnknown { fn test_ignore_unknown() { // 'Default' allows unknown. Basic smoke test of ignore... assert_de_tokens( - &Default { a1: 1, a2: 2}, + &DefaultStruct { a1: 1, a2: 2, a3: 3 }, vec![ - Token::StructStart("Default", Some(5)), + Token::StructStart("DefaultStruct", Some(5)), Token::MapSep, Token::Str("whoops1"), @@ -84,6 +146,10 @@ fn test_ignore_unknown() { Token::Str("whoops3"), Token::I32(2), + Token::MapSep, + Token::Str("a3"), + Token::I32(3), + Token::MapEnd, ] );