tabled_derive/
lib.rs

1extern crate proc_macro;
2
3mod attributes;
4mod casing_style;
5mod error;
6mod parse;
7
8use proc_macro2::TokenStream;
9use proc_macro_error::proc_macro_error;
10use quote::{quote, ToTokens, TokenStreamExt};
11use std::{collections::HashMap, str};
12use syn::{
13    parse_macro_input, token, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, Ident, Index,
14    Type, Variant,
15};
16
17use attributes::{Attributes, ObjectAttributes};
18use error::Error;
19
20#[proc_macro_derive(Tabled, attributes(tabled))]
21#[proc_macro_error]
22pub fn tabled(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
23    let input = parse_macro_input!(input as DeriveInput);
24    let ast = impl_tabled(&input);
25    proc_macro::TokenStream::from(ast)
26}
27
28fn impl_tabled(ast: &DeriveInput) -> TokenStream {
29    let attrs = ObjectAttributes::parse(&ast.attrs)
30        .map_err(error::abort)
31        .unwrap();
32
33    let length = get_tabled_length(ast).map_err(error::abort).unwrap();
34    let info = collect_info(ast, &attrs).map_err(error::abort).unwrap();
35    let fields = info.values;
36    let headers = info.headers;
37
38    let name = &ast.ident;
39    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
40    let expanded = quote! {
41        impl #impl_generics Tabled for #name #ty_generics #where_clause {
42            const LENGTH: usize = #length;
43
44            fn fields(&self) -> Vec<::std::borrow::Cow<'_, str>> {
45                #fields
46            }
47
48            fn headers() -> Vec<::std::borrow::Cow<'static, str>> {
49                #headers
50            }
51        }
52    };
53
54    expanded
55}
56
57fn get_tabled_length(ast: &DeriveInput) -> Result<TokenStream, Error> {
58    match &ast.data {
59        Data::Struct(data) => get_fields_length(&data.fields),
60        Data::Enum(data) => get_enum_length(data),
61        Data::Union(_) => Err(Error::message("Union type isn't supported")),
62    }
63}
64
65fn get_fields_length(fields: &Fields) -> Result<TokenStream, Error> {
66    let size_components = fields
67        .iter()
68        .map(|field| {
69            let attributes = Attributes::parse(&field.attrs)?;
70            Ok((field, attributes))
71        })
72        .collect::<Result<Vec<_>, Error>>()?
73        .into_iter()
74        .filter(|(_, attr)| !attr.is_ignored())
75        .map(|(field, attr)| {
76            if attr.inline {
77                let field_type = &field.ty;
78                quote!({<#field_type as Tabled>::LENGTH})
79            } else {
80                quote!({ 1 })
81            }
82        });
83
84    let size_components = std::iter::once(quote!(0)).chain(size_components);
85
86    let mut stream = TokenStream::new();
87    stream.append_separated(size_components, syn::token::Add::default());
88
89    Ok(stream)
90}
91
92fn get_enum_length(enum_ast: &DataEnum) -> Result<TokenStream, Error> {
93    let variant_sizes = get_enum_variant_length(enum_ast);
94
95    let mut stream = TokenStream::new();
96    for (i, size) in variant_sizes.enumerate() {
97        let size = size?;
98
99        if i != 0 {
100            stream.append_all(syn::token::Add::default().into_token_stream());
101        }
102
103        stream.append_all(size);
104    }
105
106    Ok(stream)
107}
108
109fn get_enum_variant_length(
110    enum_ast: &DataEnum,
111) -> impl Iterator<Item = Result<TokenStream, Error>> + '_ {
112    enum_ast
113        .variants
114        .iter()
115        .map(|variant| -> Result<_, Error> {
116            let attributes = Attributes::parse(&variant.attrs)?;
117            Ok((variant, attributes))
118        })
119        .filter(|result| result.is_err() || matches!(result, Ok((_, attr)) if !attr.is_ignored()))
120        .map(|result| {
121            let (variant, attr) = result?;
122
123            if attr.inline {
124                get_fields_length(&variant.fields)
125            } else {
126                Ok(quote!(1))
127            }
128        })
129}
130
131fn collect_info(ast: &DeriveInput, attrs: &ObjectAttributes) -> Result<Impl, Error> {
132    match &ast.data {
133        Data::Struct(data) => collect_info_struct(data, attrs),
134        Data::Enum(data) => collect_info_enum(data, attrs),
135        Data::Union(_) => Err(Error::message("Union type isn't supported")),
136    }
137}
138
139fn collect_info_struct(ast: &DataStruct, attrs: &ObjectAttributes) -> Result<Impl, Error> {
140    info_from_fields(&ast.fields, attrs, field_var_name, "")
141}
142
143// todo: refactoring. instead of using a lambda + prefix
144// we could just not emit `self.` `_x` inside
145// So the called would prefix it on its own
146fn info_from_fields(
147    fields: &Fields,
148    attrs: &ObjectAttributes,
149    field_name: impl Fn(usize, &Field) -> TokenStream,
150    header_prefix: &str,
151) -> Result<Impl, Error> {
152    let count_fields = fields.len();
153
154    let fields = fields
155        .into_iter()
156        .enumerate()
157        .map(|(i, field)| -> Result<_, Error> {
158            let mut attributes = Attributes::parse(&field.attrs)?;
159            merge_attributes(&mut attributes, attrs);
160
161            Ok((i, field, attributes))
162        });
163
164    let mut headers = Vec::new();
165    let mut values = Vec::new();
166    let mut reorder = HashMap::new();
167
168    for result in fields {
169        let (i, field, attributes) = result?;
170        if attributes.is_ignored() {
171            continue;
172        }
173
174        if let Some(order) = attributes.order {
175            if order >= count_fields {
176                return Err(Error::message(format!(
177                    "An order index '{}' is out of fields scope",
178                    order
179                )));
180            }
181
182            reorder.insert(order, i);
183        }
184
185        let header = field_headers(field, i, &attributes, header_prefix);
186
187        headers.push(header);
188
189        let field_name = field_name(i, field);
190        let value = get_field_fields(&field_name, &attributes);
191
192        values.push(value);
193    }
194
195    if !reorder.is_empty() {
196        values = reorder_fields(&reorder, &values);
197        headers = reorder_fields(&reorder, &headers);
198    }
199
200    let headers = quote!({
201        let mut out = Vec::new();
202        #(out.extend(#headers);)*
203        out
204    });
205
206    let values = quote!({
207        let mut out = Vec::new();
208        #(out.extend(#values);)*
209        out
210    });
211
212    Ok(Impl { headers, values })
213}
214
215fn reorder_fields<T: Clone>(order: &HashMap<usize, usize>, elements: &[T]) -> Vec<T> {
216    let mut out: Vec<Option<T>> = Vec::with_capacity(elements.len());
217    out.resize(elements.len(), None);
218
219    for (pos, index) in order {
220        let value = elements[*index].clone();
221        out[*pos] = Some(value);
222    }
223
224    let mut j = 0;
225    for el in &mut out {
226        if el.is_some() {
227            continue;
228        }
229
230        while order.values().any(|&pos| j == pos) {
231            j += 1;
232        }
233
234        let v = elements[j].clone();
235        *el = Some(v);
236
237        j += 1;
238    }
239
240    out.into_iter().flatten().collect()
241}
242
243fn field_headers(
244    field: &Field,
245    index: usize,
246    attributes: &Attributes,
247    prefix: &str,
248) -> TokenStream {
249    if attributes.inline {
250        let prefix = attributes
251            .inline_prefix
252            .as_ref()
253            .map_or_else(|| "", |s| s.as_str());
254        return get_type_headers(&field.ty, prefix, "");
255    }
256
257    let header_name = field_header_name(field, attributes, index);
258    if prefix.is_empty() {
259        quote!(vec![::std::borrow::Cow::Borrowed(#header_name)])
260    } else {
261        let name = format!("{}{}", prefix, header_name);
262        quote!(vec![::std::borrow::Cow::Borrowed(#name)])
263    }
264}
265
266fn collect_info_enum(ast: &DataEnum, attrs: &ObjectAttributes) -> Result<Impl, Error> {
267    let mut headers_list = Vec::new();
268    let mut variants = Vec::new();
269    for variant in &ast.variants {
270        let mut attributes = Attributes::parse(&variant.attrs)?;
271        merge_attributes(&mut attributes, attrs);
272        if attributes.is_ignored() {
273            continue;
274        }
275
276        let info = info_from_variant(variant, &attributes, attrs)?;
277        variants.push((variant, info.values));
278        headers_list.push(info.headers);
279    }
280
281    let variant_sizes = get_enum_variant_length(ast)
282        .collect::<Result<Vec<_>, Error>>()?
283        .into_iter();
284    let values = values_for_enum(variant_sizes, &variants);
285
286    let headers = quote! {
287        vec![
288            #(#headers_list,)*
289        ]
290        .concat()
291    };
292
293    Ok(Impl { headers, values })
294}
295
296fn info_from_variant(
297    variant: &Variant,
298    attributes: &Attributes,
299    attrs: &ObjectAttributes,
300) -> Result<Impl, Error> {
301    if attributes.inline {
302        let prefix = attributes
303            .inline_prefix
304            .as_ref()
305            .map_or_else(|| "", |s| s.as_str());
306        return info_from_fields(&variant.fields, attrs, variant_var_name, prefix);
307    }
308
309    let variant_name = variant_name(variant, attributes);
310    let value = "+";
311
312    // we need exactly string because of it must be inlined as string
313    let headers = quote! { vec![::std::borrow::Cow::Borrowed(#variant_name)] };
314    // we need exactly string because of it must be inlined as string
315    let values = quote! { vec![::std::borrow::Cow::Borrowed(#value)] };
316
317    Ok(Impl { headers, values })
318}
319
320struct Impl {
321    headers: TokenStream,
322    values: TokenStream,
323}
324
325fn get_type_headers(field_type: &Type, inline_prefix: &str, prefix: &str) -> TokenStream {
326    if prefix.is_empty() && inline_prefix.is_empty() {
327        quote! { <#field_type as Tabled>::headers() }
328    } else {
329        quote! {
330            <#field_type as Tabled>::headers().into_iter()
331                .map(|header| {
332                    let header = format!("{}{}{}", #prefix, #inline_prefix, header);
333                    ::std::borrow::Cow::Owned(header)
334                })
335                .collect::<Vec<_>>()
336        }
337    }
338}
339
340fn get_field_fields(field: &TokenStream, attr: &Attributes) -> TokenStream {
341    if attr.inline {
342        return quote! { #field.fields() };
343    }
344
345    if let Some(func) = &attr.display_with {
346        let func_call = match attr.display_with_use_self {
347            true => use_function_with_self(func),
348            false => use_function_for(field, func),
349        };
350
351        return quote!(vec![::std::borrow::Cow::from(#func_call)]);
352    }
353
354    quote!(vec![::std::borrow::Cow::Owned(format!("{}", #field))])
355}
356
357fn use_function_for(field: &TokenStream, function: &str) -> TokenStream {
358    let path: syn::Result<syn::ExprPath> = syn::parse_str(function);
359    match path {
360        Ok(path) => {
361            quote! { #path(&#field) }
362        }
363        Err(_) => {
364            let function = Ident::new(function, proc_macro2::Span::call_site());
365            quote! { #function(&#field) }
366        }
367    }
368}
369
370fn use_function_with_self(function: &str) -> TokenStream {
371    let path: syn::Result<syn::ExprPath> = syn::parse_str(function);
372    match path {
373        Ok(path) => {
374            quote! { #path(&self) }
375        }
376        Err(_) => {
377            let function = Ident::new(function, proc_macro2::Span::call_site());
378            quote! { #function(&self) }
379        }
380    }
381}
382
383fn field_var_name(index: usize, field: &Field) -> TokenStream {
384    let f = field.ident.as_ref().map_or_else(
385        || Index::from(index).to_token_stream(),
386        quote::ToTokens::to_token_stream,
387    );
388    quote!(self.#f)
389}
390
391fn variant_var_name(index: usize, field: &Field) -> TokenStream {
392    match &field.ident {
393        Some(indent) => indent.to_token_stream(),
394        None => Ident::new(
395            format!("x_{}", index).as_str(),
396            proc_macro2::Span::call_site(),
397        )
398        .to_token_stream(),
399    }
400}
401
402fn values_for_enum(
403    variant_sizes: impl Iterator<Item = TokenStream>,
404    variants: &[(&Variant, TokenStream)],
405) -> TokenStream {
406    let branches = variants.iter().map(|(variant, _)| match_variant(variant));
407
408    let fields = variants
409        .iter()
410        .map(|(_, values)| values)
411        .collect::<Vec<_>>();
412
413    let mut stream = TokenStream::new();
414    for (i, (branch, fields)) in branches.into_iter().zip(fields).enumerate() {
415        let branch = quote! {
416            Self::#branch => {
417                let offset = offsets[#i];
418                let fields: Vec<::std::borrow::Cow<'_, str>> = #fields;
419
420                for (i, field) in fields.into_iter().enumerate() {
421                    out_vec[i+offset] = field;
422                }
423            },
424        };
425
426        stream.append_all(branch);
427    }
428
429    quote! {
430        // To be able to insert variant fields in proper places we do this MAGIC with offset.
431        //
432        // We check headers output as it's static and has an information
433        // about length of each field header if it was inlined.
434        //
435        // It's a bit strange trick but I haven't found any better
436        // how to calculate a size and offset.
437        let mut offsets: &mut [usize] = &mut [0, #(#variant_sizes,)*];
438        for i in 1 .. offsets.len() {
439            offsets[i] += offsets[i-1]
440        }
441
442        let size = <Self as Tabled>::LENGTH;
443        let mut out_vec = vec![::std::borrow::Cow::Borrowed(""); size];
444
445        #[allow(unused_variables)]
446        match &self {
447            #stream
448            _ => return vec![], // variant is hidden so we return an empty vector
449        };
450
451        out_vec
452    }
453}
454
455fn variant_idents(v: &Variant) -> Vec<TokenStream> {
456    v.fields
457        .iter()
458        .enumerate()
459        // we intentionally not ignore these fields to be able to build a pattern correctly
460        // .filter(|(_, field)| !Attr::parse(&field.attrs).is_ignored())
461        .map(|(index, field)| variant_var_name(index, field))
462        .collect::<Vec<_>>()
463}
464
465fn match_variant(v: &Variant) -> TokenStream {
466    let mut token = TokenStream::new();
467    token.append_all(v.ident.to_token_stream());
468
469    let fields = variant_idents(v);
470
471    match &v.fields {
472        Fields::Named(_) => {
473            token::Brace::default().surround(&mut token, |s| {
474                s.append_separated(fields, quote! {,});
475            });
476        }
477        Fields::Unnamed(_) => {
478            token::Paren::default().surround(&mut token, |s| {
479                s.append_separated(fields, quote! {,});
480            });
481        }
482        Fields::Unit => {}
483    };
484
485    token
486}
487
488fn variant_name(variant: &Variant, attributes: &Attributes) -> String {
489    attributes
490        .rename
491        .clone()
492        .or_else(|| {
493            attributes
494                .rename_all
495                .as_ref()
496                .map(|case| case.cast(variant.ident.to_string()))
497        })
498        .unwrap_or_else(|| variant.ident.to_string())
499}
500
501fn field_header_name(f: &Field, attr: &Attributes, index: usize) -> String {
502    if let Some(name) = &attr.rename {
503        return name.to_string();
504    }
505
506    match &f.ident {
507        Some(name) => {
508            let name = name.to_string();
509            match &attr.rename_all {
510                Some(case) => case.cast(name),
511                None => name,
512            }
513        }
514        None => index.to_string(),
515    }
516}
517
518fn merge_attributes(attr: &mut Attributes, global_attr: &ObjectAttributes) {
519    if attr.rename_all.is_none() {
520        attr.rename_all = global_attr.rename_all;
521    }
522}