Repository: sharksforarms/deku
Pull request #91 (2020-08-19 20:11:43)

feat: Add `ctx_default` attribute
Fixes https://github.com/sharksforarms/deku/issues/90

sharksforarms: @constfold you might be interested in this
codecov[bot]: # [Codecov](https://codecov.io/gh/sharksforarms/deku/pull/91?src=pr&el=h1) Report
> Merging [#91](https://codecov.io/gh/sharksforarms/deku/pull/91?src=pr&el=desc) into [master](https://codecov.io/gh/sharksforarms/deku/commit/f13a32c1bae1e4c1673cdb8fe4f8faa318b174e8&el=desc) will **increase** coverage by `0.65%`.
> The diff coverage is `98.43%`.

[![Impacted file tree graph](https://codecov.io/gh/sharksforarms/deku/pull/91/graphs/tree.svg?width=650&height=150&src=pr&token=MFMETW02MZ)](https://codecov.io/gh/sharksforarms/deku/pull/91?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #91      +/-   ##
==========================================
+ Coverage   85.80%   86.46%   +0.65%     
==========================================
  Files           8        8              
  Lines         944     1012      +68     
==========================================
+ Hits          810      875      +65     
- Misses        134      137       +3     
```


| [Impacted Files](https://codecov.io/gh/sharksforarms/deku/pull/91?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/lib.rs](https://codecov.io/gh/sharksforarms/deku/pull/91/diff?src=pr&el=tree#diff-c3JjL2xpYi5ycw==) | `93.31% <ø> (+0.08%)` | :arrow_up: |
| [deku-derive/src/lib.rs](https://codecov.io/gh/sharksforarms/deku/pull/91/diff?src=pr&el=tree#diff-ZGVrdS1kZXJpdmUvc3JjL2xpYi5ycw==) | `75.94% <93.33%> (+0.72%)` | :arrow_up: |
| [deku-derive/src/macros/deku\_read.rs](https://codecov.io/gh/sharksforarms/deku/pull/91/diff?src=pr&el=tree#diff-ZGVrdS1kZXJpdmUvc3JjL21hY3Jvcy9kZWt1X3JlYWQucnM=) | `98.46% <100.00%> (+0.27%)` | :arrow_up: |
| [deku-derive/src/macros/deku\_write.rs](https://codecov.io/gh/sharksforarms/deku/pull/91/diff?src=pr&el=tree#diff-ZGVrdS1kZXJpdmUvc3JjL21hY3Jvcy9kZWt1X3dyaXRlLnJz) | `98.44% <100.00%> (+0.28%)` | :arrow_up: |
| [deku-derive/src/macros/mod.rs](https://codecov.io/gh/sharksforarms/deku/pull/91/diff?src=pr&el=tree#diff-ZGVrdS1kZXJpdmUvc3JjL21hY3Jvcy9tb2QucnM=) | `96.59% <100.00%> (+0.29%)` | :arrow_up: |
| [src/error.rs](https://codecov.io/gh/sharksforarms/deku/pull/91/diff?src=pr&el=tree#diff-c3JjL2Vycm9yLnJz) | `20.00% <0.00%> (-0.69%)` | :arrow_down: |
| [src/slice\_impls.rs](https://codecov.io/gh/sharksforarms/deku/pull/91/diff?src=pr&el=tree#diff-c3JjL3NsaWNlX2ltcGxzLnJz) | `100.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/sharksforarms/deku/pull/91?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sharksforarms/deku/pull/91?src=pr&el=footer). Last update [c214a44...ed91938](https://codecov.io/gh/sharksforarms/deku/pull/91?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

github-actions[bot]: ## Benchmark for b9fbfe6
  <details>
    <summary>Click to view benchmark</summary>

| Test | PR Benchmark | Master Benchmark | % |
|------|--------------|------------------|---|
| deku_read_byte | 21.5±1.29ns | 21.5±1.05ns | 100% |
| deku_read_enum | **35.2±2.68ns** | 36.0±2.71ns | 98% |
| deku_read_vec | **1319.2±67.76ns** | 1372.4±76.67ns | 96% |
| deku_write_byte | **123.2±6.74ns** | 128.7±9.44ns | 95% |
| deku_write_enum | 236.6±10.92ns | 237.3±24.72ns | 100% |
| deku_write_vec | 20.3±1.65µs | **18.6±1.14µs** | 109.00000000000001% |

  </details>
  
github-actions[bot]: ## Benchmark for d29ddf3
  <details>
    <summary>Click to view benchmark</summary>

| Test | PR Benchmark | Master Benchmark | % |
|------|--------------|------------------|---|
| deku_read_byte | 12.3±0.36ns | **12.1±0.39ns** | 100% |
| deku_read_enum | 22.6±1.01ns | **21.8±1.06ns** | 103% |
| deku_read_vec | **1031.4±72.46ns** | 1060.6±80.69ns | 97% |
| deku_write_byte | 104.6±4.90ns | **99.8±4.38ns** | 105% |
| deku_write_enum | 198.0±9.82ns | **191.8±6.44ns** | 103% |
| deku_write_vec | **12.4±0.56µs** | 12.7±0.75µs | 98% |

  </details>
  
github-actions[bot]: ## Benchmark for 39bffb5
  <details>
    <summary>Click to view benchmark</summary>

| Test | PR Benchmark | Master Benchmark | % |
|------|--------------|------------------|---|
| deku_read_byte | 11.9±1.06ns | **11.8±0.78ns** | 100% |
| deku_read_enum | 21.5±1.18ns | 21.4±1.66ns | 100% |
| deku_read_vec | 1010.6±57.41ns | **978.0±64.01ns** | 103% |
| deku_write_byte | 97.5±9.36ns | **95.9±6.32ns** | 102% |
| deku_write_enum | **175.7±7.88ns** | 182.5±9.65ns | 96% |
| deku_write_vec | **11.4±0.71µs** | 11.9±1.21µs | 96% |

  </details>
  

Patch:
diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs
index 0a7e24b..22cdea6 100644
--- a/deku-derive/src/lib.rs
+++ b/deku-derive/src/lib.rs
@@ -20,6 +20,9 @@ struct DekuData {
     /// top-level context, argument list
     ctx: Option<syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>>,
 
+    /// default context passed to the field
+    ctx_default: Option<Punctuated<syn::Expr, syn::token::Comma>>,
+
     /// enum only: `id` value
     id: Option<TokenStream>,
 
@@ -62,6 +65,12 @@ impl DekuData {
             .transpose()
             .map_err(|e| e.to_compile_error())?;
 
+        let ctx_default = receiver
+            .ctx_default
+            .map(|s| s.parse_with(Punctuated::parse_terminated))
+            .transpose()
+            .map_err(|e| e.to_compile_error())?;
+
         let id_bits = receiver.id_bytes.map(|b| b * 8).or(receiver.id_bits);
 
         Ok(Self {
@@ -71,6 +80,7 @@ impl DekuData {
             data,
             endian: receiver.endian,
             ctx,
+            ctx_default,
             id: receiver.id,
             id_type: receiver.id_type,
             id_bits,
@@ -82,6 +92,14 @@ impl DekuData {
         FIXME: Issue with `span`, see `FieldData::validate`.
         */
 
+        // Validate `ctx_default`
+        if receiver.ctx_default.is_some() && receiver.ctx.is_none() {
+            return Err((
+                receiver.ctx_default.span(),
+                "`ctx_default` must be used with `ctx`",
+            ));
+        }
+
         match receiver.data {
             ast::Data::Struct(_) => {
                 // Validate id_* attributes are being used on an enum
@@ -364,6 +382,13 @@ struct DekuReceiver {
     #[darling(default)]
     ctx: Option<syn::LitStr>,
 
+    /// default context passed to the field
+    // TODO: The type of it should be
+    //       `syn::punctuated::Punctuated<syn::Expr, syn::token::Comma>`
+    //       https://github.com/TedDriggs/darling/pull/98
+    #[darling(default)]
+    ctx_default: Option<syn::LitStr>,
+
     /// enum only: `id` value
     #[darling(default, map = "option_as_tokenstream")]
     id: Option<TokenStream>,
diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs
index fbf07db..e940273 100644
--- a/deku-derive/src/macros/deku_read.rs
+++ b/deku-derive/src/macros/deku_read.rs
@@ -1,6 +1,6 @@
 use crate::macros::{
     gen_ctx_types_and_arg, gen_field_args, gen_id_args, gen_internal_field_ident,
-    gen_internal_field_idents,
+    gen_internal_field_idents, wrap_default_ctx,
 };
 use crate::{DekuData, FieldData};
 use darling::ast::{Data, Fields};
@@ -39,7 +39,27 @@ fn emit_struct(input: &DekuData) -> Result<TokenStream, syn::Error> {
     let initialize_struct = super::gen_struct_init(is_named_struct, internal_fields);
 
     // Implement `DekuContainerRead` for types that don't need a context
-    if input.ctx.is_none() {
+    if input.ctx.is_none() || (input.ctx.is_some() && input.ctx_default.is_some()) {
+        let from_bytes_body = wrap_default_ctx(
+            quote! {
+                use core::convert::TryFrom;
+                let input_bits = input.0.view_bits::<Msb0>();
+
+                let mut rest = input.0.view_bits::<Msb0>();
+                rest = &rest[input.1..];
+
+                #(#field_reads)*
+                let value = #initialize_struct;
+
+                let pad = 8 * ((rest.len() + 7) / 8) - rest.len();
+                let read_idx = input_bits.len() - (rest.len() + pad);
+
+                Ok(((input_bits[read_idx..].as_slice(), pad), value))
+            },
+            &input.ctx,
+            &input.ctx_default,
+        );
+
         tokens.extend(quote! {
         impl #imp core::convert::TryFrom<&[u8]> for #ident #wher {
             type Error = DekuError;
@@ -55,39 +75,44 @@ fn emit_struct(input: &DekuData) -> Result<TokenStream, syn::Error> {
 
         impl #imp DekuContainerRead for #ident #wher {
             fn from_bytes(input: (&[u8], usize)) -> Result<((&[u8], usize), Self), DekuError> {
-                use core::convert::TryFrom;
-                let input_bits = input.0.view_bits::<Msb0>();
-
-                let mut rest = input.0.view_bits::<Msb0>();
-                rest = &rest[input.1..];
-
-                #(#field_reads)*
-                let value = #initialize_struct;
-
-                let pad = 8 * ((rest.len() + 7) / 8) - rest.len();
-                let read_idx = input_bits.len() - (rest.len() + pad);
-
-                Ok(((&input_bits[read_idx..].as_slice(), pad), value))
+                #from_bytes_body
             }
-        }})
+        }
+        })
     }
 
     let (ctx_types, ctx_arg) = gen_ctx_types_and_arg(input.ctx.as_ref())?;
 
+    let read_body = quote! {
+        use core::convert::TryFrom;
+        let mut rest = input;
+
+        #(#field_reads)*
+        let value = #initialize_struct;
+
+        Ok((rest, value))
+    };
+
     tokens.extend(quote! {
         impl #imp DekuRead<#ctx_types> for #ident #wher {
             fn read<'a>(input: &'a BitSlice<Msb0, u8>, #ctx_arg) -> Result<(&'a BitSlice<Msb0, u8>, Self), DekuError> {
-                use core::convert::TryFrom;
-                let mut rest = input;
-
-                #(#field_reads)*
-                let value = #initialize_struct;
-
-                Ok((rest, value))
+                #read_body
             }
         }
     });
 
+    if input.ctx.is_some() && input.ctx_default.is_some() {
+        let read_body = wrap_default_ctx(read_body, &input.ctx, &input.ctx_default);
+
+        tokens.extend(quote! {
+            impl #imp DekuRead for #ident #wher {
+                fn read<'a>(input: &'a BitSlice<Msb0, u8>, _: ()) -> Result<(&'a BitSlice<Msb0, u8>, Self), DekuError> {
+                    #read_body
+                }
+            }
+        });
+    }
+
     // println!("{}", tokens.to_string());
     Ok(tokens)
 }
@@ -202,9 +227,27 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
     };
 
     // Implement `DekuContainerRead` for types that don't need a context
-    if input.ctx.is_none() {
-        tokens.extend(quote! {
+    if input.ctx.is_none() || (input.ctx.is_some() && input.ctx_default.is_some()) {
+        let from_bytes_body = wrap_default_ctx(
+            quote! {
+                use core::convert::TryFrom;
+                let input_bits = input.0.bits::<Msb0>();
 
+                let mut rest = input.0.bits::<Msb0>();
+                rest = &rest[input.1..];
+
+                #variant_read
+
+                let pad = 8 * ((rest.len() + 7) / 8) - rest.len();
+                let read_idx = input_bits.len() - (rest.len() + pad);
+
+                Ok(((input_bits[read_idx..].as_slice(), pad), value))
+            },
+            &input.ctx,
+            &input.ctx_default,
+        );
+
+        tokens.extend(quote! {
         impl #imp core::convert::TryFrom<&[u8]> for #ident #wher {
             type Error = DekuError;
 
@@ -219,18 +262,7 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
 
         impl #imp DekuContainerRead for #ident #wher {
             fn from_bytes(input: (&[u8], usize)) -> Result<((&[u8], usize), Self), DekuError> {
-                use core::convert::TryFrom;
-                let input_bits = input.0.view_bits::<Msb0>();
-
-                let mut rest = input.0.view_bits::<Msb0>();
-                rest = &rest[input.1..];
-
-                #variant_read
-
-                let pad = 8 * ((rest.len() + 7) / 8) - rest.len();
-                let read_idx = input_bits.len() - (rest.len() + pad);
-
-                Ok(((&input_bits[read_idx..].as_slice(), pad), value))
+                #from_bytes_body
             }
         }
         })
@@ -238,19 +270,35 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
 
     let (ctx_types, ctx_arg) = gen_ctx_types_and_arg(input.ctx.as_ref())?;
 
+    let read_body = quote! {
+        use core::convert::TryFrom;
+        let mut rest = input;
+
+        #variant_read
+
+        Ok((rest, value))
+    };
+
     tokens.extend(quote! {
         impl #imp DekuRead<#ctx_types> for #ident #wher {
             fn read<'a>(input: &'a BitSlice<Msb0, u8>, #ctx_arg) -> Result<(&'a BitSlice<Msb0, u8>, Self), DekuError> {
-                use core::convert::TryFrom;
-                let mut rest = input;
-
-                #variant_read
-
-                Ok((rest, value))
+                #read_body
             }
         }
     });
 
+    if input.ctx.is_some() && input.ctx_default.is_some() {
+        let read_body = wrap_default_ctx(read_body, &input.ctx, &input.ctx_default);
+
+        tokens.extend(quote! {
+            impl #imp DekuRead for #ident #wher {
+                fn read<'a>(input: &'a BitSlice<Msb0, u8>, _: ()) -> Result<(&'a BitSlice<Msb0, u8>, Self), DekuError> {
+                    #read_body
+                }
+            }
+        });
+    }
+
     // println!("{}", tokens.to_string());
     Ok(tokens)
 }
diff --git a/deku-derive/src/macros/deku_write.rs b/deku-derive/src/macros/deku_write.rs
index 71b5d55..e5ec823 100644
--- a/deku-derive/src/macros/deku_write.rs
+++ b/deku-derive/src/macros/deku_write.rs
@@ -1,4 +1,6 @@
-use crate::macros::{gen_ctx_types_and_arg, gen_field_args, gen_id_args, gen_struct_destruction};
+use crate::macros::{
+    gen_ctx_types_and_arg, gen_field_args, gen_id_args, gen_struct_destruction, wrap_default_ctx,
+};
 use crate::{DekuData, FieldData};
 use darling::ast::{Data, Fields};
 use proc_macro2::TokenStream;
@@ -36,7 +38,22 @@ fn emit_struct(input: &DekuData) -> Result<TokenStream, syn::Error> {
     let destructured = gen_struct_destruction(named, &input.ident, &field_idents);
 
     // Implement `DekuContainerWrite` for types that don't need a context
-    if input.ctx.is_none() {
+    if input.ctx.is_none() || (input.ctx.is_some() && input.ctx_default.is_some()) {
+        let to_bits_body = wrap_default_ctx(
+            quote! {
+                match *self {
+                    #destructured => {
+                        let mut acc: BitVec<Msb0, u8> = BitVec::new();
+                        #(#field_writes)*
+
+                        Ok(acc)
+                    }
+                }
+            },
+            &input.ctx,
+            &input.ctx_default,
+        );
+
         tokens.extend(quote! {
             impl #imp core::convert::TryFrom<#ident> for BitVec<Msb0, u8> #wher {
                 type Error = DekuError;
@@ -60,22 +77,27 @@ fn emit_struct(input: &DekuData) -> Result<TokenStream, syn::Error> {
                     Ok(acc.into_vec())
                 }
 
+                #[allow(unused_variables)]
                 fn to_bits(&self) -> Result<BitVec<Msb0, u8>, DekuError> {
-                    match *self {
-                        #destructured => {
-                            let mut acc: BitVec<Msb0, u8> = BitVec::new();
-                            #(#field_writes)*
-
-                            Ok(acc)
-                        }
-                    }
+                    #to_bits_body
                 }
             }
-        })
+        });
     }
 
     let (ctx_types, ctx_arg) = gen_ctx_types_and_arg(input.ctx.as_ref())?;
 
