sycamore_macro/
props.rs

1//! The `Props` derive macro implementation.
2//!
3//! _Credits: This code is mostly taken from <https://github.com/idanarye/rust-typed-builder>_
4
5use proc_macro2::TokenStream;
6use quote::quote;
7use syn::spanned::Spanned;
8use syn::{DeriveInput, Error, Result};
9
10pub fn impl_derive_props(ast: &DeriveInput) -> Result<TokenStream> {
11    let data = match &ast.data {
12        syn::Data::Struct(data) => match &data.fields {
13            syn::Fields::Named(fields) => {
14                let struct_info = struct_info::StructInfo::new(ast, fields.named.iter())?;
15                let builder_creation = struct_info.builder_creation_impl()?;
16                let conversion_helper = struct_info.conversion_helper_impl()?;
17                let fields = struct_info
18                    .included_fields()
19                    .map(|f| struct_info.field_impl(f))
20                    .collect::<Result<Vec<_>>>()?;
21                let fields = quote!(#(#fields)*).into_iter();
22                let required_fields = struct_info
23                    .included_fields()
24                    .filter(|f| f.builder_attr.default.is_none())
25                    .map(|f| struct_info.required_field_impl(f))
26                    .collect::<Result<Vec<_>>>()?;
27                let build_method = struct_info.build_method_impl();
28
29                quote! {
30                    #builder_creation
31                    #conversion_helper
32                    #( #fields )*
33                    #( #required_fields )*
34                    #build_method
35                }
36            }
37            syn::Fields::Unnamed(_) => {
38                return Err(Error::new(
39                    ast.span(),
40                    "Props is not supported for tuple structs",
41                ))
42            }
43            syn::Fields::Unit => {
44                return Err(Error::new(
45                    ast.span(),
46                    "Props is not supported for unit structs",
47                ))
48            }
49        },
50        syn::Data::Enum(_) => {
51            return Err(Error::new(ast.span(), "Props is not supported for enums"))
52        }
53        syn::Data::Union(_) => {
54            return Err(Error::new(ast.span(), "Props is not supported for unions"))
55        }
56    };
57    Ok(data)
58}
59
60mod struct_info {
61    use std::fmt::Write;
62
63    use proc_macro2::TokenStream;
64    use quote::quote;
65    use syn::parse::Error;
66    use syn::punctuated::Punctuated;
67    use syn::Token;
68
69    use super::field_info::{AttributeBase, FieldBuilderAttr, FieldInfo};
70    use super::util::{
71        empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single,
72        modify_types_generics_hack, path_to_single_string, strip_raw_ident_prefix, type_tuple,
73    };
74
75    #[derive(Debug)]
76    pub struct StructInfo<'a> {
77        pub vis: &'a syn::Visibility,
78        pub name: &'a syn::Ident,
79        pub generics: &'a syn::Generics,
80        pub fields: Vec<FieldInfo<'a>>,
81
82        pub builder_attr: TypeBuilderAttr,
83        pub builder_name: syn::Ident,
84        pub conversion_helper_trait_name: syn::Ident,
85        #[allow(dead_code)] // TODO: remove this field?
86        pub core: syn::Ident,
87
88        pub attributes: Option<(AttributeBase, String)>,
89    }
90
91    impl<'a> StructInfo<'a> {
92        pub fn included_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
93            self.fields
94                .iter()
95                .filter(|f| f.builder_attr.setter.skip.is_none())
96        }
97
98        pub fn new(
99            ast: &'a syn::DeriveInput,
100            fields: impl Iterator<Item = &'a syn::Field>,
101        ) -> Result<StructInfo<'a>, Error> {
102            let builder_attr = TypeBuilderAttr::new(&ast.attrs)?;
103            let builder_name = strip_raw_ident_prefix(format!("{}Builder", ast.ident));
104            let mut fields = fields
105                .enumerate()
106                .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone()))
107                .collect::<Result<Vec<FieldInfo>, _>>()?;
108
109            // Search `fields` for `attributes`. If one is found, make sure that it is the only
110            // one.
111            let mut attributes = None;
112            for field in &fields {
113                if let Some((base, tag)) = &field.builder_attr.attributes {
114                    if attributes.is_some() {
115                        return Err(Error::new(
116                            field.name.span(),
117                            "Only one field can have `#[prop(attributes(...))]`",
118                        ));
119                    };
120                    if field.name != "attributes" {
121                        return Err(Error::new(
122                            field.name.span(),
123                            "The field with `#[prop(attributes(...))]` must be named `attributes`",
124                        ));
125                    }
126                    attributes = Some((base.clone(), tag.clone()));
127                }
128            }
129            // Now filter out `attributes` from `fields`.
130            fields.retain(|f| f.builder_attr.attributes.is_none());
131
132            Ok(StructInfo {
133                vis: &ast.vis,
134                name: &ast.ident,
135                generics: &ast.generics,
136                fields,
137                builder_attr,
138                builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()),
139                conversion_helper_trait_name: syn::Ident::new(
140                    &format!("{}_Optional", builder_name),
141                    proc_macro2::Span::call_site(),
142                ),
143                core: syn::Ident::new(
144                    &format!("{}_core", builder_name),
145                    proc_macro2::Span::call_site(),
146                ),
147                attributes,
148            })
149        }
150
151        fn modify_generics<F: FnMut(&mut syn::Generics)>(&self, mut mutator: F) -> syn::Generics {
152            let mut generics = self.generics.clone();
153            mutator(&mut generics);
154            generics
155        }
156
157        pub fn builder_creation_impl(&self) -> Result<TokenStream, Error> {
158            let StructInfo {
159                ref vis,
160                ref name,
161                ref builder_name,
162                ..
163            } = self;
164            let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
165            let all_fields_param = syn::GenericParam::Type(
166                syn::Ident::new("PropsFields", proc_macro2::Span::call_site()).into(),
167            );
168            let b_generics = self.modify_generics(|g| {
169                g.params.insert(0, all_fields_param.clone());
170            });
171            let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type()));
172            let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| {
173                args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into()));
174            });
175            let phantom_generics = self.generics.params.iter().map(|param| match param {
176                syn::GenericParam::Lifetime(lifetime) => {
177                    let lifetime = &lifetime.lifetime;
178                    quote!(::core::marker::PhantomData<&#lifetime ()>)
179                }
180                syn::GenericParam::Type(ty) => {
181                    let ty = &ty.ident;
182                    quote!(::core::marker::PhantomData<#ty>)
183                }
184                syn::GenericParam::Const(_cnst) => {
185                    quote!()
186                }
187            });
188            let builder_method_doc = match self.builder_attr.builder_method_doc {
189                Some(ref doc) => quote!(#doc),
190                None => {
191                    let doc = format!(
192                        "
193                    Create a builder for building `{name}`.
194                    On the builder, call {setters} to set the values of the fields.
195                    Finally, call `.build()` to create the instance of `{name}`.
196                    ",
197                        name = self.name,
198                        setters = {
199                            let mut result = String::new();
200                            let mut is_first = true;
201                            for field in self.included_fields() {
202                                use std::fmt::Write;
203                                if is_first {
204                                    is_first = false;
205                                } else {
206                                    write!(&mut result, ", ").unwrap();
207                                }
208                                write!(&mut result, "`.{}(...)`", field.name).unwrap();
209                                if field.builder_attr.default.is_some() {
210                                    write!(&mut result, "(optional)").unwrap();
211                                }
212                            }
213                            result
214                        }
215                    );
216                    quote!(#doc)
217                }
218            };
219            let builder_type_doc = if self.builder_attr.doc {
220                match self.builder_attr.builder_type_doc {
221                    Some(ref doc) => quote!(#[doc = #doc]),
222                    None => {
223                        let doc = format!(
224                        "Builder for [`{name}`] instances.\n\nSee [`{name}::builder()`] for more info.",
225                        name = name
226                    );
227                        quote!(#[doc = #doc])
228                    }
229                }
230            } else {
231                quote!(#[doc(hidden)])
232            };
233
234            let (b_generics_impl, b_generics_ty, b_generics_where) = b_generics.split_for_impl();
235
236            let attributes_field = if self.attributes.is_some() {
237                quote! { attributes: ::sycamore::web::Attributes, }
238            } else {
239                quote! { attributes: (), }
240            };
241            // Check if we need to generate impls for attributes.
242            let attributes_impl = if let Some((base, tag)) = &self.attributes {
243                let base_trait_ident = match base {
244                    AttributeBase::Html => quote!(HtmlGlobalAttributes),
245                    AttributeBase::Svg => quote!(SvgGlobalAttributes),
246                };
247                let tag_camel = tag.split('_').fold(String::new(), |mut acc, segment| {
248                    let (first, rest) = segment.split_at(1);
249                    write!(&mut acc, "{first}{rest}", first = first.to_uppercase()).unwrap();
250                    acc
251                });
252                let tag_trait_ident = match base {
253                    AttributeBase::Html => quote::format_ident!("Html{tag_camel}Attributes"),
254                    AttributeBase::Svg => quote::format_ident!("Svg{tag_camel}Attributes"),
255                };
256                quote! {
257                    impl #b_generics_impl ::sycamore::web::GlobalAttributes for #builder_name #b_generics_ty #b_generics_where {}
258                    impl #b_generics_impl ::sycamore::web::#base_trait_ident for #builder_name #b_generics_ty #b_generics_where {}
259                    impl #b_generics_impl ::sycamore::web::tags::#tag_trait_ident for #builder_name #b_generics_ty #b_generics_where {}
260
261                    impl #b_generics_impl ::sycamore::web::SetAttribute for #builder_name #b_generics_ty #b_generics_where {
262                        fn set_attribute(&mut self, name: &'static ::std::primitive::str, value: impl ::sycamore::web::AttributeValue) {
263                            self.attributes.set_attribute(name, value);
264                        }
265                        fn set_event_handler(
266                            &mut self,
267                            name: &'static ::std::primitive::str,
268                            handler: impl ::std::ops::FnMut(::sycamore::rt::Event) + 'static,
269                        ) {
270                            self.attributes.set_event_handler(name, handler);
271                        }
272                    }
273                }
274            } else {
275                quote! {}
276            };
277
278            Ok(quote! {
279                impl #impl_generics ::sycamore::rt::Props for #name #ty_generics #where_clause {
280                    type Builder = #builder_name #generics_with_empty;
281                    #[doc = #builder_method_doc]
282                    #[allow(dead_code, clippy::default_trait_access)]
283                    fn builder() -> Self::Builder {
284                        #builder_name {
285                            fields: #empties_tuple,
286                            phantom: ::core::default::Default::default(),
287                            attributes: ::core::default::Default::default(),
288                        }
289                    }
290                }
291
292                #[must_use]
293                #builder_type_doc
294                #[allow(dead_code, non_camel_case_types, non_snake_case)]
295                #vis struct #builder_name #b_generics {
296                    fields: #all_fields_param,
297                    phantom: (#( #phantom_generics ),*),
298                    #attributes_field
299                }
300
301                #attributes_impl
302            })
303        }
304
305        // TODO: once the proc-macro crate limitation is lifted, make this an util trait of this
306        // crate.
307        pub fn conversion_helper_impl(&self) -> Result<TokenStream, Error> {
308            let trait_name = &self.conversion_helper_trait_name;
309            Ok(quote! {
310                #[doc(hidden)]
311                #[allow(dead_code, non_camel_case_types, non_snake_case)]
312                pub trait #trait_name<T> {
313                    fn into_value<F: FnOnce() -> T>(self, default: F) -> T;
314                }
315
316                impl<T> #trait_name<T> for () {
317                    fn into_value<F: FnOnce() -> T>(self, default: F) -> T {
318                        default()
319                    }
320                }
321
322                impl<T> #trait_name<T> for (T,) {
323                    fn into_value<F: FnOnce() -> T>(self, _: F) -> T {
324                        self.0
325                    }
326                }
327            })
328        }
329
330        pub fn field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
331            let StructInfo {
332                ref builder_name, ..
333            } = self;
334
335            let destructuring = self.included_fields().map(|f| {
336                if f.ordinal == field.ordinal {
337                    quote!(_)
338                } else {
339                    let name = f.name;
340                    quote!(#name)
341                }
342            });
343            let reconstructing = self.included_fields().map(|f| f.name);
344
345            let FieldInfo {
346                name: ref field_name,
347                ty: ref field_type,
348                ..
349            } = field;
350            let mut ty_generics: Vec<syn::GenericArgument> = self
351                .generics
352                .params
353                .iter()
354                .map(|generic_param| match generic_param {
355                    syn::GenericParam::Type(type_param) => {
356                        let ident = type_param.ident.clone();
357                        syn::parse(quote!(#ident).into()).unwrap()
358                    }
359                    syn::GenericParam::Lifetime(lifetime_def) => {
360                        syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())
361                    }
362                    syn::GenericParam::Const(const_param) => {
363                        let ident = const_param.ident.clone();
364                        syn::parse(quote!(#ident).into()).unwrap()
365                    }
366                })
367                .collect();
368            let mut target_generics_tuple = empty_type_tuple();
369            let mut ty_generics_tuple = empty_type_tuple();
370            let generics = self.modify_generics(|g| {
371                let index_after_lifetime_in_generics = g
372                    .params
373                    .iter()
374                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
375                    .count();
376                for f in self.included_fields() {
377                    if f.ordinal == field.ordinal {
378                        ty_generics_tuple.elems.push_value(empty_type());
379                        target_generics_tuple
380                            .elems
381                            .push_value(f.tuplized_type_ty_param());
382                    } else {
383                        g.params
384                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());
385                        let generic_argument: syn::Type = f.type_ident();
386                        ty_generics_tuple.elems.push_value(generic_argument.clone());
387                        target_generics_tuple.elems.push_value(generic_argument);
388                    }
389                    ty_generics_tuple.elems.push_punct(Default::default());
390                    target_generics_tuple.elems.push_punct(Default::default());
391                }
392            });
393            let mut target_generics = ty_generics.clone();
394            let index_after_lifetime_in_generics = target_generics
395                .iter()
396                .filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))
397                .count();
398            target_generics.insert(
399                index_after_lifetime_in_generics,
400                syn::GenericArgument::Type(target_generics_tuple.into()),
401            );
402            ty_generics.insert(
403                index_after_lifetime_in_generics,
404                syn::GenericArgument::Type(ty_generics_tuple.into()),
405            );
406            let (impl_generics, _, where_clause) = generics.split_for_impl();
407            let doc = match field.builder_attr.setter.doc {
408                Some(ref doc) => quote!(#[doc = #doc]),
409                None => quote!(),
410            };
411
412            // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order
413            // of nesting is different so we have to do this little dance.
414            let arg_type = if field.builder_attr.setter.strip_option.is_some()
415                && field.builder_attr.setter.transform.is_none()
416            {
417                field.type_from_inside_option().ok_or_else(|| {
418                    Error::new_spanned(
419                        field_type,
420                        "can't `strip_option` - field is not `Option<...>`",
421                    )
422                })?
423            } else {
424                field_type
425            };
426            let (arg_type, arg_expr) = if field.builder_attr.setter.auto_into.is_some() {
427                (
428                    quote!(impl ::core::convert::Into<#arg_type>),
429                    quote!(#field_name.into()),
430                )
431            } else {
432                (quote!(#arg_type), quote!(#field_name))
433            };
434
435            let (param_list, arg_expr) =
436                if let Some(transform) = &field.builder_attr.setter.transform {
437                    let params = transform.params.iter().map(|(pat, ty)| quote!(#pat: #ty));
438                    let body = &transform.body;
439                    (quote!(#(#params),*), quote!({ #body }))
440                } else if field.builder_attr.setter.strip_option.is_some() {
441                    (quote!(#field_name: #arg_type), quote!(Some(#arg_expr)))
442                } else {
443                    (quote!(#field_name: #arg_type), arg_expr)
444                };
445
446            let repeated_fields_error_type_name = syn::Ident::new(
447                &format!(
448                    "{}_Error_Repeated_field_{}",
449                    builder_name,
450                    strip_raw_ident_prefix(field_name.to_string())
451                ),
452                proc_macro2::Span::call_site(),
453            );
454            let repeated_fields_error_message = format!("Repeated field {}", field_name);
455
456            Ok(quote! {
457                #[allow(dead_code, non_camel_case_types, missing_docs)]
458                impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
459                    #doc
460                    pub fn #field_name (self, #param_list) -> #builder_name < #( #target_generics ),* > {
461                        let #field_name = (#arg_expr,);
462                        let ( #(#destructuring,)* ) = self.fields;
463                        #builder_name {
464                            fields: ( #(#reconstructing,)* ),
465                            phantom: self.phantom,
466                            attributes: self.attributes,
467                        }
468                    }
469                }
470                #[doc(hidden)]
471                #[allow(dead_code, non_camel_case_types, non_snake_case)]
472                pub enum #repeated_fields_error_type_name {}
473                #[doc(hidden)]
474                #[allow(dead_code, non_camel_case_types, missing_docs)]
475                impl #impl_generics #builder_name < #( #target_generics ),* > #where_clause {
476                    #[deprecated(
477                        note = #repeated_fields_error_message
478                    )]
479                    pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > {
480                        self
481                    }
482                }
483            })
484        }
485
486        pub fn required_field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
487            let StructInfo {
488                ref name,
489                ref builder_name,
490                ..
491            } = self;
492
493            let FieldInfo {
494                name: ref field_name,
495                ..
496            } = field;
497            let mut builder_generics: Vec<syn::GenericArgument> = self
498                .generics
499                .params
500                .iter()
501                .map(|generic_param| match generic_param {
502                    syn::GenericParam::Type(type_param) => {
503                        let ident = &type_param.ident;
504                        syn::parse(quote!(#ident).into()).unwrap()
505                    }
506                    syn::GenericParam::Lifetime(lifetime_def) => {
507                        syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())
508                    }
509                    syn::GenericParam::Const(const_param) => {
510                        let ident = &const_param.ident;
511                        syn::parse(quote!(#ident).into()).unwrap()
512                    }
513                })
514                .collect();
515            let mut builder_generics_tuple = empty_type_tuple();
516            let generics = self.modify_generics(|g| {
517                let index_after_lifetime_in_generics = g
518                    .params
519                    .iter()
520                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
521                    .count();
522                for f in self.included_fields() {
523                    if f.builder_attr.default.is_some() {
524                        // `f` is not mandatory - it does not have it's own fake `build` method, so
525                        // `field` will need to warn about missing `field`
526                        // whether or not `f` is set.
527                        assert!(
528                            f.ordinal != field.ordinal,
529                            "`required_field_impl` called for optional field {}",
530                            field.name
531                        );
532                        g.params
533                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());
534                        builder_generics_tuple.elems.push_value(f.type_ident());
535                    } else if f.ordinal < field.ordinal {
536                        // Only add a `build` method that warns about missing `field` if `f` is set.
537                        // If `f` is not set, `f`'s `build` method will
538                        // warn, since it appears earlier in the argument list.
539                        builder_generics_tuple
540                            .elems
541                            .push_value(f.tuplized_type_ty_param());
542                    } else if f.ordinal == field.ordinal {
543                        builder_generics_tuple.elems.push_value(empty_type());
544                    } else {
545                        // `f` appears later in the argument list after `field`, so if they are both
546                        // missing we will show a warning for `field` and
547                        // not for `f` - which means this warning should appear whether
548                        // or not `f` is set.
549                        g.params
550                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());
551                        builder_generics_tuple.elems.push_value(f.type_ident());
552                    }
553
554                    builder_generics_tuple.elems.push_punct(Default::default());
555                }
556            });
557
558            let index_after_lifetime_in_generics = builder_generics
559                .iter()
560                .filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))
561                .count();
562            builder_generics.insert(
563                index_after_lifetime_in_generics,
564                syn::GenericArgument::Type(builder_generics_tuple.into()),
565            );
566            let (impl_generics, _, where_clause) = generics.split_for_impl();
567            let (_, ty_generics, _) = self.generics.split_for_impl();
568
569            let early_build_error_type_name = syn::Ident::new(
570                &format!(
571                    "{}_Error_Missing_required_field_{}",
572                    builder_name,
573                    strip_raw_ident_prefix(field_name.to_string())
574                ),
575                proc_macro2::Span::call_site(),
576            );
577            let early_build_error_message = format!("Missing required field {}", field_name);
578
579            Ok(quote! {
580                #[doc(hidden)]
581                #[allow(dead_code, non_camel_case_types, non_snake_case)]
582                pub enum #early_build_error_type_name {}
583                #[doc(hidden)]
584                #[allow(dead_code, non_camel_case_types, missing_docs, clippy::panic)]
585                impl #impl_generics #builder_name < #( #builder_generics ),* > #where_clause {
586                    #[deprecated(
587                        note = #early_build_error_message
588                    )]
589                    pub fn build(self, _: #early_build_error_type_name) -> #name #ty_generics {
590                        panic!();
591                    }
592                }
593            })
594        }
595
596        pub fn build_method_impl(&self) -> TokenStream {
597            let StructInfo {
598                ref name,
599                ref builder_name,
600                ref attributes,
601                ..
602            } = self;
603
604            let generics = self.modify_generics(|g| {
605                let index_after_lifetime_in_generics = g
606                    .params
607                    .iter()
608                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
609                    .count();
610                for field in self.included_fields() {
611                    if field.builder_attr.default.is_some() {
612                        let trait_ref = syn::TraitBound {
613                            paren_token: None,
614                            lifetimes: None,
615                            modifier: syn::TraitBoundModifier::None,
616                            path: syn::PathSegment {
617                                ident: self.conversion_helper_trait_name.clone(),
618                                arguments: syn::PathArguments::AngleBracketed(
619                                    syn::AngleBracketedGenericArguments {
620                                        colon2_token: None,
621                                        lt_token: Default::default(),
622                                        args: make_punctuated_single(syn::GenericArgument::Type(
623                                            field.ty.clone(),
624                                        )),
625                                        gt_token: Default::default(),
626                                    },
627                                ),
628                            }
629                            .into(),
630                        };
631                        let mut generic_param: syn::TypeParam = field.generic_ident.clone().into();
632                        generic_param.bounds.push(trait_ref.into());
633                        g.params
634                            .insert(index_after_lifetime_in_generics, generic_param.into());
635                    }
636                }
637            });
638            let (impl_generics, _, _) = generics.split_for_impl();
639
640            let (_, ty_generics, where_clause) = self.generics.split_for_impl();
641
642            let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| {
643                args.insert(
644                    0,
645                    syn::GenericArgument::Type(
646                        type_tuple(self.included_fields().map(|field| {
647                            if field.builder_attr.default.is_some() {
648                                field.type_ident()
649                            } else {
650                                field.tuplized_type_ty_param()
651                            }
652                        }))
653                        .into(),
654                    ),
655                );
656            });
657
658            let descructuring = self.included_fields().map(|f| f.name);
659
660            let helper_trait_name = &self.conversion_helper_trait_name;
661            // The default of a field can refer to earlier-defined fields, which we handle by
662            // writing out a bunch of `let` statements first, which can each refer to earlier ones.
663            // This means that field ordering may actually be significant, which isn't ideal. We
664            // could relax that restriction by calculating a DAG of field default
665            // dependencies and reordering based on that, but for now this much simpler
666            // thing is a reasonable approach.
667            let assignments = self.fields.iter().map(|field| {
668                let name = &field.name;
669                if let Some(ref default) = field.builder_attr.default {
670                    if field.builder_attr.setter.skip.is_some() {
671                        quote!(let #name = #default;)
672                    } else {
673                        quote!(let #name = #helper_trait_name::into_value(#name, || #default);)
674                    }
675                } else {
676                    quote!(let #name = #name.0;)
677                }
678            });
679            let field_names = self.fields.iter().map(|field| field.name);
680            let doc = if self.builder_attr.doc {
681                match self.builder_attr.build_method_doc {
682                    Some(ref doc) => quote!(#[doc = #doc]),
683                    None => {
684                        // I'd prefer “a” or “an” to “its”, but determining which is grammatically
685                        // correct is roughly impossible.
686                        let doc =
687                            format!("Finalise the builder and create its [`{}`] instance", name);
688                        quote!(#[doc = #doc])
689                    }
690                }
691            } else {
692                quote!()
693            };
694
695            let attributes_field = if attributes.is_some() {
696                quote! { attributes: self.attributes, }
697            } else {
698                quote! {}
699            };
700            quote!(
701                #[allow(dead_code, non_camel_case_types, missing_docs)]
702                impl #impl_generics #builder_name #modified_ty_generics #where_clause {
703                    #doc
704                    #[allow(clippy::default_trait_access)]
705                    pub fn build(self) -> #name #ty_generics {
706                        let ( #(#descructuring,)* ) = self.fields;
707                        #( #assignments )*
708                        #name {
709                            #( #field_names, )*
710                            #attributes_field
711                        }
712                    }
713                }
714            )
715        }
716    }
717
718    #[derive(Debug, Default)]
719    pub struct TypeBuilderAttr {
720        /// Whether to show docs for the `TypeBuilder` type (rather than hiding them).
721        pub doc: bool,
722
723        /// Docs on the `Type::builder()` method.
724        pub builder_method_doc: Option<syn::Expr>,
725
726        /// Docs on the `TypeBuilder` type. Specifying this implies `doc`, but you can just specify
727        /// `doc` instead and a default value will be filled in here.
728        pub builder_type_doc: Option<syn::Expr>,
729
730        /// Docs on the `TypeBuilder.build()` method. Specifying this implies `doc`, but you can
731        /// just specify `doc` instead and a default value will be filled in here.
732        pub build_method_doc: Option<syn::Expr>,
733
734        pub field_defaults: FieldBuilderAttr,
735    }
736
737    impl TypeBuilderAttr {
738        pub fn new(attrs: &[syn::Attribute]) -> Result<TypeBuilderAttr, Error> {
739            let mut result = TypeBuilderAttr::default();
740            for attr in attrs {
741                if !attr.path().is_ident("prop") {
742                    continue;
743                }
744                let as_expr: Punctuated<syn::Expr, Token![,]> =
745                    attr.parse_args_with(Punctuated::parse_terminated)?;
746                for expr in as_expr {
747                    result.apply_meta(expr)?;
748                }
749            }
750
751            Ok(result)
752        }
753
754        fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {
755            match expr {
756                syn::Expr::Assign(assign) => {
757                    let name = expr_to_single_string(&assign.left)
758                        .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
759                    match name.as_str() {
760                        "builder_method_doc" => {
761                            self.builder_method_doc = Some(*assign.right);
762                            Ok(())
763                        }
764                        "builder_type_doc" => {
765                            self.builder_type_doc = Some(*assign.right);
766                            self.doc = true;
767                            Ok(())
768                        }
769                        "build_method_doc" => {
770                            self.build_method_doc = Some(*assign.right);
771                            self.doc = true;
772                            Ok(())
773                        }
774                        _ => Err(Error::new_spanned(
775                            &assign,
776                            format!("Unknown parameter {:?}", name),
777                        )),
778                    }
779                }
780                syn::Expr::Path(path) => {
781                    let name = path_to_single_string(&path.path)
782                        .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
783                    match name.as_str() {
784                        "doc" => {
785                            self.doc = true;
786                            Ok(())
787                        }
788                        _ => Err(Error::new_spanned(
789                            &path,
790                            format!("Unknown parameter {:?}", name),
791                        )),
792                    }
793                }
794                syn::Expr::Call(call) => {
795                    let subsetting_name = if let syn::Expr::Path(path) = &*call.func {
796                        path_to_single_string(&path.path)
797                    } else {
798                        None
799                    }
800                    .ok_or_else(|| {
801                        let call_func = &call.func;
802                        let call_func = quote!(#call_func);
803                        Error::new_spanned(
804                            &call.func,
805                            format!("Illegal prop setting group {}", call_func),
806                        )
807                    })?;
808                    match subsetting_name.as_str() {
809                        "field_defaults" => {
810                            for arg in call.args {
811                                self.field_defaults.apply_meta(arg)?;
812                            }
813                            Ok(())
814                        }
815                        _ => Err(Error::new_spanned(
816                            &call.func,
817                            format!("Illegal prop setting group name {}", subsetting_name),
818                        )),
819                    }
820                }
821                _ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")),
822            }
823        }
824    }
825}
826
827mod field_info {
828    use proc_macro2::{Span, TokenStream};
829    use quote::quote;
830    use syn::parse::Error;
831    use syn::punctuated::Punctuated;
832    use syn::spanned::Spanned;
833    use syn::Token;
834
835    use super::util::{
836        expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix,
837        type_from_inside_option,
838    };
839
840    #[derive(Debug)]
841    pub struct FieldInfo<'a> {
842        pub ordinal: usize,
843        pub name: &'a syn::Ident,
844        pub generic_ident: syn::Ident,
845        pub ty: &'a syn::Type,
846        pub builder_attr: FieldBuilderAttr,
847    }
848
849    impl<'a> FieldInfo<'a> {
850        pub fn new(
851            ordinal: usize,
852            field: &syn::Field,
853            field_defaults: FieldBuilderAttr,
854        ) -> Result<FieldInfo, Error> {
855            if let Some(ref name) = field.ident {
856                let mut builder_attr = field_defaults.with(&field.attrs)?;
857
858                let strip_option_auto = builder_attr.setter.strip_option.is_some()
859                    || !builder_attr.ignore_option && type_from_inside_option(&field.ty).is_some();
860                if builder_attr.setter.strip_option.is_none() && strip_option_auto {
861                    builder_attr.default =
862                        Some(syn::parse_quote!(::std::default::Default::default()));
863                    builder_attr.setter.strip_option = Some(field.ty.span());
864                } else if name == "children" || name == "attributes" {
865                    // If this field is the `children` or `attributes` field, make it implicitly
866                    // have a default value.
867                    builder_attr.default =
868                        Some(syn::parse_quote! { ::std::default::Default::default() });
869                }
870
871                Ok(FieldInfo {
872                    ordinal,
873                    name,
874                    generic_ident: syn::Ident::new(
875                        &format!("__{}", strip_raw_ident_prefix(name.to_string())),
876                        Span::call_site(),
877                    ),
878                    ty: &field.ty,
879                    builder_attr,
880                })
881            } else {
882                Err(Error::new(field.span(), "Nameless field in struct"))
883            }
884        }
885
886        pub fn generic_ty_param(&self) -> syn::GenericParam {
887            syn::GenericParam::Type(self.generic_ident.clone().into())
888        }
889
890        pub fn type_ident(&self) -> syn::Type {
891            ident_to_type(self.generic_ident.clone())
892        }
893
894        pub fn tuplized_type_ty_param(&self) -> syn::Type {
895            let mut types = syn::punctuated::Punctuated::default();
896            types.push(self.ty.clone());
897            types.push_punct(Default::default());
898            syn::TypeTuple {
899                paren_token: Default::default(),
900                elems: types,
901            }
902            .into()
903        }
904
905        pub fn type_from_inside_option(&self) -> Option<&syn::Type> {
906            type_from_inside_option(self.ty)
907        }
908    }
909
910    #[derive(Debug, Default, Clone)]
911    pub struct FieldBuilderAttr {
912        pub default: Option<syn::Expr>,
913        pub ignore_option: bool,
914        pub setter: SetterSettings,
915        /// Example: `#[prop(attributes(html, div))]`
916        pub attributes: Option<(AttributeBase, String)>,
917    }
918
919    #[derive(Debug, Default, Clone)]
920    pub struct SetterSettings {
921        pub doc: Option<syn::Expr>,
922        pub skip: Option<Span>,
923        pub auto_into: Option<Span>,
924        pub strip_option: Option<Span>,
925        pub transform: Option<Transform>,
926    }
927
928    #[derive(Debug, Clone)]
929    pub enum AttributeBase {
930        Html,
931        Svg,
932    }
933
934    impl FieldBuilderAttr {
935        pub fn with(mut self, attrs: &[syn::Attribute]) -> Result<Self, Error> {
936            for attr in attrs {
937                if !attr.path().is_ident("prop") {
938                    continue;
939                }
940                let as_expr: Punctuated<syn::Expr, Token![,]> =
941                    attr.parse_args_with(Punctuated::parse_terminated)?;
942                for expr in as_expr {
943                    self.apply_meta(expr)?;
944                }
945            }
946
947            self.inter_fields_conflicts()?;
948
949            Ok(self)
950        }
951
952        pub fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {
953            match expr {
954                syn::Expr::Assign(assign) => {
955                    let name = expr_to_single_string(&assign.left)
956                        .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
957                    match name.as_str() {
958                        "default" => {
959                            self.default = Some(*assign.right);
960                            Ok(())
961                        }
962                        "default_code" => {
963                            if let syn::Expr::Lit(syn::ExprLit {
964                                lit: syn::Lit::Str(code),
965                                ..
966                            }) = *assign.right
967                            {
968                                use std::str::FromStr;
969                                let tokenized_code = TokenStream::from_str(&code.value())?;
970                                self.default = Some(
971                                    syn::parse(tokenized_code.into())
972                                        .map_err(|e| Error::new_spanned(code, format!("{}", e)))?,
973                                );
974                            } else {
975                                return Err(Error::new_spanned(assign.right, "Expected string"));
976                            }
977                            Ok(())
978                        }
979                        _ => Err(Error::new_spanned(
980                            &assign,
981                            format!("Unknown parameter {:?}", name),
982                        )),
983                    }
984                }
985                syn::Expr::Path(path) => {
986                    let name = path_to_single_string(&path.path)
987                        .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
988                    match name.as_str() {
989                        "default" => {
990                            self.default = Some(
991                                syn::parse(quote!(::core::default::Default::default()).into())
992                                    .unwrap(),
993                            );
994                            Ok(())
995                        }
996                        _ => Err(Error::new_spanned(
997                            &path,
998                            format!("Unknown parameter {:?}", name),
999                        )),
1000                    }
1001                }
1002                syn::Expr::Call(call) => {
1003                    let subsetting_name = if let syn::Expr::Path(path) = &*call.func {
1004                        path_to_single_string(&path.path)
1005                    } else {
1006                        None
1007                    }
1008                    .ok_or_else(|| {
1009                        let call_func = &call.func;
1010                        let call_func = quote!(#call_func);
1011                        Error::new_spanned(
1012                            &call.func,
1013                            format!("Illegal prop setting group {}", call_func),
1014                        )
1015                    })?;
1016                    match subsetting_name.as_ref() {
1017                        "setter" => {
1018                            for arg in call.args {
1019                                self.setter.apply_meta(arg)?;
1020                            }
1021                            Ok(())
1022                        }
1023                        "attributes" => {
1024                            let args = call.args.iter().collect::<Vec<_>>();
1025                            if args.len() != 2 {
1026                                Err(Error::new_spanned(
1027                                    &call.args,
1028                                    "Expected 2 arguments for `attributes`",
1029                                ))
1030                            } else {
1031                                let arg0 = expr_to_single_string(args[0]);
1032                                let arg1 = expr_to_single_string(args[1]);
1033                                if let (Some(arg0), Some(arg1)) = (arg0, arg1) {
1034                                    self.attributes = match arg0.as_str() {
1035                                        "html" => Some((AttributeBase::Html, arg1)),
1036                                        "svg" => Some((AttributeBase::Svg, arg1)),
1037                                        _ => {
1038                                            return Err(Error::new_spanned(
1039                                                args[0],
1040                                                "The first argument to `attributes` should be either `html` or `svg",
1041                                            ));
1042                                        }
1043                                    };
1044                                    Ok(())
1045                                } else {
1046                                    Err(Error::new_spanned(
1047                                        &call.args,
1048                                        "Arguments to `attributes` should be identifiers",
1049                                    ))
1050                                }
1051                            }
1052                        }
1053                        _ => Err(Error::new_spanned(
1054                            &call.func,
1055                            format!("Illegal prop setting group name {}", subsetting_name),
1056                        )),
1057                    }
1058                }
1059                syn::Expr::Unary(syn::ExprUnary {
1060                    op: syn::UnOp::Not(_),
1061                    expr,
1062                    ..
1063                }) => {
1064                    if let syn::Expr::Path(path) = *expr {
1065                        let name = path_to_single_string(&path.path)
1066                            .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
1067                        match name.as_str() {
1068                            "default" => {
1069                                self.default = None;
1070                                Ok(())
1071                            }
1072                            "optional" => {
1073                                self.ignore_option = true;
1074                                Ok(())
1075                            }
1076                            _ => Err(Error::new_spanned(path, "Unknown setting".to_owned())),
1077                        }
1078                    } else {
1079                        Err(Error::new_spanned(
1080                            expr,
1081                            "Expected simple identifier".to_owned(),
1082                        ))
1083                    }
1084                }
1085                _ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")),
1086            }
1087        }
1088
1089        fn inter_fields_conflicts(&self) -> Result<(), Error> {
1090            if let (Some(skip), None) = (&self.setter.skip, &self.default) {
1091                return Err(Error::new(
1092                    *skip,
1093                    "#[prop(skip)] must be accompanied by default or default_code",
1094                ));
1095            }
1096
1097            if let (Some(strip_option), Some(transform)) =
1098                (&self.setter.strip_option, &self.setter.transform)
1099            {
1100                let mut error = Error::new(transform.span, "transform conflicts with strip_option");
1101                error.combine(Error::new(*strip_option, "strip_option set here"));
1102                return Err(error);
1103            }
1104            Ok(())
1105        }
1106    }
1107
1108    impl SetterSettings {
1109        fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {
1110            match expr {
1111                syn::Expr::Assign(assign) => {
1112                    let name = expr_to_single_string(&assign.left)
1113                        .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
1114                    match name.as_str() {
1115                        "doc" => {
1116                            self.doc = Some(*assign.right);
1117                            Ok(())
1118                        }
1119                        "transform" => {
1120                            // if self.strip_option.is_some() {
1121                            // return Err(Error::new(assign.span(), "Illegal setting - transform
1122                            // conflicts with strip_option")); }
1123                            self.transform =
1124                                Some(parse_transform_closure(assign.left.span(), &assign.right)?);
1125                            Ok(())
1126                        }
1127                        _ => Err(Error::new_spanned(
1128                            &assign,
1129                            format!("Unknown parameter {:?}", name),
1130                        )),
1131                    }
1132                }
1133                syn::Expr::Path(path) => {
1134                    let name = path_to_single_string(&path.path)
1135                        .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
1136                    macro_rules! handle_fields {
1137                    ( $( $flag:expr, $field:ident, $already:expr, $checks:expr; )* ) => {
1138                        match name.as_str() {
1139                            $(
1140                                $flag => {
1141                                    if self.$field.is_some() {
1142                                        Err(Error::new(path.span(), concat!("Illegal setting - field is already ", $already)))
1143                                    } else {
1144                                        $checks;
1145                                        self.$field = Some(path.span());
1146                                        Ok(())
1147                                    }
1148                                }
1149                            )*
1150                            _ => Err(Error::new_spanned(
1151                                    &path,
1152                                    format!("Unknown setter parameter {:?}", name),
1153                            ))
1154                        }
1155                    }
1156                }
1157                    handle_fields!(
1158                        "skip", skip, "skipped", {};
1159                        "into", auto_into, "calling into() on the argument", {};
1160                        "strip_option", strip_option, "putting the argument in Some(...)", {
1161                            // if self.transform.is_some() {
1162                                // let mut error = Error::new(path.span(), "Illegal setting - strip_option conflicts with transform");
1163                                // error.combine(Error::new(self.transform.as_ref().unwrap().body.span(), "yup"));
1164                                // return Err(error);
1165                            // }
1166                        };
1167                    )
1168                }
1169                syn::Expr::Unary(syn::ExprUnary {
1170                    op: syn::UnOp::Not(_),
1171                    expr,
1172                    ..
1173                }) => {
1174                    if let syn::Expr::Path(path) = *expr {
1175                        let name = path_to_single_string(&path.path)
1176                            .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
1177                        match name.as_str() {
1178                            "doc" => {
1179                                self.doc = None;
1180                                Ok(())
1181                            }
1182                            "skip" => {
1183                                self.skip = None;
1184                                Ok(())
1185                            }
1186                            "auto_into" => {
1187                                self.auto_into = None;
1188                                Ok(())
1189                            }
1190                            "strip_option" => {
1191                                self.strip_option = None;
1192                                Ok(())
1193                            }
1194                            _ => Err(Error::new_spanned(path, "Unknown setting".to_owned())),
1195                        }
1196                    } else {
1197                        Err(Error::new_spanned(
1198                            expr,
1199                            "Expected simple identifier".to_owned(),
1200                        ))
1201                    }
1202                }
1203                _ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")),
1204            }
1205        }
1206    }
1207
1208    #[derive(Debug, Clone)]
1209    pub struct Transform {
1210        pub params: Vec<(syn::Pat, syn::Type)>,
1211        pub body: syn::Expr,
1212        span: Span,
1213    }
1214
1215    fn parse_transform_closure(span: Span, expr: &syn::Expr) -> Result<Transform, Error> {
1216        let closure = match expr {
1217            syn::Expr::Closure(closure) => closure,
1218            _ => return Err(Error::new_spanned(expr, "Expected closure")),
1219        };
1220        if let Some(kw) = &closure.asyncness {
1221            return Err(Error::new(kw.span, "Transform closure cannot be async"));
1222        }
1223        if let Some(kw) = &closure.capture {
1224            return Err(Error::new(kw.span, "Transform closure cannot be move"));
1225        }
1226
1227        let params = closure
1228            .inputs
1229            .iter()
1230            .map(|input| match input {
1231                syn::Pat::Type(pat_type) => Ok((
1232                    syn::Pat::clone(&pat_type.pat),
1233                    syn::Type::clone(&pat_type.ty),
1234                )),
1235                _ => Err(Error::new_spanned(
1236                    input,
1237                    "Transform closure must explicitly declare types",
1238                )),
1239            })
1240            .collect::<Result<Vec<_>, _>>()?;
1241
1242        let body = &closure.body;
1243
1244        Ok(Transform {
1245            params,
1246            body: syn::Expr::clone(body),
1247            span,
1248        })
1249    }
1250}
1251
1252mod util {
1253    use quote::ToTokens;
1254
1255    pub fn path_to_single_string(path: &syn::Path) -> Option<String> {
1256        if path.leading_colon.is_some() {
1257            return None;
1258        }
1259        let mut it = path.segments.iter();
1260        let segment = it.next()?;
1261        if it.next().is_some() {
1262            // Multipart path
1263            return None;
1264        }
1265        if segment.arguments != syn::PathArguments::None {
1266            return None;
1267        }
1268        Some(segment.ident.to_string())
1269    }
1270
1271    pub fn expr_to_single_string(expr: &syn::Expr) -> Option<String> {
1272        if let syn::Expr::Path(path) = expr {
1273            path_to_single_string(&path.path)
1274        } else {
1275            None
1276        }
1277    }
1278
1279    pub fn ident_to_type(ident: syn::Ident) -> syn::Type {
1280        let mut path = syn::Path {
1281            leading_colon: None,
1282            segments: Default::default(),
1283        };
1284        path.segments.push(syn::PathSegment {
1285            ident,
1286            arguments: Default::default(),
1287        });
1288        syn::Type::Path(syn::TypePath { qself: None, path })
1289    }
1290
1291    pub fn empty_type() -> syn::Type {
1292        syn::TypeTuple {
1293            paren_token: Default::default(),
1294            elems: Default::default(),
1295        }
1296        .into()
1297    }
1298
1299    pub fn type_tuple(elems: impl Iterator<Item = syn::Type>) -> syn::TypeTuple {
1300        let mut result = syn::TypeTuple {
1301            paren_token: Default::default(),
1302            elems: elems.collect(),
1303        };
1304        if !result.elems.empty_or_trailing() {
1305            result.elems.push_punct(Default::default());
1306        }
1307        result
1308    }
1309
1310    pub fn empty_type_tuple() -> syn::TypeTuple {
1311        syn::TypeTuple {
1312            paren_token: Default::default(),
1313            elems: Default::default(),
1314        }
1315    }
1316
1317    pub fn make_punctuated_single<T, P: Default>(value: T) -> syn::punctuated::Punctuated<T, P> {
1318        let mut punctuated = syn::punctuated::Punctuated::new();
1319        punctuated.push(value);
1320        punctuated
1321    }
1322
1323    pub fn modify_types_generics_hack<F>(
1324        ty_generics: &syn::TypeGenerics,
1325        mut mutator: F,
1326    ) -> syn::AngleBracketedGenericArguments
1327    where
1328        F: FnMut(&mut syn::punctuated::Punctuated<syn::GenericArgument, syn::token::Comma>),
1329    {
1330        let mut abga: syn::AngleBracketedGenericArguments =
1331            syn::parse(ty_generics.clone().into_token_stream().into()).unwrap_or_else(|_| {
1332                syn::AngleBracketedGenericArguments {
1333                    colon2_token: None,
1334                    lt_token: Default::default(),
1335                    args: Default::default(),
1336                    gt_token: Default::default(),
1337                }
1338            });
1339        mutator(&mut abga.args);
1340        abga
1341    }
1342
1343    pub fn strip_raw_ident_prefix(mut name: String) -> String {
1344        if name.starts_with("r#") {
1345            name.replace_range(0..2, "");
1346        }
1347        name
1348    }
1349
1350    pub fn type_from_inside_option(ty: &syn::Type) -> Option<&syn::Type> {
1351        let path = if let syn::Type::Path(type_path) = ty {
1352            if type_path.qself.is_some() {
1353                return None;
1354            } else {
1355                &type_path.path
1356            }
1357        } else {
1358            return None;
1359        };
1360        let segment = path.segments.last()?;
1361        if segment.ident != "Option" {
1362            return None;
1363        }
1364        let generic_params =
1365            if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments {
1366                generic_params
1367            } else {
1368                return None;
1369            };
1370        if let syn::GenericArgument::Type(ty) = generic_params.args.first()? {
1371            Some(ty)
1372        } else {
1373            None
1374        }
1375    }
1376}