diff --git a/CHANGELOG.md b/CHANGELOG.md index f005493..e1750c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -# v0.5.1 -- Make `write_value` field in `#[protocol(tag(...))]` only required when deriving `ProtocolWrite` +# v0.6.0 +- Allow multiple attributes in a single `#[protocol(...)]` +- Require unquoted expressions in attributes - Impose `non_exhaustive` on `Error` # v0.5.0 - Split `Protocol` into `ProtocolRead` and `ProtocolWrite` diff --git a/bin-proto-derive/Cargo.toml b/bin-proto-derive/Cargo.toml index 399919a..1b06621 100644 --- a/bin-proto-derive/Cargo.toml +++ b/bin-proto-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bin-proto-derive" -version = "0.5.0" +version = "0.6.0" authors = [ "Wojciech Graj ", "Dylan McKay " @@ -18,6 +18,6 @@ keywords = ["protocol", "binary", "bit", "codec", "serde"] proc-macro = true [dependencies] -syn = { version = "1.0.109", features = ["full"] } -quote = "1.0.36" -proc-macro2 = "1.0.82" +syn = { version = "2.0.87", features = ["full"] } +quote = "1.0.37" +proc-macro2 = "1.0.89" diff --git a/bin-proto-derive/src/attr.rs b/bin-proto-derive/src/attr.rs index 5a54b47..6e7a19c 100644 --- a/bin-proto-derive/src/attr.rs +++ b/bin-proto-derive/src/attr.rs @@ -1,14 +1,14 @@ use proc_macro2::{Span, TokenStream}; -use syn::{parse::Parser, punctuated::Punctuated, spanned::Spanned, token::Add, Error, Result}; +use syn::{punctuated::Punctuated, spanned::Spanned, token::Plus, Error, Result}; #[derive(Default)] pub struct Attrs { pub discriminant_type: Option, pub discriminant: Option, pub ctx: Option, - pub ctx_bounds: Option>, + pub ctx_bounds: Option>, pub write_value: Option, - pub bits: Option, + pub bits: Option, pub flexible_array_member: bool, pub tag: Option, } @@ -148,138 +148,56 @@ impl Attrs { impl TryFrom<&[syn::Attribute]> for Attrs { type Error = syn::Error; - #[allow(clippy::too_many_lines)] - fn try_from(value: &[syn::Attribute]) -> Result { - let meta_lists = value.iter().filter_map(|attr| match attr.parse_meta() { - Ok(syn::Meta::List(meta_list)) => { - if meta_list.path.get_ident() - == Some(&syn::Ident::new("protocol", Span::call_site())) - { - Some(meta_list) - } else { - None - } - } - _ => None, - }); - + fn try_from(attrs: &[syn::Attribute]) -> Result { let mut attribs = Attrs::default(); - for meta_list in meta_lists { - for meta in &meta_list.nested { - match meta { - syn::NestedMeta::Meta(syn::Meta::NameValue(name_value)) => match name_value - .path - .get_ident() - { - Some(ident) => match ident.to_string().as_str() { - "discriminant_type" => { - attribs.discriminant_type = - Some(meta_name_value_to_parse(name_value)?); - } - "discriminant" => { - attribs.discriminant = Some(meta_name_value_to_parse(name_value)?); - } - "ctx" => attribs.ctx = Some(meta_name_value_to_parse(name_value)?), - "ctx_bounds" => { - attribs.ctx_bounds = - Some(meta_name_value_to_punctuated(name_value)?); - } - "bits" => attribs.bits = Some(meta_name_value_to_u32(name_value)?), - "write_value" => { - attribs.write_value = Some(meta_name_value_to_parse(name_value)?); - } - "tag" => { - attribs.tag = - Some(Tag::External(meta_name_value_to_parse(name_value)?)); - } - _ => return Err(Error::new(ident.span(), "unrecognised attribute")), - }, - None => return Err(Error::new(meta.span(), "failed to parse attribute")), - }, - syn::NestedMeta::Meta(syn::Meta::Path(path)) => match path.get_ident() { - Some(ident) => match ident.to_string().as_str() { - "flexible_array_member" => attribs.flexible_array_member = true, - _ => return Err(Error::new(ident.span(), "unrecognised attribute")), - }, - None => { - return Err(Error::new( - path.get_ident().span(), - "expected identifier 1234", - )); - } - }, - syn::NestedMeta::Meta(syn::Meta::List(list)) => { - let mut typ = None; - let mut write_value = None; - for nested in &list.nested { - let name_value = - if let syn::NestedMeta::Meta(syn::Meta::NameValue(name_value)) = - nested - { - name_value - } else { - return Err(Error::new(list.span(), "unrecognized attribute")); - }; - let ident = if let Some(ident) = name_value.path.get_ident() { - ident - } else { - return Err(Error::new( - name_value.span(), - "unrecognized attribute", - )); - }; - match ident.to_string().as_str() { - "type" => typ = Some(meta_name_value_to_parse(name_value)?), - "write_value" => { - write_value = Some(meta_name_value_to_parse(name_value)?); - } - _ => { - return Err(Error::new( - name_value.span(), - "unrecognized attribute", - )) - } - } - } - if let Some(typ) = typ { - attribs.tag = Some(Tag::Prepend { typ, write_value }); - } else { - return Err(Error::new(list.span(), "Tag lacks type.")); - } + let mut tag = None; + let mut tag_type = None; + let mut tag_value = None; + + for attr in attrs { + if attr.path().is_ident("protocol") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("flexible_array_member") { + attribs.flexible_array_member = true; + } else if meta.path.is_ident("discriminant_type") { + attribs.discriminant_type = Some(meta.value()?.parse()?); + } else if meta.path.is_ident("discriminant") { + attribs.discriminant = Some(meta.value()?.parse()?); + } else if meta.path.is_ident("ctx") { + attribs.ctx = Some(meta.value()?.parse()?); + } else if meta.path.is_ident("ctx_bounds") { + attribs.ctx_bounds = + Some(Punctuated::parse_separated_nonempty(meta.value()?)?); + } else if meta.path.is_ident("bits") { + attribs.bits = Some(meta.value()?.parse()?); + } else if meta.path.is_ident("write_value") { + attribs.write_value = Some(meta.value()?.parse()?); + } else if meta.path.is_ident("tag") { + tag = Some(meta.value()?.parse()?); + } else if meta.path.is_ident("tag_type") { + tag_type = Some(meta.value()?.parse()?); + } else if meta.path.is_ident("tag_value") { + tag_value = Some(meta.value()?.parse()?); + } else { + return Err(meta.error("unrecognized protocol")); } - _ => return Err(Error::new(meta_list.span(), "unrecognised attribute")), - }; + Ok(()) + })?; } } - Ok(attribs) - } -} - -fn meta_name_value_to_parse(name_value: &syn::MetaNameValue) -> Result { - match name_value.lit { - syn::Lit::Str(ref s) => syn::parse_str::(s.value().as_str()) - .map_err(|e| Error::new(name_value.span(), format!("Failed to parse: {e}"))), - _ => Err(Error::new(name_value.span(), "Expected string")), - } -} - -fn meta_name_value_to_u32(name_value: &syn::MetaNameValue) -> Result { - match name_value.lit { - syn::Lit::Int(ref i) => i - .base10_parse() - .map_err(|e| Error::new(name_value.span(), format!("Failed to parse u32: {e}"))), - _ => Err(Error::new(name_value.span(), "Expected integer")), - } -} + match (tag, tag_type, tag_value) { + (Some(tag), None, None) => attribs.tag = Some(Tag::External(tag)), + (None, Some(tag_type), tag_value) => { + attribs.tag = Some(Tag::Prepend { + typ: tag_type, + write_value: tag_value, + }) + } + (None, None, None) => {} + _ => return Err(Error::new(attrs[0].span(), "TODO")), + } -fn meta_name_value_to_punctuated( - name_value: &syn::MetaNameValue, -) -> Result> { - match name_value.lit { - syn::Lit::Str(ref s) => Punctuated::parse_terminated - .parse_str(s.value().as_str()) - .map_err(|e| Error::new(name_value.span(), format!("Failed to parse: {e}"))), - _ => Err(Error::new(name_value.span(), "Expected string")), + Ok(attribs) } } diff --git a/bin-proto-derive/src/codegen/enums.rs b/bin-proto-derive/src/codegen/enums.rs index 52f3e7f..276b900 100644 --- a/bin-proto-derive/src/codegen/enums.rs +++ b/bin-proto-derive/src/codegen/enums.rs @@ -2,7 +2,7 @@ use crate::{attr::Attrs, codegen, plan}; use proc_macro2::{Span, TokenStream}; pub fn read_discriminant(attribs: &Attrs) -> TokenStream { - if let Some(bits) = attribs.bits { + if let Some(bits) = &attribs.bits { quote!(::bin_proto::BitFieldRead::read(__io_reader, __byte_order, __ctx, #bits)) } else { quote!(::bin_proto::ProtocolRead::read( @@ -14,7 +14,7 @@ pub fn read_discriminant(attribs: &Attrs) -> TokenStream { } pub fn write_discriminant(attribs: &Attrs) -> TokenStream { - let write_tag = if let Some(bits) = attribs.bits { + let write_tag = if let Some(bits) = &attribs.bits { quote!(::bin_proto::BitFieldWrite::write(&__tag, __io_writer, __byte_order, __ctx, #bits)) } else { quote!(::bin_proto::ProtocolWrite::write( @@ -61,10 +61,10 @@ pub fn variant_discriminant(plan: &plan::Enum, attribs: &Attrs) -> TokenStream { let variant_name = &variant.ident; let fields_pattern = bind_fields_pattern(variant_name, &variant.fields); let discriminant_expr = &variant.discriminant_value; - let write_variant = if let Some(field_width) = attribs.bits { + let write_variant = if let Some(field_width) = &attribs.bits { let error_message = format!( - "Discriminant for variant '{}' does not fit in bitfield with width {}.", - variant.ident, field_width + "Discriminant for variant '{}' does not fit in bitfield.", + variant.ident ); quote!( const _: () = ::std::assert!(#discriminant_expr < (1 as #discriminant_ty) << #field_width, #error_message); diff --git a/bin-proto/Cargo.toml b/bin-proto/Cargo.toml index 0806f62..9201485 100644 --- a/bin-proto/Cargo.toml +++ b/bin-proto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bin-proto" -version = "0.5.0" +version = "0.6.0" authors = [ "Wojciech Graj ", "Dylan McKay " @@ -20,6 +20,6 @@ default = ["derive"] derive = ["bin-proto-derive"] [dependencies] -bin-proto-derive = { version = "0.5.0", path = "../bin-proto-derive", optional = true } +bin-proto-derive = { version = "0.6.0", path = "../bin-proto-derive", optional = true } bitstream-io = "2.3.0" -thiserror = "1.0.61" +thiserror = "2.0.2" diff --git a/bin-proto/src/lib.rs b/bin-proto/src/lib.rs index 862a6a3..5e90b9a 100644 --- a/bin-proto/src/lib.rs +++ b/bin-proto/src/lib.rs @@ -7,11 +7,11 @@ //! ``` //! # use bin_proto::{ProtocolRead, ProtocolWrite, ProtocolNoCtx}; //! #[derive(Debug, ProtocolRead, ProtocolWrite, PartialEq)] -//! #[protocol(discriminant_type = "u8")] +//! #[protocol(discriminant_type = u8)] //! #[protocol(bits = 4)] //! enum E { //! V1 = 1, -//! #[protocol(discriminant = "4")] +//! #[protocol(discriminant = 4)] //! V4, //! } //! @@ -22,11 +22,11 @@ //! #[protocol(bits = 3)] //! bitfield: u8, //! enum_: E, -//! #[protocol(write_value = "self.arr.len() as u8")] +//! #[protocol(write_value = self.arr.len() as u8)] //! arr_len: u8, -//! #[protocol(tag = "arr_len as usize")] +//! #[protocol(tag = arr_len as usize)] //! arr: Vec, -//! #[protocol(tag(type = "u16", write_value = "self.prefixed_arr.len() as u16"))] +//! #[protocol(tag_type = u16, tag_value = self.prefixed_arr.len() as u16)] //! prefixed_arr: Vec, //! #[protocol(flexible_array_member)] //! read_to_end: Vec, @@ -85,7 +85,7 @@ pub use self::tagged::{TaggedRead, UntaggedWrite}; /// ``` /// # use bin_proto::{ProtocolRead, ProtocolWrite}; /// #[derive(ProtocolRead, ProtocolWrite)] -/// #[protocol(discriminant_type = "u8")] +/// #[protocol(discriminant_type = u8)] /// enum Example { /// Variant1 = 1, /// Variant5 = 5, @@ -99,9 +99,9 @@ pub use self::tagged::{TaggedRead, UntaggedWrite}; /// ``` /// # use bin_proto::{ProtocolRead, ProtocolWrite}; /// #[derive(ProtocolRead, ProtocolWrite)] -/// #[protocol(discriminant_type = "u8")] +/// #[protocol(discriminant_type = u8)] /// enum Example { -/// #[protocol(discriminant = "1")] +/// #[protocol(discriminant = 1)] /// Variant1, /// Variant5 = 5, /// } @@ -150,7 +150,7 @@ pub use self::tagged::{TaggedRead, UntaggedWrite}; /// pub struct WithElementsLength { /// pub count: u32, /// pub foo: bool, -/// #[protocol(tag = "count as usize")] +/// #[protocol(tag = count as usize)] /// pub data: Vec, /// } /// ``` @@ -170,7 +170,7 @@ pub use self::tagged::{TaggedRead, UntaggedWrite}; /// # use bin_proto::{ProtocolRead, ProtocolWrite}; /// #[derive(ProtocolRead, ProtocolWrite)] /// pub struct WithElementsLength { -/// #[protocol(tag(type = "u16", write_value = "self.data.len() as u16"))] +/// #[protocol(tag_type = u16, tag_value = self.data.len() as u16)] /// pub data: Vec, /// } /// ``` @@ -186,10 +186,10 @@ pub use self::tagged::{TaggedRead, UntaggedWrite}; /// # use bin_proto::{ProtocolRead, ProtocolWrite}; /// #[derive(ProtocolRead, ProtocolWrite)] /// pub struct WithElementsLengthAuto { -/// #[protocol(write_value = "self.data.len() as u32")] +/// #[protocol(write_value = self.data.len() as u32)] /// pub count: u32, /// pub foo: bool, -/// #[protocol(tag = "count as usize")] +/// #[protocol(tag = count as usize)] /// pub data: Vec, /// } /// ``` @@ -231,7 +231,7 @@ pub use self::tagged::{TaggedRead, UntaggedWrite}; /// } /// /// #[derive(ProtocolRead, ProtocolWrite)] -/// #[protocol(ctx = "Ctx")] +/// #[protocol(ctx = Ctx)] /// pub struct WithCtx(NeedsCtx); /// /// WithCtx(NeedsCtx) @@ -243,7 +243,7 @@ pub use self::tagged::{TaggedRead, UntaggedWrite}; /// # use bin_proto::{ProtocolRead, ProtocolWrite}; /// # use std::marker::PhantomData; /// #[derive(ProtocolRead, ProtocolWrite)] -/// #[protocol(ctx = "Ctx")] +/// #[protocol(ctx = Ctx)] /// pub struct NestedProtocol + ProtocolWrite>(A, PhantomData); /// ``` /// @@ -283,7 +283,7 @@ pub use self::tagged::{TaggedRead, UntaggedWrite}; /// } /// /// #[derive(ProtocolRead, ProtocolWrite)] -/// #[protocol(ctx_bounds = "CtxTrait")] +/// #[protocol(ctx_bounds = CtxTrait)] /// pub struct WithCtx(NeedsCtx); /// ``` #[cfg(feature = "derive")] diff --git a/bin-proto/tests/ctx.rs b/bin-proto/tests/ctx.rs index cab9d35..9dfec42 100644 --- a/bin-proto/tests/ctx.rs +++ b/bin-proto/tests/ctx.rs @@ -40,11 +40,11 @@ impl ProtocolWrite for CtxCheck { } #[derive(Debug, ProtocolRead, ProtocolWrite)] -#[protocol(ctx = "CtxStruct")] +#[protocol(ctx = CtxStruct)] struct CtxCheckStructWrapper(CtxCheck); #[derive(Debug, ProtocolRead, ProtocolWrite)] -#[protocol(ctx_bounds = "CtxTrait")] +#[protocol(ctx_bounds = CtxTrait)] struct CtxCheckTraitWrapper(CtxCheck); #[test] diff --git a/bin-proto/tests/enums.rs b/bin-proto/tests/enums.rs index d5320f8..8845dc5 100644 --- a/bin-proto/tests/enums.rs +++ b/bin-proto/tests/enums.rs @@ -3,27 +3,27 @@ use std::marker::PhantomData; use bin_proto::{ByteOrder, ProtocolNoCtx, ProtocolRead, ProtocolWrite}; #[derive(Debug, ProtocolRead, ProtocolWrite, PartialEq)] -#[protocol(discriminant_type = "u8")] -#[protocol(ctx = "()")] +#[protocol(discriminant_type = u8)] +#[protocol(ctx = ())] pub enum Enum<'a, T: ProtocolRead + ProtocolWrite> { - #[protocol(discriminant = "1")] + #[protocol(discriminant = 1)] Variant1 { a: T, len: u8, - #[protocol(tag = "len as usize")] + #[protocol(tag = len as usize)] arr: Vec, }, - #[protocol(discriminant = "2")] + #[protocol(discriminant = 2)] Variant2(u32, bool, PhantomData<&'a T>), } #[derive(Debug, ProtocolRead, ProtocolWrite, PartialEq)] -#[protocol(discriminant_type = "u8")] +#[protocol(discriminant_type = u8)] #[protocol(bits = 2)] pub enum Enum2 { - #[protocol(discriminant = "1")] + #[protocol(discriminant = 1)] Variant1(u8), - #[protocol(discriminant = "2")] + #[protocol(discriminant = 2)] Variant2(u16), } @@ -34,19 +34,16 @@ pub struct EnumContainer { #[derive(Debug, ProtocolRead, ProtocolWrite, PartialEq)] pub struct TaggedEnumContainer { - #[protocol(tag( - type = "u16", - write_value = "::bin_proto::Discriminable::discriminant(&self.e) as u16" - ))] + #[protocol(tag_type = u16, tag_value = ::bin_proto::Discriminable::discriminant(&self.e) as u16)] e: Enum2, } #[derive(Debug, ProtocolRead, ProtocolWrite, PartialEq)] pub struct BitFieldTaggedEnumContainer { - #[protocol(write_value = "::bin_proto::Discriminable::discriminant(&self.e)")] + #[protocol(write_value = ::bin_proto::Discriminable::discriminant(&self.e))] #[protocol(bits = 3)] discriminant: u8, - #[protocol(tag = "discriminant")] + #[protocol(tag = discriminant)] e: Enum2, } diff --git a/bin-proto/tests/ipv4.rs b/bin-proto/tests/ipv4.rs index d1a6ba7..a8c49c0 100644 --- a/bin-proto/tests/ipv4.rs +++ b/bin-proto/tests/ipv4.rs @@ -1,7 +1,7 @@ use bin_proto::{ByteOrder, ProtocolNoCtx, ProtocolRead, ProtocolWrite}; #[derive(Debug, ProtocolRead, ProtocolWrite, PartialEq)] -#[protocol(discriminant_type = "u8")] +#[protocol(discriminant_type = u8)] #[protocol(bits = 4)] enum Version { V4 = 4, diff --git a/bin-proto/tests/lib.rs b/bin-proto/tests/lib.rs deleted file mode 100644 index a2db37e..0000000 --- a/bin-proto/tests/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![cfg(test)] - -#[cfg(test)] -mod ctx; -#[cfg(test)] -mod enums; -#[cfg(test)] -mod flexible_array_member; -#[cfg(test)] -mod ipv4; -#[cfg(test)] -mod structs; -#[cfg(test)] -mod tag; diff --git a/bin-proto/tests/structs.rs b/bin-proto/tests/structs.rs index 061bfbc..a0d998e 100644 --- a/bin-proto/tests/structs.rs +++ b/bin-proto/tests/structs.rs @@ -16,7 +16,7 @@ pub struct BizBong(u8, u8, pub u8); pub struct PartyInTheFront; #[derive(ProtocolRead, ProtocolWrite, Debug, PartialEq, Eq)] -#[protocol(ctx = "()")] +#[protocol(ctx = ())] pub struct NamedFieldsWithGenerics { pub value: A, @@ -24,7 +24,7 @@ pub struct NamedFieldsWithGenerics + ProtocolWrite, @@ -32,7 +32,7 @@ pub struct UnnamedFieldsWithGenerics< >(A, D, PhantomData); #[derive(ProtocolRead, ProtocolWrite, Debug, PartialEq, Eq)] -#[protocol(ctx = "()")] +#[protocol(ctx = ())] pub struct StructWithExistingBoundedGenerics< A: ::std::fmt::Display + ::std::fmt::Debug + ProtocolRead + ProtocolWrite, > { diff --git a/bin-proto/tests/tag.rs b/bin-proto/tests/tag.rs index 4b91bf5..3c8b5fe 100644 --- a/bin-proto/tests/tag.rs +++ b/bin-proto/tests/tag.rs @@ -9,41 +9,41 @@ pub struct Prefix { pub struct WithElementsLength { pub count: u32, pub foo: bool, - #[protocol(tag = "count as usize")] + #[protocol(tag = count as usize)] pub data: Vec, } #[derive(ProtocolRead, Debug, PartialEq, Eq)] pub struct OptionalWriteValue { - #[protocol(tag(type = "u8"))] + #[protocol(tag_type = u8)] pub data: Vec, } #[derive(ProtocolRead, ProtocolWrite, Debug, PartialEq, Eq)] pub struct WithElementsLengthAuto { - #[protocol(write_value = "self.data.len() as u32")] + #[protocol(write_value = self.data.len() as u32)] pub count: u32, pub foo: bool, - #[protocol(tag = "count as usize")] + #[protocol(tag = count as usize)] pub data: Vec, } #[derive(ProtocolRead, ProtocolWrite, Debug, PartialEq, Eq)] -#[protocol(discriminant_type = "u8")] +#[protocol(discriminant_type = u8)] pub enum WithElementsLengthAutoEnum { - #[protocol(discriminant = "1")] + #[protocol(discriminant = 1)] Variant { - #[protocol(write_value = "data.len() as u32")] + #[protocol(write_value = data.len() as u32)] count: u32, foo: bool, - #[protocol(tag = "count as usize")] + #[protocol(tag = count as usize)] data: Vec, }, } #[derive(ProtocolRead, ProtocolWrite, Debug, PartialEq, Eq)] pub struct Prepended { - #[protocol(tag(type = "u32", write_value = "self.data.len() as u32"))] + #[protocol(tag_type = u32, tag_value = self.data.len() as u32)] pub data: Vec, }