+    let write_body = quote! {
+        match *self {
+            #destructured => {
+                let mut acc: BitVec<Msb0, u8> = BitVec::new();
+                #(#field_writes)*
+
+                Ok(acc)
+            }
+        }
+    };
+
     tokens.extend(quote! {
         impl #imp DekuUpdate for #ident #wher {
             fn update(&mut self) -> Result<(), DekuError> {
@@ -87,19 +109,26 @@ fn emit_struct(input: &DekuData) -> Result<TokenStream, syn::Error> {
         }
 
         impl #imp DekuWrite<#ctx_types> for #ident #wher {
+            #[allow(unused_variables)]
             fn write(&self, #ctx_arg) -> Result<BitVec<Msb0, u8>, DekuError> {
-                match *self {
-                    #destructured => {
-                        let mut acc: BitVec<Msb0, u8> = BitVec::new();
-                        #(#field_writes)*
-
-                        Ok(acc)
-                    }
-                }
+                #write_body
             }
         }
     });
 
+    if input.ctx.is_some() && input.ctx_default.is_some() {
+        let write_body = wrap_default_ctx(write_body, &input.ctx, &input.ctx_default);
+
+        tokens.extend(quote! {
+            impl #imp DekuWrite for #ident #wher {
+                #[allow(unused_variables)]
+                fn write(&self, _: ()) -> Result<BitVec<Msb0, u8>, DekuError> {
+                    #write_body
+                }
+            }
+        });
+    }
+
     // println!("{}", tokens.to_string());
     Ok(tokens)
 }
