Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expanded #[serder(default = ...)] capabilities. #2728

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions serde/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "serde"
version = "1.0.197"
version = "1.0.198"
authors = ["Erick Tryzelaar <erick.tryzelaar@gmail.com>", "David Tolnay <dtolnay@gmail.com>"]
build = "build.rs"
categories = ["encoding", "no-std", "no-std::no-alloc"]
Expand Down Expand Up @@ -37,7 +37,7 @@ rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"]
# is compatible with exactly one serde release because the generated code
# involves nonpublic APIs which are not bound by semver.
[target.'cfg(any())'.dependencies]
serde_derive = { version = "=1.0.197", path = "../serde_derive" }
serde_derive = { version = "=1.0.198", path = "../serde_derive" }


### FEATURES #################################################################
Expand Down
2 changes: 1 addition & 1 deletion serde_derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "serde_derive"
version = "1.0.197"
version = "1.0.198"
authors = ["Erick Tryzelaar <erick.tryzelaar@gmail.com>", "David Tolnay <dtolnay@gmail.com>"]
categories = ["no-std", "no-std::no-alloc"]
description = "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]"
Expand Down
31 changes: 28 additions & 3 deletions serde_derive/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ fn build_generics(cont: &Container, borrowed: &BorrowedLifetimes) -> syn::Generi
&generics,
&parse_quote!(_serde::__private::Default),
),
attr::Default::None | attr::Default::Path(_) => generics,
attr::Default::None |
attr::Default::Expr(_) |
attr::Default::Path(_) => generics,
};

