From 3fc61e4be74e38b61df7742185671a2ae8f1785b Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Wed, 10 Apr 2024 15:36:50 +0200 Subject: [PATCH] Implement explicit defaults for fields --- crates/musli-macros/src/de.rs | 12 +++--- crates/musli-macros/src/internals/attr.rs | 10 ++++- crates/musli-macros/src/internals/build.rs | 7 ++- crates/musli/src/derives.rs | 6 +++ crates/tests/tests/default_value.rs | 50 ++++++++++++++++++++-- 5 files changed, 71 insertions(+), 14 deletions(-) diff --git a/crates/musli-macros/src/de.rs b/crates/musli-macros/src/de.rs index 3fda4d03a..717adbac8 100644 --- a/crates/musli-macros/src/de.rs +++ b/crates/musli-macros/src/de.rs @@ -805,12 +805,12 @@ fn decode_tagged( fields_with.push((f, decode, (enter, leave))); - let fallback = if f.default_attr.is_some() { - quote!(#default_function()) - } else { - quote! { + let fallback = match f.default_attr { + Some((span, None)) => quote_spanned!(span => #default_function()), + Some((_, Some(path))) => quote!(#path()), + None => quote! { return #result_err(#context_t::expected_tag(#ctx_var, #type_name, &#tag)) - } + }, }; let var = &f.var; @@ -1085,7 +1085,7 @@ fn decode_packed(cx: &Ctxt<'_>, e: &Build<'_>, st_: &Body<'_>) -> Result, /// Use a default value for the field if it's not available. skip: FieldSkip, /// Use the alternate TraceDecode for the field. @@ -696,7 +696,13 @@ pub(crate) fn field_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> Field { // parse #[musli(default)] if meta.path.is_ident("default") { - new.is_default.push((meta.path.span(), ())); + if meta.input.parse::>()?.is_some() { + new.is_default + .push((meta.path.span(), Some(meta.input.parse()?))); + } else { + new.is_default.push((meta.path.span(), None)); + } + return Ok(()); } diff --git a/crates/musli-macros/src/internals/build.rs b/crates/musli-macros/src/internals/build.rs index 7788a0844..e4dc39667 100644 --- a/crates/musli-macros/src/internals/build.rs +++ b/crates/musli-macros/src/internals/build.rs @@ -179,7 +179,7 @@ pub(crate) struct Field<'a> { pub(crate) tag: syn::Expr, pub(crate) skip_encoding_if: Option<&'a (Span, syn::Path)>, /// Fill with default value, if missing. - pub(crate) default_attr: Option, + pub(crate) default_attr: Option<(Span, Option<&'a syn::Path>)>, /// Skip field entirely and always initialize with the specified expresion, /// or default value if none is specified. pub(crate) skip: Option<&'a FieldSkip>, @@ -420,7 +420,10 @@ fn setup_field<'a>( let (tag, tag_method) = data.expand_tag(e, mode, default_field)?; tag_methods.insert(data.span, tag_method); let skip_encoding_if = data.attr.skip_encoding_if(mode); - let default_attr = data.attr.is_default(mode).map(|&(s, ())| s); + let default_attr = data + .attr + .is_default(mode) + .map(|(s, path)| (*s, path.as_ref())); let skip = data.attr.skip(mode).map(|(_, skip)| skip); let member = match data.ident { diff --git a/crates/musli/src/derives.rs b/crates/musli/src/derives.rs index a751e63e2..211d1c5b4 100644 --- a/crates/musli/src/derives.rs +++ b/crates/musli/src/derives.rs @@ -583,6 +583,12 @@ //! name: String, //! #[musli(default)] //! age: Option, +//! #[musli(default = default_height)] +//! height: Option, +//! } +//! +//! fn default_height() -> Option { +//! Some(180) //! } //! ``` //! diff --git a/crates/tests/tests/default_value.rs b/crates/tests/tests/default_value.rs index fb5e66ff9..bebc408b5 100644 --- a/crates/tests/tests/default_value.rs +++ b/crates/tests/tests/default_value.rs @@ -16,10 +16,6 @@ struct StructWithDefault { country: String, } -fn is_zero(value: &u32) -> bool { - *value == 0 -} - #[derive(Debug, PartialEq, Encode, Decode)] struct StructWithOption { name: String, @@ -28,6 +24,22 @@ struct StructWithOption { country: String, } +#[derive(Debug, PartialEq, Encode, Decode)] +struct StructWithDefaultValue { + name: String, + #[musli(default = default_age, skip_encoding_if = is_zero)] + age: u32, + country: String, +} + +fn is_zero(value: &u32) -> bool { + *value == 0 +} + +fn default_age() -> u32 { + 180 +} + // Ensure that skipped over fields ensures compatibility. #[test] fn decode_with_default() -> Result<(), Box> { @@ -76,5 +88,35 @@ fn decode_with_default() -> Result<(), Box> { json = r#"{"0":"Aristotle","2":"Greece"}"#, ); + tests::assert_decode_eq!( + full, + StructWithDefaultValue { + name: String::from("Aristotle"), + age: 0, + country: String::from("Greece"), + }, + StructWithDefaultValue { + name: String::from("Aristotle"), + age: 180, + country: String::from("Greece"), + }, + json = r#"{"0":"Aristotle","2":"Greece"}"#, + ); + + tests::assert_decode_eq!( + full, + StructWithDefaultValue { + name: String::from("Aristotle"), + age: 170, + country: String::from("Greece"), + }, + StructWithDefaultValue { + name: String::from("Aristotle"), + age: 170, + country: String::from("Greece"), + }, + json = r#"{"0":"Aristotle","1":170,"2":"Greece"}"#, + ); + Ok(()) }