@@ -200,7 +229,21 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
     }
 
     // Implement `DekuContainerWrite` for types that don't need a context
-    if input.ctx.is_none() {
+    if input.ctx.is_none() || (input.ctx.is_some() && input.ctx_default.is_some()) {
+        let to_bits_body = wrap_default_ctx(
+            quote! {
+                let mut acc: BitVec<Msb0, u8> = BitVec::new();
+
+                match self {
+                    #(#variant_writes),*
+                }
+
+                Ok(acc)
+            },
+            &input.ctx,
+            &input.ctx_default,
+        );
+
         tokens.extend(quote! {
             impl #imp core::convert::TryFrom<#ident> for BitVec<Msb0, u8> #wher {
                 type Error = DekuError;
@@ -224,14 +267,9 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
                     Ok(acc.into_vec())
                 }
 
+                #[allow(unused_variables)]
                 fn to_bits(&self) -> Result<BitVec<Msb0, u8>, DekuError> {
-                    let mut acc: BitVec<Msb0, u8> = BitVec::new();
-
-                    match self {
-                        #(#variant_writes),*
-                    }
-
-                    Ok(acc)
+                    #to_bits_body
                 }
             }
         })
@@ -239,6 +277,15 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
 
     let (ctx_types, ctx_arg) = gen_ctx_types_and_arg(input.ctx.as_ref())?;
 
