use proc_macro2::TokenStream;
use quote::quote;
use syn::{
FnArg, Ident, ImplItem, ImplItemFn, ItemImpl, ItemTrait, PatType, TraitItem, TraitItemFn,
Visibility, parse2,
};
pub fn trait_impl(double_trait_name: Ident, org_trait: ItemTrait) -> ItemImpl {
let items = org_trait
.items
.into_iter()
.filter_map(|trait_item| map_methods(trait_item, &double_trait_name))
.collect();
let org_trait_name = org_trait.ident;
let impl_ = quote! {
impl<T> #org_trait_name for T where T: #double_trait_name {
}
};
let impl_ = parse2(impl_).unwrap();
ItemImpl { items, ..impl_ }
}
fn map_methods(trait_item: TraitItem, double_trait_name: &Ident) -> Option<ImplItem> {
if let TraitItem::Fn(fn_item) = trait_item {
let trait_item_fn = function_with_forwarding(fn_item, double_trait_name);
Some(ImplItem::Fn(trait_item_fn))
} else {
None
}
}
fn function_with_forwarding(fn_item: TraitItemFn, double_trait_name: &Ident) -> ImplItemFn {
let fn_name = fn_item.sig.ident.clone();
let async_invocation = if fn_item.sig.asyncness.is_some() {
quote! { .await }
} else {
quote! {}
};
let inputs = fn_item
.sig
.inputs
.clone()
.into_iter()
.map(parameter_to_argument);
ImplItemFn {
attrs: Vec::new(),
vis: Visibility::Inherited,
defaultness: None,
sig: fn_item.sig,
block: parse2(
quote! {{ <Self as #double_trait_name>::#fn_name(#(#inputs,)*)#async_invocation }},
)
.unwrap(),
}
}
fn parameter_to_argument(input: FnArg) -> TokenStream {
match input {
FnArg::Receiver(_) => quote! { self },
FnArg::Typed(PatType { pat, .. }) => {
quote! { # pat}
}
}
}
#[cfg(test)]
mod tests {
use super::{Ident, trait_impl};
use quote::quote;
use syn::{ItemTrait, parse2};
#[test]
fn forward_self() {
let (attr, item) = given(
quote! { MyTraitDummy },
quote! {
pub trait MyTrait {
fn foobar(&mut self);
}
},
);
let output = trait_impl(attr, item);
let output = quote! { #output };
let expected = quote! {
impl<T> MyTrait for T where T: MyTraitDummy {
fn foobar(&mut self) {
<Self as MyTraitDummy>::foobar(self,)
}
}
};
assert_eq!(expected.to_string(), output.to_string());
}
#[test]
fn forward_non_self_parameter() {
let (attr, item) = given(
quote! { MyTraitDummy },
quote! {
pub trait MyTrait {
fn foobar(x: i32);
}
},
);
let output = trait_impl(attr, item);
let output = quote! { #output };
let expected = quote! {
impl<T> MyTrait for T where T: MyTraitDummy {
fn foobar(x: i32) { <Self as MyTraitDummy>::foobar(x,) }
}
};
assert_eq!(expected.to_string(), output.to_string());
}
#[test]
fn forward_multiple_arguments() {
let (attr, item) = given(
quote! { MyTraitDummy },
quote! {
pub trait MyTrait {
fn foobar(one: i32, two: i32);
}
},
);
let output = trait_impl(attr, item);
let output = quote! { #output };
let expected = quote! {
impl<T> MyTrait for T where T: MyTraitDummy {
fn foobar(one: i32, two: i32) {
<Self as MyTraitDummy>::foobar(one, two,)
}
}
};
assert_eq!(expected.to_string(), output.to_string());
}
#[test]
fn forward_async() {
let (attr, item) = given(
quote! { MyTraitDummy },
quote! {
pub trait MyTrait {
async fn foobar(&mut self);
}
},
);
let output = trait_impl(attr, item);
let output = quote! { #output };
let expected = quote! {
impl<T> MyTrait for T where T: MyTraitDummy {
async fn foobar(&mut self) { <Self as MyTraitDummy>::foobar(self,).await }
}
};
assert_eq!(expected.to_string(), output.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)
}
}