fix: diagnostics insted of panicking

This commit is contained in:
davidon-top 2023-11-03 21:29:21 +01:00
parent 42632f81e4
commit f7735bec52
4 changed files with 38 additions and 16 deletions

View file

@ -15,4 +15,4 @@ members = [
] ]
[dependencies] [dependencies]
binf_macros = { version = "1.0.1" } binf_macros = { version = "1.0.1", path = "macros" }

View file

@ -14,5 +14,6 @@ proc-macro = true
[dependencies] [dependencies]
proc-macro2 = "1.0.68" proc-macro2 = "1.0.68"
proc-macro2-diagnostics = "0.10.1"
quote = "1.0.33" quote = "1.0.33"
syn = { version = "2.0.38", features = ["full"] } syn = { version = "2.0.38", features = ["full"] }

View file

@ -1,4 +1,5 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2_diagnostics::Diagnostic;
/// Attribute proc macro that turns a struct into a bitflag /// Attribute proc macro that turns a struct into a bitflag
/// The underlying type of the bitflag is chosen based on the number of fields in the struct /// The underlying type of the bitflag is chosen based on the number of fields in the struct
@ -24,15 +25,16 @@ use proc_macro::TokenStream;
/// you can also call functions from BitFlag trait beacause the new struct implements deref and deref_mut to the underlying type /// you can also call functions from BitFlag trait beacause the new struct implements deref and deref_mut to the underlying type
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn bitflag(_attr: TokenStream, input: TokenStream) -> TokenStream { pub fn bitflag(_attr: TokenStream, input: TokenStream) -> TokenStream {
let mut diagnostics = Vec::new();
let structdef: syn::ItemStruct = syn::parse_macro_input!(input as syn::ItemStruct); let structdef: syn::ItemStruct = syn::parse_macro_input!(input as syn::ItemStruct);
// check if the struct has correct shape // check if the struct has correct shape
if !check_struct(&structdef) { check_struct(&mut diagnostics, &structdef);
panic!("struct has incorrect shape");
}
let structname = structdef.ident; let structname = structdef.ident;
let structfields = match &structdef.fields { let structfields = match &structdef.fields {
syn::Fields::Named(f) => f.named.iter().map(|f| f.ident.clone().unwrap()).collect::<Vec<_>>(), syn::Fields::Named(f) => f.named.iter().map(|f| f.ident.clone().unwrap()).collect::<Vec<_>>(),
_ => panic!("struct has incorrect shape"), _ => {diagnostics.push(syn::Error::new_spanned(&structdef.fields, "struct has incorrect shape, only works on struct with named fields").into()); Vec::new()},
}; };
let vis = structdef.vis.clone(); let vis = structdef.vis.clone();
@ -47,7 +49,7 @@ pub fn bitflag(_attr: TokenStream, input: TokenStream) -> TokenStream {
17..=32 => quote::quote! { u32 }, 17..=32 => quote::quote! { u32 },
33..=64 => quote::quote! { u64 }, 33..=64 => quote::quote! { u64 },
65..=128 => quote::quote! { u128 }, 65..=128 => quote::quote! { u128 },
_ => panic!("struct has too many fields"), _ => {diagnostics.push(syn::Error::new_spanned(structdef.fields, "struct has too many fields").into()); quote::quote! { u8 }}
} }
}; };
@ -107,7 +109,9 @@ pub fn bitflag(_attr: TokenStream, input: TokenStream) -> TokenStream {
} }
}; };
let diagnostics = diagnostics.iter().map(|d| d.clone().emit_as_item_tokens());
quote::quote! { quote::quote! {
#(#diagnostics)*
#newstruct #newstruct
#impls #impls
#deref_impl #deref_impl
@ -116,28 +120,33 @@ pub fn bitflag(_attr: TokenStream, input: TokenStream) -> TokenStream {
} }
/// returns true if the struct has a correct shape /// returns true if the struct has a correct shape
fn check_struct(input: &syn::ItemStruct) -> bool { fn check_struct(diagnostics: &mut Vec<Diagnostic>, input: &syn::ItemStruct) {
if input.generics.lt_token.is_some() { if input.generics.lt_token.is_some() {
return false; diagnostics.push(syn::Error::new_spanned(&input.generics, "generics not allowed in bitflag structs").into());
} }
if input.generics.gt_token.is_some() { if input.generics.gt_token.is_some() {
return false; diagnostics.push(syn::Error::new_spanned(&input.generics, "generics not allowed in bitflag structs").into());
} }
if input.generics.where_clause.is_some() { if input.generics.where_clause.is_some() {
return false; diagnostics.push(syn::Error::new_spanned(&input.generics, "generics not allowed in bitflag structs").into());
} }
if !input.generics.params.is_empty() { if !input.generics.params.is_empty() {
return false; diagnostics.push(syn::Error::new_spanned(&input.generics, "generics not allowed in bitflag structs").into());
} }
match &input.fields { match &input.fields {
syn::Fields::Unnamed(_) => false, syn::Fields::Unnamed(f) => diagnostics.push(syn::Error::new_spanned(f, "struct has incorrect shape, found tuple struct").into()),
syn::Fields::Unit => false, syn::Fields::Unit => diagnostics.push(syn::Error::new_spanned(input, "struct has incorrect shape, found unit struct").into()),
syn::Fields::Named(f) => { syn::Fields::Named(f) => {
if f.named.len() > 128 { if f.named.len() > 128 {
return false; diagnostics.push(syn::Error::new_spanned(f, "struct has too many fields").into());
}
for field in f.named.iter() {
match &field.ty {
syn::Type::Path(path_type) if path_type.path.is_ident("bool") => {},
_ => diagnostics.push(syn::Error::new_spanned(field, "struct has incorrect shape, found non bool field").into()),
}
} }
true
}, },
} }
} }

View file

@ -1,5 +1,4 @@
use binf::*; use binf::*;
use binf_macros::*;
#[bitflag] #[bitflag]
pub struct Test { pub struct Test {
@ -70,3 +69,16 @@ fn u64_test() {
flags.set_flag(0, true); flags.set_flag(0, true);
assert_eq!(flags, 0b10101011); assert_eq!(flags, 0b10101011);
} }
#[test]
fn off_test() {
let flags = Test::new(0b0);
assert_eq!(flags.a(), false);
assert_eq!(flags.b(), false);
assert_eq!(flags.c(), false);
assert_eq!(flags.d(), false);
assert_eq!(flags.e(), false);
assert_eq!(flags.f(), false);
assert_eq!(flags.g(), false);
assert_eq!(flags.h(), false);
}