1use std::collections::HashSet;
5
6use attribute_derive::FromAttr;
7use manyhow::manyhow;
8use proc_macro2::{Span, TokenStream};
9use quote::quote;
10use syn::{GenericParam, Generics, Lifetime, TraitBound};
11
12#[manyhow]
16#[proc_macro_derive(MapAs, attributes(map_as))]
17pub fn derive_map_as(input: syn::Item) -> manyhow::Result {
18 match input {
19 syn::Item::Struct(item) => derive_map_as_inner(&item.ident, &item.generics, &item.attrs),
20 syn::Item::Enum(item) => derive_map_as_inner(&item.ident, &item.generics, &item.attrs),
21 _ => manyhow::bail!("Collectable can only derived on structs and enums"),
22 }
23}
24
25fn derive_map_as_inner(
26 ident: &syn::Ident,
27 generics: &syn::Generics,
28 attrs: &[syn::Attribute],
29) -> manyhow::Result {
30 let (impl_gen, type_gen, where_clause) = generics.split_for_impl();
31 let attr = MapAsAttr::from_attributes(attrs)?;
32
33 match (&attr.target, &attr.map) {
34 (Some(target), map_as) => {
35 let map_as = if let Some(map_as) = map_as {
36 quote!(#map_as)
37 } else {
38 quote!(self)
39 };
40 Ok(quote! {
41 impl<#impl_gen> refuse::MapAs for #ident<#type_gen> #where_clause {
42 type Target = #target;
43
44 fn map_as(&self) -> &Self::Target {
45 #map_as
46 }
47 }
48 })
49 }
50 (None, None) => Ok(quote! {
51 impl<#impl_gen> refuse::NoMapping for #ident<#type_gen> #where_clause {}
52 }),
53 (_, Some(map_as)) => {
54 manyhow::bail!(map_as, "target must be specified when map is provided")
55 }
56 }
57}
58
59#[manyhow]
63#[proc_macro_derive(Trace, attributes(trace))]
64pub fn derive_trace(input: syn::Item) -> manyhow::Result {
65 match input {
66 syn::Item::Struct(item) => derive_struct_trace(item),
67 syn::Item::Enum(item) => derive_enum_trace(item),
68 _ => manyhow::bail!("Collectable can only derived on structs and enums"),
69 }
70}
71
72fn field_accessor(field: &syn::Field, index: usize) -> TokenStream {
73 if let Some(ident) = field.ident.clone() {
74 quote!(#ident)
75 } else {
76 let index = proc_macro2::Literal::usize_unsuffixed(index);
77 quote!(#index)
78 }
79}
80
81fn derive_struct_trace(
82 syn::ItemStruct {
83 ident,
84 mut generics,
85 fields,
86 ..
87 }: syn::ItemStruct,
88) -> manyhow::Result {
89 require_trace_for_generics(&mut generics);
90 let (impl_gen, type_gen, where_clause) = generics.split_for_impl();
91
92 let mut fields_and_types = Vec::new();
93 for (index, field) in fields.iter().enumerate() {
94 let field_attr = TraceFieldAttr::from_attributes(&field.attrs)?;
95 if !field_attr.ignore {
96 let field_accessor = field_accessor(field, index);
97
98 fields_and_types.push((field_accessor, field.ty.clone()));
99 }
100 }
101
102 let types = fields_and_types
103 .iter()
104 .map(|(_, ty)| ty.clone())
105 .collect::<HashSet<_>>();
106 let type_mays = types
107 .into_iter()
108 .map(|ty| quote! {<#ty as refuse::Trace>::MAY_CONTAIN_REFERENCES});
109 let may_contain_refs = quote! {
110 #(#type_mays |)* false
111 };
112
113 let traces = fields_and_types.iter().map(|(field, _)| {
114 quote! {refuse::Trace::trace(&self.#field, tracer)}
115 });
116
117 Ok(quote! {
118 impl #impl_gen refuse::Trace for #ident #type_gen #where_clause {
119 const MAY_CONTAIN_REFERENCES: bool = #may_contain_refs;
120
121 fn trace(&self, tracer: &mut refuse::Tracer) {
122 #(#traces;)*
123 }
124 }
125 })
126}
127
128fn require_trace_for_generics(generics: &mut Generics) {
129 for mut pair in generics.params.pairs_mut() {
130 if let GenericParam::Type(t) = pair.value_mut() {
131 t.bounds.push(syn::TypeParamBound::Trait(
132 TraitBound::from_input(quote!(refuse::Trace)).unwrap(),
133 ));
134 t.bounds.push(syn::TypeParamBound::Trait(
135 TraitBound::from_input(quote!(::std::marker::Send)).unwrap(),
136 ));
137 t.bounds.push(syn::TypeParamBound::Trait(
138 TraitBound::from_input(quote!(::std::marker::Sync)).unwrap(),
139 ));
140 t.bounds.push(syn::TypeParamBound::Lifetime(
141 Lifetime::from_input(quote!('static)).unwrap(),
142 ));
143 }
144 }
145}
146
147fn derive_enum_trace(
148 syn::ItemEnum {
149 ident: enum_name,
150 mut generics,
151 variants,
152 ..
153 }: syn::ItemEnum,
154) -> manyhow::Result {
155 require_trace_for_generics(&mut generics);
156 let (impl_gen, type_gen, where_clause) = generics.split_for_impl();
157
158 let mut all_types = HashSet::new();
159 let mut traces = Vec::new();
160
161 for syn::Variant {
162 ident,
163 fields,
164 attrs,
165 ..
166 } in &variants
167 {
168 let field_attr = TraceFieldAttr::from_attributes(attrs)?;
169 let trace = match fields {
170 syn::Fields::Named(fields) => {
171 if field_attr.ignore {
172 quote!(Self::#ident { .. } => {})
173 } else {
174 let mut field_names = Vec::new();
175 for field in &fields.named {
176 let field_attr = TraceFieldAttr::from_attributes(&field.attrs)?;
177 if !field_attr.ignore {
178 all_types.insert(field.ty.clone());
179
180 let name = field.ident.clone().expect("name missing");
181
182 field_names.push(name);
183 }
184 }
185 quote! {Self::#ident { #(#field_names,)* } => {
186 #(refuse::Trace::trace(#field_names, tracer);)*
187 }}
188 }
189 }
190 syn::Fields::Unnamed(fields) => {
191 if field_attr.ignore {
192 quote!(Self::#ident(..) => {})
193 } else {
194 let mut field_names = Vec::new();
195 for (index, field) in fields.unnamed.iter().enumerate() {
196 let field_attr = TraceFieldAttr::from_attributes(&field.attrs)?;
197 if !field_attr.ignore {
198 all_types.insert(field.ty.clone());
199
200 field_names
201 .push(syn::Ident::new(&format!("f{index}"), Span::call_site()));
202 }
203 }
204 quote! {Self::#ident ( #(#field_names,)* ) => {
205 #(refuse::Trace::trace(#field_names, tracer);)*
206 }}
207 }
208 }
209 syn::Fields::Unit => {
210 quote! {Self::#ident => {}}
211 }
212 };
213 traces.push(trace);
214 }
215
216 let type_mays = all_types
217 .into_iter()
218 .map(|ty| quote! {<#ty as refuse::Trace>::MAY_CONTAIN_REFERENCES});
219 let may_contain_refs = quote! {
220 #(#type_mays |)* false
221 };
222 let traces = if traces.is_empty() {
223 TokenStream::default()
224 } else {
225 quote!(
226 match self {
227 #(#traces)*
228 }
229 )
230 };
231 Ok(quote! {
232 impl #impl_gen refuse::Trace for #enum_name #type_gen #where_clause {
233 const MAY_CONTAIN_REFERENCES: bool = #may_contain_refs;
234
235 fn trace(&self, tracer: &mut refuse::Tracer) {
236 #traces
237 }
238 }
239 })
240}
241
242#[derive(FromAttr)]
243#[attribute(ident = map_as)]
244struct MapAsAttr {
245 target: Option<syn::Type>,
246 map: Option<syn::Expr>,
247}
248
249#[derive(FromAttr)]
250#[attribute(ident = trace)]
251struct TraceFieldAttr {
252 ignore: bool,
253}