Repository: sharksforarms/deku
Pull request #322 (2023-02-28 17:15:03)

Temp value writer
Provide value for temp field on write.
IMO it is suitable for a lot of cases, e.g write vector with predicate of it's length, compute mask of option values and so on.


Patch:
diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs
index b1950dc..b0be1f2 100644
--- a/deku-derive/src/lib.rs
+++ b/deku-derive/src/lib.rs
@@ -1,6 +1,6 @@
 /*!
-    Procedural macros that implement `DekuRead` and `DekuWrite` traits
-*/
+Procedural macros that implement `DekuRead` and `DekuWrite` traits
+ */
 
 #![warn(missing_docs)]
 
@@ -415,6 +415,9 @@ struct FieldData {
     /// read field as temporary value, isn't stored
     temp: bool,
 
+    /// write given value of temp field
+    temp_value: Option<TokenStream>,
+
     /// default value code when used with skip or cond
     default: Option<TokenStream>,
 
@@ -457,6 +460,7 @@ impl FieldData {
             pad_bits_after: receiver.pad_bits_after?,
             pad_bytes_after: receiver.pad_bytes_after?,
             temp: receiver.temp,
+            temp_value: receiver.temp_value?,
             default: receiver.default?,
             cond: receiver.cond?,
             assert: receiver.assert?,
@@ -650,7 +654,7 @@ fn apply_replacements(input: &syn::LitStr) -> Result<Cow<'_, syn::LitStr>, Repla
     if input_value.contains("__deku_") {
         return Err(darling::Error::unsupported_format(
             "attribute cannot contain `__deku_` these are internal variables. Please use the `deku::` instead."
-        )) .map_err(|e| e.with_span(&input).write_errors());
+        )).map_err(|e| e.with_span(&input).write_errors());
     }
 
     let input_str = input_value
