use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
parse::{discouraged::Speculative, Parse},
parse_macro_input,
punctuated::Punctuated,
ItemEnum, ItemFn, ItemImpl, ItemStruct, ItemTrait, ItemType, ItemUnion, Signature, Token,
TraitItemFn, TraitItemType, WhereClause, WherePredicate,
};
enum Item {
Enum(ItemEnum),
Fn(ItemFn),
Impl(ItemImpl),
Struct(ItemStruct),
Trait(ItemTrait),
Type(ItemType),
Union(ItemUnion),
AssocType(TraitItemType),
FnDecl(TraitItemFn),
}
impl Item {
fn make_where_clause(&mut self) -> &mut WhereClause {
let generics = match self {
Item::Enum(ItemEnum { generics, .. })
| Item::Fn(ItemFn {
sig: Signature { generics, .. },
..
})
| Item::Impl(ItemImpl { generics, .. })
| Item::Struct(ItemStruct { generics, .. })
| Item::Trait(ItemTrait { generics, .. })
| Item::Type(ItemType { generics, .. })
| Item::Union(ItemUnion { generics, .. })
| Item::AssocType(TraitItemType { generics, .. })
| Item::FnDecl(TraitItemFn {
sig: Signature { generics, .. },
..
}) => generics,
};
generics.make_where_clause()
}
}
impl Parse for Item {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let fork = input.fork();
if let Ok(item) = fork
.parse::<syn::Item>()
.map_or(Err(()), |item| match item {
syn::Item::Enum(item) => Ok(Item::Enum(item)),
syn::Item::Fn(item) => Ok(Item::Fn(item)),
syn::Item::Impl(item) => Ok(Item::Impl(item)),
syn::Item::Struct(item) => Ok(Item::Struct(item)),
syn::Item::Trait(item) => Ok(Item::Trait(item)),
syn::Item::Type(item) => Ok(Item::Type(item)),
syn::Item::Union(item) => Ok(Item::Union(item)),
_ => Err(()),
})
{
input.advance_to(&fork);
return Ok(item);
}
if let Ok(item) = input
.parse::<syn::TraitItem>()
.map_or(Err(()), |item| match item {
syn::TraitItem::Fn(item) => Ok(Item::FnDecl(item)),
syn::TraitItem::Type(item) => Ok(Item::AssocType(item)),
_ => Err(()),
})
{
return Ok(item);
}
Err(input.error("Unexpected item."))
}
}
impl ToTokens for Item {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Item::Enum(item) => item.to_tokens(tokens),
Item::Fn(item) => item.to_tokens(tokens),
Item::Impl(item) => item.to_tokens(tokens),
Item::Struct(item) => item.to_tokens(tokens),
Item::Trait(item) => item.to_tokens(tokens),
Item::Type(item) => item.to_tokens(tokens),
Item::Union(item) => item.to_tokens(tokens),
Item::AssocType(item) => item.to_tokens(tokens),
Item::FnDecl(item) => item.to_tokens(tokens),
}
}
}
#[proc_macro_attribute]
pub fn bounds(
attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let parser = Punctuated::<WherePredicate, Token![,]>::parse_terminated;
let attr = parse_macro_input!(attr with parser);
match syn::parse::<Item>(input) {
Ok(mut item) => {
let where_clause = item.make_where_clause();
where_clause.predicates.extend(attr);
item.into_token_stream().into()
}
Err(_) => {
quote! {
compile_error!("The attribute may only be applied to `struct`s, `enum`s, `union`s, `trait`s, `fn`s, `type`s, and `impl` blocks.");
}.into()
}
}
}