let delife = borrowed.de_lifetime();
Expand Down Expand Up @@ -373,6 +375,7 @@ fn deserialize_transparent(cont: &Container, params: &Parameters) -> Fragment {
attr::Default::Default => quote!(_serde::__private::Default::default()),
attr::Default::Path(path) => quote!(#path()),
attr::Default::None => quote!(_serde::__private::PhantomData),
attr::Default::Expr(lit) => quote!(#lit),
};
quote!(#member: #value)
}
Expand Down Expand Up @@ -754,6 +757,9 @@ fn deserialize_seq(
attr::Default::Path(path) => Some(quote!(
let __default: Self::Value = #path();
)),
attr::Default::Expr(lit) => Some(quote!(
let __default: Self::Value = #lit;
)),
attr::Default::None => {
// We don't need the default value, to prevent an unused variable warning
// we'll leave the line empty.
Expand Down Expand Up @@ -836,6 +842,9 @@ fn deserialize_seq_in_place(
attr::Default::Path(path) => Some(quote!(
let __default: #this_type #ty_generics = #path();
)),
attr::Default::Expr(lit) => Some(quote!(
let __default: #this_type #ty_generics = #lit;
)),
attr::Default::None => {
// We don't need the default value, to prevent an unused variable warning
// we'll leave the line empty.
Expand Down Expand Up @@ -2637,6 +2646,9 @@ fn deserialize_map(
attr::Default::Path(path) => Some(quote!(
let __default: Self::Value = #path();
)),
attr::Default::Expr(lit) => Some(quote!(
let __default: Self::Value = #lit
)),
attr::Default::None => {
// We don't need the default value, to prevent an unused variable warning
// we'll leave the line empty.
Expand Down Expand Up @@ -2804,6 +2816,9 @@ fn deserialize_map_in_place(
attr::Default::Path(path) => Some(quote!(
let __default: #this_type #ty_generics = #path();
)),
attr::Default::Expr(lit) => Some(quote!(
let __default: #this_type #ty_generics = #lit;
)),
attr::Default::None => {
// We don't need the default value, to prevent an unused variable warning
// we'll leave the line empty.
Expand Down Expand Up @@ -2947,11 +2962,16 @@ fn expr_is_missing(field: &Field, cattrs: &attr::Container) -> Fragment {
attr::Default::Path(path) => {
return quote_expr!(#path());
}
attr::Default::Expr(lit) => {
return quote_expr!(#lit);
}
attr::Default::None => { /* below */ }
}

match *cattrs.default() {
attr::Default::Default | attr::Default::Path(_) => {
attr::Default::Default |
attr::Default::Path(_) |
attr::Default::Expr(_) => {
let member = &field.member;
return quote_expr!(__default.#member);
}
Expand Down Expand Up @@ -2990,11 +3010,16 @@ fn expr_is_missing_seq(
attr::Default::Path(path) => {
return quote_spanned!(path.span()=> #assign_to #path());
}
attr::Default::Expr(lit) => {
return quote_spanned!(lit.span()=> #assign_to #lit);
}
attr::Default::None => { /* below */ }
}

match *cattrs.default() {
attr::Default::Default | attr::Default::Path(_) => {
attr::Default::Default |
attr::Default::Path(_) |
attr::Default::Expr(_) => {
let member = &field.member;
quote!(#assign_to __default.#member)
}
Expand Down
75 changes: 62 additions & 13 deletions serde_derive/src/internals/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,24 +396,21 @@ impl Container {
deny_unknown_fields.set_true(meta.path);
} else if meta.path == DEFAULT {
if meta.input.peek(Token![=]) {
// #[serde(default = "...")]
if let Some(path) = parse_lit_into_expr_path(cx, DEFAULT, &meta)? {
// #[serde(default = ...)]
if let Some(dflt) = parse_default(cx, &meta)? {
match &item.data {
syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields {
syn::Fields::Named(_) | syn::Fields::Unnamed(_) => {
default.set(&meta.path, Default::Path(path));
default.set(&meta.path, dflt);
}
syn::Fields::Unit => {
let msg = "#[serde(default = \"...\")] can only be used on structs that have fields";
let msg = "#[serde(default = ...)] can only be used on structs that have fields";
cx.syn_error(meta.error(msg));
}
},
syn::Data::Enum(_) => {
let msg = "#[serde(default = \"...\")] can only be used on structs";
cx.syn_error(meta.error(msg));
}
syn::Data::Enum(_) |
syn::Data::Union(_) => {
let msg = "#[serde(default = \"...\")] can only be used on structs";
let msg = "#[serde(default = ...)] can only be used on structs";
cx.syn_error(meta.error(msg));
}
}
Expand Down Expand Up @@ -1055,13 +1052,17 @@ pub enum Default {
Default,
/// The default is given by this function.
Path(syn::ExprPath),
/// The default is a literal value
Expr(Box<syn::Expr>),
}

impl Default {
pub fn is_none(&self) -> bool {
match self {
Default::None => true,
Default::Default | Default::Path(_) => false,
Default::Default |
Default::Path(_) |
Default::Expr(_) => false,
}
}
}
Expand Down Expand Up @@ -1140,9 +1141,9 @@ impl Field {
}
} else if meta.path == DEFAULT {
if meta.input.peek(Token![=]) {
// #[serde(default = "...")]
if let Some(path) = parse_lit_into_expr_path(cx, DEFAULT, &meta)? {
default.set(&meta.path, Default::Path(path));
// #[serde(default = ...)]
if let Some(default_value) = parse_default(cx, &meta)? {
default.set(&meta.path, default_value)
}
} else {
// #[serde(default)]
Expand Down Expand Up @@ -1548,6 +1549,54 @@ fn parse_lit_into_expr_path(
})
}

/// Parses either an [`ExprPath`](syn::ExprPath) to a `fn` which will return the
/// default value for a field, or an [`Expr`](syn::ExprParen) wrapped in parenthesies
/// which is a valid value for a field.
///
/// ```no_run
/// use serde::{Serialize, Deserialize};
///
/// #[derive(Clone, Default, Serialize, Deserialize)]
/// pub struct DemoStruct {
/// #[serde(default = "my_fn")]
/// a: i32,
/// #[serde(default = (12))]
/// b: i32,
/// }
///
/// fn my_fn() -> i32 { 16 }
/// ```
fn parse_default(
cx: &Ctxt,
meta: &ParseNestedMeta,
) -> syn::Result<Option<Default>> {

let expr: syn::Expr = meta.value()?.parse()?;
match expr {
// #[serde(default = "...")]
syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) => {
match lit_str.parse::<syn::ExprPath>() {
Ok(path) => Ok(Some(Default::Path(path))),
_ => {
let msg = format!("failed to parse path: {:?}", lit_str.value());
cx.error_spanned_by(&lit_str, msg);
Ok(None)
}
}
},

// #[serde(default = (...))]
syn::Expr::Paren(paren) => {
Ok(Some(Default::Expr(paren.expr)))
},
_ => {
let msg = format!("failed to parse literal or fn path: {:?}", expr.to_token_stream());
cx.error_spanned_by(&expr, msg);
Ok(None)
}
}
}

fn parse_lit_into_where(
cx: &Ctxt,
attr_name: Symbol,
Expand Down
11 changes: 10 additions & 1 deletion test_suite/tests/test_annotations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ where
a4: D,
#[serde(skip_deserializing, default = "MyDefault::my_default")]
a5: E,
#[serde(skip_deserializing, default = (12.3))]
a6: f32
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -123,6 +125,7 @@ fn test_default_struct() {
a3: 3,
a4: 0,
a5: 123,
a6: 12.3,
},
&[
Token::Struct {
Expand All @@ -139,6 +142,8 @@ fn test_default_struct() {
Token::I32(4),
Token::Str("a5"),
Token::I32(5),
Token::Str("a6"),
Token::F32(12.3),
Token::StructEnd,
],
);
Expand All @@ -150,14 +155,17 @@ fn test_default_struct() {
a3: 123,
a4: 0,
a5: 123,
a6: 12.3
},
&[
Token::Struct {
name: "DefaultStruct",
len: 3,
len: 6,
},
Token::Str("a1"),
Token::I32(1),
Token::Str("a6"),
Token::F32(12.3),
Token::StructEnd,
],
);
Expand Down Expand Up @@ -436,6 +444,7 @@ fn test_ignore_unknown() {
a3: 3,
a4: 0,
a5: 123,
a6: 12.3
},
&[
Token::Struct {
Expand Down
2 changes: 1 addition & 1 deletion test_suite/tests/ui/default-attribute/enum_path.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: #[serde(default = "...")] can only be used on structs
error: #[serde(default = ...)] can only be used on structs
--> tests/ui/default-attribute/enum_path.rs:4:9
|
4 | #[serde(default = "default_e")]
Expand Down