extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;
use proc_macro::TokenStream;
use proc_macro2::{Ident, TokenStream as TokenStream2};
use syn::{Attribute, DeriveInput, Meta, MetaList, NestedMeta, Path};
#[proc_macro_derive(RenderDoc, attributes(renderdoc_convert))]
pub fn renderdoc(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
impl_renderdoc(&ast)
}
fn impl_renderdoc(ast: &DeriveInput) -> TokenStream {
let name = &ast.ident;
let apis = build_api_list(&ast.attrs);
let from_impls = gen_from_impls(&name, &apis, TokenStream2::new());
let expanded = quote! {
#from_impls
};
expanded.into()
}
fn build_api_list(attrs: &[Attribute]) -> Vec<Path> {
let meta = attrs
.into_iter()
.flat_map(|attr| attr.parse_meta().ok())
.find(|meta| meta.path().is_ident("renderdoc_convert"))
.expect("Missing required attribute `#[renderdoc_convert(...)]`");
let mut apis: Vec<Path> = match meta {
Meta::List(MetaList { nested, .. }) => nested
.into_iter()
.flat_map(|elem| match elem {
NestedMeta::Meta(Meta::Path(api)) => Some(api),
_ => None,
})
.collect(),
_ => panic!("Expected list attribute `#[renderdoc_convert(...)]`"),
};
apis.sort_by_key(|api| api.segments.last().unwrap().ident.clone());
apis
}
fn gen_from_impls(name: &Ident, apis: &[Path], tokens: TokenStream2) -> TokenStream2 {
if apis.len() <= 1 {
return tokens;
}
let last_idx = apis.len() - 1;
let newer = &apis[last_idx];
let impls: TokenStream2 = apis[0..last_idx]
.iter()
.map(|older| {
quote! {
impl From<#name<#newer>> for #name<#older>
where
Self: Sized,
{
fn from(newer: #name<#newer>) -> Self {
let #name(entry, _) = newer;
#name(entry, PhantomData)
}
}
}
})
.collect();
gen_from_impls(
name,
&apis[0..last_idx],
tokens.into_iter().chain(impls).collect(),
)
}