@@ -795,6 +799,10 @@ struct DekuFieldReceiver {
     #[darling(default)]
     temp: bool,
 
+    /// write given value of temp field
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    temp_value: Result<Option<TokenStream>, ReplacementError>,
+
     /// default value code when used with skip
     #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
     default: Result<Option<TokenStream>, ReplacementError>,
@@ -875,6 +883,7 @@ fn remove_deku_temp_fields(fields: &mut syn::punctuated::Punctuated<syn::Field,
         .filter(|x| !is_temp(x.value()))
         .collect()
 }
+
 fn remove_deku_field_attrs(fields: &mut syn::punctuated::Punctuated<syn::Field, syn::Token![,]>) {
     *fields = fields
         .clone()
@@ -928,7 +937,7 @@ pub fn deku_derive(
     };
 
     // Parse item
-    let mut data = match DekuData::from_input(item.clone().into()) {
+    let data = match DekuData::from_input(item.clone().into()) {
         Ok(data) => data,
         Err(err) => return err.into(),
     };
@@ -953,15 +962,6 @@ pub fn deku_derive(
         _ => unimplemented!(),
     }
 
-    match data.data {
-        ast::Data::Struct(ref mut fields) => fields.fields.retain(|field| !field.temp),
-        ast::Data::Enum(ref mut variants) => {
-            for variant in variants.iter_mut() {
-                variant.fields.fields.retain(|field| !field.temp);
-            }
-        }
-    }
-
     // Generate `DekuWrite` impl
     let write_impl = if args.write {
         data.emit_writer()
diff --git a/deku-derive/src/macros/deku_write.rs b/deku-derive/src/macros/deku_write.rs
index ee6b89d..35d8145 100644
--- a/deku-derive/src/macros/deku_write.rs
+++ b/deku-derive/src/macros/deku_write.rs
@@ -33,7 +33,13 @@ fn emit_struct(input: &DekuData) -> Result<TokenStream, syn::Error> {
 
     let named = fields.style.is_struct();
 
-    let field_idents = fields.iter().enumerate().map(|(i, f)| f.get_ident(i, true));
+    let field_idents = fields.iter().enumerate().filter_map(|(i, f)| {
+        if !f.temp {
+            Some(f.get_ident(i, true))
+        } else {
+            None
+        }
+    });
 
     let destructured = gen_struct_destruction(named, &input.ident, field_idents);
 
@@ -172,11 +178,13 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
         let variant_ident = &variant.ident;
         let variant_writer = &variant.writer;
 
-        let field_idents = variant
-            .fields
-            .iter()
-            .enumerate()
-            .map(|(i, f)| f.get_ident(i, true));
+        let field_idents = variant.fields.iter().enumerate().filter_map(|(i, f)| {
+            if !f.temp {
+                Some(f.get_ident(i, true))
+            } else {
+                None
+            }
+        });
 
         let variant_id_write = if id.is_some() {
             quote! {
@@ -390,6 +398,9 @@ fn emit_field_update(
     f: &FieldData,
     object_prefix: &Option<TokenStream>,
 ) -> Option<TokenStream> {
+    if f.temp {
+        return None;
+    }
     let field_ident = f.get_ident(i, object_prefix.is_none());
     let deref = if object_prefix.is_none() {
         Some(quote! { * })
@@ -519,7 +530,19 @@ fn emit_field_write(
             f.ctx.as_ref(),
         )?;
 
-        quote! { ::#crate_::DekuWrite::write(#object_prefix #field_ident, __deku_output, (#write_args)) }
+        if f.temp {
+            if let Some(temp_value) = &f.temp_value {
+                let field_type = &f.ty;
+                quote! {
+                    let #field_ident: #field_type = #temp_value;
+                    ::#crate_::DekuWrite::write(#object_prefix &#field_ident, __deku_output, (#write_args))
+                }
+            } else {
+                quote! { Result::<(), ::#crate_::DekuError>::Ok(()) }
+            }
+        } else {
+            quote! { ::#crate_::DekuWrite::write(#object_prefix #field_ident, __deku_output, (#write_args)) }
+        }
     };
 
     let pad_bits_before = pad_bits(
diff --git a/src/attributes.rs b/src/attributes.rs
index 22158a2..45b70fc 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -44,6 +44,7 @@ enum DekuEnum {
 | [until](#until) | field | Set a predicate returning when to stop reading elements into a container
 | [update](#update) | field | Apply code over the field when `.update()` is called
 | [temp](#temp) | field | Read the field but exclude it from the struct/enum
+| [temp_value](#temp_value) | field | Write the field but exclude it from the struct/enum
 | [skip](#skip) | field | Skip the reading/writing of a field
 | [pad_bytes_before](#pad_bytes_before) | field | Skip bytes before reading, pad before writing
 | [pad_bits_before](#pad_bits_before) | field | Skip bits before reading, pad before writing
@@ -492,6 +493,38 @@ let value: Vec<u8> = value.try_into().unwrap();
 assert_eq!(vec![0xBE, 0xEF], value);
 ```
 
+
+# temp_value
+
+Value for temporary field
+
+Will be written on corresponding offset of the struct/enum
+
+**Note**: Struct/enum must be derived with `#[deku_derive(...)]` to derive
+`DekuRead` and/or `DekuWrite`, not with `#[derive(...)]`. This is because the
+struct/enum needs to be modified at compile time.
+
+Example:
+```rust
+# use deku::prelude::*;
+# use std::convert::{TryInto, TryFrom};
+#[deku_derive(DekuRead, DekuWrite)]
+#[derive(Debug, PartialEq)]
+struct DekuTest {
+    #[deku(temp, temp_value = "items.len() as u8")]
+    num_items: u8,
+
+    #[deku(count = "num_items", endian = "big")]
+    items: Vec<u16>,
+}
+
+let value = DekuTest {
+    items: vec![0xDEAD, 0xBEEF]
+};
+let value: Vec<u8> = value.try_into().unwrap();
+assert_eq!(vec![0x02, 0xDE, 0xAD, 0xBE, 0xEF], value);
+```
+
 # skip
 
 Skip the reading/writing of a field.


Test patch:
diff --git a/tests/test_attributes/test_temp.rs b/tests/test_attributes/test_temp.rs
index ac913ab..3893ad9 100644
--- a/tests/test_attributes/test_temp.rs
+++ b/tests/test_attributes/test_temp.rs
@@ -1,6 +1,48 @@
 use deku::prelude::*;
 use std::convert::{TryFrom, TryInto};
 
+#[test]
+fn test_temp_field_write() {
+    #[deku_derive(DekuRead, DekuWrite)]
+    #[derive(PartialEq, Debug)]
+    struct TestStruct {
+        #[deku(temp, temp_value = "self.field_b.len() as _")]
+        field_a: u8,
+        #[deku(count = "field_a")]
+        field_b: Vec<u8>,
+    }
+
+    let test_data: Vec<u8> = [0x01, 0x02].to_vec();
+    let test_struct = TestStruct {
+        field_b: vec![0x02],
+    };
+    let ret_write: Vec<u8> = test_struct.to_bytes().unwrap();
+
+    assert_eq!(test_data, ret_write);
+}
+
+#[test]
+fn test_temp_field_value_ignore_on_read() {
+    #[deku_derive(DekuRead, DekuWrite)]
+    #[derive(PartialEq, Debug)]
+    struct TestStruct {
+        #[deku(temp, temp_value = "100")]
+        field_a: u8,
+        #[deku(count = "field_a")]
+        field_b: Vec<u8>,
+    }
+
+    let test_data: Vec<u8> = [0x02, 0x02, 0x03].to_vec();
+
+    let ret_read = TestStruct::try_from(test_data.as_ref()).unwrap();
+    assert_eq!(
+        TestStruct {
+            field_b: vec![0x02, 0x03]
+        },
+        ret_read
+    );
+}
+
 #[test]
 fn test_temp_field() {
     #[deku_derive(DekuRead, DekuWrite)]
@@ -41,6 +83,22 @@ fn test_temp_field_unnamed() {
     assert_eq!(test_data[1..].to_vec(), ret_write);
 }
 
+#[test]
+fn test_temp_field_unnamed_write() {
+    #[deku_derive(DekuRead, DekuWrite)]
+    #[derive(PartialEq, Debug)]
+    struct TestStruct(
+        #[deku(temp, temp_value = "self.0.len() as _")] u8,
+        #[deku(count = "field_0")] Vec<u8>,
+    );
+
+    let test_data: Vec<u8> = [0x01, 0x02].to_vec();
+    let test_struct = TestStruct(vec![0x02]);
+    let ret_write: Vec<u8> = test_struct.to_bytes().unwrap();
+
+    assert_eq!(test_data, ret_write);
+}
+
 #[test]
 fn test_temp_enum_field() {
     #[deku_derive(DekuRead, DekuWrite)]
@@ -69,3 +127,36 @@ fn test_temp_enum_field() {
     let ret_write: Vec<u8> = ret_read.try_into().unwrap();
     assert_eq!(vec![0xAB, 0x02], ret_write);
 }
+
+#[test]
+fn test_temp_enum_field_write() {
+    #[deku_derive(DekuRead, DekuWrite)]
+    #[derive(PartialEq, Debug)]
+    #[deku(type = "u8")]
+    enum TestEnum {
+        #[deku(id = "0xAB")]
+        VarA {
+            #[deku(
+                temp,
+                temp_value = "if let TestEnum::VarA { field_b } = self { field_b.len() as _ } else { unreachable!() };"
+            )]
+            field_a: u8,
+            #[deku(count = "field_a")]
+            field_b: Vec<u8>,
+        },
+        #[deku(id = "0xBA")]
+        VarB(u8),
+    }
+
+    let test_data: Vec<u8> = [0xAB, 0x01, 0x02].to_vec();
+    let ret_write: Vec<u8> = TestEnum::VarA {
+        field_b: vec![0x02],
+    }
+    .to_bytes()
+    .unwrap();
+    assert_eq!(test_data, ret_write);
+
+    let test_data: Vec<u8> = [0xBA, 0x10].to_vec();
+    let ret_write: Vec<u8> = TestEnum::VarB(0x10).to_bytes().unwrap();
+    assert_eq!(test_data, ret_write);
+}
diff --git a/tests/test_compile/cases/temp_field.stderr b/tests/test_compile/cases/temp_field.stderr
index 6100951..f19994d 100644
--- a/tests/test_compile/cases/temp_field.stderr
+++ b/tests/test_compile/cases/temp_field.stderr
@@ -5,3 +5,19 @@ error[E0063]: missing field `field_a` in initializer of `Test1`
   |          ^^^^^^^^ missing `field_a`
   |
   = note: this error originates in the derive macro `DekuRead` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0027]: pattern does not mention field `field_a`
+ --> $DIR/temp_field.rs:4:20
+  |
+4 | #[derive(DekuRead, DekuWrite)]
+  |                    ^^^^^^^^^ missing field `field_a`
+  |
+  = note: this error originates in the derive macro `DekuWrite` (in Nightly builds, run with -Z macro-backtrace for more info)
+help: include the missing field in the pattern
+  |
+4 | #[derive(DekuRead, DekuWrite { field_a })]
+  |                              +++++++++++
+help: if you don't care about this missing field, you can explicitly ignore it
+  |
+4 | #[derive(DekuRead, DekuWrite { .. })]
+  |                              ++++++