Skip to content

Commit

Permalink
Implement explicit defaults for fields
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Apr 10, 2024
1 parent 31197d3 commit 3fc61e4
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 14 deletions.
12 changes: 6 additions & 6 deletions crates/musli-macros/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1085,7 +1085,7 @@ fn decode_packed(cx: &Ctxt<'_>, e: &Build<'_>, st_: &Body<'_>) -> Result<TokenSt
let mut assign = Vec::new();

for f in &st_.unskipped_fields {
if let Some(span) = f.default_attr {
if let Some((span, _)) = f.default_attr {
e.packed_default_diagnostics(span);
}

Expand Down
10 changes: 8 additions & 2 deletions crates/musli-macros/src/internals/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ layer! {
/// Rename a field to the given literal.
rename: syn::Expr,
/// Use a default value for the field if it's not available.
is_default: (),
is_default: Option<syn::Path>,
/// Use a default value for the field if it's not available.
skip: FieldSkip,
/// Use the alternate TraceDecode for the field.
Expand Down Expand Up @@ -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::<Option<Token![=]>>()?.is_some() {
new.is_default
.push((meta.path.span(), Some(meta.input.parse()?)));
} else {
new.is_default.push((meta.path.span(), None));
}

return Ok(());
}

Expand Down
7 changes: 5 additions & 2 deletions crates/musli-macros/src/internals/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Span>,
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>,
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions crates/musli/src/derives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,12 @@
//! name: String,
//! #[musli(default)]
//! age: Option<u32>,
//! #[musli(default = default_height)]
//! height: Option<u32>,
//! }
//!
//! fn default_height() -> Option<u32> {
//! Some(180)
//! }
//! ```
//!
Expand Down
50 changes: 46 additions & 4 deletions crates/tests/tests/default_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<dyn std::error::Error>> {
Expand Down Expand Up @@ -76,5 +88,35 @@ fn decode_with_default() -> Result<(), Box<dyn std::error::Error>> {
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(())
}

0 comments on commit 3fc61e4

Please sign in to comment.