diff --git a/bilge-impl/Cargo.toml b/bilge-impl/Cargo.toml index ab4596f..2e83e6b 100644 --- a/bilge-impl/Cargo.toml +++ b/bilge-impl/Cargo.toml @@ -23,7 +23,7 @@ serde = [] syn = { version = "2.0", features = ["full"] } quote = "1.0" proc-macro2 = "1.0" -proc-macro-error2 = { version = "2.0", default-features = false } +manyhow = "0.11" itertools = ">=0.11.0, <=0.14" [dev-dependencies] diff --git a/bilge-impl/src/bitsize.rs b/bilge-impl/src/bitsize.rs index 68704ea..4f70584 100644 --- a/bilge-impl/src/bitsize.rs +++ b/bilge-impl/src/bitsize.rs @@ -1,7 +1,7 @@ mod split; +use manyhow::bail; use proc_macro2::{Ident, TokenStream}; -use proc_macro_error2::{abort, abort_call_site}; use quote::quote; use split::SplitAttributes; use syn::{punctuated::Iter, spanned::Spanned, Fields, Item, ItemEnum, ItemStruct, Type, Variant}; @@ -14,42 +14,42 @@ struct ItemIr { expanded: TokenStream, } -pub(super) fn bitsize(args: TokenStream, item: TokenStream) -> TokenStream { - let (item, declared_bitsize) = parse(item, args); - let attrs = SplitAttributes::from_item(&item); +pub(super) fn bitsize(args: TokenStream, item: TokenStream) -> manyhow::Result { + let (item, declared_bitsize) = parse(item, args)?; + let attrs = SplitAttributes::from_item(&item)?; let ir = match item { Item::Struct(mut item) => { modify_special_field_names(&mut item.fields); - analyze_struct(&item.fields); + analyze_struct(&item.fields)?; let expanded = generate_struct(&item, declared_bitsize); ItemIr { expanded } } Item::Enum(item) => { - analyze_enum(declared_bitsize, item.variants.iter()); + analyze_enum(declared_bitsize, item.variants.iter())?; let expanded = generate_enum(&item); ItemIr { expanded } } _ => unreachable(()), }; - generate_common(ir, attrs, declared_bitsize) + Ok(generate_common(ir, attrs, declared_bitsize)) } -fn parse(item: TokenStream, args: TokenStream) -> (Item, BitSize) { +fn parse(item: TokenStream, args: TokenStream) -> manyhow::Result<(Item, BitSize)> { let item = syn::parse2(item).unwrap_or_else(unreachable); if args.is_empty() { - abort_call_site!("missing attribute value"; help = "you need to define the size like this: `#[bitsize(32)]`") + bail!("missing attribute value"; help = "you need to define the size like this: `#[bitsize(32)]`") } - let (declared_bitsize, _arb_int) = shared::bitsize_and_arbitrary_int_from(args); - (item, declared_bitsize) + let (declared_bitsize, _arb_int) = shared::bitsize_and_arbitrary_int_from(args)?; + Ok((item, declared_bitsize)) } -fn check_type_is_supported(ty: &Type) { +fn check_type_is_supported(ty: &Type) -> manyhow::Result<()> { use Type::*; match ty { - Tuple(tuple) => tuple.elems.iter().for_each(check_type_is_supported), - Array(array) => check_type_is_supported(&array.elem), + Tuple(tuple) => tuple.elems.iter().try_for_each(check_type_is_supported)?, + Array(array) => check_type_is_supported(&array.elem)?, // Probably okay (compilation would validate that this type is also Bitsized) Path(_) => (), // These don't work with structs or aren't useful in bitfields. @@ -61,9 +61,10 @@ fn check_type_is_supported(ty: &Type) { // Something to investigate, but doesn't seem useful/usable here either. TraitObject(_) | // I have no idea where this is used. - Verbatim(_) | Paren(_) => abort!(ty, "This field type is not supported"), - _ => abort!(ty, "This field type is currently not supported"), + Verbatim(_) | Paren(_) => bail!(ty, "This field type is not supported"), + _ => bail!(ty, "This field type is currently not supported"), } + Ok(()) } /// Allows you to give multiple fields the name `reserved` or `padding` @@ -90,34 +91,36 @@ fn modify_special_field_names(fields: &mut Fields) { } } -fn analyze_struct(fields: &Fields) { +fn analyze_struct(fields: &Fields) -> manyhow::Result<()> { if fields.is_empty() { - abort_call_site!("structs without fields are not supported") + bail!("structs without fields are not supported") } // don't move this. we validate all nested field types here as well // and later assume this was checked. for field in fields { - check_type_is_supported(&field.ty) + check_type_is_supported(&field.ty)? } + Ok(()) } -fn analyze_enum(bitsize: BitSize, variants: Iter) { +fn analyze_enum(bitsize: BitSize, variants: Iter) -> manyhow::Result<()> { if bitsize > MAX_ENUM_BIT_SIZE { - abort_call_site!("enum bitsize is limited to {}", MAX_ENUM_BIT_SIZE) + bail!("enum bitsize is limited to {}", MAX_ENUM_BIT_SIZE) } let variant_count = variants.clone().count(); if variant_count == 0 { - abort_call_site!("empty enums are not supported"); + bail!("empty enums are not supported"); } let has_fallback = variants.flat_map(|variant| &variant.attrs).any(is_fallback_attribute); if !has_fallback { // this has a side-effect of validating the enum count - let _ = enum_fills_bitsize(bitsize, variant_count); + let _ = enum_fills_bitsize(bitsize, variant_count)?; } + Ok(()) } fn generate_struct(item: &ItemStruct, declared_bitsize: u8) -> TokenStream { diff --git a/bilge-impl/src/bitsize/split.rs b/bilge-impl/src/bitsize/split.rs index 1487216..840f139 100644 --- a/bilge-impl/src/bitsize/split.rs +++ b/bilge-impl/src/bitsize/split.rs @@ -1,4 +1,4 @@ -use proc_macro_error2::{abort, abort_call_site}; +use manyhow::bail; use quote::ToTokens; use syn::{meta::ParseNestedMeta, parse_quote, Attribute, Item, Meta, Path}; @@ -51,15 +51,13 @@ impl SplitAttributes { /// /// Any derives with suffix `Bits` will be able to access field information. /// This way, users of `bilge` can define their own derives working on the uncompressed bitfield. - pub fn from_item(item: &Item) -> SplitAttributes { + pub fn from_item(item: &Item) -> manyhow::Result { let attrs = match item { Item::Enum(item) => &item.attrs, Item::Struct(item) => &item.attrs, - _ => abort_call_site!("item is not a struct or enum"; help = "`#[bitsize]` can only be used on structs and enums"), + _ => bail!("item is not a struct or enum"; help = "`#[bitsize]` can only be used on structs and enums"), }; - let parsed = attrs.iter().map(parse_attribute); - let is_struct = matches!(item, Item::Struct(..)); let mut from_bytes = None; @@ -68,8 +66,8 @@ impl SplitAttributes { let mut before_compression = vec![]; let mut after_compression = vec![]; - for parsed_attr in parsed { - match parsed_attr { + for attr in attrs { + match parse_attribute(attr)? { ParsedAttribute::DeriveList(derives) => { for mut derive in derives { if derive.matches(&["zerocopy", "FromBytes"]) { @@ -77,7 +75,7 @@ impl SplitAttributes { } else if derive.matches(&["bilge", "FromBits"]) { has_frombits = true; } else if derive.matches_core_or_std(&["fmt", "Debug"]) && is_struct { - abort!(derive.0, "use derive(DebugBits) for structs") + bail!(derive.0, "use derive(DebugBits) for structs") } else if derive.matches_core_or_std(&["default", "Default"]) && is_struct { // emit_warning!(derive.0, "use derive(DefaultBits) for structs") derive.0 = syn::parse_quote!(::bilge::DefaultBits); @@ -93,7 +91,7 @@ impl SplitAttributes { } ParsedAttribute::BitsizeInternal(attr) => { - abort!(attr, "remove bitsize_internal"; help = "attribute bitsize_internal can only be applied internally by the bitsize macros") + bail!(attr, "remove bitsize_internal"; help = "attribute bitsize_internal can only be applied internally by the bitsize macros") } ParsedAttribute::Other(attr) => { @@ -106,7 +104,7 @@ impl SplitAttributes { if let Some(from_bytes) = from_bytes { if !has_frombits { - abort!(from_bytes.0, "a bitfield with zerocopy::FromBytes also needs to have FromBits") + bail!(from_bytes.0, "a bitfield with zerocopy::FromBytes also needs to have FromBits") } } @@ -115,15 +113,15 @@ impl SplitAttributes { before_compression.append(&mut after_compression) } - SplitAttributes { + Ok(SplitAttributes { before_compression, after_compression, - } + }) } } -fn parse_attribute(attribute: &Attribute) -> ParsedAttribute<'_> { - match &attribute.meta { +fn parse_attribute(attribute: &Attribute) -> manyhow::Result> { + Ok(match &attribute.meta { Meta::List(list) if list.path.is_ident("derive") => { let mut derives = Vec::new(); let add_derive = |meta: ParseNestedMeta| { @@ -133,8 +131,9 @@ fn parse_attribute(attribute: &Attribute) -> ParsedAttribute<'_> { Ok(()) }; - list.parse_nested_meta(add_derive) - .unwrap_or_else(|e| abort!(list.tokens, "failed to parse derive: {}", e)); + if let Err(e) = list.parse_nested_meta(add_derive) { + bail!(list.tokens, "failed to parse derive: {}", e); + } ParsedAttribute::DeriveList(derives) } @@ -142,7 +141,7 @@ fn parse_attribute(attribute: &Attribute) -> ParsedAttribute<'_> { meta if contains_anywhere(meta, "bitsize_internal") => ParsedAttribute::BitsizeInternal(attribute), _ => ParsedAttribute::Other(attribute), - } + }) } /// a crude approximation of things we currently consider in item attributes diff --git a/bilge-impl/src/bitsize_internal.rs b/bilge-impl/src/bitsize_internal.rs index 71aef66..410d55f 100644 --- a/bilge-impl/src/bitsize_internal.rs +++ b/bilge-impl/src/bitsize_internal.rs @@ -14,8 +14,8 @@ struct ItemIr<'a> { expanded: TokenStream, } -pub(super) fn bitsize_internal(args: TokenStream, item: TokenStream) -> TokenStream { - let (item, arb_int) = parse(item, args); +pub(super) fn bitsize_internal(args: TokenStream, item: TokenStream) -> manyhow::Result { + let (item, arb_int) = parse(item, args)?; let ir = match item { Item::Struct(ref item) => { let expanded = generate_struct(item, &arb_int); @@ -31,13 +31,13 @@ pub(super) fn bitsize_internal(args: TokenStream, item: TokenStream) -> TokenStr } _ => unreachable(()), }; - generate_common(ir, &arb_int) + Ok(generate_common(ir, &arb_int)) } -fn parse(item: TokenStream, args: TokenStream) -> (Item, TokenStream) { +fn parse(item: TokenStream, args: TokenStream) -> manyhow::Result<(Item, TokenStream)> { let item = syn::parse2(item).unwrap_or_else(unreachable); - let (_declared_bitsize, arb_int) = shared::bitsize_and_arbitrary_int_from(args); - (item, arb_int) + let (_declared_bitsize, arb_int) = shared::bitsize_and_arbitrary_int_from(args)?; + Ok((item, arb_int)) } fn generate_struct(struct_data: &ItemStruct, arb_int: &TokenStream) -> TokenStream { diff --git a/bilge-impl/src/debug_bits.rs b/bilge-impl/src/debug_bits.rs index e425656..9d489f4 100644 --- a/bilge-impl/src/debug_bits.rs +++ b/bilge-impl/src/debug_bits.rs @@ -1,17 +1,17 @@ +use manyhow::bail; use proc_macro2::{Ident, TokenStream}; -use proc_macro_error2::abort_call_site; use quote::quote; use syn::{Data, Fields}; use crate::shared::{self, unreachable}; -pub(super) fn debug_bits(item: TokenStream) -> TokenStream { +pub(super) fn debug_bits(item: TokenStream) -> manyhow::Result { let derive_input = shared::parse_derive(item); let name = &derive_input.ident; let name_str = name.to_string(); let struct_data = match derive_input.data { Data::Struct(s) => s, - Data::Enum(_) => abort_call_site!("use derive(Debug) for enums"), + Data::Enum(_) => bail!("use derive(Debug) for enums"), Data::Union(_) => unreachable(()), }; @@ -43,11 +43,11 @@ pub(super) fn debug_bits(item: TokenStream) -> TokenStream { Fields::Unit => todo!("this is a unit struct, which is not supported right now"), }; - quote! { + Ok(quote! { impl ::core::fmt::Debug for #name { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { #fmt_impl } } - } + }) } diff --git a/bilge-impl/src/default_bits.rs b/bilge-impl/src/default_bits.rs index d76975c..ec778e9 100644 --- a/bilge-impl/src/default_bits.rs +++ b/bilge-impl/src/default_bits.rs @@ -1,18 +1,18 @@ +use manyhow::bail; use proc_macro2::{Ident, TokenStream}; -use proc_macro_error2::abort_call_site; use quote::quote; use syn::{Data, DeriveInput, Fields, Type}; use crate::shared::{self, fallback::Fallback, unreachable, BitSize}; -pub(crate) fn default_bits(item: TokenStream) -> TokenStream { +pub(crate) fn default_bits(item: TokenStream) -> manyhow::Result { let derive_input = parse(item); //TODO: does fallback need handling? - let (derive_data, _, name, ..) = analyze(&derive_input); + let (derive_data, _, name, ..) = analyze(&derive_input)?; match derive_data { - Data::Struct(data) => generate_struct_default_impl(name, &data.fields), - Data::Enum(_) => abort_call_site!("use derive(Default) for enums"), + Data::Struct(data) => Ok(generate_struct_default_impl(name, &data.fields)), + Data::Enum(_) => bail!("use derive(Default) for enums"), _ => unreachable(()), } } @@ -87,6 +87,6 @@ fn parse(item: TokenStream) -> DeriveInput { shared::parse_derive(item) } -fn analyze(derive_input: &DeriveInput) -> (&Data, TokenStream, &Ident, BitSize, Option) { +fn analyze(derive_input: &DeriveInput) -> manyhow::Result<(&Data, TokenStream, &Ident, BitSize, Option)> { shared::analyze_derive(derive_input, false) } diff --git a/bilge-impl/src/fmt_bits.rs b/bilge-impl/src/fmt_bits.rs index 6d71da7..82192d3 100644 --- a/bilge-impl/src/fmt_bits.rs +++ b/bilge-impl/src/fmt_bits.rs @@ -4,12 +4,12 @@ use syn::{punctuated::Iter, Data, DeriveInput, Fields, Variant}; use crate::shared::{self, discriminant_assigner::DiscriminantAssigner, fallback::Fallback, unreachable, BitSize}; -pub(crate) fn binary(item: TokenStream) -> TokenStream { +pub(crate) fn binary(item: TokenStream) -> manyhow::Result { let derive_input = parse(item); - let (derive_data, arb_int, name, bitsize, fallback) = analyze(&derive_input); + let (derive_data, arb_int, name, bitsize, fallback) = analyze(&derive_input)?; match derive_data { - Data::Struct(data) => generate_struct_binary_impl(name, &data.fields), + Data::Struct(data) => Ok(generate_struct_binary_impl(name, &data.fields)), Data::Enum(data) => generate_enum_binary_impl(name, data.variants.iter(), arb_int, bitsize, fallback), _ => unreachable(()), } @@ -52,8 +52,8 @@ fn generate_struct_binary_impl(struct_name: &Ident, fields: &Fields) -> TokenStr fn generate_enum_binary_impl( enum_name: &Ident, variants: Iter, arb_int: TokenStream, bitsize: BitSize, fallback: Option, -) -> TokenStream { - let to_int_match_arms = generate_to_int_match_arms(variants, enum_name, bitsize, arb_int, fallback); +) -> manyhow::Result { + let to_int_match_arms = generate_to_int_match_arms(variants, enum_name, bitsize, arb_int, fallback)?; let body = if to_int_match_arms.is_empty() { quote! { Ok(()) } @@ -66,19 +66,19 @@ fn generate_enum_binary_impl( } }; - quote! { + Ok(quote! { impl ::core::fmt::Binary for #enum_name { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { #body } } - } + }) } /// generates the arms for an (infallible) conversion from an enum to the enum's underlying arbitrary_int fn generate_to_int_match_arms( variants: Iter, enum_name: &Ident, bitsize: BitSize, arb_int: TokenStream, fallback: Option, -) -> Vec { +) -> manyhow::Result> { let is_value_fallback = |variant_name| { if let Some(Fallback::WithValue(name)) = &fallback { variant_name == name @@ -90,15 +90,15 @@ fn generate_to_int_match_arms( let mut assigner = DiscriminantAssigner::new(bitsize); variants - .map(|variant| { + .map(|variant| -> manyhow::Result { let variant_name = &variant.ident; - let variant_value = assigner.assign_unsuffixed(variant); + let variant_value = assigner.assign_unsuffixed(variant)?; - if is_value_fallback(variant_name) { + Ok(if is_value_fallback(variant_name) { quote! { #enum_name::#variant_name(number) => *number, } } else { shared::to_int_match_arm(enum_name, variant_name, &arb_int, variant_value) - } + }) }) .collect() } @@ -107,6 +107,6 @@ fn parse(item: TokenStream) -> DeriveInput { shared::parse_derive(item) } -fn analyze(derive_input: &DeriveInput) -> (&Data, TokenStream, &Ident, BitSize, Option) { +fn analyze(derive_input: &DeriveInput) -> manyhow::Result<(&Data, TokenStream, &Ident, BitSize, Option)> { shared::analyze_derive(derive_input, false) } diff --git a/bilge-impl/src/from_bits.rs b/bilge-impl/src/from_bits.rs index 6e54ec6..b14fcab 100644 --- a/bilge-impl/src/from_bits.rs +++ b/bilge-impl/src/from_bits.rs @@ -1,46 +1,46 @@ use itertools::Itertools; +use manyhow::bail; use proc_macro2::{Ident, TokenStream}; -use proc_macro_error2::{abort, abort_call_site}; use quote::quote; use syn::{punctuated::Iter, Data, DeriveInput, Fields, Type, Variant}; use crate::shared::{self, discriminant_assigner::DiscriminantAssigner, enum_fills_bitsize, fallback::Fallback, unreachable, BitSize}; -pub(super) fn from_bits(item: TokenStream) -> TokenStream { +pub(super) fn from_bits(item: TokenStream) -> manyhow::Result { let derive_input = parse(item); - let (derive_data, arb_int, name, internal_bitsize, fallback) = analyze(&derive_input); + let (derive_data, arb_int, name, internal_bitsize, fallback) = analyze(&derive_input)?; let expanded = match &derive_data { Data::Struct(struct_data) => generate_struct(arb_int, name, &struct_data.fields), Data::Enum(enum_data) => { let variants = enum_data.variants.iter(); - let match_arms = analyze_enum(variants, name, internal_bitsize, fallback.as_ref(), &arb_int); + let match_arms = analyze_enum(variants, name, internal_bitsize, fallback.as_ref(), &arb_int)?; generate_enum(arb_int, name, match_arms, fallback) } _ => unreachable(()), }; - generate_common(expanded) + Ok(generate_common(expanded)) } fn parse(item: TokenStream) -> DeriveInput { shared::parse_derive(item) } -fn analyze(derive_input: &DeriveInput) -> (&syn::Data, TokenStream, &Ident, BitSize, Option) { +fn analyze(derive_input: &DeriveInput) -> manyhow::Result<(&syn::Data, TokenStream, &Ident, BitSize, Option)> { shared::analyze_derive(derive_input, false) } fn analyze_enum( variants: Iter, name: &Ident, internal_bitsize: BitSize, fallback: Option<&Fallback>, arb_int: &TokenStream, -) -> (Vec, Vec) { - validate_enum_variants(variants.clone(), fallback); +) -> manyhow::Result<(Vec, Vec)> { + validate_enum_variants(variants.clone(), fallback)?; - let enum_is_filled = enum_fills_bitsize(internal_bitsize, variants.len()); + let enum_is_filled = enum_fills_bitsize(internal_bitsize, variants.len())?; if !enum_is_filled && fallback.is_none() { - abort_call_site!("enum doesn't fill its bitsize"; help = "you need to use `#[derive(TryFromBits)]` instead, or specify one of the variants as #[fallback]") + bail!("enum doesn't fill its bitsize"; help = "you need to use `#[derive(TryFromBits)]` instead, or specify one of the variants as #[fallback]") } if enum_is_filled && fallback.is_some() { // NOTE: I've shortly tried pointing to `#[fallback]` here but it wasn't easy enough - abort_call_site!("enum already has {} variants", variants.len(); help = "remove the `#[fallback]` attribute") + bail!("enum already has {} variants", variants.len(); help = "remove the `#[fallback]` attribute") } let mut assigner = DiscriminantAssigner::new(internal_bitsize); @@ -62,9 +62,9 @@ fn analyze_enum( }; variants - .map(|variant| { + .map(|variant| -> manyhow::Result<(TokenStream, TokenStream)> { let variant_name = &variant.ident; - let variant_value = assigner.assign_unsuffixed(variant); + let variant_value = assigner.assign_unsuffixed(variant)?; let from_int_match_arm = if is_fallback(variant_name) { // this value will be handled by the catch-all arm @@ -79,9 +79,10 @@ fn analyze_enum( shared::to_int_match_arm(name, variant_name, arb_int, variant_value) }; - (from_int_match_arm, to_int_match_arm) + Ok((from_int_match_arm, to_int_match_arm)) }) - .unzip() + .collect::>>() + .map(|arms| arms.into_iter().unzip()) } fn generate_enum( @@ -173,7 +174,7 @@ fn generate_common(expanded: TokenStream) -> TokenStream { } } -fn validate_enum_variants(variants: Iter, fallback: Option<&Fallback>) { +fn validate_enum_variants(variants: Iter, fallback: Option<&Fallback>) -> manyhow::Result<()> { for variant in variants { // we've already validated the correctness of the fallback variant, and that there's at most one such variant. // this means we can safely skip a fallback variant if we find one. @@ -189,7 +190,8 @@ fn validate_enum_variants(variants: Iter, fallback: Option<&Fallback>) } else { "add a fallback variant or change this variant to a unit" }; - abort!(variant, "FromBits only supports unit variants for variants without `#[fallback]`"; help = help_message); + bail!(variant, "FromBits only supports unit variants for variants without `#[fallback]`"; help = "{}", help_message); } } + Ok(()) } diff --git a/bilge-impl/src/lib.rs b/bilge-impl/src/lib.rs index 56b5b0f..4f61e35 100644 --- a/bilge-impl/src/lib.rs +++ b/bilge-impl/src/lib.rs @@ -1,5 +1,5 @@ -use proc_macro::TokenStream; -use proc_macro_error2::proc_macro_error; +use manyhow::manyhow; +use proc_macro2::TokenStream; mod bitsize; mod bitsize_internal; @@ -20,28 +20,28 @@ mod shared; /// The size of structs is currently limited to 128 bits. /// The size of enums is limited to 64 bits. /// Please open an issue if you have a usecase for bigger bitfields. -#[proc_macro_error] +#[manyhow] #[proc_macro_attribute] -pub fn bitsize(args: TokenStream, item: TokenStream) -> TokenStream { - bitsize::bitsize(args.into(), item.into()).into() +pub fn bitsize(args: TokenStream, item: TokenStream) -> manyhow::Result { + bitsize::bitsize(args, item) } /// This is internally used, not to be used by anything besides `bitsize`. /// No guarantees are given. -#[proc_macro_error] +#[manyhow] #[proc_macro_attribute] -pub fn bitsize_internal(args: TokenStream, item: TokenStream) -> TokenStream { - bitsize_internal::bitsize_internal(args.into(), item.into()).into() +pub fn bitsize_internal(args: TokenStream, item: TokenStream) -> manyhow::Result { + bitsize_internal::bitsize_internal(args, item) } /// Generate an `impl TryFrom` for unfilled bitfields. /// /// This should be used when your enum or enums nested in /// a struct don't fill their given `bitsize`. -#[proc_macro_error] +#[manyhow] #[proc_macro_derive(TryFromBits, attributes(bitsize_internal, fallback))] -pub fn derive_try_from_bits(item: TokenStream) -> TokenStream { - try_from_bits::try_from_bits(item.into()).into() +pub fn derive_try_from_bits(item: TokenStream) -> manyhow::Result { + try_from_bits::try_from_bits(item) } /// Generate an `impl From` for filled bitfields. @@ -49,51 +49,51 @@ pub fn derive_try_from_bits(item: TokenStream) -> TokenStream { /// This should be used when your enum or enums nested in /// a struct fill their given `bitsize` or if you're not /// using enums. -#[proc_macro_error] +#[manyhow] #[proc_macro_derive(FromBits, attributes(bitsize_internal, fallback))] -pub fn derive_from_bits(item: TokenStream) -> TokenStream { - from_bits::from_bits(item.into()).into() +pub fn derive_from_bits(item: TokenStream) -> manyhow::Result { + from_bits::from_bits(item) } /// Generate an `impl core::fmt::Debug` for bitfield structs. /// /// Please use normal #[derive(Debug)] for enums. -#[proc_macro_error] +#[manyhow] #[proc_macro_derive(DebugBits, attributes(bitsize_internal))] -pub fn debug_bits(item: TokenStream) -> TokenStream { - debug_bits::debug_bits(item.into()).into() +pub fn debug_bits(item: TokenStream) -> manyhow::Result { + debug_bits::debug_bits(item) } /// Generate an `impl core::fmt::Binary` for bitfields. -#[proc_macro_error] +#[manyhow] #[proc_macro_derive(BinaryBits)] -pub fn derive_binary_bits(item: TokenStream) -> TokenStream { - fmt_bits::binary(item.into()).into() +pub fn derive_binary_bits(item: TokenStream) -> manyhow::Result { + fmt_bits::binary(item) } /// Generate an `impl core::default::Default` for bitfield structs. -#[proc_macro_error] +#[manyhow] #[proc_macro_derive(DefaultBits)] -pub fn derive_default_bits(item: TokenStream) -> TokenStream { - default_bits::default_bits(item.into()).into() +pub fn derive_default_bits(item: TokenStream) -> manyhow::Result { + default_bits::default_bits(item) } /// Generate an `impl serde::Serialize` for bitfield structs. /// /// Please use normal #[derive(Serialize)] for enums. #[cfg(feature = "serde")] -#[proc_macro_error] +#[manyhow] #[proc_macro_derive(SerializeBits, attributes(bitsize_internal))] -pub fn serialize_bits(item: TokenStream) -> TokenStream { - serde_bits::serialize_bits(item.into()).into() +pub fn serialize_bits(item: TokenStream) -> manyhow::Result { + serde_bits::serialize_bits(item) } /// Generate an `impl serde::Deserialize` for bitfield structs. /// /// Please use normal #[derive(Deserialize)] for enums. #[cfg(feature = "serde")] -#[proc_macro_error] +#[manyhow] #[proc_macro_derive(DeserializeBits, attributes(bitsize_internal))] -pub fn deserialize_bits(item: TokenStream) -> TokenStream { - serde_bits::deserialize_bits(item.into()).into() +pub fn deserialize_bits(item: TokenStream) -> manyhow::Result { + serde_bits::deserialize_bits(item) } diff --git a/bilge-impl/src/serde_bits.rs b/bilge-impl/src/serde_bits.rs index 66fc3f2..5cf7a07 100644 --- a/bilge-impl/src/serde_bits.rs +++ b/bilge-impl/src/serde_bits.rs @@ -1,6 +1,6 @@ use itertools::MultiUnzip; +use manyhow::bail; use proc_macro2::{Ident, TokenStream}; -use proc_macro_error2::abort_call_site; use quote::quote; use syn::{Data, Field, Fields}; @@ -11,13 +11,13 @@ fn filter_not_reserved_or_padding(field: &&Field) -> bool { !field_name_string.starts_with("reserved_") && !field_name_string.starts_with("padding_") } -pub(super) fn serialize_bits(item: TokenStream) -> TokenStream { +pub(super) fn serialize_bits(item: TokenStream) -> manyhow::Result { let derive_input = shared::parse_derive(item); let name = &derive_input.ident; let name_str = name.to_string(); let struct_data = match derive_input.data { Data::Struct(s) => s, - Data::Enum(_) => abort_call_site!("use derive(Serialize) for enums"), + Data::Enum(_) => bail!("use derive(Serialize) for enums"), Data::Union(_) => unreachable(()), }; @@ -55,7 +55,7 @@ pub(super) fn serialize_bits(item: TokenStream) -> TokenStream { Fields::Unit => todo!("this is a unit struct, which is not supported right now"), }; - quote! { + Ok(quote! { impl ::serde::Serialize for #name { fn serialize(&self, serializer: S) -> Result where @@ -64,7 +64,7 @@ pub(super) fn serialize_bits(item: TokenStream) -> TokenStream { #serialize_impl } } - } + }) } fn deserialize_field_parts( @@ -97,14 +97,14 @@ fn deserialize_field_parts( ) } -pub(super) fn deserialize_bits(item: TokenStream) -> TokenStream { +pub(super) fn deserialize_bits(item: TokenStream) -> manyhow::Result { let derive_input = shared::parse_derive(item); let name = &derive_input.ident; let name_str = name.to_string(); let struct_name_str = format!("struct {}", name_str); let struct_data = match derive_input.data { Data::Struct(s) => s, - Data::Enum(_) => abort_call_site!("use derive(Serialize) for enums"), + Data::Enum(_) => bail!("use derive(Serialize) for enums"), Data::Union(_) => unreachable(()), }; @@ -159,7 +159,7 @@ pub(super) fn deserialize_bits(item: TokenStream) -> TokenStream { quote!() }; - quote! { + Ok(quote! { impl<'de> ::serde::Deserialize<'de> for #name { fn deserialize(deserializer: D) -> Result where @@ -220,5 +220,5 @@ pub(super) fn deserialize_bits(item: TokenStream) -> TokenStream { deserializer.deserialize_struct(#name_str, FIELDS, Visitor) } } - } + }) } diff --git a/bilge-impl/src/shared.rs b/bilge-impl/src/shared.rs index 40ed376..a521e6a 100644 --- a/bilge-impl/src/shared.rs +++ b/bilge-impl/src/shared.rs @@ -3,8 +3,8 @@ pub mod fallback; pub mod util; use fallback::{fallback_variant, Fallback}; +use manyhow::{bail, ensure}; use proc_macro2::{Ident, Literal, TokenStream}; -use proc_macro_error2::{abort, abort_call_site}; use quote::quote; use syn::{Attribute, DeriveInput, LitInt, Meta, Type}; use util::PathExt; @@ -23,7 +23,9 @@ pub(crate) fn parse_derive(item: TokenStream) -> DeriveInput { // allow since we want `if try_from` blocks to stand out #[allow(clippy::collapsible_if)] -pub(crate) fn analyze_derive(derive_input: &DeriveInput, try_from: bool) -> (&syn::Data, TokenStream, &Ident, BitSize, Option) { +pub(crate) fn analyze_derive( + derive_input: &DeriveInput, try_from: bool, +) -> manyhow::Result<(&syn::Data, TokenStream, &Ident, BitSize, Option)> { let DeriveInput { attrs, ident, @@ -34,44 +36,45 @@ pub(crate) fn analyze_derive(derive_input: &DeriveInput, try_from: bool) -> (&sy if !try_from { if attrs.iter().any(is_non_exhaustive_attribute) { - abort_call_site!("Item can't be FromBits and non_exhaustive"; help = "remove #[non_exhaustive] or derive(FromBits) here") + bail!("Item can't be FromBits and non_exhaustive"; help = "remove #[non_exhaustive] or derive(FromBits) here") } } else { // currently not allowed, would need some thinking: if let syn::Data::Struct(_) = data { if attrs.iter().any(is_non_exhaustive_attribute) { - abort_call_site!("Using #[non_exhaustive] on structs is currently not supported"; help = "open an issue on our repository if needed") + bail!("Using #[non_exhaustive] on structs is currently not supported"; help = "open an issue on our repository if needed") } } } // parsing the #[bitsize_internal(num)] attribute macro - let args = attrs - .iter() - .find_map(bitsize_internal_arg) - .unwrap_or_else(|| abort_call_site!("add #[bitsize] attribute above your derive attribute")); - let (bitsize, arb_int) = bitsize_and_arbitrary_int_from(args); + ensure!( + let Some(args) = attrs.iter().find_map(bitsize_internal_arg), + "add #[bitsize] attribute above your derive attribute" + ); + let (bitsize, arb_int) = bitsize_and_arbitrary_int_from(args)?; - let fallback = fallback_variant(data, bitsize); + let fallback = fallback_variant(data, bitsize)?; if fallback.is_some() && try_from { - abort_call_site!("fallback is not allowed with `TryFromBits`"; help = "use `#[derive(FromBits)]` or remove this `#[fallback]`") + bail!("fallback is not allowed with `TryFromBits`"; help = "use `#[derive(FromBits)]` or remove this `#[fallback]`") } - (data, arb_int, ident, bitsize, fallback) + Ok((data, arb_int, ident, bitsize, fallback)) } // If we want to support bitsize(u4) besides bitsize(4), do that here. -pub fn bitsize_and_arbitrary_int_from(bitsize_arg: TokenStream) -> (BitSize, TokenStream) { - let bitsize: LitInt = syn::parse2(bitsize_arg.clone()) - .unwrap_or_else(|_| abort!(bitsize_arg, "attribute value is not a number"; help = "you need to define the size like this: `#[bitsize(32)]`")); +pub fn bitsize_and_arbitrary_int_from(bitsize_arg: TokenStream) -> manyhow::Result<(BitSize, TokenStream)> { + ensure!( + let Ok(bitsize) = syn::parse2::(bitsize_arg.clone()), + bitsize_arg, "attribute value is not a number"; help = "you need to define the size like this: `#[bitsize(32)]`" + ); // without postfix - let bitsize = bitsize - .base10_parse() - .ok() - .filter(|&n| n != 0 && n <= MAX_STRUCT_BIT_SIZE) - .unwrap_or_else(|| abort!(bitsize_arg, "attribute value is not a valid number"; help = "currently, numbers from 1 to {} are allowed", MAX_STRUCT_BIT_SIZE)); + ensure!( + let Some(bitsize) = bitsize.base10_parse().ok().filter(|&n| n != 0 && n <= MAX_STRUCT_BIT_SIZE), + bitsize_arg, "attribute value is not a valid number"; help = "currently, numbers from 1 to {} are allowed", MAX_STRUCT_BIT_SIZE + ); let arb_int = syn::parse_str(&format!("u{bitsize}")).unwrap_or_else(unreachable); - (bitsize, arb_int) + Ok((bitsize, arb_int)) } pub fn generate_type_bitsize(ty: &Type) -> TokenStream { @@ -132,12 +135,12 @@ pub fn last_ident_of_path(ty: &Type) -> Option<&Ident> { /// in enums, internal_bitsize <= 64; u64::MAX + 1 = u128 /// therefore the bitshift would not overflow. -pub fn enum_fills_bitsize(bitsize: u8, variants_count: usize) -> bool { +pub fn enum_fills_bitsize(bitsize: u8, variants_count: usize) -> manyhow::Result { let max_variants_count = 1u128 << bitsize; if variants_count as u128 > max_variants_count { - abort_call_site!("enum overflows its bitsize"; help = "there should only be at most {} variants defined", max_variants_count); + bail!("enum overflows its bitsize"; help = "there should only be at most {} variants defined", max_variants_count); } - variants_count as u128 == max_variants_count + Ok(variants_count as u128 == max_variants_count) } #[inline] diff --git a/bilge-impl/src/shared/discriminant_assigner.rs b/bilge-impl/src/shared/discriminant_assigner.rs index bd5784c..9f10055 100644 --- a/bilge-impl/src/shared/discriminant_assigner.rs +++ b/bilge-impl/src/shared/discriminant_assigner.rs @@ -1,5 +1,5 @@ +use manyhow::bail; use proc_macro2::Literal; -use proc_macro_error2::abort; use syn::{Expr, ExprLit, Lit, Variant}; use super::{unreachable, BitSize}; @@ -21,13 +21,15 @@ impl DiscriminantAssigner { (1u128 << self.bitsize) - 1 } - fn value_from_discriminant(&self, variant: &Variant) -> Option { - let discriminant = variant.discriminant.as_ref()?; + fn value_from_discriminant(&self, variant: &Variant) -> manyhow::Result> { + let Some(discriminant) = variant.discriminant.as_ref() else { + return Ok(None); + }; let discriminant_expr = &discriminant.1; let variant_name = &variant.ident; let Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) = discriminant_expr else { - abort!( + bail!( discriminant_expr, "variant `{}` is not a number", variant_name; help = "only literal integers currently supported" @@ -36,21 +38,21 @@ impl DiscriminantAssigner { let discriminant_value: u128 = int.base10_parse().unwrap_or_else(unreachable); if discriminant_value > self.max_value() { - abort!(variant, "Value of variant exceeds the given number of bits") + bail!(variant, "Value of variant exceeds the given number of bits") } - Some(discriminant_value) + Ok(Some(discriminant_value)) } - fn assign(&mut self, variant: &Variant) -> u128 { - let value = self.value_from_discriminant(variant).unwrap_or(self.next_expected_assignment); + fn assign(&mut self, variant: &Variant) -> manyhow::Result { + let value = self.value_from_discriminant(variant)?.unwrap_or(self.next_expected_assignment); self.next_expected_assignment = value + 1; - value + Ok(value) } /// syn adds a suffix when printing Rust integers. we use an unsuffixed `Literal` for better-looking codegen - pub fn assign_unsuffixed(&mut self, variant: &Variant) -> Literal { - let next = self.assign(variant); - Literal::u128_unsuffixed(next) + pub fn assign_unsuffixed(&mut self, variant: &Variant) -> manyhow::Result { + let next = self.assign(variant)?; + Ok(Literal::u128_unsuffixed(next)) } } diff --git a/bilge-impl/src/shared/fallback.rs b/bilge-impl/src/shared/fallback.rs index f0830e0..583da24 100644 --- a/bilge-impl/src/shared/fallback.rs +++ b/bilge-impl/src/shared/fallback.rs @@ -1,6 +1,6 @@ use itertools::Itertools; +use manyhow::bail; use proc_macro2::Ident; -use proc_macro_error2::{abort, abort_call_site}; use syn::{Data, Variant}; use super::{bitsize_from_type_ident, is_fallback_attribute, last_ident_of_path, unreachable, BitSize}; @@ -11,23 +11,23 @@ pub enum Fallback { } impl Fallback { - fn from_variant(variant: &Variant, enum_bitsize: BitSize, is_last_variant: bool) -> Fallback { + fn from_variant(variant: &Variant, enum_bitsize: BitSize, is_last_variant: bool) -> manyhow::Result { use syn::Fields::*; let ident = variant.ident.to_owned(); - match &variant.fields { + Ok(match &variant.fields { Named(_) => { - abort!(variant, "`#[fallback]` does not support variants with named fields"; help = "use a tuple variant or remove this `#[fallback]`") + bail!(variant, "`#[fallback]` does not support variants with named fields"; help = "use a tuple variant or remove this `#[fallback]`") } Unnamed(fields) => { let variant_fields = fields.unnamed.iter(); let Ok(fallback_value) = variant_fields.exactly_one() else { - abort!(variant, "fallback variant must have exactly one field"; help = "use only one field or change to a unit variant") + bail!(variant, "fallback variant must have exactly one field"; help = "use only one field or change to a unit variant") }; if !is_last_variant { - abort!(variant, "value fallback is not the last variant"; help = "a fallback variant with value must be the last variant of the enum") + bail!(variant, "value fallback is not the last variant"; help = "a fallback variant with value must be the last variant of the enum") } // here we validate that the fallback variant field type matches the bitsize @@ -35,17 +35,17 @@ impl Fallback { match size_from_type { Some(bitsize) if bitsize == enum_bitsize => Fallback::WithValue(ident), - Some(bitsize) => abort!( + Some(bitsize) => bail!( variant.fields, "bitsize of fallback field ({}) does not match bitsize of enum ({})", bitsize, enum_bitsize ), - None => abort!(variant.fields, "`#[fallback]` only supports arbitrary_int or bool types"), + None => bail!(variant.fields, "`#[fallback]` only supports arbitrary_int or bool types"), } } Unit => Fallback::Unit(ident), - } + }) } pub fn is_fallback_variant(&self, variant_ident: &Ident) -> bool { @@ -58,8 +58,8 @@ impl Fallback { /// 1. `#[fallback] Foo`, which we map to `Fallback::Unit` /// 2. `#[fallback] Foo(uN)`, where `N` is the enum's bitsize and `Foo` is the enum's last variant, /// which we map to `Fallback::WithValue` -pub fn fallback_variant(data: &Data, enum_bitsize: BitSize) -> Option { - match data { +pub fn fallback_variant(data: &Data, enum_bitsize: BitSize) -> manyhow::Result> { + Ok(match data { Data::Enum(enum_data) => { let variants_with_fallback = enum_data .variants @@ -70,11 +70,11 @@ pub fn fallback_variant(data: &Data, enum_bitsize: BitSize) -> Option Ok(None) => None, Ok(Some(variant)) => { let is_last_variant = variant.ident == enum_data.variants.last().unwrap().ident; - let fallback = Fallback::from_variant(variant, enum_bitsize, is_last_variant); + let fallback = Fallback::from_variant(variant, enum_bitsize, is_last_variant)?; Some(fallback) } Err(_) => { - abort_call_site!("only one enum variant may be `#[fallback]`"; help = "remove #[fallback] attributes until you only have one") + bail!("only one enum variant may be `#[fallback]`"; help = "remove #[fallback] attributes until you only have one") } } } @@ -82,11 +82,11 @@ pub fn fallback_variant(data: &Data, enum_bitsize: BitSize) -> Option let mut field_attrs = struct_data.fields.iter().flat_map(|field| &field.attrs); if field_attrs.any(is_fallback_attribute) { - abort_call_site!("`#[fallback]` is only applicable to enums"; help = "remove all `#[fallback]` from this struct") + bail!("`#[fallback]` is only applicable to enums"; help = "remove all `#[fallback]` from this struct") } else { None } } _ => unreachable(()), - } + }) } diff --git a/bilge-impl/src/try_from_bits.rs b/bilge-impl/src/try_from_bits.rs index 3240800..b82af06 100644 --- a/bilge-impl/src/try_from_bits.rs +++ b/bilge-impl/src/try_from_bits.rs @@ -1,20 +1,20 @@ +use manyhow::bail; use proc_macro2::{Ident, TokenStream}; -use proc_macro_error2::{abort, emit_call_site_warning}; use quote::quote; use syn::{punctuated::Iter, Data, DeriveInput, Fields, Type, Variant}; use crate::shared::{self, discriminant_assigner::DiscriminantAssigner, enum_fills_bitsize, fallback::Fallback, unreachable, BitSize}; use crate::shared::{bitsize_from_type_ident, last_ident_of_path}; -pub(super) fn try_from_bits(item: TokenStream) -> TokenStream { +pub(super) fn try_from_bits(item: TokenStream) -> manyhow::Result { let derive_input = parse(item); - let (derive_data, arb_int, name, internal_bitsize, ..) = analyze(&derive_input); + let (derive_data, arb_int, name, internal_bitsize, ..) = analyze(&derive_input)?; match derive_data { - Data::Struct(ref data) => codegen_struct(arb_int, name, &data.fields), + Data::Struct(ref data) => Ok(codegen_struct(arb_int, name, &data.fields)), Data::Enum(ref enum_data) => { let variants = enum_data.variants.iter(); - let match_arms = analyze_enum(variants, name, internal_bitsize, &arb_int); - codegen_enum(arb_int, name, match_arms) + let match_arms = analyze_enum(variants, name, internal_bitsize, &arb_int)?; + Ok(codegen_enum(arb_int, name, match_arms)) } _ => unreachable(()), } @@ -24,23 +24,29 @@ fn parse(item: TokenStream) -> DeriveInput { shared::parse_derive(item) } -fn analyze(derive_input: &DeriveInput) -> (&syn::Data, TokenStream, &Ident, BitSize, Option) { +fn analyze(derive_input: &DeriveInput) -> manyhow::Result<(&syn::Data, TokenStream, &Ident, BitSize, Option)> { shared::analyze_derive(derive_input, true) } -fn analyze_enum(variants: Iter, name: &Ident, internal_bitsize: BitSize, arb_int: &TokenStream) -> (Vec, Vec) { - validate_enum_variants(variants.clone()); +fn analyze_enum( + variants: Iter, name: &Ident, internal_bitsize: BitSize, arb_int: &TokenStream, +) -> manyhow::Result<(Vec, Vec)> { + validate_enum_variants(variants.clone())?; - if enum_fills_bitsize(internal_bitsize, variants.len()) { - emit_call_site_warning!("enum fills its bitsize"; help = "you can use `#[derive(FromBits)]` instead, rust will provide `TryFrom` for you (so you don't necessarily have to update call-sites)"); - } + // The previous `emit_call_site_warning!("enum fills its bitsize"; ...)` is dropped here. + // A standalone, non-fatal warning needs the nightly-only `proc_macro::Diagnostic`, so it was + // already a no-op under `proc-macro-error2` on stable (this crate is stable-only). `manyhow` + // has no equivalent: `Emitter`, `ErrorMessage::warning` and `ResultExt::warning` are all just + // `= warning:` attachments on an `Err` that still expands to `compile_error!`, which would turn + // this valid "filled enum" case into a hard error (see tests/single_filled_enum.rs). + let _ = enum_fills_bitsize(internal_bitsize, variants.len())?; let mut assigner = DiscriminantAssigner::new(internal_bitsize); variants - .map(|variant| { + .map(|variant| -> manyhow::Result<(TokenStream, TokenStream)> { let variant_name = &variant.ident; - let variant_value = assigner.assign_unsuffixed(variant); + let variant_value = assigner.assign_unsuffixed(variant)?; let from_int_match_arm = quote! { #variant_value => Ok(Self::#variant_name), @@ -48,9 +54,10 @@ fn analyze_enum(variants: Iter, name: &Ident, internal_bitsize: BitSize let to_int_match_arm = shared::to_int_match_arm(name, variant_name, arb_int, variant_value); - (from_int_match_arm, to_int_match_arm) + Ok((from_int_match_arm, to_int_match_arm)) }) - .unzip() + .collect::>>() + .map(|arms| arms.into_iter().unzip()) } fn codegen_enum(arb_int: TokenStream, enum_type: &Ident, match_arms: (Vec, Vec)) -> TokenStream { @@ -134,10 +141,11 @@ fn codegen_struct(arb_int: TokenStream, struct_type: &Ident, fields: &Fields) -> } } -fn validate_enum_variants(variants: Iter) { +fn validate_enum_variants(variants: Iter) -> manyhow::Result<()> { for variant in variants { if !matches!(variant.fields, Fields::Unit) { - abort!(variant, "TryFromBits only supports unit variants in enums"; help = "change this variant to a unit"); + bail!(variant, "TryFromBits only supports unit variants in enums"; help = "change this variant to a unit"); } } + Ok(()) }