From d53b84d79697d004fabee320ae05fc3c23ea8e97 Mon Sep 17 00:00:00 2001 From: Philip Sampaio Date: Sat, 25 Mar 2023 17:18:08 -0300 Subject: [PATCH] Improve nif macro error messages for invalid attributes The idea is to provide more contextual messages when an invalid "schedule" option is used. This also includes a new dev dependency - trybuild - that helps with the testing of compile error messages. --- rustler_codegen/Cargo.toml | 5 +- rustler_codegen/src/lib.rs | 18 +----- rustler_codegen/src/nif.rs | 57 ++++++++++++------- rustler_codegen/tests/test.rs | 5 ++ .../ui/nif-macro-unrecognized-attribute.rs | 8 +++ .../nif-macro-unrecognized-attribute.stderr | 5 ++ ...if-macro-wrong-schedule-attribute-value.rs | 8 +++ ...acro-wrong-schedule-attribute-value.stderr | 5 ++ 8 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 rustler_codegen/tests/test.rs create mode 100644 rustler_codegen/tests/ui/nif-macro-unrecognized-attribute.rs create mode 100644 rustler_codegen/tests/ui/nif-macro-unrecognized-attribute.stderr create mode 100644 rustler_codegen/tests/ui/nif-macro-wrong-schedule-attribute-value.rs create mode 100644 rustler_codegen/tests/ui/nif-macro-wrong-schedule-attribute-value.stderr diff --git a/rustler_codegen/Cargo.toml b/rustler_codegen/Cargo.toml index 4513d679..5dc892c0 100644 --- a/rustler_codegen/Cargo.toml +++ b/rustler_codegen/Cargo.toml @@ -6,7 +6,7 @@ version = "0.27.0" # rustler_codegen version authors = ["Hansihe "] license = "MIT/Apache-2.0" readme = "../README.md" -edition = "2018" +edition = "2021" [lib] name = "rustler_codegen" @@ -17,3 +17,6 @@ syn = { version = "2.0", features = ["full", "extra-traits"] } quote = "1.0" heck = "0.4" proc-macro2 = "1.0" + +[dev-dependencies] +trybuild = "1.0" diff --git a/rustler_codegen/src/lib.rs b/rustler_codegen/src/lib.rs index ae4a0910..5c012826 100644 --- a/rustler_codegen/src/lib.rs +++ b/rustler_codegen/src/lib.rs @@ -1,7 +1,6 @@ #![recursion_limit = "128"] use proc_macro::TokenStream; -use syn::LitStr; mod context; mod encode_decode_templates; @@ -92,28 +91,17 @@ pub fn init(input: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn nif(args: TokenStream, input: TokenStream) -> TokenStream { - let mut schedule: Option = None; - let mut custom_name: Option = None; + let mut nif_attributes = nif::NifAttributes::default(); if !args.is_empty() { - let nif_macro_parser = syn::meta::parser(|meta| { - if meta.path.is_ident("schedule") { - schedule = Some(meta.value()?.parse()?); - Ok(()) - } else if meta.path.is_ident("name") { - custom_name = Some(meta.value()?.parse()?); - Ok(()) - } else { - Err(meta.error("Unsupported nif macro property. Expecting schedule or name.")) - } - }); + let nif_macro_parser = syn::meta::parser(|meta| nif_attributes.parse(meta)); syn::parse_macro_input!(args with nif_macro_parser); } let input = syn::parse_macro_input!(input as syn::ItemFn); - nif::transcoder_decorator(schedule, custom_name, input).into() + nif::transcoder_decorator(nif_attributes, input).into() } /// Derives implementations for the `Encoder` and `Decoder` traits diff --git a/rustler_codegen/src/nif.rs b/rustler_codegen/src/nif.rs index 3c179108..e664034f 100644 --- a/rustler_codegen/src/nif.rs +++ b/rustler_codegen/src/nif.rs @@ -1,24 +1,53 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; +use syn::meta::ParseNestedMeta; use syn::punctuated::Punctuated; use syn::token::Comma; use syn::LitStr; -pub fn transcoder_decorator( +const VALID_SCHEDULE_OPTIONS: [&str; 3] = ["Normal", "DirtyCpu", "DirtyIo"]; + +#[derive(Default)] +pub struct NifAttributes { schedule: Option, custom_name: Option, - fun: syn::ItemFn, -) -> TokenStream { +} + +impl NifAttributes { + pub fn parse(&mut self, meta: ParseNestedMeta) -> syn::parse::Result<()> { + if meta.path.is_ident("schedule") { + let schedule: LitStr = meta.value()?.parse()?; + + if VALID_SCHEDULE_OPTIONS.contains(&schedule.value().as_str()) { + self.schedule = Some(schedule); + Ok(()) + } else { + Err(meta.error(format!( + "The schedule option is expecting one of the values: {:?}", + VALID_SCHEDULE_OPTIONS + ))) + } + } else if meta.path.is_ident("name") { + self.custom_name = Some(meta.value()?.parse()?); + Ok(()) + } else { + Err(meta.error("Unsupported nif macro attribute. Expecting schedule or name.")) + } + } +} + +pub fn transcoder_decorator(nif_attributes: NifAttributes, fun: syn::ItemFn) -> TokenStream { let sig = &fun.sig; let name = &sig.ident; let inputs = &sig.inputs; - let flags = schedule_flag(schedule); + let flags = schedule_flag(nif_attributes.schedule); let function = fun.to_owned().into_token_stream(); let arity = arity(inputs.clone()); let decoded_terms = extract_inputs(inputs.clone()); let argument_names = create_function_params(inputs.clone()); - let erl_func_name = custom_name + let erl_func_name = nif_attributes + .custom_name .map(|n| syn::Ident::new(n.value().as_str(), Span::call_site())) .unwrap_or_else(|| name.clone()); @@ -77,22 +106,10 @@ pub fn transcoder_decorator( fn schedule_flag(schedule: Option) -> TokenStream { let mut tokens = TokenStream::new(); - let valid = ["DirtyCpu", "DirtyIo", "Normal"]; - - let flag = match schedule { - Some(lit_str) => { - let value = lit_str.value(); - - if valid.contains(&value.as_str()) { - syn::Ident::new(value.as_str(), Span::call_site()) - } else { - panic!("Invalid schedule option `{}`", value); - } - } - None => syn::Ident::new("Normal", Span::call_site()), - }; + let flag = schedule.map_or("Normal".into(), |lit_str| lit_str.value()); + let flag_ident = syn::Ident::new(&flag, Span::call_site()); - tokens.extend(quote! { rustler::SchedulerFlags::#flag }); + tokens.extend(quote! { rustler::SchedulerFlags::#flag_ident }); tokens } diff --git a/rustler_codegen/tests/test.rs b/rustler_codegen/tests/test.rs new file mode 100644 index 00000000..870c2f95 --- /dev/null +++ b/rustler_codegen/tests/test.rs @@ -0,0 +1,5 @@ +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/rustler_codegen/tests/ui/nif-macro-unrecognized-attribute.rs b/rustler_codegen/tests/ui/nif-macro-unrecognized-attribute.rs new file mode 100644 index 00000000..4ad4d776 --- /dev/null +++ b/rustler_codegen/tests/ui/nif-macro-unrecognized-attribute.rs @@ -0,0 +1,8 @@ +use rustler_codegen::nif; + +#[nif(scheduler = "DirtyCpu")] +fn add(a: i64, b: i64) -> i64 { + a + b +} + +fn main() {} diff --git a/rustler_codegen/tests/ui/nif-macro-unrecognized-attribute.stderr b/rustler_codegen/tests/ui/nif-macro-unrecognized-attribute.stderr new file mode 100644 index 00000000..f53bb7a8 --- /dev/null +++ b/rustler_codegen/tests/ui/nif-macro-unrecognized-attribute.stderr @@ -0,0 +1,5 @@ +error: Unsupported nif macro attribute. Expecting schedule or name. + --> tests/ui/nif-macro-unrecognized-attribute.rs:3:7 + | +3 | #[nif(scheduler = "DirtyCpu")] + | ^^^^^^^^^ diff --git a/rustler_codegen/tests/ui/nif-macro-wrong-schedule-attribute-value.rs b/rustler_codegen/tests/ui/nif-macro-wrong-schedule-attribute-value.rs new file mode 100644 index 00000000..90007ed6 --- /dev/null +++ b/rustler_codegen/tests/ui/nif-macro-wrong-schedule-attribute-value.rs @@ -0,0 +1,8 @@ +use rustler_codegen::nif; + +#[nif(schedule = "DirtyGPU")] +fn add(a: i64, b: i64) -> i64 { + a + b +} + +fn main() {} diff --git a/rustler_codegen/tests/ui/nif-macro-wrong-schedule-attribute-value.stderr b/rustler_codegen/tests/ui/nif-macro-wrong-schedule-attribute-value.stderr new file mode 100644 index 00000000..54ea68dd --- /dev/null +++ b/rustler_codegen/tests/ui/nif-macro-wrong-schedule-attribute-value.stderr @@ -0,0 +1,5 @@ +error: The schedule option is expecting one of the values: ["Normal", "DirtyCpu", "DirtyIo"] + --> tests/ui/nif-macro-wrong-schedule-attribute-value.rs:3:7 + | +3 | #[nif(schedule = "DirtyGPU")] + | ^^^^^^^^^^^^^^^^^^^^^