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,
]
);