diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs index a45b3158..559deb68 100644 --- a/deku-derive/src/lib.rs +++ b/deku-derive/src/lib.rs @@ -346,6 +346,28 @@ fn repr(attrs: &[Attribute]) -> Option { None } +/// If `ctx` contains a parameter typed as `Order` (or `deku::ctx::Order`, `ctx::Order`), +/// return the parameter name so it can be used as a runtime `bit_order` variable. +#[cfg(feature = "bits")] +fn find_order_param_in_ctx( + ctx: &syn::punctuated::Punctuated, +) -> Option { + for arg in ctx { + if let syn::FnArg::Typed(pat_type) = arg { + if let syn::Type::Path(type_path) = pat_type.ty.as_ref() { + if let Some(last_segment) = type_path.path.segments.last() { + if last_segment.ident == "Order" { + if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() { + return Some(pat_ident.ident.to_string()); + } + } + } + } + } + } + None +} + impl DekuData { fn from_input(input: TokenStream) -> Result { let input = match syn::parse2(input) { @@ -384,6 +406,20 @@ impl DekuData { let repr = repr(&attrs); + // If no explicit `bit_order` attribute is set but `ctx` contains an + // `Order`-typed parameter, automatically forward that parameter as the + // runtime bit_order. This ensures nested types receiving Order via ctx + // propagate it to their own discriminant/field writes. + #[cfg(feature = "bits")] + let bit_order = receiver.bit_order.or_else(|| { + receiver.ctx.as_ref().and_then(|ctx| { + find_order_param_in_ctx(ctx) + .map(|name| syn::LitStr::new(&name, proc_macro2::Span::call_site())) + }) + }); + #[cfg(not(feature = "bits"))] + let bit_order = receiver.bit_order; + let data = Self { ident: receiver.ident, generics: receiver.generics, @@ -403,7 +439,7 @@ impl DekuData { seek_from_current: receiver.seek_from_current?, seek_from_end: receiver.seek_from_end?, seek_from_start: receiver.seek_from_start?, - bit_order: receiver.bit_order, + bit_order, }; DekuData::validate(&data)?; diff --git a/tests/bit_order.rs b/tests/bit_order.rs index a105eebb..7adfda06 100644 --- a/tests/bit_order.rs +++ b/tests/bit_order.rs @@ -219,6 +219,40 @@ mod tests { assert_eq_hex!(bytes, data); } + // Regression for #602: an `Order`-typed `ctx` parameter on a nested + // enum/struct should be used as that type's `bit_order` even when no + // explicit `bit_order = ""` attribute is given. + #[derive(Debug, DekuRead, DekuWrite, PartialEq)] + #[deku(bit_order = "lsb")] + pub struct EnumsAutoOrder { + right: ChoiceAuto, + left: ChoiceAuto, + } + + #[derive(Debug, DekuRead, DekuWrite, PartialEq)] + #[repr(u8)] + #[deku(bits = "4", id_type = "u8", ctx = "_bit_order: deku::ctx::Order")] + pub enum ChoiceAuto { + Empty = 0x0, + Full = 0xf, + } + + #[test] + fn test_bit_order_enums_auto_from_ctx() { + let data = vec![0xf0]; + let parsed = EnumsAutoOrder::try_from(data.as_ref()).unwrap(); + assert_eq!( + parsed, + EnumsAutoOrder { + right: ChoiceAuto::Empty, + left: ChoiceAuto::Full + } + ); + + let bytes = parsed.to_bytes().unwrap(); + assert_eq_hex!(bytes, data); + } + #[derive(Debug, DekuRead, DekuWrite, PartialEq)] #[deku(bit_order = "lsb")] pub struct MoreFirst {