feat(codegen): Allow #[serde(default="123")]

This feature adds support for the default to be specified to be
some expression (which unfortunately needs to be parsed from
a string) without needing this value to have an implementation
of `Default`, or for a new-type wrapper in order to provide an
alternative implementation. This expression is run in a function,
and therefore has no access to any of the internal state of
the deserializer.
This commit is contained in:
Erick Tryzelaar 2016-02-12 21:53:35 -08:00
parent 8ea6c66cf7
commit 9812a4c9c6
4 changed files with 184 additions and 41 deletions

View File

@ -1,7 +1,8 @@
use syntax::ast; use syntax::ast;
use syntax::attr; use syntax::attr;
use syntax::ext::base::ExtCtxt; 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 syntax::ptr::P;
use aster::AstBuilder; use aster::AstBuilder;
@ -167,12 +168,16 @@ pub struct FieldAttrs {
skip_serializing_field: bool, skip_serializing_field: bool,
skip_serializing_field_if_empty: bool, skip_serializing_field_if_empty: bool,
skip_serializing_field_if_none: bool, skip_serializing_field_if_none: bool,
use_default: bool, default_expr_if_missing: Option<P<ast::Expr>>,
} }
impl FieldAttrs { impl FieldAttrs {
/// Extract out the `#[serde(...)]` attributes from a struct field. /// Extract out the `#[serde(...)]` attributes from a struct field.
pub fn from_field(cx: &ExtCtxt, field: &ast::StructField) -> Result<Self, Error> { pub fn from_field(cx: &ExtCtxt,
generics: &ast::Generics,
field: &ast::StructField) -> Result<Self, Error> {
let builder = AstBuilder::new();
let field_ident = match field.node.ident() { let field_ident = match field.node.ident() {
Some(ident) => ident, Some(ident) => ident,
None => { cx.span_bug(field.span, "struct field has no name?") } None => { cx.span_bug(field.span, "struct field has no name?") }
@ -185,7 +190,7 @@ impl FieldAttrs {
skip_serializing_field: false, skip_serializing_field: false,
skip_serializing_field_if_empty: false, skip_serializing_field_if_empty: false,
skip_serializing_field_if_none: 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) { for meta_items in field.node.attrs.iter().filter_map(get_serde_meta_items) {
@ -206,7 +211,20 @@ impl FieldAttrs {
// Parse `#[serde(default)]` // Parse `#[serde(default)]`
ast::MetaItemKind::Word(ref name) if name == &"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)]` // Parse `#[serde(skip_serializing)]`
@ -261,8 +279,18 @@ impl FieldAttrs {
} }
/// Predicate for using a field's default value /// Predicate for using a field's default value
pub fn use_default(&self) -> bool { pub fn expr_is_missing(&self) -> P<ast::Expr> {
self.use_default 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 /// Predicate for ignoring a field when serializing a value
@ -281,9 +309,10 @@ impl FieldAttrs {
/// Extract out the `#[serde(...)]` attributes from a struct field. /// Extract out the `#[serde(...)]` attributes from a struct field.
pub fn get_struct_field_attrs(cx: &ExtCtxt, pub fn get_struct_field_attrs(cx: &ExtCtxt,
generics: &ast::Generics,
fields: &[ast::StructField]) -> Result<Vec<FieldAttrs>, Error> { fields: &[ast::StructField]) -> Result<Vec<FieldAttrs>, Error> {
fields.iter() fields.iter()
.map(|field| FieldAttrs::from_field(cx, field)) .map(|field| FieldAttrs::from_field(cx, generics, field))
.collect() .collect()
} }
@ -325,3 +354,50 @@ fn get_serde_meta_items(attr: &ast::Attribute) -> Option<&[P<ast::MetaItem>]> {
_ => None _ => None
} }
} }
fn parse_lit_into_expr(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Result<P<ast::Expr>, 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("<lit expansion>".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<ast::Ty>,
generics: &ast::Generics,
expr: P<ast::Expr>) -> P<ast::Expr> {
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()
})
}

View File

@ -503,6 +503,7 @@ fn deserialize_struct(
cx, cx,
builder, builder,
type_path.clone(), type_path.clone(),
impl_generics,
fields, fields,
container_attrs container_attrs
)); ));
@ -756,6 +757,7 @@ fn deserialize_struct_variant(
cx, cx,
builder, builder,
type_path, type_path,
generics,
fields, fields,
container_attrs, container_attrs,
)); ));
@ -918,20 +920,21 @@ fn deserialize_struct_visitor(
cx: &ExtCtxt, cx: &ExtCtxt,
builder: &aster::AstBuilder, builder: &aster::AstBuilder,
struct_path: ast::Path, struct_path: ast::Path,
generics: &ast::Generics,
fields: &[ast::StructField], fields: &[ast::StructField],
container_attrs: &attr::ContainerAttrs, container_attrs: &attr::ContainerAttrs,
) -> Result<(Vec<P<ast::Item>>, ast::Stmt, P<ast::Expr>), Error> { ) -> Result<(Vec<P<ast::Item>>, ast::Stmt, P<ast::Expr>), 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( let field_visitor = deserialize_field_visitor(
cx, cx,
builder, builder,
try!( try!(field_exprs),
fields.iter()
.map(|field| {
let attrs = try!(attr::FieldAttrs::from_field(cx, field));
Ok(attrs.deserialize_name_expr())
})
.collect()
),
container_attrs container_attrs
); );
@ -939,6 +942,7 @@ fn deserialize_struct_visitor(
cx, cx,
builder, builder,
struct_path, struct_path,
generics,
fields, fields,
container_attrs, container_attrs,
)); ));
@ -968,6 +972,7 @@ fn deserialize_map(
cx: &ExtCtxt, cx: &ExtCtxt,
builder: &aster::AstBuilder, builder: &aster::AstBuilder,
struct_path: ast::Path, struct_path: ast::Path,
generics: &ast::Generics,
fields: &[ast::StructField], fields: &[ast::StructField],
container_attrs: &attr::ContainerAttrs, container_attrs: &attr::ContainerAttrs,
) -> Result<P<ast::Expr>, Error> { ) -> Result<P<ast::Expr>, Error> {
@ -1003,26 +1008,22 @@ fn deserialize_map(
.chain(ignored_arm.into_iter()) .chain(ignored_arm.into_iter())
.collect(); .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() Ok(quote_stmt!(cx,
.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,
let $field_name = match $field_name { let $field_name = match $field_name {
Some($field_name) => $field_name, Some($field_name) => $field_name,
None => $missing_expr None => $missing_expr
}; };
).unwrap() ).unwrap())
}) })
.collect::<Vec<_>>(); .collect::<Result<Vec<_>, _>>();
let extract_values = try!(extract_values);
let result = builder.expr().struct_path(struct_path) let result = builder.expr().struct_path(struct_path)
.with_id_exprs( .with_id_exprs(

View File

@ -605,7 +605,7 @@ fn serialize_struct_visitor<I>(
{ {
let value_exprs = value_exprs.collect::<Vec<_>>(); let value_exprs = value_exprs.collect::<Vec<_>>();
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<ast::Arm> = field_attrs.iter() let arms: Vec<ast::Arm> = field_attrs.iter()
.zip(value_exprs.iter()) .zip(value_exprs.iter())

View File

@ -7,19 +7,29 @@ use token::{
assert_de_tokens_error assert_de_tokens_error
}; };
trait Trait {
fn my_default() -> Self;
}
impl Trait for i32 {
fn my_default() -> Self { 123 }
}
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Default { struct DefaultStruct<A, B: Default, C> where C: Trait {
a1: i32, a1: A,
#[serde(default)] #[serde(default)]
a2: i32, a2: B,
#[serde(default="Trait::my_default()")]
a3: C,
} }
#[test] #[test]
fn test_default() { fn test_default_struct() {
assert_de_tokens( assert_de_tokens(
&Default { a1: 1, a2: 2 }, &DefaultStruct { a1: 1, a2: 2, a3: 3 },
vec![ vec![
Token::StructStart("Default", Some(2)), Token::StructStart("DefaultStruct", Some(3)),
Token::MapSep, Token::MapSep,
Token::Str("a1"), Token::Str("a1"),
@ -29,14 +39,66 @@ fn test_default() {
Token::Str("a2"), Token::Str("a2"),
Token::I32(2), Token::I32(2),
Token::MapSep,
Token::Str("a3"),
Token::I32(3),
Token::MapEnd, Token::MapEnd,
] ]
); );
assert_de_tokens( assert_de_tokens(
&Default { a1: 1, a2: 0 }, &DefaultStruct { a1: 1, a2: 0, a3: 123 },
vec![ 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<A, B: Default, C> 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::MapSep,
Token::Str("a1"), Token::Str("a1"),
@ -57,9 +119,9 @@ struct DenyUnknown {
fn test_ignore_unknown() { fn test_ignore_unknown() {
// 'Default' allows unknown. Basic smoke test of ignore... // 'Default' allows unknown. Basic smoke test of ignore...
assert_de_tokens( assert_de_tokens(
&Default { a1: 1, a2: 2}, &DefaultStruct { a1: 1, a2: 2, a3: 3 },
vec![ vec![
Token::StructStart("Default", Some(5)), Token::StructStart("DefaultStruct", Some(5)),
Token::MapSep, Token::MapSep,
Token::Str("whoops1"), Token::Str("whoops1"),
@ -84,6 +146,10 @@ fn test_ignore_unknown() {
Token::Str("whoops3"), Token::Str("whoops3"),
Token::I32(2), Token::I32(2),
Token::MapSep,
Token::Str("a3"),
Token::I32(3),
Token::MapEnd, Token::MapEnd,
] ]
); );