ruffle/core/macros/src/lib.rs

146 lines
4.7 KiB
Rust

//! Proc macros used by Ruffle to generate various boilerplate.
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, parse_quote, FnArg, ImplItem, ImplItemMethod, ItemEnum, ItemTrait, Pat,
TraitItem, Visibility,
};
/// `enum_trat_object` will define an enum whose variants each implement a trait.
/// It can be used as faux-dynamic dispatch. This is used as an alternative to a
/// trait object, which doesn't get along with GC'd types.
///
/// This will auto-implement the trait for the enum, delegating all methods to the
/// underlying type. Additionally, `From` will be implemented for all of the variants,
/// so an underlying type can easily be converted into the enum.
///
/// TODO: This isn't completely robust for all cases, but should be good enough
/// for our usage.
///
/// Usage:
/// ```
/// use ruffle_macros::enum_trait_object;
///
/// #[enum_trait_object(
/// pub enum MyTraitEnum {
/// Object(Object)
/// }
/// )]
/// trait MyTrait {}
///
/// struct Object {}
/// impl MyTrait for Object {}
/// ```
#[proc_macro_attribute]
pub fn enum_trait_object(args: TokenStream, item: TokenStream) -> TokenStream {
// Parse the input.
let input_trait = parse_macro_input!(item as ItemTrait);
let trait_name = input_trait.ident.clone();
let trait_generics = input_trait.generics.clone();
let enum_input = parse_macro_input!(args as ItemEnum);
let enum_name = enum_input.ident.clone();
if trait_generics.lifetimes().count() > 1 {
panic!("Only one lifetime parameter is currently supported");
}
if trait_generics.type_params().count() > 1 {
panic!("Generic type parameters are currently unsupported");
}
if trait_generics != enum_input.generics {
panic!("Trait and enum should have the same generic parameters");
}
// Implement each trait. This will match against each enum variant and delegate
// to the underlying type.
let trait_methods: Vec<_> = input_trait
.items
.iter()
.map(|item| match item {
TraitItem::Method(method) => {
let method_name = method.sig.ident.clone();
let params: Vec<_> = method
.sig
.inputs
.iter()
.filter_map(|arg| {
if let FnArg::Typed(arg) = arg {
if let Pat::Ident(i) = &*arg.pat {
let arg_name = i.ident.clone();
return Some(quote!(#arg_name,));
}
}
None
})
.collect();
let match_arms: Vec<_> = enum_input
.variants
.iter()
.map(|variant| {
let variant_name = variant.ident.clone();
quote! {
#enum_name::#variant_name(o) => o.#method_name(#(#params)*),
}
})
.collect();
let method_block = quote!({
match self {
#(#match_arms)*
}
});
ImplItem::Method(ImplItemMethod {
attrs: method.attrs.clone(),
vis: Visibility::Inherited,
defaultness: None,
sig: method.sig.clone(),
block: parse_quote!(#method_block),
})
}
_ => panic!("Unsupported trait item: {:?}", item),
})
.collect();
let (impl_generics, ty_generics, where_clause) = trait_generics.split_for_impl();
// Implement `From` for each variant type.
let from_impls: Vec<_> = enum_input
.variants
.iter()
.map(|variant| {
let variant_name = variant.ident.clone();
let variant_type = variant
.fields
.iter()
.next()
.expect("Missing field for enum variant")
.ty
.clone();
quote!(
impl #impl_generics From<#variant_type> for #enum_name #ty_generics {
fn from(obj: #variant_type) -> #enum_name #trait_generics {
#enum_name::#variant_name(obj)
}
}
)
})
.collect();
let out = quote!(
#input_trait
#enum_input
impl #impl_generics #trait_name #ty_generics for #enum_name #ty_generics #where_clause {
#(#trait_methods)*
}
#(#from_impls)*
);
out.into()
}