diff --git a/actix-macros/CHANGES.md b/actix-macros/CHANGES.md index 8d7037cf..31f64207 100644 --- a/actix-macros/CHANGES.md +++ b/actix-macros/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +* Improve error recovery potential when macro input is invalid. [#391] +* Allow custom `System`s on test macro. [#391] + +[#391]: https://github.com/actix/actix-net/pull/391 ## 0.2.1 - 2021-02-02 diff --git a/actix-macros/src/lib.rs b/actix-macros/src/lib.rs index d8aa5f77..4be79178 100644 --- a/actix-macros/src/lib.rs +++ b/actix-macros/src/lib.rs @@ -28,7 +28,12 @@ use quote::quote; #[proc_macro_attribute] #[cfg(not(test))] // Work around for rust-lang/rust#62127 pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { - let mut input = syn::parse_macro_input!(item as syn::ItemFn); + let mut input = match syn::parse::(item.clone()) { + Ok(input) => input, + // on parse err, make IDEs happy; see fn docs + Err(err) => return input_and_compile_error(item, err), + }; + let args = syn::parse_macro_input!(args as syn::AttributeArgs); let attrs = &input.attrs; @@ -101,8 +106,15 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { /// } /// ``` #[proc_macro_attribute] -pub fn test(_: TokenStream, item: TokenStream) -> TokenStream { - let mut input = syn::parse_macro_input!(item as syn::ItemFn); +pub fn test(args: TokenStream, item: TokenStream) -> TokenStream { + let mut input = match syn::parse::(item.clone()) { + Ok(input) => input, + // on parse err, make IDEs happy; see fn docs + Err(err) => return input_and_compile_error(item, err), + }; + + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let attrs = &input.attrs; let vis = &input.vis; let sig = &mut input.sig; @@ -132,13 +144,59 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream { quote!(#[test]) }; + let mut system = syn::parse_str::("::actix_rt::System").unwrap(); + + for arg in &args { + match arg { + syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { + lit: syn::Lit::Str(lit), + path, + .. + })) => match path + .get_ident() + .map(|i| i.to_string().to_lowercase()) + .as_deref() + { + Some("system") => match lit.parse() { + Ok(path) => system = path, + Err(_) => { + return syn::Error::new_spanned(lit, "Expected path") + .to_compile_error() + .into(); + } + }, + _ => { + return syn::Error::new_spanned(arg, "Unknown attribute specified") + .to_compile_error() + .into(); + } + }, + _ => { + return syn::Error::new_spanned(arg, "Unknown attribute specified") + .to_compile_error() + .into(); + } + } + } + (quote! { #missing_test_attr #(#attrs)* #vis #sig { - actix_rt::System::new() - .block_on(async { #body }) + <#system>::new().block_on(async { #body }) } }) .into() } + +/// Converts the error to a token stream and appends it to the original input. +/// +/// Returning the original input in addition to the error is good for IDEs which can gracefully +/// recover and show more precise errors within the macro body. +/// +/// See for more info. +fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream { + let compile_err = TokenStream::from(err.to_compile_error()); + item.extend(compile_err); + return item; +} diff --git a/actix-macros/tests/trybuild.rs b/actix-macros/tests/trybuild.rs index 410d9499..c7f4a5ca 100644 --- a/actix-macros/tests/trybuild.rs +++ b/actix-macros/tests/trybuild.rs @@ -11,4 +11,7 @@ fn compile_macros() { t.pass("tests/trybuild/test-01-basic.rs"); t.pass("tests/trybuild/test-02-keep-attrs.rs"); t.compile_fail("tests/trybuild/test-03-only-async.rs"); + t.pass("tests/trybuild/test-04-system-path.rs"); + t.compile_fail("tests/trybuild/test-05-system-expect-path.rs"); + t.compile_fail("tests/trybuild/test-06-unknown-attr.rs"); } diff --git a/actix-macros/tests/trybuild/test-04-system-path.rs b/actix-macros/tests/trybuild/test-04-system-path.rs new file mode 100644 index 00000000..6d0f9951 --- /dev/null +++ b/actix-macros/tests/trybuild/test-04-system-path.rs @@ -0,0 +1,10 @@ +mod system { + pub use actix_rt::System as MySystem; +} + +#[actix_rt::test(system = "system::MySystem")] +async fn my_test() { + futures_util::future::ready(()).await +} + +fn main() {} diff --git a/actix-macros/tests/trybuild/test-05-system-expect-path.rs b/actix-macros/tests/trybuild/test-05-system-expect-path.rs new file mode 100644 index 00000000..fda3502b --- /dev/null +++ b/actix-macros/tests/trybuild/test-05-system-expect-path.rs @@ -0,0 +1,4 @@ +#[actix_rt::test(system = "!@#*&")] +async fn my_test() {} + +fn main() {} diff --git a/actix-macros/tests/trybuild/test-05-system-expect-path.stderr b/actix-macros/tests/trybuild/test-05-system-expect-path.stderr new file mode 100644 index 00000000..5eab3cf5 --- /dev/null +++ b/actix-macros/tests/trybuild/test-05-system-expect-path.stderr @@ -0,0 +1,5 @@ +error: Expected path + --> $DIR/test-05-system-expect-path.rs:1:27 + | +1 | #[actix_rt::test(system = "!@#*&")] + | ^^^^^^^ diff --git a/actix-macros/tests/trybuild/test-06-unknown-attr.rs b/actix-macros/tests/trybuild/test-06-unknown-attr.rs new file mode 100644 index 00000000..5d0c20c1 --- /dev/null +++ b/actix-macros/tests/trybuild/test-06-unknown-attr.rs @@ -0,0 +1,7 @@ +#[actix_rt::test(foo = "bar")] +async fn my_test_1() {} + +#[actix_rt::test(bar::baz)] +async fn my_test_2() {} + +fn main() {} diff --git a/actix-macros/tests/trybuild/test-06-unknown-attr.stderr b/actix-macros/tests/trybuild/test-06-unknown-attr.stderr new file mode 100644 index 00000000..f6896ddc --- /dev/null +++ b/actix-macros/tests/trybuild/test-06-unknown-attr.stderr @@ -0,0 +1,11 @@ +error: Unknown attribute specified + --> $DIR/test-06-unknown-attr.rs:1:18 + | +1 | #[actix_rt::test(foo = "bar")] + | ^^^^^^^^^^^ + +error: Unknown attribute specified + --> $DIR/test-06-unknown-attr.rs:4:18 + | +4 | #[actix_rt::test(bar::baz)] + | ^^^^^^^^ diff --git a/bytestring/Cargo.toml b/bytestring/Cargo.toml index 3dbf07b7..34237ce9 100644 --- a/bytestring/Cargo.toml +++ b/bytestring/Cargo.toml @@ -24,4 +24,5 @@ serde = { version = "1.0", optional = true } [dev-dependencies] serde_json = "1.0" -ahash = { version = "0.7", default-features = false } +# TODO: remove when ahash MSRV is restored +ahash = { version = "=0.7.4", default-features = false }