frunk_proc_macro_helpers/
lib.rs1#![doc(html_playground_url = "https://play.rust-lang.org/")]
2extern crate frunk_core;
11extern crate proc_macro;
12extern crate proc_macro2;
13
14#[macro_use]
15extern crate quote;
16extern crate syn;
17
18use proc_macro::TokenStream;
19use proc_macro2::{Span, TokenStream as TokenStream2};
20use quote::ToTokens;
21use syn::spanned::Spanned;
22use syn::{
23 DeriveInput, Expr, Field, Fields, GenericParam, Generics, Ident, Lifetime, LifetimeParam,
24 Member, Variant,
25};
26
27const ALPHA_CHARS: &[char] = &[
29 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
30 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
31 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
32];
33
34const UNDERSCORE_CHARS: &[char] = &['_', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
36
37pub fn to_ast(input: TokenStream) -> DeriveInput {
41 syn::parse(input).unwrap()
43}
44
45pub fn call_site_ident(s: &str) -> Ident {
47 Ident::new(s, Span::call_site())
48}
49
50pub fn build_hlist_type<L: IntoIterator>(items: L) -> TokenStream2
53where
54 L::Item: ToTokens,
55 L::IntoIter: DoubleEndedIterator,
56{
57 let mut result = quote! { ::frunk_core::hlist::HNil };
58 for item in items.into_iter().rev() {
59 result = quote! { ::frunk_core::hlist::HCons<#item, #result> }
60 }
61 result
62}
63
64pub fn build_hlist_constr<L: IntoIterator>(items: L) -> TokenStream2
67where
68 L::Item: ToTokens,
69 L::IntoIter: DoubleEndedIterator,
70{
71 let mut result = quote! { ::frunk_core::hlist::HNil };
72 for item in items.into_iter().rev() {
73 result = quote! { ::frunk_core::hlist::HCons { head: #item, tail: #result }}
74 }
75 result
76}
77
78pub fn build_coprod_type<L: IntoIterator>(items: L) -> TokenStream2
81where
82 L::Item: ToTokens,
83 L::IntoIter: DoubleEndedIterator,
84{
85 let mut result = quote! { ::frunk_core::coproduct::CNil };
86 for item in items.into_iter().rev() {
87 result = quote! { ::frunk_core::coproduct::Coproduct<#item, #result> }
88 }
89 result
90}
91
92pub fn build_coprod_constr(index: usize, item: impl ToTokens) -> TokenStream2 {
95 let mut result = quote! { ::frunk_core::coproduct::Coproduct::Inl(#item) };
96 for _ in 0..index {
97 result = quote! { ::frunk_core::coproduct::Coproduct::Inr(#result) }
98 }
99 result
100}
101
102pub fn build_coprod_unreachable_arm(length: usize, deref: bool) -> TokenStream2 {
106 let mut result = quote! { _frunk_unreachable_ };
107 for _ in 0..length {
108 result = quote! { ::frunk_core::coproduct::Coproduct::Inr(#result)}
109 }
110 if deref {
111 quote! { #result => match *_frunk_unreachable_ {} }
112 } else {
113 quote! { #result => match _frunk_unreachable_ {} }
114 }
115}
116
117pub fn build_field_type(name: &Ident, inner_type: impl ToTokens) -> TokenStream2 {
118 let label_type = build_label_type(name);
119 quote! { ::frunk_core::labelled::Field<#label_type, #inner_type> }
120}
121pub fn build_field_expr(name: &Ident, inner_expr: impl ToTokens) -> TokenStream2 {
122 let label_type = build_label_type(name);
123 let literal_name = name.to_string();
124 quote! { ::frunk_core::labelled::field_with_name::<#label_type, _>(#literal_name, #inner_expr) }
125}
126pub fn build_field_pat(inner_pat: impl ToTokens) -> TokenStream2 {
127 quote! { ::frunk_core::labelled::Field { value: #inner_pat, .. } }
128}
129
130pub fn build_label_type(ident: &Ident) -> impl ToTokens {
135 let as_string = ident.to_string();
136 let name = as_string.as_str();
137 let name_as_idents: Vec<Ident> = name.chars().flat_map(|c| encode_as_ident(&c)).collect();
138 let name_as_tokens: Vec<_> = name_as_idents
139 .iter()
140 .map(|ident| {
141 quote! { ::frunk_core::labelled::chars::#ident }
142 })
143 .collect();
144 quote! { (#(#name_as_tokens),*) }
145}
146
147fn encode_as_ident(c: &char) -> Vec<Ident> {
155 if ALPHA_CHARS.contains(c) {
156 vec![call_site_ident(&c.to_string())]
157 } else if UNDERSCORE_CHARS.contains(c) {
158 vec![call_site_ident(&format!("_{}", c))]
159 } else {
160 let as_unicode = c.escape_unicode();
162 let delimited_hex = as_unicode.filter(|c| c.is_alphanumeric());
166 let mut hex_idents: Vec<Ident> = delimited_hex.flat_map(|c| encode_as_ident(&c)).collect();
167 let mut book_ended: Vec<Ident> = vec![call_site_ident("_uc")];
169 book_ended.append(&mut hex_idents);
170 book_ended.push(call_site_ident("uc_"));
171 book_ended
172 }
173}
174
175pub fn build_path_type(path_expr: Expr) -> impl ToTokens {
176 let idents = find_idents_in_expr(path_expr);
177 idents
178 .iter()
179 .map(build_label_type)
180 .fold(quote!(::frunk_core::hlist::HNil), |acc, t| {
181 quote! {
182 ::frunk_core::path::Path<
183 ::frunk_core::hlist::HCons<
184 #t,
185 #acc
186 >
187 >
188 }
189 })
190}
191
192pub fn find_idents_in_expr(path_expr: Expr) -> Vec<Ident> {
194 fn go(current: Expr, mut v: Vec<Ident>) -> Vec<Ident> {
195 match current {
196 Expr::Field(e) => {
197 let m = e.member;
198 match m {
199 Member::Named(i) => {
200 v.push(i);
201 }
202 _ => panic!("Only named access is supported"),
203 }
204 go(*e.base, v)
205 }
206 Expr::Path(p) => {
207 if p.path.segments.len() != 1 {
208 panic!("Invalid name; this has collons in it")
209 } else {
210 let i = p.path.segments[0].ident.clone();
211 v.push(i);
212 v
213 }
214 }
215 _ => panic!("Invalid input"),
216 }
217 }
218 go(path_expr, Vec::new())
219}
220
221pub enum StructType {
222 Named,
223 Tuple,
224 Unit,
225}
226
227pub struct FieldBinding {
228 pub field: Field,
229 pub binding: Ident,
230}
231
232impl FieldBinding {
233 pub fn build_type(&self) -> TokenStream2 {
234 let ty = &self.field.ty;
235 quote! { #ty }
236 }
237 pub fn build_type_ref(&self) -> TokenStream2 {
238 let ty = &self.field.ty;
239 quote! { &'_frunk_ref_ #ty }
240 }
241 pub fn build_type_mut(&self) -> TokenStream2 {
242 let ty = &self.field.ty;
243 quote! { &'_frunk_ref_ mut #ty }
244 }
245 pub fn build(&self) -> TokenStream2 {
246 let binding = &self.binding;
247 quote! { #binding }
248 }
249 pub fn build_pat_ref(&self) -> TokenStream2 {
250 let binding = &self.binding;
251 quote! { ref #binding }
252 }
253 pub fn build_pat_mut(&self) -> TokenStream2 {
254 let binding = &self.binding;
255 quote! { ref mut #binding }
256 }
257 pub fn build_field_type(&self) -> TokenStream2 {
258 build_field_type(&self.binding, self.build_type())
259 }
260 pub fn build_field_type_ref(&self) -> TokenStream2 {
261 build_field_type(&self.binding, self.build_type_ref())
262 }
263 pub fn build_field_type_mut(&self) -> TokenStream2 {
264 build_field_type(&self.binding, self.build_type_mut())
265 }
266 pub fn build_field_expr(&self) -> TokenStream2 {
267 build_field_expr(&self.binding, &self.binding)
268 }
269 pub fn build_field_pat(&self) -> TokenStream2 {
270 build_field_pat(&self.binding)
271 }
272}
273
274pub struct FieldBindings {
277 pub type_: StructType,
278 pub fields: Vec<FieldBinding>,
279}
280
281impl FieldBindings {
282 pub fn new(fields: &Fields) -> Self {
283 Self {
284 type_: match fields {
285 Fields::Named(_) => StructType::Named,
286 Fields::Unnamed(_) => StructType::Tuple,
287 Fields::Unit => StructType::Unit,
288 },
289 fields: fields
290 .iter()
291 .enumerate()
292 .map(|(index, field)| FieldBinding {
293 field: field.clone(),
294 binding: field
295 .ident
296 .clone()
297 .unwrap_or_else(|| Ident::new(&format!("_{}", index), field.span())),
298 })
299 .collect(),
300 }
301 }
302
303 pub fn build_type_constr<R: ToTokens>(&self, f: impl Fn(&FieldBinding) -> R) -> TokenStream2 {
306 let bindings: Vec<_> = self.fields.iter().map(f).collect();
307 match self.type_ {
308 StructType::Named => quote! { { #(#bindings,)* } },
309 StructType::Tuple => quote! { ( #(#bindings,)* ) },
310 StructType::Unit => TokenStream2::new(),
311 }
312 }
313
314 pub fn build_hlist_type<R: ToTokens>(&self, f: impl Fn(&FieldBinding) -> R) -> TokenStream2 {
315 build_hlist_type(self.fields.iter().map(f))
316 }
317
318 pub fn build_hlist_constr<R: ToTokens>(&self, f: impl Fn(&FieldBinding) -> R) -> TokenStream2 {
319 build_hlist_constr(self.fields.iter().map(f))
320 }
321}
322
323pub fn ref_generics(generics: &Generics) -> Generics {
324 let mut generics_ref = generics.clone();
325
326 let ref_lifetime = Lifetime::new("'_frunk_ref_", Span::call_site());
328 let ref_lifetime_def = LifetimeParam::new(ref_lifetime.clone());
329
330 {
333 let generics_ref_lifetimes_mut = generics_ref.lifetimes_mut();
334 for existing_lifetime_mut in generics_ref_lifetimes_mut {
335 existing_lifetime_mut.bounds.push(ref_lifetime.clone());
336 }
337 }
338
339 let ref_lifetime_generic = GenericParam::Lifetime(ref_lifetime_def);
341 generics_ref.params.push(ref_lifetime_generic);
342
343 generics_ref
344}
345
346pub struct VariantBinding {
347 pub name: Ident,
348 pub fields: FieldBindings,
349}
350
351impl VariantBinding {
352 pub fn build_type_constr(&self) -> TokenStream2 {
353 let name = &self.name;
354 let constr = self.fields.build_type_constr(FieldBinding::build);
355 quote! { #name #constr }
356 }
357 pub fn build_type_pat_ref(&self) -> TokenStream2 {
358 let name = &self.name;
359 let constr = self.fields.build_type_constr(FieldBinding::build_pat_ref);
360 quote! { #name #constr }
361 }
362 pub fn build_type_pat_mut(&self) -> TokenStream2 {
363 let name = &self.name;
364 let constr = self.fields.build_type_constr(FieldBinding::build_pat_mut);
365 quote! { #name #constr }
366 }
367 pub fn build_hlist_field_type(&self) -> TokenStream2 {
368 build_field_type(
369 &self.name,
370 self.fields.build_hlist_type(FieldBinding::build_field_type),
371 )
372 }
373 pub fn build_hlist_field_type_ref(&self) -> TokenStream2 {
374 build_field_type(
375 &self.name,
376 self.fields
377 .build_hlist_type(FieldBinding::build_field_type_ref),
378 )
379 }
380 pub fn build_hlist_field_type_mut(&self) -> TokenStream2 {
381 build_field_type(
382 &self.name,
383 self.fields
384 .build_hlist_type(FieldBinding::build_field_type_mut),
385 )
386 }
387 pub fn build_hlist_field_expr(&self) -> TokenStream2 {
388 build_field_expr(
389 &self.name,
390 self.fields
391 .build_hlist_constr(FieldBinding::build_field_expr),
392 )
393 }
394 pub fn build_hlist_field_pat(&self) -> TokenStream2 {
395 build_field_pat(
396 self.fields
397 .build_hlist_constr(FieldBinding::build_field_pat),
398 )
399 }
400}
401
402pub struct VariantBindings {
403 pub variants: Vec<VariantBinding>,
404}
405
406impl VariantBindings {
407 pub fn new<'a>(data: impl IntoIterator<Item = &'a Variant>) -> Self {
408 VariantBindings {
409 variants: data
410 .into_iter()
411 .map(|variant| VariantBinding {
412 name: variant.ident.clone(),
413 fields: FieldBindings::new(&variant.fields),
414 })
415 .collect(),
416 }
417 }
418
419 pub fn build_coprod_type<R: ToTokens>(&self, f: impl Fn(&VariantBinding) -> R) -> TokenStream2 {
420 build_coprod_type(self.variants.iter().map(f))
421 }
422
423 pub fn build_coprod_constrs<R: ToTokens>(
424 &self,
425 f: impl Fn(&VariantBinding) -> R,
426 ) -> Vec<TokenStream2> {
427 self.variants
428 .iter()
429 .enumerate()
430 .map(|(index, variant)| build_coprod_constr(index, f(variant)))
431 .collect()
432 }
433
434 pub fn build_variant_constrs<R: ToTokens>(&self, f: impl Fn(&VariantBinding) -> R) -> Vec<R> {
435 self.variants.iter().map(f).collect()
436 }
437
438 pub fn build_coprod_unreachable_arm(&self, deref: bool) -> TokenStream2 {
439 build_coprod_unreachable_arm(self.variants.len(), deref)
440 }
441}