diff --git a/.gitignore b/.gitignore index 165eb22d0..13fc3a3e5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ target/ **/*.rs.bk *.sw[po] Cargo.lock +.vscode/ +default.nix \ No newline at end of file diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index e3b737c61..354b2a867 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -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(); @@ -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) } @@ -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. @@ -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. @@ -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. @@ -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. @@ -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); } @@ -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) } diff --git a/serde_derive/src/internals/attr.rs b/serde_derive/src/internals/attr.rs index 0cfb23bf1..d770690e3 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -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)); } } @@ -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), } 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, } } } @@ -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)] @@ -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> { + + 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::() { + 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, diff --git a/test_suite/tests/test_annotations.rs b/test_suite/tests/test_annotations.rs index 566f7d43f..ccc4cc8f4 100644 --- a/test_suite/tests/test_annotations.rs +++ b/test_suite/tests/test_annotations.rs @@ -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)] @@ -123,6 +125,7 @@ fn test_default_struct() { a3: 3, a4: 0, a5: 123, + a6: 12.3, }, &[ Token::Struct { @@ -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, ], ); @@ -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, ], ); @@ -436,6 +444,7 @@ fn test_ignore_unknown() { a3: 3, a4: 0, a5: 123, + a6: 12.3 }, &[ Token::Struct { diff --git a/test_suite/tests/ui/default-attribute/enum_path.stderr b/test_suite/tests/ui/default-attribute/enum_path.stderr index 65ef3ac43..682ffdd8d 100644 --- a/test_suite/tests/ui/default-attribute/enum_path.stderr +++ b/test_suite/tests/ui/default-attribute/enum_path.stderr @@ -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")]