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:
parent
8ea6c66cf7
commit
9812a4c9c6
@ -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()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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())
|
||||||
|
@ -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,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user