use quote::quote;
use syn::{
FnArg, Ident, ItemTrait, Pat, PatWild, ReturnType, Token, TraitItem, TraitItemFn, Type, parse2,
punctuated::Punctuated, spanned::Spanned, token::Comma,
};
pub fn double_trait(double_trait_name: Ident, org_trait: ItemTrait) -> syn::Result<ItemTrait> {
let items = org_trait
.items
.into_iter()
.map(|item| transform_trait_item(item))
.collect::<syn::Result<_>>()?;
Ok(ItemTrait {
ident: double_trait_name.clone(),
items,
..org_trait
})
}
fn transform_trait_item(trait_item: TraitItem) -> syn::Result<TraitItem> {
let transformed_trait_item = match trait_item {
TraitItem::Fn(fn_item) => TraitItem::Fn(transform_function(fn_item)?),
_ => {
trait_item
}
};
Ok(transformed_trait_item)
}
fn transform_function(mut fn_item: TraitItemFn) -> syn::Result<TraitItemFn> {
if fn_item.default.is_some() {
return Ok(fn_item);
}
strip_parameter_names(&mut fn_item.sig.inputs);
let is_impl_future = is_maybe_impl_future(&fn_item.sig.output);
let default_impl =
if is_impl_future {
parse2(quote! {{ async { unimplemented!() }} })
.map_err(|_| syn::Error::new(
fn_item.sig.output.span(),
"impl Trait is currently not supported by double-derive. Apart from the special \
case of impl Future."))?
} else {
parse2(quote! {{ unimplemented!() }}).unwrap()
};
fn_item.default = Some(default_impl);
Ok(fn_item)
}
fn strip_parameter_names(input: &mut Punctuated<FnArg, Comma>) {
for arg in input {
if let FnArg::Typed(pat_type) = arg {
*pat_type.pat = Pat::Wild(PatWild {
attrs: Vec::new(),
underscore_token: Token),
})
}
}
}
fn is_maybe_impl_future(output: &ReturnType) -> bool {
if let ReturnType::Type(_rarrow, ty) = output {
if let Type::ImplTrait(ref _impl_trait) = **ty {
true
} else {
false
}
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::double_trait;
use quote::quote;
use syn::{Ident, ItemTrait, parse2};
#[test]
fn default_impl_for_method_with_impl_future_return() {
let (double_trait_name, org_trait) = given(
quote! { DoubleTrait },
quote! {
trait OriginalTrait {
fn method(&self) -> impl Future<Output = ()>;
}
},
);
let double_trait = double_trait(double_trait_name, org_trait).unwrap();
let actual = quote! { #double_trait };
let expected = quote! {
trait DoubleTrait {
fn method(&self) -> impl Future<Output = ()> {
async { unimplemented!() }
}
}
};
assert_eq!(actual.to_string(), expected.to_string());
}
#[test]
fn strip_parameter_names_from_default_implementation() {
let (double_trait_name, org_trait) = given(
quote! { DoubleTrait },
quote! {
trait OriginalTrait {
fn method(x: i32);
}
},
);
let double_trait = double_trait(double_trait_name, org_trait).unwrap();
let actual = quote! { #double_trait };
let expected = quote! {
trait DoubleTrait {
fn method(_: i32) {
unimplemented!()
}
}
};
assert_eq!(actual.to_string(), expected.to_string());
}
fn given(attr: proc_macro2::TokenStream, item: proc_macro2::TokenStream) -> (Ident, ItemTrait) {
let attr: Ident = parse2(attr).unwrap();
let item: ItemTrait = parse2(item).unwrap();
(attr, item)
}
}