From e6bec6cf31a0f8fe1ca835dc32cf65cb96d7ee5e Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Fri, 22 Mar 2024 08:22:30 +0100 Subject: [PATCH] Using closure encoding to avoid type confusion --- .github/workflows/ci.yml | 11 +- .../musli-common/src/context/stack_context.rs | 7 +- crates/musli-descriptive/src/en.rs | 2 +- crates/musli-json/src/encoding.rs | 2 +- crates/musli-macros/src/en.rs | 123 ++++++----- crates/musli-macros/src/expander.rs | 4 +- crates/musli-macros/src/internals.rs | 3 +- crates/musli-macros/src/internals/attr.rs | 80 +++---- crates/musli-macros/src/internals/build.rs | 24 +- crates/musli-macros/src/internals/symbol.rs | 62 ------ crates/musli/src/context.rs | 10 + crates/musli/src/de/decoder.rs | 24 +- crates/musli/src/en/encode.rs | 4 +- crates/musli/src/en/encode_bytes.rs | 4 +- crates/musli/src/en/encoder.rs | 207 +++++++++++++++++- crates/musli/src/en/struct_encoder.rs | 12 + crates/musli/src/impls/alloc.rs | 9 +- crates/musli/src/impls/mod.rs | 2 +- crates/musli/src/impls/net.rs | 4 +- crates/musli/src/impls/tuples.rs | 2 +- crates/tests/Cargo.toml | 6 +- crates/tests/tests/recursive_models.rs | 173 +++++++++++++++ 22 files changed, 565 insertions(+), 210 deletions(-) delete mode 100644 crates/musli-macros/src/internals/symbol.rs create mode 100644 crates/tests/tests/recursive_models.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23609afbd..c4a18a460 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: - run: cargo +nightly run -p no-std-examples --example no-std-${{matrix.example}} fuzz: - needs: [test, each_package] + needs: [test, each_package, recursive] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -55,7 +55,7 @@ jobs: - run: cargo run --release -p tests --features test --bin fuzz -- --random miri: - needs: [test, each_package] + needs: [test, each_package, recursive] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -86,6 +86,13 @@ jobs: - run: cargo build -p ${{matrix.package}} --no-default-features --features parse-full if: matrix.package == 'musli-json' + recursive: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo build --release -p tests --test recursive_models --features test + clippy: runs-on: ubuntu-latest steps: diff --git a/crates/musli-common/src/context/stack_context.rs b/crates/musli-common/src/context/stack_context.rs index 337448328..3c0514f53 100644 --- a/crates/musli-common/src/context/stack_context.rs +++ b/crates/musli-common/src/context/stack_context.rs @@ -131,12 +131,9 @@ where where T: fmt::Display, { - let Some(buf) = self.alloc.alloc() else { - return None; - }; - + let buf = self.alloc.alloc()?; let mut string = BufString::new(buf); - _ = write!(string, "{}", value); + write!(string, "{value}").ok()?; Some(string) } } diff --git a/crates/musli-descriptive/src/en.rs b/crates/musli-descriptive/src/en.rs index ce4ce2a6e..309c3aaba 100644 --- a/crates/musli-descriptive/src/en.rs +++ b/crates/musli-descriptive/src/en.rs @@ -276,7 +276,7 @@ where #[inline] fn encode_unit_variant(self, cx: &C, tag: &T) -> Result<(), C::Error> where - T: Encode, + T: ?Sized + Encode, { let mut variant = self.encode_variant(cx)?; tag.encode(cx, variant.encode_tag(cx)?)?; diff --git a/crates/musli-json/src/encoding.rs b/crates/musli-json/src/encoding.rs index 633f2cf83..4a224c7f2 100644 --- a/crates/musli-json/src/encoding.rs +++ b/crates/musli-json/src/encoding.rs @@ -178,7 +178,7 @@ impl Encoding { W: Writer, T: ?Sized + Encode, { - T::encode(value, cx, JsonEncoder::new(writer)) + value.encode(cx, JsonEncoder::new(writer)) } /// Encode the given value to a [`String`] using the current configuration. diff --git a/crates/musli-macros/src/en.rs b/crates/musli-macros/src/en.rs index 867d3f7d3..add37ccc3 100644 --- a/crates/musli-macros/src/en.rs +++ b/crates/musli-macros/src/en.rs @@ -92,8 +92,6 @@ fn encode_struct(cx: &Ctxt<'_>, e: &Build<'_>, st: &Body<'_>) -> Result, e: &Build<'_>, st: &Body<'_>) -> Result::encode_struct(#encoder_var, #ctx_var, #len)?; - #(#encoders)* - let #output_var = #struct_encoder_t::<#c_param>::end(#encoder_var, #ctx_var)?; + let #output_var = #encoder_t::<#c_param>::encode_struct_fn(#encoder_var, #ctx_var, #len, |#encoder_var| { + #(#encoders)* + #result_ok(()) + })?; #leave #output_var }}; @@ -152,10 +151,11 @@ fn encode_struct(cx: &Ctxt<'_>, e: &Build<'_>, st: &Body<'_>) -> Result::encode_pack(#encoder_var, #ctx_var)?; - #(#encoders)* - let #output_var = #sequence_encoder_t::<#c_param>::end(#pack_var, #ctx_var)?; + let #output_var = #encoder_t::<#c_param>::encode_pack_fn(#encoder_var, #ctx_var, |#pack_var| { + #(#decls)* + #(#encoders)* + #result_ok(()) + })?; #leave #output_var }}; @@ -184,10 +184,11 @@ fn insert_fields( } = *cx; let Tokens { - struct_field_encoder_t, - struct_encoder_t, - sequence_encoder_t, context_t, + result_ok, + sequence_encoder_t, + struct_encoder_t, + struct_field_encoder_t, .. } = e.tokens; @@ -237,19 +238,22 @@ fn insert_fields( Packing::Tagged | Packing::Transparent => { encode = quote! { #enter - let mut #pair_encoder_var = #struct_encoder_t::<#c_param>::encode_field(&mut #encoder_var, #ctx_var)?; - let #field_encoder_var = #struct_field_encoder_t::<#c_param>::encode_field_name(&mut #pair_encoder_var, #ctx_var)?; - #encode_t_encode(&#tag, #ctx_var, #field_encoder_var)?; - let #value_encoder_var = #struct_field_encoder_t::<#c_param>::encode_field_value(&mut #pair_encoder_var, #ctx_var)?; - #encode_path(#access, #ctx_var, #value_encoder_var)?; - #struct_field_encoder_t::<#c_param>::end(#pair_encoder_var, #ctx_var)?; + + #struct_encoder_t::<#c_param>::encode_field_fn(#encoder_var, #ctx_var, |#pair_encoder_var| { + let #field_encoder_var = #struct_field_encoder_t::<#c_param>::encode_field_name(#pair_encoder_var, #ctx_var)?; + #encode_t_encode(&#tag, #ctx_var, #field_encoder_var)?; + let #value_encoder_var = #struct_field_encoder_t::<#c_param>::encode_field_value(#pair_encoder_var, #ctx_var)?; + #encode_path(#access, #ctx_var, #value_encoder_var)?; + #result_ok(()) + })?; + #leave }; } Packing::Packed => { encode = quote! { #enter - let #sequence_decoder_next_var = #sequence_encoder_t::<#c_param>::encode_next(&mut #pack_var, #ctx_var)?; + let #sequence_decoder_next_var = #sequence_encoder_t::<#c_param>::encode_next(#pack_var, #ctx_var)?; #encode_path(#access, #ctx_var, #sequence_decoder_next_var)?; #leave }; @@ -337,12 +341,12 @@ fn encode_variant( } = *cx; let Tokens { + context_t, encoder_t, + result_ok, struct_encoder_t, struct_field_encoder_t, variant_encoder_t, - sequence_encoder_t, - context_t, .. } = b.tokens; @@ -367,10 +371,11 @@ fn encode_variant( let decls = tests.iter().map(|t| &t.decl); encode = quote! {{ - let mut #pack_var = #encoder_t::<#c_param>::encode_pack(#encoder_var, #ctx_var)?; - #(#decls)* - #(#encoders)* - #sequence_encoder_t::<#c_param>::end(#pack_var, #ctx_var)? + #encoder_t::<#c_param>::encode_pack_fn(#encoder_var, #ctx_var, |#pack_var| { + #(#decls)* + #(#encoders)* + #result_ok(()) + })? }}; } Packing::Tagged => { @@ -378,10 +383,11 @@ fn encode_variant( let len = length_test(v.st.fields.len(), &tests); encode = quote! {{ - let mut #encoder_var = #encoder_t::<#c_param>::encode_struct(#encoder_var, #ctx_var, #len)?; - #(#decls)* - #(#encoders)* - #struct_encoder_t::<#c_param>::end(#encoder_var, #ctx_var)? + #encoder_t::<#c_param>::encode_struct_fn(#encoder_var, #ctx_var, #len, |#encoder_var| { + #(#decls)* + #(#encoders)* + #result_ok(()) + })? }}; } } @@ -393,14 +399,14 @@ fn encode_variant( let tag_encoder = b.cx.ident("tag_encoder"); encode = quote! {{ - let mut #variant_encoder = #encoder_t::<#c_param>::encode_variant(#encoder_var, #ctx_var)?; - - let #tag_encoder = #variant_encoder_t::<#c_param>::encode_tag(&mut #variant_encoder, #ctx_var)?; - #encode_t_encode(&#tag, #ctx_var, #tag_encoder)?; - - let #encoder_var = #variant_encoder_t::<#c_param>::encode_value(&mut #variant_encoder, #ctx_var)?; - #encode; - #variant_encoder_t::<#c_param>::end(#variant_encoder, #ctx_var)? + #encoder_t::<#c_param>::encode_variant_fn(#encoder_var, #ctx_var, |#variant_encoder| { + let #tag_encoder = #variant_encoder_t::<#c_param>::encode_tag(#variant_encoder, #ctx_var)?; + #encode_t_encode(&#tag, #ctx_var, #tag_encoder)?; + + let #encoder_var = #variant_encoder_t::<#c_param>::encode_value(#variant_encoder, #ctx_var)?; + #encode; + #result_ok(()) + })? }}; } } @@ -414,11 +420,12 @@ fn encode_variant( let decls = tests.iter().map(|t| &t.decl); encode = quote! {{ - let mut #encoder_var = #encoder_t::<#c_param>::encode_struct(#encoder_var, #ctx_var, 0)?; - #struct_encoder_t::<#c_param>::insert_field(&mut #encoder_var, #ctx_var, #field_tag, #tag)?; - #(#decls)* - #(#encoders)* - #struct_encoder_t::<#c_param>::end(#encoder_var, #ctx_var)? + #encoder_t::<#c_param>::encode_struct_fn(#encoder_var, #ctx_var, 0, |#encoder_var| { + #struct_encoder_t::<#c_param>::insert_field(#encoder_var, #ctx_var, #field_tag, #tag)?; + #(#decls)* + #(#encoders)* + #result_ok(()) + })? }}; } EnumTagging::Adjacent { @@ -440,20 +447,26 @@ fn encode_variant( let content_tag = b.cx.ident("content_tag"); encode = quote! {{ - let mut #struct_encoder = #encoder_t::<#c_param>::encode_struct(#encoder_var, #ctx_var, 2)?; - #struct_encoder_t::<#c_param>::insert_field(&mut #struct_encoder, #ctx_var, &#field_tag, #tag)?; - let mut #pair = #struct_encoder_t::<#c_param>::encode_field(&mut #struct_encoder, #ctx_var)?; - let #content_tag = #struct_field_encoder_t::<#c_param>::encode_field_name(&mut #pair, #ctx_var)?; - #encode_t_encode(&#content, #ctx_var, #content_tag)?; - - let #content_struct = #struct_field_encoder_t::<#c_param>::encode_field_value(&mut #pair, #ctx_var)?; - let mut #encoder_var = #encoder_t::<#c_param>::encode_struct(#content_struct, #ctx_var, #len)?; - #(#decls)* - #(#encoders)* - #struct_encoder_t::<#c_param>::end(#encoder_var, #ctx_var)?; + #encoder_t::<#c_param>::encode_struct_fn(#encoder_var, #ctx_var, 2, |#struct_encoder| { + #struct_encoder_t::<#c_param>::insert_field(#struct_encoder, #ctx_var, &#field_tag, #tag)?; + + #struct_encoder_t::<#c_param>::encode_field_fn(#struct_encoder, #ctx_var, |#pair| { + let #content_tag = #struct_field_encoder_t::<#c_param>::encode_field_name(#pair, #ctx_var)?; + #encode_t_encode(&#content, #ctx_var, #content_tag)?; + + let #content_struct = #struct_field_encoder_t::<#c_param>::encode_field_value(#pair, #ctx_var)?; + + #encoder_t::<#c_param>::encode_struct_fn(#content_struct, #ctx_var, #len, |#encoder_var| { + #(#decls)* + #(#encoders)* + #result_ok(()) + })?; + + #result_ok(()) + })?; - #struct_field_encoder_t::<#c_param>::end(#pair, #ctx_var)?; - #struct_encoder_t::<#c_param>::end(#struct_encoder, #ctx_var)? + #result_ok(()) + })? }}; } }, diff --git a/crates/musli-macros/src/expander.rs b/crates/musli-macros/src/expander.rs index 08c5c9497..dfe018c76 100644 --- a/crates/musli-macros/src/expander.rs +++ b/crates/musli-macros/src/expander.rs @@ -3,8 +3,8 @@ use syn::spanned::Spanned; use crate::internals::attr::{self, DefaultTag, TypeAttr}; use crate::internals::build::Build; -use crate::internals::symbol::*; use crate::internals::tokens::Tokens; +use crate::internals::ATTR; use crate::internals::{Ctxt, Expansion, Mode, Only}; pub(crate) type Result = std::result::Result; @@ -233,7 +233,7 @@ pub(crate) trait Taggable { e.cx.error_span( self.span(), format_args!( - "#[{ATTR}({DEFAULT_FIELD_NAME} = \"name\")] is not supported with unnamed fields", + "#[{ATTR}(default_field = \"name\")] is not supported on unnamed fields", ), ); return Err(()); diff --git a/crates/musli-macros/src/internals.rs b/crates/musli-macros/src/internals.rs index ed1e5f18c..3c0c9ba31 100644 --- a/crates/musli-macros/src/internals.rs +++ b/crates/musli-macros/src/internals.rs @@ -3,9 +3,10 @@ pub(crate) mod build; mod ctxt; mod expansion; mod mode; -pub(crate) mod symbol; pub(crate) mod tokens; +pub(crate) const ATTR: &str = "musli"; + pub(crate) use self::attr::Only; pub(crate) use self::ctxt::Ctxt; pub(crate) use self::expansion::Expansion; diff --git a/crates/musli-macros/src/internals/attr.rs b/crates/musli-macros/src/internals/attr.rs index c5bf5d7bb..7bcf408fd 100644 --- a/crates/musli-macros/src/internals/attr.rs +++ b/crates/musli-macros/src/internals/attr.rs @@ -9,7 +9,7 @@ use syn::Token; use crate::expander::determine_tag_method; use crate::expander::TagMethod; -use crate::internals::symbol::*; +use crate::internals::ATTR; use crate::internals::{Ctxt, Mode}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -240,7 +240,7 @@ impl TypeAttr { if let Some((_, krate)) = self.root.krate.any.as_ref() { krate.clone() } else { - let mut path = syn::Path::from(syn::Ident::new(&ATTR, Span::call_site())); + let mut path = syn::Path::from(syn::Ident::new(ATTR, Span::call_site())); path.leading_colon = Some(::default()); path } @@ -251,7 +251,7 @@ pub(crate) fn type_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> TypeAttr { let mut attr = TypeAttr::default(); for a in attrs { - if a.path() != ATTR { + if !a.path().is_ident(ATTR) { continue; } @@ -261,38 +261,38 @@ pub(crate) fn type_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> TypeAttr { let result = a.parse_nested_meta(|meta| { // parse #[musli(mode = )] - if meta.path == MODE { + if meta.path.is_ident("mode") { meta.input.parse::()?; mode = Some(meta.input.parse()?); return Ok(()); } - if meta.path == ENCODE_ONLY { + if meta.path.is_ident("encode_only") { only = Some(Only::Encode); return Ok(()); } - if meta.path == DECODE_ONLY { + if meta.path.is_ident("decode_only") { only = Some(Only::Decode); return Ok(()); } // parse #[musli(tag = )] - if meta.path == TAG { + if meta.path.is_ident("tag") { meta.input.parse::()?; new.tag.push((meta.path.span(), meta.input.parse()?)); return Ok(()); } // parse #[musli(content = )] - if meta.path == CONTENT { + if meta.path.is_ident("content") { meta.input.parse::()?; new.content.push((meta.path.span(), meta.input.parse()?)); return Ok(()); } // parse #[musli(crate = )] - if meta.path == CRATE { + if meta.path.is_ident("crate") { let path = if meta.input.parse::>()?.is_some() { meta.input.parse()? } else { @@ -319,17 +319,17 @@ pub(crate) fn type_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> TypeAttr { } // parse #[musli(default_variant = "..")] - if meta.path == DEFAULT_VARIANT_NAME { + if meta.path.is_ident("default_variant") { meta.input.parse::()?; let string = meta.input.parse::()?; new.default_variant.push(match string.value().as_str() { "index" => (meta.path.span(), DefaultTag::Index), "name" => (meta.path.span(), DefaultTag::Name), - _ => { + value => { return Err(syn::Error::new_spanned( string, - format_args!("#[{ATTR}({DEFAULT_VARIANT_NAME})] Bad value"), + format_args!("#[{ATTR}(default_variant = {value:?})] Bad value, expected one of \"index\" or \"name\""), )); } }); @@ -338,17 +338,17 @@ pub(crate) fn type_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> TypeAttr { } // parse #[musli(default_field = "..")] - if meta.path == DEFAULT_FIELD_NAME { + if meta.path.is_ident("default_field") { meta.input.parse::()?; let string = meta.input.parse::()?; new.default_field.push(match string.value().as_str() { "index" => (meta.path.span(), DefaultTag::Index), "name" => (meta.path.span(), DefaultTag::Name), - _ => { + value => { return Err(syn::Error::new_spanned( string, - format_args!("#[{ATTR}({DEFAULT_FIELD_NAME})]: Bad value."), + format_args!("#[{ATTR}(default_field = {value:?})]: Bad value, expected one of \"index\" or \"name\""), )); } }); @@ -357,27 +357,27 @@ pub(crate) fn type_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> TypeAttr { } // parse #[musli(bound = {..})] - if meta.path == BOUND { + if meta.path.is_ident("bound") { meta.input.parse::()?; parse_bounds(&meta, &mut new.bounds)?; return Ok(()); } // parse #[musli(decode_bound = {..})] - if meta.path == DECODE_BOUND { + if meta.path.is_ident("decode_bound") { meta.input.parse::()?; parse_bounds(&meta, &mut new.decode_bounds)?; return Ok(()); } // parse #[musli(packed)] - if meta.path == PACKED { + if meta.path.is_ident("packed") { new.packing.push((meta.path.span(), Packing::Packed)); return Ok(()); } // parse #[musli(transparent)] - if meta.path == TRANSPARENT { + if meta.path.is_ident("transparent") { new.packing.push((meta.path.span(), Packing::Transparent)); return Ok(()); } @@ -451,7 +451,7 @@ pub(crate) fn variant_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> VariantAttr let mut attr = VariantAttr::default(); for a in attrs { - if a.path() != ATTR { + if !a.path().is_ident(ATTR) { continue; } @@ -461,18 +461,18 @@ pub(crate) fn variant_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> VariantAttr let result = a.parse_nested_meta(|meta| { // parse #[musli(mode = )] - if meta.path == MODE { + if meta.path.is_ident("mode") { meta.input.parse::()?; mode = Some(meta.input.parse()?); return Ok(()); } - if meta.path == ENCODE_ONLY { + if meta.path.is_ident("encode_only") { only = Some(Only::Encode); return Ok(()); } - if meta.path == DECODE_ONLY { + if meta.path.is_ident("decode_only") { only = Some(Only::Decode); return Ok(()); } @@ -493,30 +493,30 @@ pub(crate) fn variant_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> VariantAttr } // parse #[musli(rename = )] - if meta.path == RENAME { + if meta.path.is_ident("rename") { meta.input.parse::()?; new.rename.push((meta.path.span(), meta.input.parse()?)); return Ok(()); } // parse #[musli(default)] - if meta.path == DEFAULT { + if meta.path.is_ident("default") { new.default_attr_field.push((meta.path.span(), ())); return Ok(()); } // parse #[musli(default_field = "..")] - if meta.path == DEFAULT_FIELD_NAME { + if meta.path.is_ident("default_field") { meta.input.parse::()?; let string = meta.input.parse::()?; new.default_field.push(match string.value().as_str() { "index" => (meta.path.span(), DefaultTag::Index), "name" => (meta.path.span(), DefaultTag::Name), - _ => { + value => { return Err(syn::Error::new_spanned( string, - format_args!("#[{ATTR}({DEFAULT_FIELD_NAME})]: Bad value."), + format_args!("#[{ATTR}(default_field = {value:?})]: Bad value, expected one of \"index\" or \"name\"."), )); } }); @@ -525,13 +525,13 @@ pub(crate) fn variant_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> VariantAttr } // parse #[musli(packed)] - if meta.path == PACKED { + if meta.path.is_ident("packed") { new.packing.push((meta.path.span(), Packing::Packed)); return Ok(()); } // parse #[musli(transparent)] - if meta.path == TRANSPARENT { + if meta.path.is_ident("transparent") { new.packing.push((meta.path.span(), Packing::Transparent)); return Ok(()); } @@ -571,7 +571,7 @@ layer! { /// Rename a field to the given literal. rename: syn::Expr, /// Use a default value for the field if it's not available. - default_field: (), + is_default: (), /// Use the alternate TraceDecode for the field. trace: (), /// Use the alternate EncodeBytes for the field. @@ -615,7 +615,7 @@ pub(crate) fn field_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> Field { let mut attr = Field::default(); for a in attrs { - if a.path() != ATTR { + if !a.path().is_ident(ATTR) { continue; } @@ -625,24 +625,24 @@ pub(crate) fn field_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> Field { let result = a.parse_nested_meta(|meta| { // parse #[musli(mode = )] - if meta.path == MODE { + if meta.path.is_ident("mode") { meta.input.parse::()?; mode = Some(meta.input.parse()?); return Ok(()); } - if meta.path == ENCODE_ONLY { + if meta.path.is_ident("encode_only") { only = Some(Only::Encode); return Ok(()); } - if meta.path == DECODE_ONLY { + if meta.path.is_ident("decode_only") { only = Some(Only::Decode); return Ok(()); } // parse parse #[musli(with = )] - if meta.path == WITH { + if meta.path.is_ident("with") { meta.input.parse::()?; let mut path = meta.input.parse::()?; @@ -676,7 +676,7 @@ pub(crate) fn field_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> Field { } // parse #[musli(skip_encoding_if = )] - if meta.path == SKIP_ENCODING_IF { + if meta.path.is_ident("skip_encoding_if") { meta.input.parse::()?; new.skip_encoding_if .push((meta.path.span(), meta.input.parse()?)); @@ -684,15 +684,15 @@ pub(crate) fn field_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> Field { } // parse #[musli(rename = )] - if meta.path == RENAME { + if meta.path.is_ident("rename") { meta.input.parse::()?; new.rename.push((meta.path.span(), meta.input.parse()?)); return Ok(()); } // parse #[musli(default)] - if meta.path == DEFAULT { - new.default_field.push((meta.path.span(), ())); + if meta.path.is_ident("default") { + new.is_default.push((meta.path.span(), ())); return Ok(()); } diff --git a/crates/musli-macros/src/internals/build.rs b/crates/musli-macros/src/internals/build.rs index 016000c85..5f9579c4b 100644 --- a/crates/musli-macros/src/internals/build.rs +++ b/crates/musli-macros/src/internals/build.rs @@ -10,8 +10,8 @@ use crate::expander::{ Data, EnumData, Expander, FieldData, Result, StructData, TagMethod, VariantData, }; use crate::internals::attr::{DefaultTag, EnumTagging, Packing}; -use crate::internals::symbol::*; use crate::internals::tokens::Tokens; +use crate::internals::ATTR; use crate::internals::{Ctxt, Expansion, Mode, Only}; pub(crate) struct Build<'a> { @@ -33,7 +33,7 @@ impl Build<'_> { pub(crate) fn encode_transparent_enum_diagnostics(&self, span: Span) { self.cx.error_span( span, - format_args!("#[{ATTR}({TRANSPARENT})] cannot be used to encode enums",), + format_args!("#[{ATTR}(transparent)] cannot be used to encode enums",), ); } @@ -42,7 +42,7 @@ impl Build<'_> { pub(crate) fn decode_packed_enum_diagnostics(&self, span: Span) { self.cx.error_span( span, - format_args!("#[{ATTR}({PACKED})] cannot be used to decode enums"), + format_args!("#[{ATTR}(packed)] cannot be used to decode enums"), ); } @@ -51,7 +51,7 @@ impl Build<'_> { pub(crate) fn packed_default_diagnostics(&self, span: Span) { self.cx.error_span( span, - format_args!("#[{ATTR}({DEFAULT})] fields cannot be used in an packed container",), + format_args!("#[{ATTR}(default)] fields cannot be used in an packed container",), ); } @@ -61,13 +61,13 @@ impl Build<'_> { if fields.is_empty() { self.cx.error_span( span, - format_args!("#[{ATTR}({TRANSPARENT})] types must have a single field",), + format_args!("#[{ATTR}(transparent)] types must have a single field",), ); } else { self.cx.error_span( span, format_args!( - "#[{ATTR}({TRANSPARENT})] can only be used on types which have a single field", + "#[{ATTR}(transparent)] can only be used on types which have a single field", ), ); } @@ -90,7 +90,7 @@ impl Build<'_> { self.cx.error_span( span, format_args!( - "#[{ATTR}({TAG})] and #[{ATTR}({CONTENT})] are only supported on enums" + "#[{ATTR}(tag)] and #[{ATTR}(content)] are only supported on enums" ), ); @@ -264,7 +264,7 @@ fn setup_enum<'a>(e: &'a Expander, mode: Mode<'_>, data: &'a EnumData<'a>) -> Re match packing_span { Some((_, Packing::Tagged)) => (), Some(&(span, packing)) => { - e.cx.error_span(span, format_args!("#[{ATTR}({packing})] cannot be combined with #[{ATTR}({TAG})] or #[{ATTR}({CONTENT})]")); + e.cx.error_span(span, format_args!("#[{ATTR}({packing})] cannot be combined with #[{ATTR}(tag)] or #[{ATTR}(content)]")); return Err(()); } _ => (), @@ -322,14 +322,14 @@ fn setup_variant<'a>( if !data.fields.is_empty() { e.cx.error_span( data.span, - format_args!("#[{ATTR}({DEFAULT})] variant must be empty"), + format_args!("#[{ATTR}(default)] variant must be empty"), ); false } else if fallback.is_some() { e.cx.error_span( data.span, - format_args!("#[{ATTR}({DEFAULT})] only one fallback variant is supported",), + format_args!("#[{ATTR}(default)] only one fallback variant is supported",), ); false @@ -389,7 +389,7 @@ 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.default_field(mode).map(|&(s, ())| s); + let default_attr = data.attr.is_default(mode).map(|&(s, ())| s); let member = match data.ident { Some(ident) => syn::Member::Named(ident.clone()), @@ -502,7 +502,7 @@ impl<'a> TagMethods<'a> { if before == 1 && self.methods.len() > 1 { self.cx - .error_span(span, format_args!("#[{ATTR}({TAG})] conflicting tag kind")); + .error_span(span, format_args!("#[{ATTR}(tag)] conflicting tag kind")); } } } diff --git a/crates/musli-macros/src/internals/symbol.rs b/crates/musli-macros/src/internals/symbol.rs deleted file mode 100644 index a76771ebd..000000000 --- a/crates/musli-macros/src/internals/symbol.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::fmt; -use std::ops::Deref; -use syn::{Ident, Path}; - -#[derive(Copy, Clone)] -pub struct Symbol(&'static str); - -pub const ATTR: Symbol = Symbol("musli"); -pub const BOUND: Symbol = Symbol("bound"); -pub const DECODE_BOUND: Symbol = Symbol("decode_bound"); -pub const DECODE_ONLY: Symbol = Symbol("decode_only"); -pub const ENCODE_ONLY: Symbol = Symbol("encode_only"); -pub const CONTENT: Symbol = Symbol("content"); -pub const CRATE: Symbol = Symbol("crate"); -pub const DEFAULT_FIELD_NAME: Symbol = Symbol("default_field"); -pub const DEFAULT_VARIANT_NAME: Symbol = Symbol("default_variant"); -pub const DEFAULT: Symbol = Symbol("default"); -pub const MODE: Symbol = Symbol("mode"); -pub const PACKED: Symbol = Symbol("packed"); -pub const RENAME: Symbol = Symbol("rename"); -pub const SKIP_ENCODING_IF: Symbol = Symbol("skip_encoding_if"); -pub const TAG: Symbol = Symbol("tag"); -pub const TRANSPARENT: Symbol = Symbol("transparent"); -pub const WITH: Symbol = Symbol("with"); - -impl PartialEq for Ident { - fn eq(&self, word: &Symbol) -> bool { - self == word.0 - } -} - -impl<'a> PartialEq for &'a Ident { - fn eq(&self, word: &Symbol) -> bool { - *self == word.0 - } -} - -impl PartialEq for Path { - fn eq(&self, word: &Symbol) -> bool { - self.is_ident(word.0) - } -} - -impl<'a> PartialEq for &'a Path { - fn eq(&self, word: &Symbol) -> bool { - self.is_ident(word.0) - } -} - -impl fmt::Display for Symbol { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str(self.0) - } -} - -impl Deref for Symbol { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.0 - } -} diff --git a/crates/musli/src/context.rs b/crates/musli/src/context.rs index 5f438aec5..d6ca2acdb 100644 --- a/crates/musli/src/context.rs +++ b/crates/musli/src/context.rs @@ -2,6 +2,7 @@ use core::fmt; +use crate::de::DecodeBytes; use crate::{Buf, Decode, Decoder}; #[cfg(feature = "std")] @@ -38,6 +39,15 @@ pub trait Context { T::decode(self, decoder) } + /// Decode the given input as bytes using the associated mode. + fn decode_bytes<'de, T, D>(&self, decoder: D) -> Result + where + T: DecodeBytes<'de, Self::Mode>, + D: Decoder<'de, Self>, + { + T::decode_bytes(self, decoder) + } + /// Allocate a buffer. fn alloc(&self) -> Option>; diff --git a/crates/musli/src/de/decoder.rs b/crates/musli/src/de/decoder.rs index 0df7cb6ad..5984b0c27 100644 --- a/crates/musli/src/de/decoder.rs +++ b/crates/musli/src/de/decoder.rs @@ -1201,7 +1201,7 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { /// C: ?Sized + Context, /// D: Decoder<'de, C>, /// { - /// decoder.decode_pack_fn(cx, |cx, pack| Ok(Self { + /// decoder.decode_pack_fn(cx, |pack| Ok(Self { /// field: pack.next(cx)?, /// data: pack.next(cx)?, /// })) @@ -1211,10 +1211,10 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { #[inline] fn decode_pack_fn(self, cx: &C, f: F) -> Result where - F: FnOnce(&C, &mut Self::DecodePack) -> Result, + F: FnOnce(&mut Self::DecodePack) -> Result, { let mut pack = self.decode_pack(cx)?; - let result = f(cx, &mut pack)?; + let result = f(&mut pack)?; pack.end(cx)?; Ok(result) } @@ -1299,7 +1299,7 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { /// C: ?Sized + Context, /// D: Decoder<'de, C>, /// { - /// decoder.decode_sequence_fn(cx, |cx, seq| { + /// decoder.decode_sequence_fn(cx, |seq| { /// let mut data = Vec::new(); /// /// while let Some(decoder) = seq.decode_next(cx)? { @@ -1314,10 +1314,10 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { #[inline] fn decode_sequence_fn(self, cx: &C, f: F) -> Result where - F: FnOnce(&C, &mut Self::DecodeSequence) -> Result, + F: FnOnce(&mut Self::DecodeSequence) -> Result, { let mut sequence = self.decode_sequence(cx)?; - let result = f(cx, &mut sequence)?; + let result = f(&mut sequence)?; sequence.end(cx)?; Ok(result) } @@ -1394,7 +1394,7 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { /// C: ?Sized + Context, /// D: Decoder<'de, C>, /// { - /// decoder.decode_tuple_fn(cx, 2, |cx, tuple| { + /// decoder.decode_tuple_fn(cx, 2, |tuple| { /// Ok(Self(tuple.next(cx)?, tuple.next(cx)?)) /// }) /// } @@ -1403,10 +1403,10 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { #[inline] fn decode_tuple_fn(self, cx: &C, len: usize, f: F) -> Result where - F: FnOnce(&C, &mut Self::DecodeTuple) -> Result, + F: FnOnce(&mut Self::DecodeTuple) -> Result, { let mut tuple = self.decode_tuple(cx, len)?; - let result = f(cx, &mut tuple)?; + let result = f(&mut tuple)?; tuple.end(cx)?; Ok(result) } @@ -1505,7 +1505,7 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { /// C: ?Sized + Context, /// D: Decoder<'de, C>, /// { - /// decoder.decode_map_fn(cx, |cx, map| { + /// decoder.decode_map_fn(cx, |map| { /// let mut data = HashMap::with_capacity(map.size_hint(cx).or_default()); /// /// while let Some((key, value)) = map.entry(cx)? { @@ -1520,10 +1520,10 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { #[inline] fn decode_map_fn(self, cx: &C, f: F) -> Result where - F: FnOnce(&C, &mut Self::DecodeMap) -> Result, + F: FnOnce(&mut Self::DecodeMap) -> Result, { let mut map = self.decode_map(cx)?; - let result = f(cx, &mut map)?; + let result = f(&mut map)?; map.end(cx)?; Ok(result) } diff --git a/crates/musli/src/en/encode.rs b/crates/musli/src/en/encode.rs index a430f75f4..c66ccb0fa 100644 --- a/crates/musli/src/en/encode.rs +++ b/crates/musli/src/en/encode.rs @@ -74,7 +74,7 @@ where C: ?Sized + Context, E: Encoder, { - T::encode(*self, cx, encoder) + (**self).encode(cx, encoder) } } @@ -88,6 +88,6 @@ where C: ?Sized + Context, E: Encoder, { - T::encode(*self, cx, encoder) + (**self).encode(cx, encoder) } } diff --git a/crates/musli/src/en/encode_bytes.rs b/crates/musli/src/en/encode_bytes.rs index 1bb10e3b4..abf9bf2c5 100644 --- a/crates/musli/src/en/encode_bytes.rs +++ b/crates/musli/src/en/encode_bytes.rs @@ -59,7 +59,7 @@ where C: ?Sized + Context, E: Encoder, { - T::encode_bytes(*self, cx, encoder) + (**self).encode_bytes(cx, encoder) } } @@ -73,6 +73,6 @@ where C: ?Sized + Context, E: Encoder, { - T::encode_bytes(*self, cx, encoder) + (**self).encode_bytes(cx, encoder) } } diff --git a/crates/musli/src/en/encoder.rs b/crates/musli/src/en/encoder.rs index 06d7e3b59..c613a146b 100644 --- a/crates/musli/src/en/encoder.rs +++ b/crates/musli/src/en/encoder.rs @@ -1051,6 +1051,43 @@ pub trait Encoder: Sized { ))) } + /// Encodes a pack using a closure. + /// + /// # Examples + /// + /// ``` + /// use musli::{Context, Encode, Encoder}; + /// use musli::en::{SequenceEncoder}; + /// + /// struct PackedStruct { + /// field: u32, + /// data: [u8; 128], + /// } + /// + /// impl Encode for PackedStruct { + /// fn encode(&self, cx: &C, encoder: E) -> Result + /// where + /// C: ?Sized + Context, + /// E: Encoder, + /// { + /// encoder.encode_pack_fn(cx, |pack| { + /// self.field.encode(cx, pack.encode_next(cx)?)?; + /// self.data.encode(cx, pack.encode_next(cx)?)?; + /// Ok(()) + /// }) + /// } + /// } + /// ``` + #[inline] + fn encode_pack_fn(self, cx: &C, f: F) -> Result + where + F: FnOnce(&mut Self::EncodePack<'_>) -> Result<(), C::Error>, + { + let mut pack = self.encode_pack(cx)?; + f(&mut pack)?; + pack.end(cx) + } + /// Encode a sequence with a known length `len`. /// /// A sequence encodes one element following another and must in some way @@ -1104,6 +1141,41 @@ pub trait Encoder: Sized { ))) } + /// Encode a sequence using a closure. + /// + /// # Examples + /// + /// ``` + /// use musli::{Context, Encode, Encoder}; + /// use musli::en::{SequenceEncoder}; + /// # struct MyType { data: Vec } + /// + /// impl Encode for MyType { + /// fn encode(&self, cx: &C, encoder: E) -> Result + /// where + /// C: ?Sized + Context, + /// E: Encoder, + /// { + /// encoder.encode_sequence_fn(cx, self.data.len(), |seq| { + /// for element in &self.data { + /// seq.push(cx, element)?; + /// } + /// + /// Ok(()) + /// }) + /// } + /// } + /// ``` + #[inline] + fn encode_sequence_fn(self, cx: &C, len: usize, f: F) -> Result + where + F: FnOnce(&mut Self::EncodeSequence) -> Result<(), C::Error>, + { + let mut seq = self.encode_sequence(cx, len)?; + f(&mut seq)?; + seq.end(cx) + } + /// Encode a tuple with a known length `len`. /// /// This is almost identical to [Encoder::encode_sequence] except that we @@ -1187,6 +1259,43 @@ pub trait Encoder: Sized { ))) } + /// Encode a map using a closure. + /// + /// # Examples + /// + /// ``` + /// use musli::{Context, Encode, Encoder}; + /// use musli::en::{MapEncoder}; + /// + /// struct Struct { + /// name: String, + /// age: u32 + /// } + /// + /// impl Encode for Struct { + /// fn encode(&self, cx: &C, encoder: E) -> Result + /// where + /// C: ?Sized + Context, + /// E: Encoder, + /// { + /// encoder.encode_map_fn(cx, 2, |map| { + /// map.insert_entry(cx, "name", &self.name)?; + /// map.insert_entry(cx, "age", self.age)?; + /// Ok(()) + /// }) + /// } + /// } + /// ``` + #[inline] + fn encode_map_fn(self, cx: &C, len: usize, f: F) -> Result + where + F: FnOnce(&mut Self::EncodeMap) -> Result<(), C::Error>, + { + let mut map = self.encode_map(cx, len)?; + f(&mut map)?; + map.end(cx) + } + /// Encode a map through pairs with a known length `len`. /// /// # Examples @@ -1260,6 +1369,43 @@ pub trait Encoder: Sized { ))) } + /// Encode a struct using a closure. + /// + /// # Examples + /// + /// ``` + /// use musli::{Context, Encode, Encoder}; + /// use musli::en::{StructEncoder}; + /// + /// struct Struct { + /// name: String, + /// age: u32, + /// } + /// + /// impl Encode for Struct { + /// fn encode(&self, cx: &C, encoder: E) -> Result + /// where + /// C: ?Sized + Context, + /// E: Encoder, + /// { + /// encoder.encode_struct_fn(cx, 2, |st| { + /// st.insert_field(cx, "name", &self.name)?; + /// st.insert_field(cx, "age", self.age)?; + /// Ok(()) + /// }) + /// } + /// } + /// ``` + #[inline] + fn encode_struct_fn(self, cx: &C, fields: usize, f: F) -> Result + where + F: FnOnce(&mut Self::EncodeStruct) -> Result<(), C::Error>, + { + let mut st = self.encode_struct(cx, fields)?; + f(&mut st)?; + st.end(cx) + } + /// Encode a variant. /// /// # Examples @@ -1322,6 +1468,63 @@ pub trait Encoder: Sized { ))) } + /// Encode a variant using a closure. + /// + /// # Examples + /// + /// ``` + /// use musli::{Context, Encode, Encoder}; + /// use musli::en::{VariantEncoder, StructEncoder, SequenceEncoder}; + /// + /// enum Enum { + /// UnitVariant, + /// TupleVariant(String), + /// StructVariant { + /// data: String, + /// age: u32, + /// } + /// } + /// + /// impl Encode for Enum { + /// fn encode(&self, cx: &C, encoder: E) -> Result + /// where + /// C: ?Sized + Context, + /// E: Encoder, + /// { + /// match self { + /// Enum::UnitVariant => { + /// encoder.encode_variant(cx)?.insert_variant(cx, "variant1", ()) + /// } + /// Enum::TupleVariant(data) => { + /// encoder.encode_variant(cx)?.insert_variant(cx, "variant2", data) + /// } + /// Enum::StructVariant { data, age } => { + /// encoder.encode_variant_fn(cx, |variant| { + /// variant.encode_tag(cx)?.encode_string(cx, "variant3")?; + /// + /// variant.encode_value(cx)?.encode_struct_fn(cx, 2, |st| { + /// st.insert_field(cx, "data", data)?; + /// st.insert_field(cx, "age", age)?; + /// Ok(()) + /// })?; + /// + /// Ok(()) + /// }) + /// } + /// } + /// } + /// } + /// ``` + #[inline] + fn encode_variant_fn(self, cx: &C, f: F) -> Result + where + F: FnOnce(&mut Self::EncodeVariant) -> Result<(), C::Error>, + { + let mut variant = self.encode_variant(cx)?; + f(&mut variant)?; + variant.end(cx) + } + /// Simplified encoding for a unit variant. /// /// # Examples @@ -1361,11 +1564,11 @@ pub trait Encoder: Sized { #[inline] fn encode_unit_variant(self, cx: &C, tag: &T) -> Result where - T: Encode, + T: ?Sized + Encode, { let mut variant = self.encode_variant(cx)?; let t = variant.encode_tag(cx)?; - Encode::encode(tag, cx, t)?; + tag.encode(cx, t)?; let v = variant.encode_value(cx)?; v.encode_unit(cx)?; variant.end(cx) diff --git a/crates/musli/src/en/struct_encoder.rs b/crates/musli/src/en/struct_encoder.rs index f0524cd8b..ee2f4145c 100644 --- a/crates/musli/src/en/struct_encoder.rs +++ b/crates/musli/src/en/struct_encoder.rs @@ -17,6 +17,18 @@ pub trait StructEncoder { /// Finish encoding the struct. fn end(self, cx: &C) -> Result; + /// Encode the next field using a closure. + #[inline] + fn encode_field_fn(&mut self, cx: &C, f: F) -> Result + where + F: FnOnce(&mut Self::EncodeField<'_>) -> Result, + { + let mut encoder = self.encode_field(cx)?; + let output = f(&mut encoder)?; + encoder.end(cx)?; + Ok(output) + } + /// Insert a field immediately. #[inline] fn insert_field(&mut self, cx: &C, field: F, value: V) -> Result<(), C::Error> diff --git a/crates/musli/src/impls/alloc.rs b/crates/musli/src/impls/alloc.rs index 647384e4d..1ce492e30 100644 --- a/crates/musli/src/impls/alloc.rs +++ b/crates/musli/src/impls/alloc.rs @@ -375,7 +375,7 @@ macro_rules! map { C: ?Sized + Context, D: Decoder<'de, C>, { - decoder.decode_map_fn($cx, |$cx, $access| { + decoder.decode_map_fn($cx, |$access| { let mut out = $with_capacity; while let Some((key, value)) = $access.entry($cx)? { @@ -399,7 +399,7 @@ macro_rules! map { C: ?Sized + Context, D: Decoder<'de, C>, { - decoder.decode_map_fn($cx, |$cx, $access| { + decoder.decode_map_fn($cx, |$access| { let mut out = $with_capacity; while let Some(mut entry) = $access.decode_entry($cx)? { @@ -589,7 +589,8 @@ impl Encode for OsStr { let mut variant = encoder.encode_variant(cx)?; PlatformTag::Unix.encode(cx, variant.encode_tag(cx)?)?; - self.as_bytes().encode(cx, variant.encode_value(cx)?)?; + self.as_bytes() + .encode_bytes(cx, variant.encode_value(cx)?)?; variant.end(cx) } @@ -619,7 +620,7 @@ impl Encode for OsStr { } } - buf.as_slice().encode(cx, variant.encode_value(cx)?)?; + buf.as_slice().encode_bytes(cx, variant.encode_value(cx)?)?; variant.end(cx) } } diff --git a/crates/musli/src/impls/mod.rs b/crates/musli/src/impls/mod.rs index de6aa1229..9035704ba 100644 --- a/crates/musli/src/impls/mod.rs +++ b/crates/musli/src/impls/mod.rs @@ -322,7 +322,7 @@ where for value in self { cx.enter_sequence_index(index); let encoder = seq.encode_next(cx)?; - T::encode(value, cx, encoder)?; + value.encode(cx, encoder)?; cx.leave_sequence_index(); index = index.wrapping_add(index); } diff --git a/crates/musli/src/impls/net.rs b/crates/musli/src/impls/net.rs index 2639404cc..6b83ac62f 100644 --- a/crates/musli/src/impls/net.rs +++ b/crates/musli/src/impls/net.rs @@ -113,7 +113,7 @@ impl<'de, M> Decode<'de, M> for SocketAddrV4 { C: ?Sized + Context, D: Decoder<'de, C>, { - decoder.decode_pack_fn(cx, |cx, pack| { + decoder.decode_pack_fn(cx, |pack| { Ok(SocketAddrV4::new(pack.next(cx)?, pack.next(cx)?)) }) } @@ -142,7 +142,7 @@ impl<'de, M> Decode<'de, M> for SocketAddrV6 { C: ?Sized + Context, D: Decoder<'de, C>, { - decoder.decode_pack_fn(cx, |cx, pack| { + decoder.decode_pack_fn(cx, |pack| { Ok(Self::new( pack.next(cx)?, pack.next(cx)?, diff --git a/crates/musli/src/impls/tuples.rs b/crates/musli/src/impls/tuples.rs index e2b4c4c9b..fb5fdeb3c 100644 --- a/crates/musli/src/impls/tuples.rs +++ b/crates/musli/src/impls/tuples.rs @@ -93,7 +93,7 @@ macro_rules! declare { C: ?Sized + Context, D: Decoder<'de, C> { - decoder.decode_pack_fn(cx, |cx, pack| { + decoder.decode_pack_fn(cx, |pack| { let $ident0 = pack.next(cx)?; $(let $ident = pack.next(cx)?;)* Ok(Packed(($ident0, $($ident),*))) diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index e0243a09b..b7a53e854 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -10,9 +10,9 @@ license = "MIT OR Apache-2.0" publish = false [features] -default = ["std", "alloc"] -std = ["musli?/std", "musli-wire?/std", "musli-storage?/std", "musli-json?/std", "musli-zerocopy?/std", "rand/std", "serde_json?/std", "rkyv?/std", "serde?/std"] -alloc = ["musli?/alloc", "musli-wire?/alloc", "musli-storage?/alloc", "musli-json?/alloc", "musli-zerocopy?/alloc"] +default = ["std", "alloc", "test"] +std = ["musli?/std", "musli-storage?/std", "musli-wire?/std", "musli-descriptive?/std", "musli-json?/std", "musli-zerocopy?/std", "rand/std", "serde_json?/std", "rkyv?/std", "serde?/std"] +alloc = ["musli?/alloc", "musli-storage?/alloc", "musli-wire?/alloc", "musli-descriptive?/alloc", "musli-json?/alloc", "musli-zerocopy?/alloc"] extra = ["rkyv", "dlhn", "serde_cbor"] full = ["rmp-serde", "bincode", "postcard", "musli-json", "serde_json", "bitcode", "bitcode-derive"] text = ["musli-json", "serde_json"] diff --git a/crates/tests/tests/recursive_models.rs b/crates/tests/tests/recursive_models.rs new file mode 100644 index 000000000..848a96831 --- /dev/null +++ b/crates/tests/tests/recursive_models.rs @@ -0,0 +1,173 @@ +//! When this file is built in release mode, it was discovered that it there is an overflow in requirements: +//! +//! ```sh +//! cargo build --release -p tests --test recursive_models --features test +//! ``` +//! +//! ```text +//! error[E0275]: overflow evaluating the requirement `<>::EncodeVariant as VariantEncoder>::EncodeValue<'_>: Encoder` +//! | +//! = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`recursive_models`) +//! ``` + +use musli::de::DecodeOwned; +use musli::{Decode, Encode}; + +pub(crate) fn encode(changes: &T) -> tests::storage::Result> +where + T: Encode, +{ + tests::storage::to_vec(changes) +} + +pub(crate) fn decode(buf: &[u8]) -> tests::storage::Result +where + T: DecodeOwned, +{ + tests::storage::from_slice(buf) +} + +#[test] +fn big_model() { + use std::ffi::OsString; + use std::ops::Range; + use std::path::PathBuf; + use std::sync::Arc; + + #[derive(Encode, Decode)] + #[non_exhaustive] + pub(crate) struct RustVersion { + pub(crate) major: u64, + pub(crate) minor: u64, + pub(crate) patch: u64, + } + + #[derive(Encode, Decode)] + pub(crate) enum CargoKey {} + + #[derive(Encode, Decode)] + pub(crate) enum CargoIssue {} + + #[derive(Encode, Decode)] + pub(crate) enum WorkflowError { + Error { name: String, reason: String }, + } + + #[derive(Encode, Decode)] + pub(crate) enum EditChange { + Insert { + reason: String, + key: String, + value: Value, + }, + Set { + reason: String, + value: Value, + }, + RemoveKey { + reason: String, + key: String, + }, + } + + #[derive(Encode, Decode)] + pub(crate) enum Value { + String(String), + Array(Vec), + Mapping(Vec<(String, Value)>), + } + + #[derive(Encode, Decode)] + pub(crate) struct Edits { + changes: Vec, + } + + #[derive(Encode, Decode)] + pub(crate) struct File { + data: String, + line_starts: Vec, + } + + #[derive(Encode, Decode)] + pub(crate) struct RepoRef {} + + #[derive(Encode, Decode)] + pub(crate) struct Manifest {} + + #[derive(Encode, Decode)] + pub(crate) struct Replaced { + path: PathBuf, + content: Vec, + replacement: Box, + ranges: Vec>, + } + + #[derive(Encode, Decode)] + pub(crate) enum Change { + MissingWorkflow { + id: String, + repo: RepoRef, + }, + BadWorkflow { + edits: Edits, + errors: Vec, + }, + UpdateLib { + lib: Arc, + }, + UpdateReadme { + readme: Arc, + }, + CargoTomlIssues { + cargo: Option, + issues: Vec, + }, + SetRustVersion { + repo: RepoRef, + version: RustVersion, + }, + RemoveRustVersion { + repo: RepoRef, + version: RustVersion, + }, + SavePackage { + manifest: Manifest, + }, + Replace { + replaced: Replaced, + }, + ReleaseCommit {}, + Publish { + name: String, + dry_run: bool, + no_verify: bool, + args: Vec, + }, + } + + let changes = Vec::::new(); + encode(&changes).unwrap(); + assert!(decode::>(&[]).is_err()); +} + +#[test] +fn recursive_model() { + #[derive(Encode, Decode)] + pub(crate) struct Value { + recursive: Vec, + } + + #[derive(Encode, Decode)] + pub(crate) struct Model { + value: Value, + } + + let model = Model { + value: Value { + recursive: Vec::new(), + }, + }; + + encode(&model).unwrap(); + assert!(decode::(&[]).is_err()); +}