1use proc_macro2::TokenStream;
2use proc_macro_error::{emit_error, proc_macro_error};
3use quote::{format_ident, quote};
4use std::borrow::Borrow;
5use syn::{
6 parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
7 DeriveInput, Fields, GenericParam, Generics, Ident, Index, Type, Variant,
8};
9
10#[proc_macro_derive(Spectacle)]
11#[proc_macro_error]
12pub fn derive_spectacle(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
13 let input = parse_macro_input!(input as DeriveInput);
14
15 let name = input.ident;
16 let generics = add_trait_bounds(input.generics);
17
18 let out = match input.data {
19 syn::Data::Struct(data) => impl_introspect_struct(&name, &generics, &data.fields),
20 syn::Data::Enum(data) => impl_introspect_enum(&name, &generics, &data.variants),
21 syn::Data::Union(_) => {
22 emit_error!(
23 name.span(),
24 "Spectacle can only be derived for structs and enums"
25 );
26
27 TokenStream::new()
28 }
29 };
30 out.into()
32}
33
34fn add_trait_bounds(mut generics: Generics) -> Generics {
36 for param in &mut generics.params {
37 if let GenericParam::Type(ref mut type_param) = *param {
38 type_param.bounds.push(parse_quote!(spectacle::Introspect));
39 type_param.bounds.push(parse_quote!('static));
40 }
41 }
42 generics
43}
44
45fn create_generic_ident(generics: &Generics) -> Ident {
47 let mut ident = Ident::new("F", generics.span());
48 let mut n: u8 = 0;
49 while generics.params.iter().any(|param| match param {
50 GenericParam::Type(param) => param.ident == ident,
51 GenericParam::Const(param) => param.ident == ident,
52 GenericParam::Lifetime(param) => param.lifetime.ident == ident,
53 }) {
54 ident = Ident::new(&format!("F{}", n), generics.span());
55 n += 1;
56 if n == std::u8::MAX {
57 emit_error!(
58 generics,
59 "could not generate an appropriate unused type parameter";
60 note = "#[derive(Spectacle)] must be able to generate an unused type parameter F or F{n} where n is in the u16 range";
61 help = "consider removing the type parameter F from your list of generic parameters";
62 );
63 }
64 }
65 ident
66}
67
68fn impl_introspect_struct(name: &Ident, generics: &Generics, fields: &Fields) -> TokenStream {
69 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
70 let f = create_generic_ident(&generics);
71 let field_names: Vec<_> = fields
72 .iter()
73 .enumerate()
74 .map(|(idx, field)| match field.ident {
75 Some(ref name) => quote!(self.#name),
76 None => {
77 let idx = Index::from(idx);
78 quote!(self.#idx)
79 }
80 })
81 .collect();
82 let recurse = recurse_fields(fields, |field_idx| field_names[field_idx].clone(), None)
83 .unwrap_or_default();
84
85 quote! {
86 impl #impl_generics spectacle::Introspect for #name #ty_generics #where_clause
87 {
88 fn introspect_from<#f>(&self, breadcrumbs: spectacle::Breadcrumbs, mut visit: #f)
89 where
90 #f: FnMut(&spectacle::Breadcrumbs, &dyn std::any::Any),
91 {
92 visit(&breadcrumbs, self);
93
94 #recurse
95 }
96 }
97 }
98}
99
100fn recurse_fields<Accessor>(
103 fields: &Fields,
104 access: Accessor,
105 inject_breadcrumb: Option<TokenStream>,
106) -> Option<TokenStream>
107where
108 Accessor: Fn(usize) -> TokenStream,
109{
110 let inject_breadcrumb = inject_breadcrumb.unwrap_or_default();
111
112 match fields {
113 Fields::Unit => None,
114 Fields::Named(fields) => {
115 let recurse = fields.named.iter().enumerate().map(|(idx, field)| {
116 let name = field.ident.clone().expect("named fields have names");
117 let name_lit = syn::LitStr::new(&name.to_string(), field.span());
118 let field = access(idx);
119
120 quote! {{
121 let mut breadcrumbs = breadcrumbs.clone();
122 #inject_breadcrumb
123 breadcrumbs.push_back(spectacle::Breadcrumb::Field(#name_lit));
124 spectacle::Introspect::introspect_from(&#field, breadcrumbs, &mut visit);
125 }}
126 });
127
128 Some(quote! { #( #recurse )* })
129 }
130 Fields::Unnamed(fields) => {
131 let recurse = fields.unnamed.iter().enumerate().map(|(idx, _)| {
132 let field = access(idx);
133
134 quote! {{
135 let mut breadcrumbs = breadcrumbs.clone();
136 #inject_breadcrumb
137 breadcrumbs.push_back(spectacle::Breadcrumb::TupleIndex(#idx));
138 spectacle::Introspect::introspect_from(&#field, breadcrumbs, &mut visit);
139 }}
140 });
141
142 Some(quote! { #( #recurse )* })
143 }
144 }
145}
146
147fn impl_introspect_enum(
148 name: &Ident,
149 generics: &Generics,
150 variants: &Punctuated<Variant, Comma>,
151) -> TokenStream {
152 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
153 let f = create_generic_ident(&generics);
154 let recurse = recurse_variants(variants);
155
156 quote! {
157 impl #impl_generics spectacle::Introspect for #name #ty_generics #where_clause
158 {
159 fn introspect_from<#f>(&self, breadcrumbs: spectacle::Breadcrumbs, mut visit: #f)
160 where
161 #f: FnMut(&spectacle::Breadcrumbs, &dyn std::any::Any),
162 {
163 visit(&breadcrumbs, self);
164
165 match self {
166 #( #recurse ),*
167 _ => {}
168 }
169 }
170 }
171 }
172}
173
174fn type_var<T>(t: T, n: Option<usize>) -> Ident
177where
178 T: Borrow<Type> + Spanned,
179{
180 let ident = match t.borrow() {
181 Type::Array(t) => return format_ident!("{}s", type_var(t.elem.borrow(), n)),
182 Type::Slice(t) => return format_ident!("{}s", type_var(t.elem.borrow(), n)),
183 Type::Group(t) => return type_var(t.elem.borrow(), n),
184 Type::Paren(t) => return type_var(t.elem.borrow(), n),
185 Type::Ptr(t) => return type_var(t.elem.borrow(), n),
186 Type::Reference(t) => return type_var(t.elem.borrow(), n),
187 Type::Path(t) => t
188 .path
189 .segments
190 .last()
191 .expect("type paths should not be empty")
192 .ident
193 .clone(),
194 Type::Tuple(t) => {
195 let names = t
196 .elems
197 .iter()
198 .map(|tt| type_var(tt, None).to_string())
199 .collect::<Vec<_>>();
200 format_ident!("{}", names.join("_"))
201 }
202 _ => {
203 emit_error!(
204 t.span(),
205 "cannot create appropriate type variable for this type"
206 );
207 format_ident!("_{}", n.unwrap_or_default())
208 }
209 };
210 match n {
211 None => ident,
212 Some(n) => format_ident!("{}{}", ident.to_string().to_lowercase(), n),
213 }
214}
215
216fn recurse_variants(variants: &Punctuated<Variant, Comma>) -> Vec<TokenStream> {
217 variants
218 .iter()
219 .filter_map(|variant| {
220 if variant.fields.is_empty() {
221 return None;
222 }
223
224 let name = &variant.ident;
225 let field_name: Vec<_> = variant
226 .fields
227 .iter()
228 .enumerate()
229 .map(|(idx, field)| {
230 let field_name = match field.ident {
231 Some(ref id) => id.clone(),
232 None => type_var(&field.ty, Some(idx)),
233 };
234 quote!(#field_name)
235 })
236 .collect();
237
238 let field_names = match variant.fields {
239 Fields::Named(_) => quote!({#( #field_name ),*}),
240 Fields::Unnamed(_) => quote!((#( #field_name ),*)),
241 _ => unreachable!(),
242 };
243 let variant_lit = syn::LitStr::new(&name.to_string(), name.span());
244 let recurse = recurse_fields(
245 &variant.fields,
246 |field_idx| field_name[field_idx].clone(),
247 Some(quote! {
248 breadcrumbs.push_back(spectacle::Breadcrumb::Variant(#variant_lit));
249 }),
250 );
251
252 Some(quote! {
253 Self::#name #field_names => {#recurse}
254 })
255 })
256 .collect()
257}