diff --git a/src/parse/future.rs b/src/parse/future.rs index 2e05a13..f6e297a 100644 --- a/src/parse/future.rs +++ b/src/parse/future.rs @@ -1,38 +1,178 @@ -use syn::{parse_quote, visit_mut::VisitMut, FnArg, ItemFn}; +use quote::{format_ident, ToTokens}; +use syn::{parse_quote, visit_mut::VisitMut, FnArg, ItemFn, Lifetime}; -use crate::{error::ErrorsVec, utils::attr_is}; +use crate::{error::ErrorsVec, refident::MaybeIdent, utils::attr_is}; #[derive(Default)] -pub(crate) struct ReplaceFutureAttribute(Vec); +pub(crate) struct ReplaceFutureAttribute { + lifetimes: Vec, + errors: Vec, +} impl ReplaceFutureAttribute { pub(crate) fn replace(item_fn: &mut ItemFn) -> Result<(), ErrorsVec> { let mut visitor = Self::default(); visitor.visit_item_fn_mut(item_fn); - if visitor.0.is_empty() { + if !visitor.lifetimes.is_empty() { + let mut generics = visitor + .lifetimes + .into_iter() + .map(|lt| { + syn::GenericParam::Lifetime(syn::LifetimeDef { + lifetime: lt, + attrs: Default::default(), + colon_token: None, + bounds: Default::default(), + }) + }) + .collect::>(); + generics.extend(item_fn.sig.generics.params.iter().cloned()); + item_fn.sig.generics = parse_quote! { + <#(#generics),*> + }; + } + if visitor.errors.is_empty() { Ok(()) } else { - Err(visitor.0.into()) + Err(visitor.errors.into()) } } } impl VisitMut for ReplaceFutureAttribute { fn visit_fn_arg_mut(&mut self, node: &mut FnArg) { + let ident = node.maybe_ident().cloned().unwrap(); match node { FnArg::Typed(t) => { let attrs = std::mem::take(&mut t.attrs); let (futures, attrs): (Vec<_>, Vec<_>) = attrs.into_iter().partition(|a| attr_is(a, "future")); - if futures.len() > 0 { - let ty = &t.ty; - t.ty = parse_quote! { - impl std::future::Future + t.attrs = attrs; + if futures.is_empty() { + return; + } else if futures.len() > 1 { + self.errors.extend(futures.iter().skip(1).map(|attr| { + syn::Error::new_spanned( + attr.into_token_stream(), + format!("Cannot use #[future] more than once."), + ) + })); + return; + } + let ty = &mut t.ty; + use syn::Type::*; + match ty.as_ref() { + Group(_) | ImplTrait(_) | Infer(_) | Macro(_) | Never(_) | Slice(_) + | TraitObject(_) | Verbatim(_) => { + self.errors.push(syn::Error::new_spanned( + ty.into_token_stream(), + format!("This type cannot used to generete impl Future."), + )); + return; + } + _ => {} + }; + if let Reference(tr) = ty.as_mut() { + if tr.lifetime.is_none() { + let lifetime = syn::Lifetime { + apostrophe: ident.span(), + ident: format_ident!("_{}", ident), + }; + self.lifetimes.push(lifetime.clone()); + tr.lifetime = lifetime.into(); } } - t.attrs = attrs; + + t.ty = parse_quote! { + impl std::future::Future + } } FnArg::Receiver(_) => {} } } } + +#[cfg(test)] +mod should { + use super::*; + use crate::test::{assert_eq, *}; + use mytest::rstest; + use rstest_test::assert_in; + + #[rstest] + #[case("fn simple(a: u32) {}")] + #[case("fn more(a: u32, b: &str) {}")] + #[case("fn gen>(a: u32, b: S) {}")] + #[case("fn attr(#[case] a: u32, #[values(1,2)] b: i32) {}")] + fn not_change_anything_if_no_future_attribute_found(#[case] item_fn: &str) { + let mut item_fn: ItemFn = item_fn.ast(); + let orig = item_fn.clone(); + + ReplaceFutureAttribute::replace(&mut item_fn).unwrap(); + + assert_eq!(orig, item_fn) + } + + #[rstest] + #[case::simple( + "fn f(#[future] a: u32) {}", + "fn f(a: impl std::future::Future) {}" + )] + #[case::more_than_one( + "fn f(#[future] a: u32, #[future] b: String, #[future] c: std::collection::HashMap) {}", + r#"fn f(a: impl std::future::Future, + b: impl std::future::Future, + c: impl std::future::Future>) {}"#, + )] + #[case::just_one( + "fn f(a: u32, #[future] b: String) {}", + r#"fn f(a: u32, + b: impl std::future::Future) {}"# + )] + #[case::generics( + "fn f>(#[future] a: S) {}", + "fn f>(a: impl std::future::Future) {}" + )] + fn replace_basic_type(#[case] item_fn: &str, #[case] expected: &str) { + let mut item_fn: ItemFn = item_fn.ast(); + let expected: ItemFn = expected.ast(); + + ReplaceFutureAttribute::replace(&mut item_fn).unwrap(); + + assert_eq!(expected, item_fn) + } + + #[rstest] + #[case::base( + "fn f(#[future] ident_name: &u32) {}", + "fn f<'_ident_name>(ident_name: impl std::future::Future) {}" + )] + #[case::lifetime_already_exists( + "fn f<'b>(#[future] a: &'b u32) {}", + "fn f<'b>(a: impl std::future::Future) {}" + )] + #[case::some_other_generics( + "fn f<'b, IT: Iterator>(#[future] a: &u32, it: IT) {}", + "fn f<'_a, 'b, IT: Iterator>(a: impl std::future::Future, it: IT) {}" + )] + fn replace_reference_type(#[case] item_fn: &str, #[case] expected: &str) { + let mut item_fn: ItemFn = item_fn.ast(); + let expected: ItemFn = expected.ast(); + + ReplaceFutureAttribute::replace(&mut item_fn).unwrap(); + + assert_eq!(expected, item_fn) + } + + #[rstest] + #[case::no_more_than_one("fn f(#[future] #[future] a: u32) {}", "more than once")] + #[case::no_impl("fn f(#[future] a: impl AsRef) {}", "generete impl Future")] + #[case::no_slice("fn f(#[future] a: [i32]) {}", "generete impl Future")] + fn raise_error(#[case] item_fn: &str, #[case] message: &str) { + let mut item_fn: ItemFn = item_fn.ast(); + + let err = ReplaceFutureAttribute::replace(&mut item_fn).unwrap_err(); + + assert_in!(format!("{:?}", err), message); + } +} diff --git a/tests/resources/rstest/errors.rs b/tests/resources/rstest/errors.rs index 60ce382..565e912 100644 --- a/tests/resources/rstest/errors.rs +++ b/tests/resources/rstest/errors.rs @@ -84,3 +84,11 @@ struct S; #[rstest] #[case("donald duck")] fn error_convert_to_type_that_not_implement_from_str(#[case] s: S) {} + +#[rstest] +#[case(async { "hello" } )] +async fn error_future_on_impl_type(#[case] #[future] s: impl AsRef) {} + +#[rstest] +#[case(async { 42 } )] +async fn error_future_on_impl_type(#[case] #[future] #[future] a: i32) {} diff --git a/tests/rstest/mod.rs b/tests/rstest/mod.rs index 627135e..d4e4ee0 100644 --- a/tests/rstest/mod.rs +++ b/tests/rstest/mod.rs @@ -1276,4 +1276,42 @@ mod should_show_correct_errors { .unindent() ); } + + #[test] + fn if_try_to_use_future_on_an_impl() { + let (output, name) = execute(); + + assert_in!( + output.stderr.str(), + format!( + " + --> {}/src/lib.rs:90:57 + | + 90 | async fn error_future_on_impl_type(#[case] #[future] s: impl AsRef) {{}} + | ^^^^^^^^^^^^^^^ + ", + name + ) + .unindent() + ); + } + + #[test] + fn if_try_to_use_future_more_that_once() { + let (output, name) = execute(); + + assert_in!( + output.stderr.str(), + format!( + " + --> {}/src/lib.rs:94:54 + | + 94 | async fn error_future_on_impl_type(#[case] #[future] #[future] a: i32) {{}} + | ^^^^^^^^^ + ", + name + ) + .unindent() + ); + } }