1use 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)] 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 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 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 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 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 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 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 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 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 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 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 pub doc: bool,
722
723 pub builder_method_doc: Option<syn::Expr>,
725
726 pub builder_type_doc: Option<syn::Expr>,
729
730 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 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 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 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 };
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 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}