Repository: sharksforarms/deku
Pull request #136 (2020-11-23 00:18:55)

feat: Add `temp` attribute
Related: https://github.com/sharksforarms/deku/issues/120

Example
```rust
use deku::prelude::*;
use std::convert::{TryFrom, TryInto};

// Note: new `deku_derive` vs `derive`, this is needed only for structs/enums which use `temp`
#[deku_derive(DekuRead, DekuWrite)]
#[derive(PartialEq, Debug)]
struct TestStruct {
    #[deku(temp)]
    field_a: u8,
    #[deku(count = "field_a")]
    field_b: Vec<u8>,
}

fn main() {
    let test_data: Vec<u8> = [0x01, 0x02].to_vec();

    let ret_read = TestStruct::try_from(test_data.as_ref()).unwrap();
    assert_eq!(
        TestStruct {
            field_b: vec![0x02]
        },
        ret_read
    );

    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
    assert_eq!(test_data[1..].to_vec(), ret_write);
}
```


Patch:
diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs
index d2390c1..3ee7782 100644
--- a/deku-derive/src/lib.rs
+++ b/deku-derive/src/lib.rs
@@ -4,13 +4,13 @@
 
 #![warn(missing_docs)]
 
-use darling::{ast, FromDeriveInput, FromField, FromVariant};
+use crate::macros::{deku_read::emit_deku_read, deku_write::emit_deku_write};
+use darling::{ast, FromDeriveInput, FromField, FromMeta, FromVariant, ToTokens};
 use proc_macro2::TokenStream;
 use quote::quote;
+use syn::{punctuated::Punctuated, spanned::Spanned, AttributeArgs};
+
 mod macros;
-use crate::macros::{deku_read::emit_deku_read, deku_write::emit_deku_write};
-use syn::punctuated::Punctuated;
-use syn::spanned::Spanned;
 
 /// A post-processed version of `DekuReceiver`
 #[derive(Debug)]
@@ -225,6 +225,9 @@ struct FieldData {
     /// skip field reading/writing
     skip: bool,
 
+    /// read field as temporary value, isn't stored
+    temp: bool,
+
     /// default value code when used with skip or cond
     default: TokenStream,
 
@@ -266,6 +269,7 @@ impl FieldData {
             reader: receiver.reader,
             writer: receiver.writer,
             skip: receiver.skip,
+            temp: receiver.temp,
             default,
             cond: receiver.cond,
         })
