synthez_core/codegen/
parse_attrs.rs

1//! `#[derive(ParseAttrs)]` proc macro implementation.
2
3use std::{collections::BTreeSet, iter};
4
5use proc_macro2::{Span, TokenStream};
6use quote::{quote, ToTokens};
7use syn::{
8    ext::IdentExt as _,
9    parse::{Parse, ParseStream},
10    spanned::Spanned as _,
11    token,
12};
13
14use crate::{
15    ext::{Data as _, Ident as _},
16    parse::{
17        attrs::{
18            dedup,
19            field::TryMerge as _,
20            kind,
21            validate::{rule, Validate as _},
22        },
23        err,
24        ext::ParseBuffer as _,
25    },
26    ParseAttrs, Required, Spanning,
27};
28
29/// Name of the derived trait.
30const TRAIT_NAME: &str = "ParseAttrs";
31
32/// Name of the helper attribute of this `proc_macro_derive`.
33const ATTR_NAME: &str = "parse";
34
35/// Expands `#[derive(ParseAttrs)]` proc macro.
36///
37/// # Errors
38///
39/// - If the proc macro isn't applied to a struct.
40/// - If parsing `#[parse]` helper attribute fails.
41pub fn derive(input: syn::DeriveInput) -> syn::Result<TokenStream> {
42    if !matches!(&input.data, syn::Data::Struct(_)) {
43        return Err(syn::Error::new_spanned(
44            input,
45            format!("only structs can derive {TRAIT_NAME}"),
46        ));
47    }
48
49    let out = Definition {
50        ty: input.ident,
51        generics: input.generics,
52        fields: input
53            .data
54            .named_fields()?
55            .into_iter()
56            .map(Field::try_from)
57            .collect::<syn::Result<Vec<_>>>()?,
58    };
59
60    let impl_syn_parse = out.impl_syn_parse();
61    let impl_parse_attrs = out.impl_parse_attrs();
62    Ok(quote! {
63        #impl_syn_parse
64        #impl_parse_attrs
65    })
66}
67
68/// Representation of a struct implementing [`ParseAttrs`], used for code
69/// generation.
70#[derive(Debug)]
71struct Definition {
72    /// [`syn::Ident`] of this structure's type.
73    ///
74    /// [`syn::Ident`]: struct@syn::Ident
75    ty: syn::Ident,
76
77    /// [`syn::Generics`] of this structure's type.
78    generics: syn::Generics,
79
80    /// [`Field`]s of this structure to generate code for.
81    fields: Vec<Field>,
82}
83
84impl Definition {
85    /// Generates implementation of [`Parse`] trait for this struct.
86    #[must_use]
87    fn impl_syn_parse(&self) -> TokenStream {
88        let parse_arms = self.fields.iter().map(|f| {
89            let field = &f.ident;
90            let ty = &f.ty;
91            let kind = f.kind;
92            let dedup = f.dedup;
93            let arg_lits = &f.names;
94
95            let val_ty = quote! {
96                <#ty as ::synthez::field::Container<_>>::Value
97            };
98
99            let code = match kind {
100                Kind::Ident => quote! {
101                    <#ty as ::synthez::parse::attrs::field::TryApply<
102                        _, #kind, #dedup,
103                    >>::try_apply(&mut out.#field, input.parse::<#val_ty>()?)?;
104                },
105                Kind::Nested => quote! {
106                    ::synthez::ParseBufferExt::skip_any_ident(input)?;
107                    let inner;
108                    let _ = ::synthez::syn::parenthesized!(inner in input);
109                    <#ty as ::synthez::parse::attrs::field::TryApply<
110                        _, #kind, #dedup,
111                    >>::try_apply(
112                        &mut out.#field,
113                        ::synthez::Spanning::new(inner.parse()?, &ident),
114                    )?;
115                },
116                Kind::Value(spaced) => {
117                    let method = syn::Ident::new_on_call_site(if spaced {
118                        "parse_maybe_wrapped_and_punctuated"
119                    } else {
120                        "parse_eq_or_wrapped_and_punctuated"
121                    });
122
123                    quote! {
124                        ::synthez::ParseBufferExt::skip_any_ident(input)?;
125                        for v in ::synthez::ParseBufferExt::#method::<
126                            #val_ty,
127                            ::synthez::syn::token::Paren,
128                            ::synthez::syn::token::Comma,
129                        >(input)? {
130                            <#ty as ::synthez::parse::attrs::field::TryApply<
131                                _, #kind, #dedup,
132                            >>::try_apply(&mut out.#field, v)?;
133                        }
134                    }
135                }
136                Kind::Map => quote! {
137                    ::synthez::ParseBufferExt::skip_any_ident(input)?;
138                    let k = input.parse()?;
139                    input.parse::<::synthez::syn::token::Eq>()?;
140                    let v = input.parse()?;
141                    <#ty as ::synthez::parse::attrs::field::TryApply<
142                        (_, _), #kind, #dedup,
143                    >>::try_apply(&mut out.#field, (k, v))?;
144                },
145            };
146
147            quote! {
148                #( #arg_lits )|* => { #code },
149            }
150        });
151
152        let ty = &self.ty;
153        let (impl_generics, ty_generics, where_clause) =
154            self.generics.split_for_impl();
155
156        quote! {
157            #[automatically_derived]
158            impl #impl_generics ::synthez::syn::parse::Parse
159             for #ty #ty_generics
160                 #where_clause
161            {
162                fn parse(
163                    input: ::synthez::syn::parse::ParseStream<'_>,
164                ) -> ::synthez::syn::Result<Self> {
165                    let mut out =
166                        <#ty #ty_generics as ::std::default::Default>
167                            ::default();
168                    while !input.is_empty() {
169                        let ident =
170                            ::synthez::ParseBufferExt::parse_any_ident(
171                                &input.fork(),
172                            )?;
173                        match ident.to_string().as_str() {
174                            #( #parse_arms )*
175                            name => {
176                                return Err(::synthez::parse::err::
177                                    unknown_attr_arg(&ident, name));
178                            },
179                        }
180                        if ::synthez::ParseBufferExt::try_parse::<
181                            ::synthez::syn::token::Comma,
182                        >(input)?.is_none() && !input.is_empty() {
183                            return Err(::synthez::parse::err::
184                                expected_followed_by_comma(&ident));
185                        }
186                    }
187                    Ok(out)
188                }
189            }
190        }
191    }
192
193    /// Generates implementation of [`ParseAttrs`] trait for this struct.
194    #[must_use]
195    fn impl_parse_attrs(&self) -> TokenStream {
196        let ty = &self.ty;
197        let (impl_generics, ty_generics, where_clause) =
198            self.generics.split_for_impl();
199
200        let try_merge_fields = self.fields.iter().map(Field::gen_merge);
201
202        let validate_provided_fields =
203            self.fields.iter().map(Field::gen_validate_provided);
204        let validate_nested_fields =
205            self.fields.iter().filter_map(Field::gen_validate_nested);
206        let validate_custom_fields = self.fields.iter().flat_map(|f| {
207            let field = &f.ident;
208            f.validators.iter().map(move |validator| {
209                quote! {
210                    #validator(&self.#field)?;
211                }
212            })
213        });
214
215        let fallback_nested_fields =
216            self.fields.iter().filter_map(Field::gen_fallback_nested);
217        let fallback_custom_fields = self.fields.iter().flat_map(|f| {
218            let field = &f.ident;
219            f.fallbacks.iter().map(move |fallback| {
220                quote! {
221                    #fallback(&mut self.#field, attrs)?;
222                }
223            })
224        });
225
226        quote! {
227            #[automatically_derived]
228            impl #impl_generics ::synthez::parse::Attrs for #ty #ty_generics
229                 #where_clause
230            {
231                fn try_merge(
232                    mut self,
233                    another: Self,
234                ) -> ::synthez::syn::Result<Self> {
235                    #( #try_merge_fields )*
236                    Ok(self)
237                }
238
239                fn validate(
240                    &self,
241                    attr_name: &str,
242                    item_span: ::synthez::proc_macro2::Span,
243                ) -> ::synthez::syn::Result<()> {
244                    #( #validate_provided_fields )*
245                    #( #validate_nested_fields )*
246                    #( #validate_custom_fields )*
247                    Ok(())
248                }
249
250                fn fallback(
251                    &mut self,
252                    attrs: &[::synthez::syn::Attribute],
253                ) -> ::synthez::syn::Result<()> {
254                    #( #fallback_nested_fields )*
255                    #( #fallback_custom_fields )*
256                    Ok(())
257                }
258            }
259        }
260    }
261}
262
263/// Representation of a [`ParseAttrs`]'s field, used for code generation.
264#[derive(Debug)]
265struct Field {
266    /// [`syn::Ident`] of this [`Field`] in the original code (without possible
267    /// `r#` part).
268    ///
269    /// [`syn::Ident`]: struct@syn::Ident
270    ident: syn::Ident,
271
272    /// [`syn::Type`] of this [`Field`] (with [`field::Container`]).
273    ///
274    /// [`field::Container`]: crate::field::Container
275    ty: syn::Type,
276
277    /// Parsing [`kind`] to use for this [`Field`] in the generated code.
278    kind: Kind,
279
280    /// [`dedup`]lication strategy to use for this [`Field`] in the generated
281    /// code.
282    dedup: Dedup,
283
284    /// Names [`syn::Attribute`]'s arguments to parse this [`Field`] from in the
285    /// generated code.
286    names: Vec<String>,
287
288    /// Additional custom validators to apply to this [`Field`] in the generated
289    /// code.
290    validators: Vec<syn::Expr>,
291
292    /// Additional custom fallback functions to apply to this [`Field`] in the
293    /// generated code.
294    fallbacks: Vec<syn::Expr>,
295}
296
297impl TryFrom<syn::Field> for Field {
298    type Error = syn::Error;
299
300    fn try_from(field: syn::Field) -> syn::Result<Self> {
301        let attrs = FieldAttrs::parse_attrs(ATTR_NAME, &field)?;
302
303        let field_span = field.span();
304        let ident = field.ident.ok_or_else(move || {
305            syn::Error::new(field_span, "Named field expected")
306        })?;
307
308        let mut names = if attrs.args.is_empty() {
309            iter::once(ident.unraw()).collect()
310        } else {
311            attrs.args
312        };
313        names.try_merge_self::<kind::Value, dedup::Unique>(attrs.aliases)?;
314
315        Ok(Self {
316            ident,
317            ty: field.ty,
318            kind: **attrs.kind,
319            dedup: attrs.dedup.as_deref().copied().unwrap_or_default(),
320            names: names.into_iter().map(|n| n.to_string()).collect(),
321            validators: attrs.validators,
322            fallbacks: attrs.fallbacks,
323        })
324    }
325}
326
327impl Field {
328    /// Generates code of merging this [`Field`] with another one.
329    #[must_use]
330    fn gen_merge(&self) -> TokenStream {
331        let field = &self.ident;
332        let ty = &self.ty;
333        let kind = self.kind;
334        let dedup = self.dedup;
335
336        quote! {
337            <#ty as ::synthez::parse::attrs::field::TryApplySelf<
338                _, #kind, #dedup,
339            >>::try_apply_self(&mut self.#field, another.#field)?;
340        }
341    }
342
343    /// Generates code of [`rule::Provided`] validation for this [`Field`].
344    #[must_use]
345    fn gen_validate_provided(&self) -> TokenStream {
346        let field = &self.ident;
347        let ty = &self.ty;
348
349        let names_len = self.names.len();
350        let arg_names = if names_len > 1 {
351            format!(
352                "either `{}` or `{}`",
353                self.names[..(names_len - 1)].join("`, `"),
354                self.names[names_len - 1],
355            )
356        } else {
357            format!("`{}`", self.names[0])
358        };
359        let err_msg =
360            format!("{arg_names} argument of `#[{{}}]` attribute {{}}");
361
362        quote! {
363            if let Err(e) = <#ty as ::synthez::parse::attrs::Validation<
364                ::synthez::parse::attrs::validate::rule::Provided,
365            >>::validation(&self.#field) {
366                return Err(::synthez::syn::Error::new(
367                    item_span,
368                    format!(#err_msg, attr_name, e),
369                ));
370            }
371        }
372    }
373
374    /// Generates code of [`kind::Nested`] validation for this [`Field`], if it
375    /// represents the one.
376    #[must_use]
377    fn gen_validate_nested(&self) -> Option<TokenStream> {
378        if self.kind != Kind::Nested {
379            return None;
380        }
381
382        let field = &self.ident;
383        let attr_fmt = format!("{{}}({})", self.names[0]);
384
385        Some(quote! {
386            for v in &self.#field {
387                ::synthez::parse::Attrs::validate(
388                    &**v,
389                    &format!(#attr_fmt, attr_name),
390                    ::synthez::spanned::IntoSpan::into_span(v),
391                )?;
392            }
393        })
394    }
395
396    /// Generates code of [`kind::Nested`] fallback for this [`Field`], if it
397    /// represents the one.
398    #[must_use]
399    fn gen_fallback_nested(&self) -> Option<TokenStream> {
400        if self.kind != Kind::Nested {
401            return None;
402        }
403
404        let field = &self.ident;
405        let ty = &self.ty;
406
407        Some(quote! {
408            if !<#ty as ::synthez::field::Container<_>>::is_empty(
409                &self.#field,
410            ) {
411                for v in &mut self.#field {
412                    ::synthez::parse::Attrs::fallback(&mut **v, attrs)?;
413                }
414            }
415        })
416    }
417}
418
419/// Representation of a `#[parse]` attribute used along with a
420/// `#[derive(ParseAttrs)]` proc macro and placed on struct fields.
421#[derive(Debug, Default)]
422struct FieldAttrs {
423    /// [`kind`] of the [`ParseAttrs`]'s field parsing.
424    // #[parse(ident, args(ident, nested, value, map))]
425    kind: Required<Spanning<Kind>>,
426
427    /// Names of [`syn::Attribute`]'s arguments to use for parsing __instead
428    /// of__ the [`ParseAttrs`]'s field's [`syn::Ident`].
429    ///
430    /// [`syn::Ident`]: struct@syn::Ident
431    // #[parse(value, alias = arg)]
432    args: BTreeSet<syn::Ident>,
433
434    /// Names of [`syn::Attribute`]'s arguments to use for parsing __along
435    /// with__ the [`ParseAttrs`]'s field's [`syn::Ident`].
436    ///
437    /// [`syn::Ident`]: struct@syn::Ident
438    // #[parse(value, alias = alias)]
439    aliases: BTreeSet<syn::Ident>,
440
441    /// [`dedup`]lication strategy of how multiple values of the
442    /// [`ParseAttrs`]'s field should be merged.
443    ///
444    /// Default is [`Dedup::Unique`].
445    // #[parse(value)]
446    dedup: Option<Spanning<Dedup>>,
447
448    /// Additional custom validators to use for the [`ParseAttrs`]'s field.
449    // #[parse(value, arg = validate)]
450    validators: Vec<syn::Expr>,
451
452    /// Additional custom fallback functions to use for the [`ParseAttrs`]'s
453    /// field.
454    // #[parse(value, alias = fallback)]
455    fallbacks: Vec<syn::Expr>,
456}
457
458impl Parse for FieldAttrs {
459    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
460        let mut out = Self::default();
461        while !input.is_empty() {
462            let ident = input.fork().parse_any_ident()?;
463            match ident.to_string().as_str() {
464                "ident" | "nested" | "value" | "map" => {
465                    out.kind.try_merge::<kind::Ident, dedup::Unique>(
466                        input.parse::<Spanning<Kind>>()?,
467                    )?;
468                }
469                "arg" | "args" => {
470                    input.skip_any_ident()?;
471                    for val in input.parse_eq_or_wrapped_and_punctuated::<
472                        syn::Ident, token::Paren, token::Comma,
473                    >()? {
474                        out.args.try_merge::<kind::Value, dedup::Unique>(val)?;
475                    }
476                }
477                "alias" | "aliases" => {
478                    input.skip_any_ident()?;
479                    for v in input.parse_eq_or_wrapped_and_punctuated::<
480                        syn::Ident, token::Paren, token::Comma,
481                    >()? {
482                        out.aliases.try_merge::<kind::Value, dedup::Unique>(v)?;
483                    }
484                }
485                "dedup" => {
486                    input.skip_any_ident()?;
487                    for val in input.parse_eq_or_wrapped_and_punctuated::<
488                        Spanning<Dedup>, token::Paren, token::Comma,
489                    >()? {
490                        out.dedup.try_merge::<kind::Value, dedup::Unique>(val)?;
491                    }
492                }
493                "validate" => {
494                    input.skip_any_ident()?;
495                    for v in input.parse_eq_or_wrapped_and_punctuated::<
496                        syn::Expr, token::Paren, token::Comma,
497                    >()? {
498                        out.validators.try_merge::<
499                            kind::Value, dedup::Unique,
500                        >(v)?;
501                    }
502                }
503                "fallbacks" | "fallback" => {
504                    input.skip_any_ident()?;
505                    for v in input.parse_eq_or_wrapped_and_punctuated::<
506                        syn::Expr, token::Paren, token::Comma,
507                    >()? {
508                        out.fallbacks.try_merge::<
509                            kind::Value, dedup::Unique,
510                        >(v)?;
511                    }
512                }
513                name => {
514                    return Err(err::unknown_attr_arg(&ident, name));
515                }
516            }
517            if input.try_parse::<token::Comma>()?.is_none() && !input.is_empty()
518            {
519                return Err(err::expected_followed_by_comma(&ident));
520            }
521        }
522        Ok(out)
523    }
524}
525
526impl ParseAttrs for FieldAttrs {
527    fn try_merge(mut self, another: Self) -> syn::Result<Self> {
528        self.kind.try_merge_self::<kind::Value, dedup::Unique>(another.kind)?;
529        self.args.try_merge_self::<kind::Value, dedup::Unique>(another.args)?;
530        self.aliases
531            .try_merge_self::<kind::Value, dedup::Unique>(another.aliases)?;
532        self.dedup
533            .try_merge_self::<kind::Value, dedup::Unique>(another.dedup)?;
534        self.validators
535            .try_merge_self::<kind::Value, dedup::Unique>(another.validators)?;
536        self.fallbacks
537            .try_merge_self::<kind::Value, dedup::Unique>(another.fallbacks)?;
538        Ok(self)
539    }
540
541    fn validate(&self, attr_name: &str, item_span: Span) -> syn::Result<()> {
542        if self.kind.validate::<rule::Provided>().is_err() {
543            return Err(syn::Error::new(
544                item_span,
545                format!(
546                    "either `ident`, `value` or `map` argument of \
547                     `#[{attr_name}]` attribute is expected",
548                ),
549            ));
550        }
551        Ok(())
552    }
553}
554
555/// Field [`kind`] of parsing it from [`syn::Attribute`]s.
556#[derive(Clone, Copy, Debug, Eq, PartialEq)]
557enum Kind {
558    /// Field is parsed as a simple [`syn::Ident`].
559    ///
560    /// [`syn::Ident`]: struct@syn::Ident
561    Ident,
562
563    /// Field is parsed as a nested structure implementing [`ParseAttrs`].
564    Nested,
565
566    /// Field is parsed as values behind a [`syn::Ident`].
567    ///
568    /// Boolean refers to whether the value and the [`syn::Ident`] are separated
569    /// with spaces only.
570    ///
571    /// [`syn::Ident`]: struct@syn::Ident
572    Value(bool),
573
574    /// Field is parsed as as key-value pairs behind a [`syn::Ident`].
575    ///
576    /// [`syn::Ident`]: struct@syn::Ident
577    Map,
578}
579
580impl Parse for Spanning<Kind> {
581    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
582        let ident = input.parse::<syn::Ident>()?;
583        Ok(Self::new(
584            match ident.to_string().as_str() {
585                "ident" => Kind::Ident,
586                "nested" => Kind::Nested,
587                "value" => {
588                    if input.is_next::<token::Paren>() {
589                        let inner;
590                        _ = syn::parenthesized!(inner in input);
591                        let inner = inner.parse::<syn::Ident>()?;
592                        let val = inner.to_string();
593                        if val != "spaced" {
594                            return Err(syn::Error::new_spanned(
595                                inner,
596                                format!("invalid value setting: {val} "),
597                            ));
598                        }
599                        Kind::Value(true)
600                    } else {
601                        Kind::Value(false)
602                    }
603                }
604                "map" => Kind::Map,
605                val => {
606                    return Err(syn::Error::new_spanned(
607                        ident,
608                        format!("invalid kind value: {val} "),
609                    ));
610                }
611            },
612            &ident,
613        ))
614    }
615}
616
617impl ToTokens for Kind {
618    fn to_tokens(&self, out: &mut TokenStream) {
619        let variant = syn::Ident::new_on_call_site(match self {
620            Self::Ident => "Ident",
621            Self::Nested => "Nested",
622            Self::Value(_) => "Value",
623            Self::Map => "Map",
624        });
625        (quote! {
626            ::synthez::parse::attrs::kind::#variant
627        })
628        .to_tokens(out);
629    }
630}
631
632/// Field [`dedup`]lication strategy parsed from [`syn::Attribute`]s.
633#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
634enum Dedup {
635    /// Only a single value of the field is allowed to appear.
636    #[default]
637    Unique,
638
639    /// Only the first parsed value of the field is picked.
640    First,
641
642    /// Only the last parsed value of the field is picked.
643    Last,
644}
645
646impl Parse for Spanning<Dedup> {
647    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
648        let ident = input.parse::<syn::Ident>()?;
649        Ok(Self::new(
650            match ident.to_string().as_str() {
651                "unique" => Dedup::Unique,
652                "first" => Dedup::First,
653                "last" => Dedup::Last,
654                val => {
655                    return Err(syn::Error::new_spanned(
656                        ident,
657                        format!("invalid dedup value: {val} "),
658                    ));
659                }
660            },
661            &ident,
662        ))
663    }
664}
665
666impl ToTokens for Dedup {
667    fn to_tokens(&self, out: &mut TokenStream) {
668        let variant = syn::Ident::new_on_call_site(match self {
669            Self::Unique => "Unique",
670            Self::First => "First",
671            Self::Last => "Last",
672        });
673        (quote! {
674            ::synthez::parse::attrs::dedup::#variant
675        })
676        .to_tokens(out);
677    }
678}