Repository: sharksforarms/deku
Pull request #162 (2021-01-07 19:16:26)

feat: Improve enum `type` and `id` attr
Fixes: #143 


Patch:
diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs
index 51137b5..d2ebccd 100644
--- a/deku-derive/src/lib.rs
+++ b/deku-derive/src/lib.rs
@@ -12,6 +12,93 @@ use syn::{punctuated::Punctuated, spanned::Spanned, AttributeArgs};
 
 mod macros;
 
+#[derive(Debug)]
+enum Id {
+    TokenStream(TokenStream),
+    LitByteStr(syn::LitByteStr),
+}
+
+impl ToString for Id {
+    fn to_string(&self) -> String {
+        self.to_token_stream().to_string()
+    }
+}
+
+impl ToTokens for Id {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        match self {
+            Id::TokenStream(v) => v.to_tokens(tokens),
+            Id::LitByteStr(v) => v.to_tokens(tokens),
+        }
+    }
+}
+
+impl FromMeta for Id {
+    fn from_value(value: &syn::Lit) -> darling::Result<Self> {
+        (match *value {
+            syn::Lit::Str(ref s) => Ok(Id::TokenStream(
+                apply_replacements(s)
+                    .map_err(darling::Error::custom)?
+                    .parse::<TokenStream>()
+                    .expect("could not parse token stream"),
+            )),
+            syn::Lit::ByteStr(ref s) => Ok(Id::LitByteStr(s.clone())),
+            _ => Err(darling::Error::unexpected_lit_type(value)),
+        })
+        .map_err(|e| e.with_span(value))
+    }
+
+    fn from_string(value: &str) -> darling::Result<Self> {
+        Ok(Id::TokenStream(
+            value.parse().expect("Failed to parse tokens"),
+        ))
+    }
+}
+
+#[derive(Debug)]
+struct Num(syn::LitInt);
+
+impl Num {
+    fn new(n: syn::LitInt) -> Self {
+        Self(n)
+    }
+}
+
+impl ToString for Num {
+    fn to_string(&self) -> String {
+        self.0.to_token_stream().to_string()
+    }
+}
+
+impl ToTokens for Num {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        self.0.to_tokens(tokens)
+    }
+}
+
+impl FromMeta for Num {
+    fn from_value(value: &syn::Lit) -> darling::Result<Self> {
+        (match *value {
+            syn::Lit::Str(ref s) => Ok(Num::new(syn::LitInt::new(
+                s.value()
+                    .as_str()
+                    .parse::<usize>()
+                    .map_err(|_| darling::Error::unknown_value(&s.value()))?
+                    .to_string()
+                    .as_str(),
+                s.span(),
+            ))),
+            syn::Lit::Int(ref s) => Ok(Num::new(s.clone())),
+            _ => Err(darling::Error::unexpected_lit_type(value)),
+        })
+        .map_err(|e| e.with_span(value))
+    }
+}
+
+fn cerror(span: proc_macro2::Span, msg: &str) -> TokenStream {
+    syn::Error::new(span, msg).to_compile_error()
+}
+
 /// A post-processed version of `DekuReceiver`
 #[derive(Debug)]
 struct DekuData {
@@ -33,26 +120,21 @@ struct DekuData {
     magic: Option<syn::LitByteStr>,
 
     /// enum only: `id` value
-    id: Option<TokenStream>,
+    id: Option<Id>,
 
     /// enum only: type of the enum `id`
-    id_type: Option<syn::Ident>,
+    id_type: Option<TokenStream>,
 
     /// enum only: bit size of the enum `id`
-    bits: Option<usize>,
+    bits: Option<Num>,
 
     /// enum only: byte size of the enum `id`
-    bytes: Option<usize>,
+    bytes: Option<Num>,
 }
 
 impl DekuData {
     /// Map a `DekuReceiver` to `DekuData`
-    /// It will check if attributes are valid, returns a compiler error if not
     fn from_receiver(receiver: DekuReceiver) -> Result<Self, TokenStream> {
-        // Validate
-        DekuData::validate(&receiver)
-            .map_err(|(span, msg)| syn::Error::new(span, msg).to_compile_error())?;
-
         let data = match receiver.data {
             ast::Data::Struct(fields) => ast::Data::Struct(ast::Fields::new(
                 fields.style,
@@ -71,7 +153,7 @@ impl DekuData {
         };
 
         let ctx = receiver
-            .ctx
+            .ctx?
             .map(|s| s.parse_with(syn::punctuated::Punctuated::parse_terminated))
             .transpose()
             .map_err(|e| e.to_compile_error())?;
@@ -82,7 +164,7 @@ impl DekuData {
             .transpose()
             .map_err(|e| e.to_compile_error())?;
 
-        Ok(Self {
+        let data = Self {
             vis: receiver.vis,
             ident: receiver.ident,
             generics: receiver.generics,
@@ -92,69 +174,76 @@ impl DekuData {
             ctx_default,
             magic: receiver.magic,
             id: receiver.id,
-            id_type: receiver.id_type,
+            id_type: receiver.id_type?,
             bits: receiver.bits,
             bytes: receiver.bytes,
-        })
-    }
+        };
 
-    fn validate(receiver: &DekuReceiver) -> Result<(), (proc_macro2::Span, &str)> {
-        /*
-        FIXME: Issue with `span`, see `FieldData::validate`.
-        */
+        DekuData::validate(&data)?;
+
+        Ok(data)
+    }
 
+    fn validate(data: &DekuData) -> Result<(), TokenStream> {
         // Validate `ctx_default`
-        if receiver.ctx_default.is_some() && receiver.ctx.is_none() {
-            return Err((
-                receiver.ctx_default.span(),
+        if data.ctx_default.is_some() && data.ctx.is_none() {
+            // FIXME: Use `Span::join` once out of nightly
+            return Err(cerror(
+                data.ctx_default.span(),
                 "`ctx_default` must be used with `ctx`",
             ));
         }
 
-        match receiver.data {
+        match data.data {
             ast::Data::Struct(_) => {
                 // Validate id_* attributes are being used on an enum
-                if receiver.id_type.is_some() {
-                    Err((receiver.id_type.span(), "`type` only supported on enum"))
-                } else if receiver.id.is_some() {
-                    Err((receiver.id.span(), "`id` only supported on enum"))
-                } else if receiver.bytes.is_some() {
-                    Err((receiver.bytes.span(), "`bytes` only supported on enum"))
-                } else if receiver.bits.is_some() {
-                    Err((receiver.bits.span(), "`bits` only supported on enum"))
+                if data.id_type.is_some() {
+                    Err(cerror(data.id_type.span(), "`type` only supported on enum"))
+                } else if data.id.is_some() {
+                    Err(cerror(data.id.span(), "`id` only supported on enum"))
+                } else if data.bytes.is_some() {
+                    Err(cerror(data.bytes.span(), "`bytes` only supported on enum"))
+                } else if data.bits.is_some() {
+                    Err(cerror(data.bits.span(), "`bits` only supported on enum"))
                 } else {
                     Ok(())
                 }
             }
             ast::Data::Enum(_) => {
                 // Validate `type` or `id` is specified
-                if receiver.id_type.is_none() && receiver.id.is_none() {
-                    return Err((
-                        receiver.ident.span(),
+                if data.id_type.is_none() && data.id.is_none() {
+                    return Err(cerror(
+                        data.ident.span(),
                         "`type` or `id` must be specified on enum",
                     ));
                 }
 
                 // Validate either `type` or `id` is specified
-                if receiver.id_type.is_some() && receiver.id.is_some() {
-                    return Err((
-                        receiver.ident.span(),
+                if data.id_type.is_some() && data.id.is_some() {
+                    return Err(cerror(
+                        data.ident.span(),
                         "conflicting: both `type` and `id` specified on enum",
                     ));
                 }
 
                 // Validate `id_*` used correctly
-                if receiver.id.is_some() && receiver.bits.is_some() {
-                    return Err((receiver.ident.span(), "error: cannot use `bits` with `id`"));
+                if data.id.is_some() && data.bits.is_some() {
+                    return Err(cerror(
+                        data.ident.span(),
+                        "error: cannot use `bits` with `id`",
+                    ));
                 }
-                if receiver.id.is_some() && receiver.bytes.is_some() {
-                    return Err((receiver.ident.span(), "error: cannot use `bytes` with `id`"));
+                if data.id.is_some() && data.bytes.is_some() {
+                    return Err(cerror(
+                        data.ident.span(),
+                        "error: cannot use `bytes` with `id`",
+                    ));
                 }
 
                 // Validate either `bits` or `bytes` is specified
-                if receiver.bits.is_some() && receiver.bytes.is_some() {
-                    return Err((
-                        receiver.bits.span(),
+                if data.bits.is_some() && data.bytes.is_some() {
+                    return Err(cerror(
+                        data.bits.span(),
                         "conflicting: both `bits` and `bytes` specified on enum",
                     ));
                 }
@@ -197,10 +286,10 @@ struct FieldData {
     endian: Option<syn::LitStr>,
 
     /// field bit size
-    bits: Option<usize>,
+    bits: Option<Num>,
 
     /// field byte size
-    bytes: Option<usize>,
+    bytes: Option<Num>,
 
     /// tokens providing the length of the container
     count: Option<TokenStream>,
@@ -236,7 +325,7 @@ struct FieldData {
     temp: bool,
 
     /// default value code when used with skip or cond
-    default: TokenStream,
+    default: Option<TokenStream>,
 
     /// condition to parse field
     cond: Option<TokenStream>,
@@ -244,90 +333,78 @@ struct FieldData {
 
 impl FieldData {
     fn from_receiver(receiver: DekuFieldReceiver) -> Result<Self, TokenStream> {
-        FieldData::validate(&receiver)
-            .map_err(|(span, msg)| syn::Error::new(span, msg).to_compile_error())?;
-
-        let default = receiver.default.unwrap_or(quote! { Default::default() });
-
         let ctx = receiver
-            .ctx
+            .ctx?
             .map(|s| s.parse_with(Punctuated::parse_terminated))
             .transpose()
             .map_err(|e| e.to_compile_error())?;
 
-        Ok(Self {
+        let data = Self {
             ident: receiver.ident,
             ty: receiver.ty,
             endian: receiver.endian,
             bits: receiver.bits,
             bytes: receiver.bytes,
-            count: receiver.count,
-            bits_read: receiver.bits_read,
-            bytes_read: receiver.bytes_read,
-            until: receiver.until,
-            map: receiver.map,
+            count: receiver.count?,
+            bits_read: receiver.bits_read?,
+            bytes_read: receiver.bytes_read?,
+            until: receiver.until?,
+            map: receiver.map?,
             ctx,
-            update: receiver.update,
-            reader: receiver.reader,
-            writer: receiver.writer,
+            update: receiver.update?,
+            reader: receiver.reader?,
+            writer: receiver.writer?,
             skip: receiver.skip,
             temp: receiver.temp,
-            default,
-            cond: receiver.cond,
-        })
+            default: receiver.default?,
+            cond: receiver.cond?,
+        };
+
+        FieldData::validate(&data)?;
+
+        let default = data.default.or_else(|| Some(quote! { Default::default() }));
+
+        Ok(Self { default, ..data })
     }
 
-    fn validate(receiver: &DekuFieldReceiver) -> Result<(), (proc_macro2::Span, &str)> {
+    fn validate(data: &FieldData) -> Result<(), TokenStream> {
         // Validate either `read_bytes` or `read_bits` is specified
-        if receiver.bits_read.is_some() && receiver.bytes_read.is_some() {
-            return Err((
-                receiver.bits_read.span(),
+        if data.bits_read.is_some() && data.bytes_read.is_some() {
+            return Err(cerror(
+                data.bits_read.span(),
                 "conflicting: both `bits_read` and `bytes_read` specified on field",
             ));
         }
 
         // Validate either `count` or `bits_read`/`bytes_read` is specified
-        if receiver.count.is_some()
-            && (receiver.bits_read.is_some() || receiver.bytes_read.is_some())
-        {
-            if receiver.bits_read.is_some() {
-                return Err((
-                    receiver.count.span(),
+        if data.count.is_some() && (data.bits_read.is_some() || data.bytes_read.is_some()) {
+            if data.bits_read.is_some() {
+                return Err(cerror(
+                    data.count.span(),
                     "conflicting: both `count` and `bits_read` specified on field",
                 ));
             } else {
-                return Err((
-                    receiver.count.span(),
+                return Err(cerror(
+                    data.count.span(),
                     "conflicting: both `count` and `bytes_read` specified on field",
                 ));
             }
         }
 
         // Validate either `bits` or `bytes` is specified
-        if receiver.bits.is_some() && receiver.bytes.is_some() {
-            /*
-            FIXME: `receiver.bits.span()` will return `call_site`, that's unexpected. The compiler
-               error gives:    `#[derive(DekuRead)]`
-                                         ^^^^^^^^
-               instead of `#[deku(bits = "", bytes = "")]`
-                                  ^^^^^^^^^^^^^^^^^^^^^
-               A possible reason might be that the `span` was discarded by `darling`(because inner
-               type don't have a `span`).
-               Maybe we should parse it manually.
-            */
-
-            // FIXME: Ideally we need to use `Span::join` to encompass `bits` and `bytes` together.
-            return Err((
-                receiver.bits.span(),
+        if data.bits.is_some() && data.bytes.is_some() {
+            // FIXME: Use `Span::join` once out of nightly
+            return Err(cerror(
+                data.bits.span(),
                 "conflicting: both `bits` and `bytes` specified on field",
             ));
         }
 
         // Validate usage of `default` attribute
-        if receiver.default.is_some() && (!receiver.skip && receiver.cond.is_none()) {
-            // FIXME: Same issue with `receiver.bits.span()` see above.
-            return Err((
-                receiver.default.span(),
+        if data.default.is_some() && (!data.skip && data.cond.is_none()) {
+            // FIXME: Use `Span::join` once out of nightly
+            return Err(cerror(
+                data.default.span(),
                 "`default` attribute cannot be used here",
             ));
         }
@@ -349,6 +426,7 @@ impl FieldData {
 struct VariantData {
     ident: syn::Ident,
     fields: ast::Fields<FieldData>,
+    discriminant: Option<syn::Expr>,
 
     /// custom variant reader code
     reader: Option<TokenStream>,
@@ -357,7 +435,7 @@ struct VariantData {
     writer: Option<TokenStream>,
 
     /// variant `id` value
-    id: Option<TokenStream>,
+    id: Option<Id>,
 
     /// variant `id_pat` value
     id_pat: Option<TokenStream>,
@@ -365,9 +443,6 @@ struct VariantData {
 
 impl VariantData {
     fn from_receiver(receiver: DekuVariantReceiver) -> Result<Self, TokenStream> {
-        VariantData::validate(&receiver)
-            .map_err(|(span, msg)| syn::Error::new(span, msg).to_compile_error())?;
-
         let fields = ast::Fields::new(
             receiver.fields.style,
             receiver
@@ -378,31 +453,34 @@ impl VariantData {
                 .collect::<Result<Vec<_>, _>>()?,
         );
 
-        Ok(Self {
+        let ret = Self {
             ident: receiver.ident,
             fields,
-            reader: receiver.reader,
-            writer: receiver.writer,
+            discriminant: receiver.discriminant,
+            reader: receiver.reader?,
+            writer: receiver.writer?,
             id: receiver.id,
-            id_pat: receiver.id_pat,
-        })
+            id_pat: receiver.id_pat?,
+        };
+
+        VariantData::validate(&ret)?;
+
+        Ok(ret)
     }
 
-    fn validate(receiver: &DekuVariantReceiver) -> Result<(), (proc_macro2::Span, &str)> {
-        if receiver.id.is_some() && receiver.id_pat.is_some() {
-            /*
-            FIXME: Issue with `span`, see `FieldData::validate`.
-            */
-            return Err((
-                receiver.id.span(),
+    fn validate(data: &VariantData) -> Result<(), TokenStream> {
+        if data.id.is_some() && data.id_pat.is_some() {
+            // FIXME: Use `Span::join` once out of nightly
+            return Err(cerror(
+                data.id.span(),
                 "conflicting: both `id` and `id_pat` specified on variant",
             ));
         }
 
-        if let Some(id) = &receiver.id {
+        if let Some(id) = &data.id {
             if id.to_string() == "_" {
-                return Err((
-                    receiver.ident.span(),
+                return Err(cerror(
+                    data.ident.span(),
                     "error: `id_pat` should be used for `_`",
                 ));
             }
@@ -429,8 +507,8 @@ struct DekuReceiver {
     // TODO: The type of it should be
     //       `syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>`
     //       https://github.com/TedDriggs/darling/pull/98
-    #[darling(default, map = "apply_replacements")]
-    ctx: Option<syn::LitStr>,
+    #[darling(default = "default_res_opt", map = "map_option_litstr")]
+    ctx: Result<Option<syn::LitStr>, ReplacementError>,
 
     /// default context passed to the field
     // TODO: The type of it should be
@@ -444,50 +522,69 @@ struct DekuReceiver {
     magic: Option<syn::LitByteStr>,
 
     /// enum only: `id` value
-    #[darling(default, map = "option_as_tokenstream")]
-    id: Option<TokenStream>,
+    #[darling(default)]
+    id: Option<Id>,
 
     /// enum only: type of the enum `id`
-    #[darling(rename = "type", default)]
-    id_type: Option<syn::Ident>,
+    #[darling(
+        rename = "type",
+        default = "default_res_opt",
+        map = "map_litstr_as_tokenstream"
+    )]
+    id_type: Result<Option<TokenStream>, ReplacementError>,
 
     /// enum only: bit size of the enum `id`
     #[darling(default)]
-    bits: Option<usize>,
+    bits: Option<Num>,
 
     /// enum only: byte size of the enum `id`
     #[darling(default)]
-    bytes: Option<usize>,
+    bytes: Option<Num>,
 }
 
-fn apply_replacements(input: Option<syn::LitStr>) -> Option<syn::LitStr> {
-    input.map(|v| {
-        if v.value().contains("__deku_") {
-            panic!(
-                "error: attribute cannot contain `__deku_` these are internal variables. Please use the `deku::` instead."
-            );
-        }
+type ReplacementError = TokenStream;
+
+fn apply_replacements(input: &syn::LitStr) -> Result<syn::LitStr, ReplacementError> {
+    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());
+    }
 
-        let v_str = v
-            .value()
-            .replace("deku::input", "__deku_input") // part of the public API `from_bytes`
-            .replace("deku::input_bits", "__deku_input_bits") // part of the public API `read`
-            .replace("deku::output", "__deku_output") // part of the public API `write`
-            .replace("deku::rest", "__deku_rest")
-            .replace("deku::bit_offset", "__deku_bit_offset")
-            .replace("deku::byte_offset", "__deku_byte_offset");
+    let input_str = input
+        .value()
+        .replace("deku::input", "__deku_input") // part of the public API `from_bytes`
+        .replace("deku::input_bits", "__deku_input_bits") // part of the public API `read`
+        .replace("deku::output", "__deku_output") // part of the public API `write`
+        .replace("deku::rest", "__deku_rest")
+        .replace("deku::bit_offset", "__deku_bit_offset")
+        .replace("deku::byte_offset", "__deku_byte_offset");
 
-        syn::LitStr::new(&v_str, v.span())
+    Ok(syn::LitStr::new(&input_str, input.span()))
+}
+
+/// Calls apply replacements on Option<LitStr>
+fn map_option_litstr(input: Option<syn::LitStr>) -> Result<Option<syn::LitStr>, ReplacementError> {
+    Ok(match input {
+        Some(v) => Some(apply_replacements(&v)?),
+        None => None,
     })
 }
 
 /// Parse a TokenStream from an Option<LitStr>
 /// Also replaces any namespaced variables to internal variables found in `input`
-fn option_as_tokenstream(input: Option<syn::LitStr>) -> Option<TokenStream> {
-    input.map(|v| {
-        let v = apply_replacements(Some(v)).unwrap();
-        v.parse::<TokenStream>()
-            .expect("could not parse token stream")
+fn map_litstr_as_tokenstream(
+    input: Option<syn::LitStr>,
+) -> Result<Option<TokenStream>, ReplacementError> {
+    Ok(match input {
+        Some(v) => {
+            let v = apply_replacements(&v)?;
+            Some(
+                v.parse::<TokenStream>()
+                    .expect("could not parse token stream"),
+            )
+        }
+        None => None,
     })
 }
 
@@ -509,6 +606,11 @@ fn gen_field_ident<T: ToString>(ident: Option<T>, index: usize, prefix: bool) ->
     field_name.parse().unwrap()
 }
 
+/// Provided default when a attribute is not available
+fn default_res_opt<T, E>() -> Result<Option<T>, E> {
+    Ok(None)
+}
+
 /// Receiver for the field-level attributes inside a struct/enum variant
 #[derive(Debug, FromField)]
 #[darling(attributes(deku))]
@@ -522,50 +624,50 @@ struct DekuFieldReceiver {
 
     /// field bit size
     #[darling(default)]
-    bits: Option<usize>,
+    bits: Option<Num>,
 
     /// field byte size
     #[darling(default)]
-    bytes: Option<usize>,
+    bytes: Option<Num>,
 
     /// tokens providing the length of the container
-    #[darling(default, map = "option_as_tokenstream")]
-    count: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    count: Result<Option<TokenStream>, ReplacementError>,
 
     /// tokens providing the number of bits for the length of the container
-    #[darling(default, map = "option_as_tokenstream")]
-    bits_read: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    bits_read: Result<Option<TokenStream>, ReplacementError>,
 
     /// tokens providing the number of bytes for the length of the container
-    #[darling(default, map = "option_as_tokenstream")]
-    bytes_read: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    bytes_read: Result<Option<TokenStream>, ReplacementError>,
 
     /// a predicate to decide when to stop reading elements into the container
-    #[darling(default, map = "option_as_tokenstream")]
-    until: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    until: Result<Option<TokenStream>, ReplacementError>,
 
     /// apply a function to the field after it's read
-    #[darling(default, map = "option_as_tokenstream")]
-    map: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    map: Result<Option<TokenStream>, ReplacementError>,
 
     /// context passed to the field.
     /// A comma separated argument list.
     // TODO: The type of it should be `Punctuated<Expr, Comma>`
     //       https://github.com/TedDriggs/darling/pull/98
-    #[darling(default, map = "apply_replacements")]
-    ctx: Option<syn::LitStr>,
+    #[darling(default = "default_res_opt", map = "map_option_litstr")]
+    ctx: Result<Option<syn::LitStr>, ReplacementError>,
 
     /// map field when updating struct
-    #[darling(default, map = "option_as_tokenstream")]
-    update: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    update: Result<Option<TokenStream>, ReplacementError>,
 
     /// custom field reader code
-    #[darling(default, map = "option_as_tokenstream")]
-    reader: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    reader: Result<Option<TokenStream>, ReplacementError>,
 
     /// custom field writer code
-    #[darling(default, map = "option_as_tokenstream")]
-    writer: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    writer: Result<Option<TokenStream>, ReplacementError>,
 
     /// skip field reading/writing
     #[darling(default)]
@@ -576,12 +678,12 @@ struct DekuFieldReceiver {
     temp: bool,
 
     /// default value code when used with skip
-    #[darling(default, map = "option_as_tokenstream")]
-    default: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    default: Result<Option<TokenStream>, ReplacementError>,
 
     /// condition to parse field
-    #[darling(default, map = "option_as_tokenstream")]
-    cond: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    cond: Result<Option<TokenStream>, ReplacementError>,
 }
 
 /// Receiver for the variant-level attributes inside a enum
@@ -590,22 +692,23 @@ struct DekuFieldReceiver {
 struct DekuVariantReceiver {
     ident: syn::Ident,
     fields: ast::Fields<DekuFieldReceiver>,
+    discriminant: Option<syn::Expr>,
 
     /// custom variant reader code
-    #[darling(default, map = "option_as_tokenstream")]
-    reader: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    reader: Result<Option<TokenStream>, ReplacementError>,
 
     /// custom variant reader code
-    #[darling(default, map = "option_as_tokenstream")]
-    writer: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    writer: Result<Option<TokenStream>, ReplacementError>,
 
     /// variant `id` value
-    #[darling(default, map = "option_as_tokenstream")]
-    id: Option<TokenStream>,
+    #[darling(default)]
+    id: Option<Id>,
 
     /// variant `id_pat` value
-    #[darling(default, map = "option_as_tokenstream")]
-    id_pat: Option<TokenStream>,
+    #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
+    id_pat: Result<Option<TokenStream>, ReplacementError>,
 }
 
 /// Entry function for `DekuRead` proc-macro
diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs
index 75c726b..1436f19 100644
--- a/deku-derive/src/macros/deku_read.rs
+++ b/deku-derive/src/macros/deku_read.rs
@@ -1,9 +1,15 @@
-use crate::macros::{
-    gen_ctx_types_and_arg, gen_field_args, gen_id_args, gen_internal_field_ident,
-    gen_internal_field_idents, token_contains_string, wrap_default_ctx,
+use crate::{
+    macros::{
+        gen_ctx_types_and_arg, gen_field_args, gen_id_args, gen_internal_field_ident,
+        gen_internal_field_idents, token_contains_string, wrap_default_ctx,
+    },
+    Id,
 };
 use crate::{DekuData, FieldData};
-use darling::ast::{Data, Fields};
+use darling::{
+    ast::{Data, Fields},
+    ToTokens,
+};
 use proc_macro2::TokenStream;
 use quote::quote;
 
@@ -147,7 +153,11 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
     let id = input.id.as_ref();
     let id_type = input.id_type.as_ref();
 
-    let id_args = gen_id_args(input.endian.as_ref(), input.bits, input.bytes)?;
+    let id_args = gen_id_args(
+        input.endian.as_ref(),
+        input.bits.as_ref(),
+        input.bytes.as_ref(),
+    )?;
 
     let magic_read = emit_magic_read(input)?;
 
@@ -155,6 +165,8 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
     let mut pre_match_tokens = vec![];
     let mut variant_matches = vec![];
 
+    let has_discriminant = variants.iter().any(|v| v.discriminant.is_some());
+
     for variant in variants {
         // check if the first field has an ident, if not, it's a unnamed struct
         let variant_is_named = variant
@@ -165,14 +177,17 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
             .is_some();
 
         let (consume_id, variant_id) = if let Some(variant_id) = &variant.id {
-            (true, variant_id.clone())
+            match variant_id {
+                Id::TokenStream(v) => (true, quote! {&#v}.into_token_stream()),
+                Id::LitByteStr(v) => (true, v.into_token_stream()),
+            }
         } else if let Some(variant_id_pat) = &variant.id_pat {
             (false, variant_id_pat.clone())
-        } else if variant.fields.style.is_unit() {
+        } else if has_discriminant {
             let ident = &variant.ident;
             let internal_ident = gen_internal_field_ident(&quote!(#ident));
             pre_match_tokens.push(quote! {
-                let #internal_ident = #id_type::try_from(Self::#ident as isize)?;
+                let #internal_ident = <#id_type>::try_from(Self::#ident as isize)?;
             });
             (true, quote! { _ if variant_id == #internal_ident })
         } else {
@@ -251,7 +266,7 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
         }
     } else if id_type.is_some() {
         quote! {
-            let (new_rest, variant_id) = #id_type::read(__deku_rest, (#id_args))?;
+            let (new_rest, variant_id) = <#id_type>::read(__deku_rest, (#id_args))?;
         }
     } else {
         // either `id` or `type` needs to be specified
@@ -263,7 +278,7 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
 
         #(#pre_match_tokens)*
 
-        let value = match variant_id {
+        let value = match &variant_id {
             #(#variant_matches),*
         };
     };
@@ -433,7 +448,7 @@ fn emit_field_read(
         &f.bytes_read,
         &f.until,
         &f.cond,
-        &Some(f.default.clone()),
+        &f.default,
         &f.map,
         &f.reader,
         &f.ctx.as_ref().map(|v| quote!(#v)),
@@ -456,7 +471,12 @@ fn emit_field_read(
     let field_read_func = if field_reader.is_some() {
         quote! { #field_reader }
     } else {
-        let read_args = gen_field_args(field_endian, f.bits, f.bytes, f.ctx.as_ref())?;
+        let read_args = gen_field_args(
+            field_endian,
+            f.bits.as_ref(),
+            f.bytes.as_ref(),
+            f.ctx.as_ref(),
+        )?;
 
         // The container limiting options are special, we need to generate `(limit, (other, ..))` for them.
         // These have a problem where when it isn't a copy type, the field will be moved.
diff --git a/deku-derive/src/macros/deku_write.rs b/deku-derive/src/macros/deku_write.rs
index c01ab19..8c52abd 100644
--- a/deku-derive/src/macros/deku_write.rs
+++ b/deku-derive/src/macros/deku_write.rs
@@ -1,6 +1,9 @@
-use crate::macros::{
-    gen_ctx_types_and_arg, gen_field_args, gen_id_args, gen_struct_destruction,
-    token_contains_string, wrap_default_ctx,
+use crate::{
+    macros::{
+        gen_ctx_types_and_arg, gen_field_args, gen_id_args, gen_struct_destruction,
+        token_contains_string, wrap_default_ctx,
+    },
+    Id,
 };
 use crate::{DekuData, FieldData};
 use darling::ast::{Data, Fields};
@@ -158,11 +161,17 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
     let id = input.id.as_ref();
     let id_type = input.id_type.as_ref();
 
-    let id_args = gen_id_args(input.endian.as_ref(), input.bits, input.bytes)?;
+    let id_args = gen_id_args(
+        input.endian.as_ref(),
+        input.bits.as_ref(),
+        input.bytes.as_ref(),
+    )?;
 
     let mut variant_writes = vec![];
     let mut variant_updates = vec![];
 
+    let has_discriminant = variants.iter().any(|v| v.discriminant.is_some());
+
     for variant in variants {
         // check if the first field has an ident, if not, it's a unnamed struct
         let variant_is_named = variant
@@ -191,13 +200,23 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
             }
         } else if id_type.is_some() {
             if let Some(variant_id) = &variant.id {
-                quote! {
-                    let mut variant_id: #id_type = #variant_id;
-                    variant_id.write(__deku_output, (#id_args))?;
+                match variant_id {
+                    Id::TokenStream(v) => {
+                        quote! {
+                            let mut variant_id: #id_type = #v;
+                            variant_id.write(__deku_output, (#id_args))?;
+                        }
+                    }
+                    Id::LitByteStr(v) => {
+                        quote! {
+                            let mut variant_id: #id_type = *#v;
+                            variant_id.write(__deku_output, (#id_args))?;
+                        }
+                    }
                 }
             } else if variant.id_pat.is_some() {
                 quote! {}
-            } else if variant.fields.style.is_unit() {
+            } else if has_discriminant {
                 quote! {
                     let mut variant_id: #id_type = Self::#variant_ident as #id_type;
                     variant_id.write(__deku_output, (#id_args))?;
@@ -458,7 +477,12 @@ fn emit_field_write(
     let field_write_func = if field_writer.is_some() {
         quote! { #field_writer }
     } else {
-        let write_args = gen_field_args(field_endian, f.bits, f.bytes, f.ctx.as_ref())?;
+        let write_args = gen_field_args(
+            field_endian,
+            f.bits.as_ref(),
+            f.bytes.as_ref(),
+            f.ctx.as_ref(),
+        )?;
 
         quote! { #object_prefix #field_ident.write(__deku_output, (#write_args)) }
     };
diff --git a/deku-derive/src/macros/mod.rs b/deku-derive/src/macros/mod.rs
index b146ef5..1cbbd53 100644
--- a/deku-derive/src/macros/mod.rs
+++ b/deku-derive/src/macros/mod.rs
@@ -1,3 +1,4 @@
+use crate::Num;
 use proc_macro2::TokenStream;
 use quote::{quote, ToTokens};
 use syn::punctuated::Punctuated;
@@ -163,8 +164,8 @@ fn gen_ctx_types_and_arg(
 /// `#deku(endian = "big", bits = "1")` -> `Endian::Big, Size::Bits(1)`
 fn gen_id_args(
     endian: Option<&syn::LitStr>,
-    bits: Option<usize>,
-    bytes: Option<usize>,
+    bits: Option<&Num>,
+    bytes: Option<&Num>,
 ) -> syn::Result<TokenStream> {
     let endian = endian.map(gen_endian_from_str).transpose()?;
     let bits = bits.map(|n| quote! {deku::ctx::Size::Bits(#n)});
@@ -187,8 +188,8 @@ fn gen_id_args(
 /// `#deku(endian = "big", bits = "1", ctx = "a")` -> `Endian::Big, Size::Bits(1), a`
 fn gen_field_args(
     endian: Option<&syn::LitStr>,
-    bits: Option<usize>,
-    bytes: Option<usize>,
+    bits: Option<&Num>,
+    bytes: Option<&Num>,
     ctx: Option<&Punctuated<syn::Expr, syn::token::Comma>>,
 ) -> syn::Result<TokenStream> {
     let endian = endian.map(gen_endian_from_str).transpose()?;
diff --git a/examples/enums.rs b/examples/enums.rs
index 4664fc1..4c6ae1d 100644
--- a/examples/enums.rs
+++ b/examples/enums.rs
@@ -19,7 +19,7 @@ enum DekuTest {
     },
     #[deku(id_pat = "4..=6")]
     Var5 { id: u8 },
-    #[deku(id_pat = "id if id > 6")]
+    #[deku(id_pat = "&id if id > 6")]
     Var6 { id: u8 },
 }
 


Test patch:
diff --git a/tests/test_compile/cases/bits_bytes_conflict.stderr b/tests/test_compile/cases/bits_bytes_conflict.stderr
index cbaa234..d8c433e 100644
--- a/tests/test_compile/cases/bits_bytes_conflict.stderr
+++ b/tests/test_compile/cases/bits_bytes_conflict.stderr
@@ -1,31 +1,23 @@
 error: conflicting: both `bits` and `bytes` specified on enum
- --> $DIR/bits_bytes_conflict.rs:3:10
+ --> $DIR/bits_bytes_conflict.rs:4:28
   |
-3 | #[derive(DekuRead)]
-  |          ^^^^^^^^
-  |
-  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
+4 | #[deku(type = "u8", bits = "1", bytes = "2")]
+  |                            ^^^
 
 error: conflicting: both `bits` and `bytes` specified on field
- --> $DIR/bits_bytes_conflict.rs:7:10
-  |
-7 | #[derive(DekuRead)]
-  |          ^^^^^^^^
-  |
-  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
+  --> $DIR/bits_bytes_conflict.rs:10:21
+   |
+10 |     A(#[deku(bits = "1", bytes = "2")] u8),
+   |                     ^^^
 
 error: conflicting: both `bits` and `bytes` specified on field
-  --> $DIR/bits_bytes_conflict.rs:17:10
-   |
-17 | #[derive(DekuRead)]
-   |          ^^^^^^^^
+  --> $DIR/bits_bytes_conflict.rs:19:19
    |
-   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
+19 |     #[deku(bits = "5", bytes = "6")]
+   |                   ^^^
 
 error: conflicting: both `bits` and `bytes` specified on field
-  --> $DIR/bits_bytes_conflict.rs:23:10
-   |
-23 | #[derive(DekuRead)]
-   |          ^^^^^^^^
+  --> $DIR/bits_bytes_conflict.rs:24:28
    |
-   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
+24 | struct Test4(#[deku(bits = "7", bytes = "8")] u8);
+   |                            ^^^
diff --git a/tests/test_compile/cases/enum_validation.stderr b/tests/test_compile/cases/enum_validation.stderr
index 05ea52f..e097cb3 100644
--- a/tests/test_compile/cases/enum_validation.stderr
+++ b/tests/test_compile/cases/enum_validation.stderr
@@ -23,20 +23,16 @@ error: `type` only supported on enum
    |               ^^^^
 
 error: `bits` only supported on enum
-  --> $DIR/enum_validation.rs:27:10
+  --> $DIR/enum_validation.rs:28:15
    |
-27 | #[derive(DekuRead)]
-   |          ^^^^^^^^
-   |
-   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
+28 | #[deku(bits = "1")]
+   |               ^^^
 
 error: `bits` only supported on enum
-  --> $DIR/enum_validation.rs:34:10
-   |
-34 | #[derive(DekuRead)]
-   |          ^^^^^^^^
+  --> $DIR/enum_validation.rs:35:15
    |
-   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
+35 | #[deku(bits = "1")]
+   |               ^^^
 
 error: `id` only supported on enum
   --> $DIR/enum_validation.rs:42:13
diff --git a/tests/test_compile/cases/internal_variables.stderr b/tests/test_compile/cases/internal_variables.stderr
index 44a6360..3db7dc2 100644
--- a/tests/test_compile/cases/internal_variables.stderr
+++ b/tests/test_compile/cases/internal_variables.stderr
@@ -1,15 +1,5 @@
-error: proc-macro derive panicked
-  --> $DIR/internal_variables.rs:89:20
+error: Unexpected meta-item format `attribute cannot contain `__deku_` these are internal variables. Please use the `deku::` instead.`
+  --> $DIR/internal_variables.rs:92:19
    |
-89 | #[derive(DekuRead, DekuWrite)]
-   |                    ^^^^^^^^^
-   |
-   = help: message: error: attribute cannot contain `__deku_` these are internal variables. Please use the `deku::` instead.
-
-error: proc-macro derive panicked
-  --> $DIR/internal_variables.rs:89:10
-   |
-89 | #[derive(DekuRead, DekuWrite)]
-   |          ^^^^^^^^
-   |
-   = help: message: error: attribute cannot contain `__deku_` these are internal variables. Please use the `deku::` instead.
+92 |     #[deku(cond = "__deku_bit_offset == *field_a as usize")]
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/test_enum.rs b/tests/test_enum.rs
index 65e6057..f652f29 100644
--- a/tests/test_enum.rs
+++ b/tests/test_enum.rs
@@ -85,3 +85,23 @@ fn test_enum_discriminant(input: &[u8], expected: TestEnumDiscriminant) {
     let ret_write: Vec<u8> = ret_read.try_into().unwrap();
     assert_eq!(input.to_vec(), ret_write);
 }
+
+#[test]
+fn test_enum_array_type() {
+    #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+    #[deku(type = "[u8; 3]")]
+    enum TestEnumArray {
+        #[deku(id = b"123")]
+        VarA,
+        #[deku(id = "[1,1,1]")]
+        VarB,
+    }
+
+    let input = b"123".as_ref();
+
+    let ret_read = TestEnumArray::try_from(input).unwrap();
+    assert_eq!(TestEnumArray::VarA, ret_read);
+
+    let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+    assert_eq!(input.to_vec(), ret_write);
+}