use quote::quote;
use syn::{Error, Ident, ItemTrait, parse_macro_input};
mod double_trait;
mod dummy_impl;
mod trait_impl;
use self::{double_trait::double_trait, trait_impl::trait_impl};
#[proc_macro_attribute]
pub fn double(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let double_name = parse_macro_input!(attr as Ident);
let item = parse_macro_input!(item as ItemTrait);
let output = expand(double_name, item).unwrap_or_else(Error::into_compile_error);
proc_macro::TokenStream::from(output)
}
fn expand(double_trait_name: Ident, org_trait: ItemTrait) -> syn::Result<proc_macro2::TokenStream> {
let double_trait = double_trait(double_trait_name.clone(), org_trait.clone())?;
let trait_impl = trait_impl(double_trait_name.clone(), org_trait.clone());
let dummy_impl = dummy_impl::dummy_impl(double_trait_name, org_trait.clone());
let token_stream = quote! {
#org_trait
#double_trait
#trait_impl
#dummy_impl
};
Ok(token_stream)
}
#[cfg(test)]
mod tests {
use super::{Ident, expand};
use quote::quote;
use syn::{ItemTrait, parse2};
#[test]
fn generate_double_trait() {
let (attr, item) = given(quote! { MyTraitDummy }, quote! { trait MyTrait {} });
let output = expand(attr, item).unwrap();
let expected = quote! {
trait MyTrait {}
trait MyTraitDummy {}
impl<T> MyTrait for T where T: MyTraitDummy {}
impl MyTraitDummy for double_trait::Dummy {}
};
assert_eq!(expected.to_string(), output.to_string());
}
#[test]
fn forward_visibility() {
let (attr, item) = given(quote! { MyTraitDummy }, quote! { pub trait MyTrait {} });
let output = expand(attr, item).unwrap();
let expected = quote! {
pub trait MyTrait {}
pub trait MyTraitDummy {}
impl<T> MyTrait for T where T: MyTraitDummy {}
impl MyTraitDummy for double_trait::Dummy {}
};
assert_eq!(expected.to_string(), output.to_string());
}
#[test]
fn forward_method() {
let (attr, item) = given(
quote! { MyTraitDummy },
quote! {
trait MyTrait {
fn foobar(&self);
}
},
);
let output = expand(attr, item).unwrap();
let expected = quote! {
trait MyTrait {
fn foobar(&self);
}
trait MyTraitDummy {
fn foobar (&self) { unimplemented!() }
}
impl<T> MyTrait for T where T: MyTraitDummy {
fn foobar(&self) { <Self as MyTraitDummy>::foobar(self,) }
}
impl MyTraitDummy for double_trait::Dummy {}
};
assert_eq!(expected.to_string(), output.to_string());
}
#[test]
fn respect_existing_default_impl() {
let (attr, item) = given(
quote! { MyTraitDummy },
quote! {
pub trait MyTrait {
fn foobar() { println!("Hello Default!") }
}
},
);
let output = expand(attr, item).unwrap();
let expected = quote! {
pub trait MyTrait {
fn foobar() { println!("Hello Default!") }
}
pub trait MyTraitDummy {
fn foobar() { println!("Hello Default!") }
}
impl<T> MyTrait for T where T: MyTraitDummy {
fn foobar() { <Self as MyTraitDummy>::foobar() }
}
impl MyTraitDummy for double_trait::Dummy {}
};
assert_eq!(expected.to_string(), output.to_string());
}
#[test]
fn forward_async_method() {
let (attr, item) = given(
quote! { MyTraitDummy },
quote! {
trait MyTrait {
async fn foobar(&self);
}
},
);
let output = expand(attr, item).unwrap();
let expected = quote! {
trait MyTrait {
async fn foobar(&self);
}
trait MyTraitDummy {
async fn foobar (&self) { unimplemented!() }
}
impl<T> MyTrait for T where T: MyTraitDummy {
async fn foobar(&self) { <Self as MyTraitDummy>::foobar(self,).await }
}
impl MyTraitDummy for double_trait::Dummy {}
};
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)
}
}