+    let write_body = quote! {
+        let mut acc: BitVec<Msb0, u8> = BitVec::new();
+
+        match self {
+            #(#variant_writes),*
+        }
+
+        Ok(acc)
+    };
     tokens.extend(quote! {
         impl #imp DekuUpdate for #ident #wher {
             fn update(&mut self) -> Result<(), DekuError> {
@@ -253,18 +300,26 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
         }
 
         impl #imp DekuWrite<#ctx_types> for #ident #wher {
+            #[allow(unused_variables)]
             fn write(&self, #ctx_arg) -> Result<BitVec<Msb0, u8>, DekuError> {
-                let mut acc: BitVec<Msb0, u8> = BitVec::new();
-
-                match self {
-                    #(#variant_writes),*
-                }
-
-                Ok(acc)
+                #write_body
             }
         }
     });
 
+    if input.ctx.is_some() && input.ctx_default.is_some() {
+        let write_body = wrap_default_ctx(write_body, &input.ctx, &input.ctx_default);
+
+        tokens.extend(quote! {
+            impl #imp DekuWrite for #ident #wher {
+                #[allow(unused_variables)]
+                fn write(&self, _: ()) -> Result<BitVec<Msb0, u8>, DekuError> {
+                    #write_body
+                }
+            }
+        });
+    }
+
     // println!("{}", tokens.to_string());
     Ok(tokens)
 }
