Skip to content

Commit

Permalink
#98 Implemented reference and error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
la10736 committed May 2, 2021
1 parent 81fd9ae commit a9d79de
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 10 deletions.
160 changes: 150 additions & 10 deletions 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<syn::Error>);
pub(crate) struct ReplaceFutureAttribute {
lifetimes: Vec<Lifetime>,
errors: Vec<syn::Error>,
}

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::<Vec<_>>();
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<Output = #ty>
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<Output = #ty>
}
}
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<S: AsRef<str>>(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<Output = u32>) {}"
)]
#[case::more_than_one(
"fn f(#[future] a: u32, #[future] b: String, #[future] c: std::collection::HashMap<usize, String>) {}",
r#"fn f(a: impl std::future::Future<Output = u32>,
b: impl std::future::Future<Output = String>,
c: impl std::future::Future<Output = std::collection::HashMap<usize, String>>) {}"#,
)]
#[case::just_one(
"fn f(a: u32, #[future] b: String) {}",
r#"fn f(a: u32,
b: impl std::future::Future<Output = String>) {}"#
)]
#[case::generics(
"fn f<S: AsRef<str>>(#[future] a: S) {}",
"fn f<S: AsRef<str>>(a: impl std::future::Future<Output = S>) {}"
)]
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<Output = &'_ident_name u32>) {}"
)]
#[case::lifetime_already_exists(
"fn f<'b>(#[future] a: &'b u32) {}",
"fn f<'b>(a: impl std::future::Future<Output = &'b u32>) {}"
)]
#[case::some_other_generics(
"fn f<'b, IT: Iterator<Item=String + 'b>>(#[future] a: &u32, it: IT) {}",
"fn f<'_a, 'b, IT: Iterator<Item=String + 'b>>(a: impl std::future::Future<Output = &'_a u32>, 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<str>) {}", "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);
}
}
8 changes: 8 additions & 0 deletions tests/resources/rstest/errors.rs
Expand Up @@ -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<str>) {}

#[rstest]
#[case(async { 42 } )]
async fn error_future_on_impl_type(#[case] #[future] #[future] a: i32) {}
38 changes: 38 additions & 0 deletions tests/rstest/mod.rs
Expand Up @@ -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<str>) {{}}
| ^^^^^^^^^^^^^^^
",
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()
);
}
}

0 comments on commit a9d79de

Please sign in to comment.