renderdoc_derive/
lib.rs

1//! Codegen crate for `renderdoc-rs` which generates trait implementation boilerplate.
2
3extern crate proc_macro;
4extern crate proc_macro2;
5#[macro_use]
6extern crate quote;
7#[macro_use]
8extern crate syn;
9
10use proc_macro::TokenStream;
11use proc_macro2::{Ident, TokenStream as TokenStream2};
12use syn::{Attribute, DeriveInput, Meta, MetaList, NestedMeta, Path};
13
14/// Generates API boilerplate for the `renderdoc` crate.
15///
16/// # Details
17///
18/// This macro expects a tuple struct of the form:
19///
20/// ```rust,ignore
21/// use renderdoc::Version;
22///
23/// struct Foo<T: Version>(Entry, PhantomData<T>);
24/// ```
25///
26/// Given the data structure above, this macro generates the following implementations:
27///
28/// * `From` conversions downgrading a newer API to a compatible older API.
29/// * Implementations of the `RenderDocV###` trait for each `Foo<V###>`.
30#[proc_macro_derive(RenderDoc, attributes(renderdoc_convert))]
31pub fn renderdoc(input: TokenStream) -> TokenStream {
32    let ast = parse_macro_input!(input as DeriveInput);
33    impl_renderdoc(&ast)
34}
35
36/// Generates RenderDoc API implementations locked by versions through traits.
37fn impl_renderdoc(ast: &DeriveInput) -> TokenStream {
38    let name = &ast.ident;
39    let apis = build_api_list(&ast.attrs);
40    let from_impls = gen_from_impls(&name, &apis, TokenStream2::new());
41
42    let expanded = quote! {
43        #from_impls
44    };
45
46    expanded.into()
47}
48
49/// Reads the `renderdoc_convert` list attribute and returns a list of API versions to implement.
50///
51/// Each API version in this list is a unique identifier of the form `V100`, `V110`, `V120`, etc.
52fn build_api_list(attrs: &[Attribute]) -> Vec<Path> {
53    let meta = attrs
54        .into_iter()
55        .flat_map(|attr| attr.parse_meta().ok())
56        .find(|meta| meta.path().is_ident("renderdoc_convert"))
57        .expect("Missing required attribute `#[renderdoc_convert(...)]`");
58
59    let mut apis: Vec<Path> = match meta {
60        Meta::List(MetaList { nested, .. }) => nested
61            .into_iter()
62            .flat_map(|elem| match elem {
63                NestedMeta::Meta(Meta::Path(api)) => Some(api),
64                _ => None,
65            })
66            .collect(),
67        _ => panic!("Expected list attribute `#[renderdoc_convert(...)]`"),
68    };
69
70    apis.sort_by_key(|api| api.segments.last().unwrap().ident.clone());
71    apis
72}
73
74/// Generates `From` implementations that permit downgrading of API versions. Unlike the
75/// `downgrade()` method, these `From` implementations let any version to downgrade to any other
76/// older backwards-compatible API version in a clean way.
77///
78/// This function takes a list of API versions sorted in ascending order and recursively generates
79/// `From` implementations for them. For instance, given the following three API versions
80/// `[V100, V110, V200]`, these trait implementations will be generated:
81///
82/// ```rust,ignore
83/// // V200 -> V110, V100
84///
85/// impl From<#name<V200>> for #name<V110>
86/// where
87///     Self: Sized,
88/// {
89///     fn from(newer: #name<V200>) -> Self {
90///         // ...
91///     }
92/// }
93///
94/// impl From<#name<V200>> for #name<V100>
95/// where
96///     Self: Sized,
97/// {
98///     fn from(newer: #name<V200>) -> Self {
99///         // ...
100///     }
101/// }
102///
103/// // V110 -> V100
104///
105/// impl From<#name<V110>> for #name<V100>
106/// where
107///     Self: Sized,
108/// {
109///     fn from(newer: #name<V200>) -> Self {
110///         // ...
111///     }
112/// }
113///
114/// // V100 -> ()
115/// ```
116fn gen_from_impls(name: &Ident, apis: &[Path], tokens: TokenStream2) -> TokenStream2 {
117    if apis.len() <= 1 {
118        return tokens;
119    }
120
121    let last_idx = apis.len() - 1;
122    let newer = &apis[last_idx];
123    let impls: TokenStream2 = apis[0..last_idx]
124        .iter()
125        .map(|older| {
126            quote! {
127                impl From<#name<#newer>> for #name<#older>
128                where
129                    Self: Sized,
130                {
131                    fn from(newer: #name<#newer>) -> Self {
132                        let #name(entry, _) = newer;
133                        #name(entry, PhantomData)
134                    }
135                }
136            }
137        })
138        .collect();
139
140    gen_from_impls(
141        name,
142        &apis[0..last_idx],
143        tokens.into_iter().chain(impls).collect(),
144    )
145}