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}