diff --git a/deku-derive/src/macros/mod.rs b/deku-derive/src/macros/mod.rs
index 605daa3..e730e66 100644
--- a/deku-derive/src/macros/mod.rs
+++ b/deku-derive/src/macros/mod.rs
@@ -214,3 +214,22 @@ fn gen_endian_from_str(s: &syn::LitStr) -> syn::Result<TokenStream> {
         }
     }
 }
+
+/// Wraps a TokenStream with a closure providing access to `ctx` variables when
+/// `ctx_default` is provided
+fn wrap_default_ctx(
+    body: TokenStream,
+    ctx: &Option<syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>>,
+    ctx_default: &Option<Punctuated<syn::Expr, syn::token::Comma>>,
+) -> TokenStream {
+    if let (Some(ctx), Some(ctx_default)) = (ctx, ctx_default) {
+        // wrap in a function to make `ctx` variables in scope
+        quote! {
+            |#ctx| -> Result<_, _> {
+                #body
+            }(#ctx_default)
+        }
+    } else {
+        body
+    }
+}
diff --git a/src/attributes.rs b/src/attributes.rs
index bb7f784..b176695 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -16,12 +16,13 @@ A documentation-only module for #[deku] attributes
 | [map](#map) | field | Apply a function over the result of reading
 | [reader](#readerwriter) | variant, field | Custom reader code
 | [writer](#readerwriter) | variant, field | Custom writer code
+| [ctx](#ctx) | top-level, field| Context list for context sensitive parsing
+| [ctx_default](#ctx_default) | top-level, field| Default context values
 | enum: [id](#id) | top-level, variant | enum or variant id value
 | enum: [id_pat](#id_pat) | variant | variant id match pattern
 | enum: [id_type](#id_type) | top-level | Set the type of the variant `id`
 | enum: [id_bits](#id_bits) | top-level | Set the bit-size of the variant `id`
 | enum: [id_bytes](#id_bytes) | top-level | Set the byte-size of the variant `id`
-| [ctx](#ctx) | top-level, field| Context list for context sensitive parsing
 
 # endian
 
@@ -422,6 +423,120 @@ let value: Vec<u8> = value.try_into().unwrap();
 assert_eq!(data, value);
 ```
 
+# ctx
+
+This attribute allows sending and receiving context (variables/values) to sub-parsers/writers
+
+**Note**: `endian`, `bytes`, `bits`, `count` attributes use `ctx` internally, see examples below
+
+**top-level**: The value of a ctx attribute is a function argument list,
+for example `#[deku(ctx = "a: u8, b: String")]`
+
+**field-level**: The value of the ctx attribute is a list of expressions,
+for example `#[deku("a, b")]`
+
+**Visibility**: The following can be accessed:
+1. All former fields which have been parsed (given as a reference).
+2. `endian`, `bytes`, `bits` attributes declared on the top-level
+    - These are prepended to the list of ctx variables
+
+Example
+```rust
+# use deku::prelude::*;
+#[derive(DekuRead, DekuWrite)]
+#[deku(ctx = "a: u8")]
+struct Subtype {
+    #[deku(map = "|b: u8| -> Result<_, DekuError> { Ok(b + a) }")]
+    b: u8
+}
+
+#[derive(DekuRead, DekuWrite)]
+struct Test {
+    a: u8,
+    #[deku(ctx = "*a")] // pass `a` to `SubType`, `a` is a reference
+    sub: Subtype
+}
+
+let data: Vec<u8> = vec![0x01, 0x02];
+
+let (rest, value) = Test::from_bytes((&data[..], 0)).unwrap();
+assert_eq!(value.a, 0x01);
+assert_eq!(value.sub.b, 0x01 + 0x02)
+```
+
+**Note**: In addition, `endian`, `bytes` and `bits` use the `ctx` concept internally, examples below are equivalent:
+
+Example:
+```ignore
+struct Type1 {
+    #[deku(endian = "big", bits = "1")]
+    field: u8,
+}
+
+// is equivalent to
+
+struct Type1 {
+    #[deku(ctx = "Endian::Big, BitSize(1)")]
+    field: u8,
+}
+```
+
+Example: Adding context
+```ignore
+#[deku(endian = "big")]
+struct Type1 {
+    field_a: u16,
+    #[deku(bits = "5", ctx = "*field_a")]
+    field_b: SubType,
+}
+
+// is equivalent to
+
+struct Type1 {
+    #[deku(ctx = "Endian::Big")]
+    field_a: u16,
+    #[deku(ctx = "Endian::Big, BitSize(5), *field_a")] // endian is prepended
+    field_b: SubType,
+}
+```
+
+# ctx_default
+
+When paired with the [`ctx`](#ctx) attribute, `ctx_default` provides default
+values for the context
+
+Example:
+```rust
+# use deku::prelude::*;
+#[derive(DekuRead, DekuWrite)]
+#[deku(ctx = "a: u8", ctx_default = "1")] // Defaults `a` to 1
+struct Subtype {
+    #[deku(map = "|b: u8| -> Result<_, DekuError> { Ok(b + a) }")]
+    b: u8
+}
+
+#[derive(DekuRead, DekuWrite)]
+struct Test {
+    a: u8,
+    #[deku(ctx = "*a")] // pass `a` to `SubType`, `a` is a reference
+    sub: Subtype
+}
+
+let data: Vec<u8> = vec![0x01, 0x02];
+
+// Use with context from `Test`
+let (rest, value) = Test::from_bytes((&data[..], 0)).unwrap();
+assert_eq!(value.a, 0x01);
+assert_eq!(value.sub.b, 0x01 + 0x02);
+
+// Use as a stand-alone container, using defaults
+// Note: `from_bytes` is now available on `SubType`
+let data: Vec<u8> = vec![0x02];
+
+let (rest, value) = Subtype::from_bytes((&data[..], 0)).unwrap();
+assert_eq!(value.b, 0x01 + 0x02)
+```
+
 # id
 
 ## id (top-level)
@@ -625,81 +740,5 @@ let value: Vec<u8> = value.try_into().unwrap();
 assert_eq!(data, value);
 ```
 
-# ctx
-
-This attribute allows sending and receiving context (variables/values) to sub-parsers/writers
-
-**Note**: `endian`, `bytes`, `bits`, `count` attributes use `ctx` internally, see examples below
-
-**top-level**: The value of a ctx attribute is a function argument list,
-for example `#[deku(ctx = "a: u8, b: String")]`
-
-**field-level**: The value of the ctx attribute is a list of expressions,
-for example `#[deku("a, b")]`
-
-**Visibility**: The following can be accessed:
-1. All former fields which have been parsed (given as a reference).
-2. `endian`, `bytes`, `bits` attributes declared on the top-level
-    - These are prepended to the list of ctx variables
-
-Example
-```rust
-# use deku::prelude::*;
-#[derive(DekuRead, DekuWrite)]
-#[deku(ctx = "a: u8")]
-struct Subtype {
-    #[deku(map = "|b: u8| -> Result<_, DekuError> { Ok(b + a) }")]
-    b: u8
-}
-
-#[derive(DekuRead, DekuWrite)]
-struct Test {
-    a: u8,
-    #[deku(ctx = "*a")] // pass `a` to `SubType`, `a` is a reference
-    sub: Subtype
-}
-
-let data: Vec<u8> = vec![0x01, 0x02];
-
-let (rest, value) = Test::from_bytes((&data[..], 0)).unwrap();
-assert_eq!(value.a, 0x01);
-assert_eq!(value.sub.b, 0x01 + 0x02)
-```
-
-**Note**: In addition, `endian`, `bytes` and `bits` use the `ctx` concept internally, examples below are equivalent:
-
-Example:
-```ignore
-struct Type1 {
-    #[deku(endian = "big", bits = "1")]
-    field: u8,
-}
-
-// is equivalent to
-
-struct Type1 {
-    #[deku(ctx = "Endian::Big, BitSize(1)")]
-    field: u8,
-}
-```
-
-Example: Adding context
-```ignore
-#[deku(endian = "big")]
-struct Type1 {
-    field_a: u16,
-    #[deku(bits = "5", ctx = "*field_a")]
-    field_b: SubType,
-}
-
-// is equivalent to
-
-struct Type1 {
-    #[deku(ctx = "Endian::Big")]
-    field_a: u16,
-    #[deku(ctx = "Endian::Big, BitSize(5), *field_a")] // endian is prepended
-    field_b: SubType,
-}
-```
 
 */


Test patch:
diff --git a/tests/macro_read/ctx_validation.rs b/tests/macro_read/ctx_validation.rs
new file mode 100644
index 0000000..a01b272
--- /dev/null
+++ b/tests/macro_read/ctx_validation.rs
@@ -0,0 +1,10 @@
+use deku::prelude::*;
+
+#[derive(DekuRead)]
+#[deku(ctx_default = "1")]
+struct Test1 {
+    a: u8
+}
+
+
+fn main() {}
diff --git a/tests/macro_read/ctx_validation.stderr b/tests/macro_read/ctx_validation.stderr
new file mode 100644
index 0000000..21abb49
--- /dev/null
+++ b/tests/macro_read/ctx_validation.stderr
@@ -0,0 +1,5 @@
+error: `ctx_default` must be used with `ctx`
+ --> $DIR/ctx_validation.rs:4:22
+  |
+4 | #[deku(ctx_default = "1")]
+  |                      ^^^
diff --git a/tests/test_macro.rs b/tests/test_macro.rs
index 9b25504..1ad1310 100644
--- a/tests/test_macro.rs
+++ b/tests/test_macro.rs
@@ -164,6 +164,15 @@ pub mod samples {
         pub(crate) i: usize,
     }
 
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    #[deku(ctx = "a: u8, b: u8", ctx_default = "1, 2")]
+    pub struct TopLevelCtxStructDefault {
+        #[deku(cond = "a == 1")]
+        pub a: Option<u8>,
+        #[deku(cond = "b == 1")]
+        pub b: Option<u8>,
+    }
+
     #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
     pub struct FieldLevelCtxStruct {
         pub a: u8,
@@ -185,6 +194,19 @@ pub mod samples {
         ),
     }
 
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    #[deku(id_type = "u8", ctx = "a: u8, b: u8", ctx_default = "1,2")]
+    pub enum TopLevelCtxEnumDefault {
+        #[deku(id = "1")]
+        VariantA(
+            #[deku(
+                reader = "(|rest|{u8::read(rest,()).map(|(slice,c)|(slice,(a+b+c)))})(rest)",
+                writer = "(|c|{u8::write(&(c-a-b), ())})(field_0)"
+            )]
+            u8,
+        ),
+    }
+
     #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
     #[deku(id_type = "u8")]
     pub enum VariantLevelCtxEnum {
@@ -517,6 +539,26 @@ fn test_top_level_ctx_enum() {
     assert_eq!(ret_write.into_vec(), &test_data[..]);
 }
 
+#[test]
+fn test_top_level_ctx_enum_default() {
+    let expected = samples::TopLevelCtxEnumDefault::VariantA(0x06);
+    let test_data = [0x01_u8, 0x03];
+
+    // Use default
+    let ret_read = samples::TopLevelCtxEnumDefault::try_from(test_data.as_ref()).unwrap();
+    assert_eq!(expected, ret_read);
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(test_data.to_vec(), ret_write);
+
+    // Use context
+    let (rest, ret_read) =
+        samples::TopLevelCtxEnumDefault::read(test_data.view_bits(), (1, 2)).unwrap();
+    assert!(rest.is_empty());
+    assert_eq!(ret_read, samples::TopLevelCtxEnumDefault::VariantA(0x06));
+    let ret_write = ret_read.write((1, 2)).unwrap();
+    assert_eq!(test_data.to_vec(), ret_write.into_vec());
+}
+
 #[test]
 fn test_struct_enum_ctx_id() {
     let test_data = [0x01_u8, 0xff, 0xab];
@@ -535,6 +577,30 @@ fn test_struct_enum_ctx_id() {
     assert_eq!(ret_write, test_data)
 }
 
+#[test]
+fn test_ctx_default_struct() {
+    let expected = samples::TopLevelCtxStructDefault {
+        a: Some(0xff),
+        b: None,
+    };
+
+    let test_data = [0xffu8];
+
+    // Use default
+    let ret_read = samples::TopLevelCtxStructDefault::try_from(test_data.as_ref()).unwrap();
+    assert_eq!(expected, ret_read);
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(ret_write, test_data);
+
+    // Use context
+    let (rest, ret_read) =
+        samples::TopLevelCtxStructDefault::read(test_data.view_bits(), (1, 2)).unwrap();
+    assert!(rest.is_empty());
+    assert_eq!(expected, ret_read);
+    let ret_write = ret_read.write((1, 2)).unwrap();
+    assert_eq!(test_data.to_vec(), ret_write.into_vec());
+}
+
 #[test]
 fn test_compile() {
     let t = trybuild::TestCases::new();