extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, parse_quote, FnArg, Generics, Ident, ItemTrait, TraitItem, TraitItemMethod,
};
fn either_method(method: &TraitItemMethod) -> proc_macro2::TokenStream {
let sig = &method.sig;
let name = &sig.ident;
if let FnArg::Receiver(_) = sig.inputs[0] {
let args_left = sig.inputs.iter().skip(1).map(|arg| {
if let FnArg::Typed(arg) = arg {
&arg.pat
} else {
unreachable!()
}
});
let args_right = args_left.clone();
quote! {
#sig {
match self {
::either::Either::Left(left) => left.#name(#(#args_left),*),
::either::Either::Right(right) => right.#name(#(#args_right),*),
}
}
}
} else {
panic!("The first parameter of a trait method must be `self`, `&self` or `&mut self`.")
}
}
fn impl_item(name: &Ident, generics: &Generics) -> proc_macro2::TokenStream {
let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let mut extended_generics = generics.clone();
assert!(
extended_generics.type_params().all(|param| {
let name = param.ident.to_string();
name != "__L" && name != "__R"
}),
"Generic type parameters must not be `__L` or `__R`."
);
extended_generics
.params
.push(parse_quote!(__L: #name #ty_generics));
extended_generics
.params
.push(parse_quote!(__R: #name #ty_generics));
quote! {
impl #extended_generics #name #ty_generics for ::either::Either<__L, __R> #where_clause
}
}
#[proc_macro_attribute]
pub fn either_trait(_args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemTrait);
let name = &input.ident;
let items = &input.items;
let impl_item = impl_item(&name, &input.generics);
let impl_methods = items.iter().map(|item| match item {
TraitItem::Method(method) => either_method(method),
_ => panic!("The trait must be without associated constants or associated types."),
});
let expand = quote! {
#input
#impl_item
{
#(#impl_methods)*
}
};
TokenStream::from(expand)
}