@@ -533,6 +537,10 @@ struct DekuFieldReceiver {
     #[darling(default)]
     skip: bool,
 
+    /// read field as temporary value, isn't stored
+    #[darling(default)]
+    temp: bool,
+
     /// default value code when used with skip
     #[darling(default, map = "option_as_tokenstream")]
     default: Option<TokenStream>,
@@ -608,6 +616,131 @@ pub fn proc_deku_write(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
     data.emit_writer().into()
 }
 
+fn is_not_deku(attr: &syn::Attribute) -> bool {
+    attr.path
+        .get_ident()
+        .map(|ident| ident != "deku" && ident != "deku_derive")
+        .unwrap_or(true)
+}
+
+fn is_temp(field: &syn::Field) -> bool {
+    DekuFieldReceiver::from_field(field)
+        .map(|attrs| attrs.temp)
+        .unwrap_or(false)
+}
+
+fn remove_deku_temp_fields(fields: &mut syn::punctuated::Punctuated<syn::Field, syn::Token![,]>) {
+    *fields = fields
+        .clone()
+        .into_pairs()
+        .filter(|x| !is_temp(x.value()))
+        .collect()
+}
+fn remove_deku_field_attrs(fields: &mut syn::punctuated::Punctuated<syn::Field, syn::Token![,]>) {
+    *fields = fields
+        .clone()
+        .into_pairs()
+        .map(|mut field| {
+            field.value_mut().attrs.retain(is_not_deku);
+            field
+        })
+        .collect()
+}
+
+fn remove_deku_attrs(fields: &mut syn::Fields) {
+    match fields {
+        syn::Fields::Named(ref mut fields) => remove_deku_field_attrs(&mut fields.named),
+        syn::Fields::Unnamed(ref mut fields) => remove_deku_field_attrs(&mut fields.unnamed),
+        syn::Fields::Unit => (),
+    }
+}
+
+fn remove_temp_fields(fields: &mut syn::Fields) {
+    match fields {
+        syn::Fields::Named(ref mut fields) => remove_deku_temp_fields(&mut fields.named),
+        syn::Fields::Unnamed(ref mut fields) => remove_deku_temp_fields(&mut fields.unnamed),
+        syn::Fields::Unit => (),
+    }
+}
+
+#[derive(Debug, FromMeta)]
+struct DekuDerive {
+    #[darling(default, rename = "DekuRead")]
+    read: bool,
+    #[darling(default, rename = "DekuWrite")]
+    write: bool,
+}
+
+/// Entry function for `deku_derive` proc-macro
+/// This attribute macro is used to derive `DekuRead` and `DekuWrite`
+/// while removing temporary variables.
+#[proc_macro_attribute]
+pub fn deku_derive(
+    attr: proc_macro::TokenStream,
+    item: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+    let mut derives = vec![];
+
+    // Parse `deku_derive` attribute
+    let attr_args = syn::parse_macro_input!(attr as AttributeArgs);
+    let args = match DekuDerive::from_list(&attr_args) {
+        Ok(v) => v,
+        Err(e) => {
+            return proc_macro::TokenStream::from(e.write_errors());
+        }
+    };
+
+    // Generate `DekuRead` impl
+    if args.read {
+        let derive: TokenStream = proc_deku_read(item.clone()).into();
+        derives.push(derive);
+    }
+
+    // Remove the temp fields
+    let mut input = syn::parse_macro_input!(item as syn::DeriveInput);
+
+    match input.data {
+        syn::Data::Struct(ref mut input_struct) => remove_temp_fields(&mut input_struct.fields),
+        syn::Data::Enum(ref mut input_enum) => {
+            for variant in input_enum.variants.iter_mut() {
+                remove_temp_fields(&mut variant.fields)
+            }
+        }
+        _ => unimplemented!(),
+    }
+
+    // Generate `DekuWrite` impl
+    if args.write {
+        let input = input.clone();
+        let derive: TokenStream = proc_deku_write(input.into_token_stream().into()).into();
+        derives.push(derive);
+    }
+
+    // Remove attributes
+    match input.data {
+        syn::Data::Struct(ref mut input_struct) => {
+            input.attrs.retain(is_not_deku);
+            remove_deku_attrs(&mut input_struct.fields)
+        }
+        syn::Data::Enum(ref mut input_enum) => {
+            for variant in input_enum.variants.iter_mut() {
+                variant.attrs.retain(is_not_deku);
+                remove_deku_attrs(&mut variant.fields)
+            }
+        }
+        _ => unimplemented!(),
+    }
+
+    input.attrs.retain(is_not_deku);
+
+    quote!(
+        #(#derives)*
+
+        #input
+    )
+    .into()
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs
index 53ed0dc..8a250e9 100644
--- a/deku-derive/src/macros/deku_read.rs
+++ b/deku-derive/src/macros/deku_read.rs
@@ -36,6 +36,13 @@ fn emit_struct(input: &DekuData) -> Result<TokenStream, syn::Error> {
 
     let (field_idents, field_reads) = emit_field_reads(input, &fields)?;
 
+    // filter out temporary fields
+    let field_idents: Vec<&TokenStream> = field_idents
+        .iter()
+        .filter(|f| !f.is_temp)
+        .map(|f| &f.field_ident)
+        .collect();
+
     let internal_fields = gen_internal_field_idents(is_named_struct, field_idents);
 
     let initialize_struct = super::gen_struct_init(is_named_struct, internal_fields);
@@ -159,6 +166,13 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
         } else {
             let (field_idents, field_reads) = emit_field_reads(input, &variant.fields.as_ref())?;
 
+            // filter out temporary fields
+            let field_idents: Vec<&TokenStream> = field_idents
+                .iter()
+                .filter(|f| !f.is_temp)
+                .map(|f| &f.field_ident)
+                .collect();
+
             let internal_fields = gen_internal_field_idents(variant_is_named, field_idents);
             let initialize_enum =
                 super::gen_enum_init(variant_is_named, variant_ident, internal_fields);
@@ -304,16 +318,24 @@ fn emit_magic_read(input: &DekuData) -> Result<TokenStream, syn::Error> {
     Ok(tokens)
 }
 
+struct FieldIdent {
+    field_ident: TokenStream,
+    is_temp: bool,
+}
+
 fn emit_field_reads(
     input: &DekuData,
     fields: &Fields<&FieldData>,
-) -> Result<(Vec<TokenStream>, Vec<TokenStream>), syn::Error> {
+) -> Result<(Vec<FieldIdent>, Vec<TokenStream>), syn::Error> {
     let mut field_reads = vec![];
     let mut field_idents = vec![];
 
     for (i, f) in fields.iter().enumerate() {
         let (field_ident, field_read) = emit_field_read(input, i, f)?;
-        field_idents.push(field_ident);
+        field_idents.push(FieldIdent {
+            field_ident,
+            is_temp: f.temp,
+        });
         field_reads.push(field_read);
     }
 
@@ -341,7 +363,7 @@ fn emit_field_read(
 
     let field_ident = f.get_ident(i, true);
 
-    let internal_field_ident = gen_internal_field_ident(field_ident.clone());
+    let internal_field_ident = gen_internal_field_ident(&field_ident);
 
     let field_read_func = if field_reader.is_some() {
         quote! { #field_reader }
diff --git a/deku-derive/src/macros/mod.rs b/deku-derive/src/macros/mod.rs
index 43009eb..9e8976a 100644
--- a/deku-derive/src/macros/mod.rs
+++ b/deku-derive/src/macros/mod.rs
@@ -86,7 +86,7 @@ fn gen_struct_destruction<I: ToTokens, F: ToTokens>(
 
 /// Convert a field ident to internal ident:
 /// `a` -> `__deku_a`
-fn gen_internal_field_ident(ident: TokenStream) -> TokenStream {
+fn gen_internal_field_ident(ident: &TokenStream) -> TokenStream {
     // Concat token: https://github.com/rust-lang/rust/issues/29599
     let span = ident.span();
     let s = ident.to_string();
@@ -100,7 +100,7 @@ fn gen_internal_field_ident(ident: TokenStream) -> TokenStream {
 ///
 /// - Named: `{ a: __deku_a }`
 /// - Unnamed: `( __deku_a )`
-fn gen_internal_field_idents(named: bool, idents: Vec<TokenStream>) -> Vec<TokenStream> {
+fn gen_internal_field_idents(named: bool, idents: Vec<&TokenStream>) -> Vec<TokenStream> {
     if named {
         idents
             .into_iter()
diff --git a/src/attributes.rs b/src/attributes.rs
index 8d99549..cc4a31b 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -14,6 +14,7 @@ A documentation-only module for #\[deku\] attributes
 | [bytes_read](#bytes_read) | field | Set the field representing the number of bytes to read into a container
 | [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
 | [skip](#skip) | field | Skip the reading/writing of a field
 | [cond](#cond) | field | Conditional expression for the field
 | [default](#default) | field | Custom defaulting code when `skip` is true
@@ -360,6 +361,45 @@ let value: Vec<u8> = value.try_into().unwrap();
 assert_eq!(vec![0x03, 0xAB, 0xCD, 0xFF], value);
 ```
 
+# temp
+
+A temporary field
+
+Included in the reading of the struct/enum but not stored
+
+**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)]
+    num_items: u8,
+
+    #[deku(count = "num_items", endian = "big")]
+    items: Vec<u16>,
+}
+
+let data: Vec<u8> = vec![0x01, 0xBE, 0xEF];
+
+let value = DekuTest::try_from(data.as_ref()).unwrap();
+
+assert_eq!(
+    DekuTest {
+       items: vec![0xBEEF]
+    },
+    value
+);
+
+let value: Vec<u8> = value.try_into().unwrap();
+assert_eq!(vec![0xBE, 0xEF], value);
+```
+
 # skip
 
 Skip the reading/writing of a field.
diff --git a/src/prelude.rs b/src/prelude.rs
index 02fcc2e..35ee80c 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -3,7 +3,8 @@
 [What is a prelude?](std::prelude)
 */
 pub use crate::{
-    error::DekuError, DekuContainerRead, DekuContainerWrite, DekuRead, DekuUpdate, DekuWrite,
+    deku_derive, error::DekuError, DekuContainerRead, DekuContainerWrite, DekuRead, DekuUpdate,
+    DekuWrite,
 };
 pub use bitvec::{
     order::BitOrder, order::Lsb0, order::Msb0, slice::BitSlice, vec::BitVec, view::BitView,


Test patch:
diff --git a/tests/test_attributes/mod.rs b/tests/test_attributes/mod.rs
index 5d68916..92379b5 100644
--- a/tests/test_attributes/mod.rs
+++ b/tests/test_attributes/mod.rs
@@ -2,5 +2,6 @@ mod test_cond;
 mod test_ctx;
 mod test_map;
 mod test_skip;
+mod test_temp;
 mod test_update;
 mod test_vec_limits;
diff --git a/tests/test_attributes/test_temp.rs b/tests/test_attributes/test_temp.rs
new file mode 100644
index 0000000..ff7d667
--- /dev/null
+++ b/tests/test_attributes/test_temp.rs
@@ -0,0 +1,71 @@
+use deku::prelude::*;
+use std::convert::{TryFrom, TryInto};
+
+#[test]
+fn test_temp_field() {
+    #[deku_derive(DekuRead, DekuWrite)]
+    #[derive(PartialEq, Debug)]
+    struct TestStruct {
+        #[deku(temp)]
+        field_a: u8,
+        #[deku(count = "field_a")]
+        field_b: Vec<u8>,
+    }
+
+    let test_data: Vec<u8> = [0x01, 0x02].to_vec();
+
+    let ret_read = TestStruct::try_from(test_data.as_ref()).unwrap();
+    assert_eq!(
+        TestStruct {
+            field_b: vec![0x02]
+        },
+        ret_read
+    );
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(test_data[1..].to_vec(), ret_write);
+}
+
+#[test]
+fn test_temp_field_unnamed() {
+    #[deku_derive(DekuRead, DekuWrite)]
+    #[derive(PartialEq, Debug)]
+    struct TestStruct(#[deku(temp)] u8, #[deku(count = "field_0")] Vec<u8>);
+
+    let test_data: Vec<u8> = [0x01, 0x02].to_vec();
+
+    let ret_read = TestStruct::try_from(test_data.as_ref()).unwrap();
+    assert_eq!(TestStruct(vec![0x02]), ret_read);
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(test_data[1..].to_vec(), ret_write);
+}
+
+#[test]
+fn test_temp_enum_field() {
+    #[deku_derive(DekuRead, DekuWrite)]
+    #[derive(PartialEq, Debug)]
+    #[deku(type = "u8")]
+    enum TestEnum {
+        #[deku(id = "0xAB")]
+        VarA {
+            #[deku(temp)]
+            field_a: u8,
+            #[deku(count = "field_a")]
+            field_b: Vec<u8>,
+        },
+    };
+
+    let test_data: Vec<u8> = [0xAB, 0x01, 0x02].to_vec();
+
+    let ret_read = TestEnum::try_from(test_data.as_ref()).unwrap();
+    assert_eq!(
+        TestEnum::VarA {
+            field_b: vec![0x02]
+        },
+        ret_read
+    );
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(vec![0xAB, 0x02], ret_write);
+}
diff --git a/tests/test_compile/cases/temp_field.rs b/tests/test_compile/cases/temp_field.rs
new file mode 100644
index 0000000..d328839
--- /dev/null
+++ b/tests/test_compile/cases/temp_field.rs
@@ -0,0 +1,8 @@
+use deku::prelude::*;
+
+// wrong way to use temp
+#[derive(DekuRead, DekuWrite)]
+struct Test1 {
+    #[deku(temp)]
+    field_a: u8,
+}
diff --git a/tests/test_compile/cases/temp_field.stderr b/tests/test_compile/cases/temp_field.stderr
new file mode 100644
index 0000000..2aabbdc
--- /dev/null
+++ b/tests/test_compile/cases/temp_field.stderr
@@ -0,0 +1,19 @@
+error[E0601]: `main` function not found in crate `$CRATE`
+ --> $DIR/temp_field.rs:1:1
+  |
+1 | / use deku::prelude::*;
+2 | |
+3 | | // wrong way to use temp
+4 | | #[derive(DekuRead, DekuWrite)]
+... |
+7 | |     field_a: u8,
+8 | | }
+  | |_^ consider adding a `main` function to `$DIR/tests/test_compile/cases/temp_field.rs`
+
+error[E0063]: missing field `field_a` in initializer of `Test1`
+ --> $DIR/temp_field.rs:4:10
+  |
+4 | #[derive(DekuRead, DekuWrite)]
+  |          ^^^^^^^^ missing `field_a`
+  |
+  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)