This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew_macro/derive_props/
field.rs

1use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
2use std::convert::TryFrom;
3
4use proc_macro2::{Ident, Span};
5use quote::{format_ident, quote, quote_spanned};
6use syn::parse::Result;
7use syn::spanned::Spanned;
8use syn::{parse_quote, Attribute, Error, Expr, Field, GenericParam, Generics, Type, Visibility};
9
10use super::should_preserve_attr;
11use crate::derive_props::generics::push_type_param;
12
13#[allow(clippy::large_enum_variant)]
14#[derive(PartialEq, Eq)]
15pub enum PropAttr {
16    Required { wrapped_name: Ident },
17    PropOr(Expr),
18    PropOrElse(Expr),
19    PropOrDefault,
20}
21
22#[derive(Eq)]
23pub struct PropField {
24    pub ty: Type,
25    name: Ident,
26    pub attr: PropAttr,
27    extra_attrs: Vec<Attribute>,
28}
29
30impl PropField {
31    /// All required property fields are wrapped in an `Option`
32    pub fn is_required(&self) -> bool {
33        matches!(self.attr, PropAttr::Required { .. })
34    }
35
36    /// This check name is descriptive to help a developer realize they missed a required prop
37    fn to_check_name(&self, props_name: &Ident) -> Ident {
38        format_ident!("Has{}{}", props_name, self.name, span = Span::mixed_site())
39    }
40
41    /// This check name is descriptive to help a developer realize they missed a required prop
42    fn to_check_arg_name(&self, props_name: &Ident) -> GenericParam {
43        let ident = format_ident!("How{}{}", props_name, self.name, span = Span::mixed_site());
44        GenericParam::Type(ident.into())
45    }
46
47    /// Ident of the wrapped field name
48    fn wrapped_name(&self) -> &Ident {
49        match &self.attr {
50            PropAttr::Required { wrapped_name } => wrapped_name,
51            _ => &self.name,
52        }
53    }
54
55    pub fn to_field_check<'a>(
56        &'a self,
57        props_name: &'a Ident,
58        vis: &'a Visibility,
59        token: &'a GenericParam,
60    ) -> PropFieldCheck<'a> {
61        let check_struct = self.to_check_name(props_name);
62        let check_arg = self.to_check_arg_name(props_name);
63        PropFieldCheck {
64            this: self,
65            vis,
66            token,
67            check_struct,
68            check_arg,
69        }
70    }
71
72    /// Used to transform the `PropWrapper` struct into `Properties`
73    pub fn to_field_setter(&self) -> proc_macro2::TokenStream {
74        let name = &self.name;
75        let setter = match &self.attr {
76            PropAttr::Required { wrapped_name } => {
77                quote! {
78                    #name: ::std::option::Option::unwrap(this.wrapped.#wrapped_name),
79                }
80            }
81            PropAttr::PropOr(value) => {
82                quote_spanned! {value.span()=>
83                    #name: ::std::option::Option::unwrap_or(this.wrapped.#name, #value),
84                }
85            }
86            PropAttr::PropOrElse(func) => {
87                quote_spanned! {func.span()=>
88                    #name: ::std::option::Option::unwrap_or_else(this.wrapped.#name, #func),
89                }
90            }
91            PropAttr::PropOrDefault => {
92                quote! {
93                    #name: ::std::option::Option::unwrap_or_default(this.wrapped.#name),
94                }
95            }
96        };
97        let extra_attrs = &self.extra_attrs;
98        quote! {
99            #( #extra_attrs )*
100            #setter
101        }
102    }
103
104    /// Wrap all required props in `Option`
105    pub fn to_field_def(&self) -> proc_macro2::TokenStream {
106        let ty = &self.ty;
107        let extra_attrs = &self.extra_attrs;
108        let wrapped_name = self.wrapped_name();
109        quote! {
110            #( #extra_attrs )*
111            #wrapped_name: ::std::option::Option<#ty>,
112        }
113    }
114
115    /// All optional props must implement the `Default` trait
116    pub fn to_default_setter(&self) -> proc_macro2::TokenStream {
117        let wrapped_name = self.wrapped_name();
118        let extra_attrs = &self.extra_attrs;
119        quote! {
120            #( #extra_attrs )*
121            #wrapped_name: ::std::option::Option::None,
122        }
123    }
124
125    /// Each field is set using a builder method
126    pub fn to_build_step_fn(
127        &self,
128        vis: &Visibility,
129        props_name: &Ident,
130    ) -> proc_macro2::TokenStream {
131        let Self { name, ty, attr, .. } = self;
132        let token_ty = Ident::new("__YewTokenTy", Span::mixed_site());
133        let build_fn = match attr {
134            PropAttr::Required { wrapped_name } => {
135                let check_struct = self.to_check_name(props_name);
136                quote! {
137                    #[doc(hidden)]
138                    #vis fn #name<#token_ty>(
139                        &mut self,
140                        token: #token_ty,
141                        value: impl ::yew::html::IntoPropValue<#ty>,
142                    ) -> #check_struct< #token_ty > {
143                        self.wrapped.#wrapped_name = ::std::option::Option::Some(value.into_prop_value());
144                        #check_struct ( ::std::marker::PhantomData )
145                    }
146                }
147            }
148            _ => {
149                quote! {
150                    #[doc(hidden)]
151                    #vis fn #name<#token_ty>(
152                        &mut self,
153                        token: #token_ty,
154                        value: impl ::yew::html::IntoPropValue<#ty>,
155                    ) -> #token_ty {
156                        self.wrapped.#name = ::std::option::Option::Some(value.into_prop_value());
157                        token
158                    }
159                }
160            }
161        };
162        let extra_attrs = &self.extra_attrs;
163        quote! {
164            #( #extra_attrs )*
165            #build_fn
166        }
167    }
168
169    // Detect Properties 2.0 attributes
170    fn attribute(named_field: &Field) -> Result<PropAttr> {
171        let attr = named_field.attrs.iter().find(|attr| {
172            attr.path().is_ident("prop_or")
173                || attr.path().is_ident("prop_or_else")
174                || attr.path().is_ident("prop_or_default")
175        });
176
177        if let Some(attr) = attr {
178            if attr.path().is_ident("prop_or") {
179                Ok(PropAttr::PropOr(attr.parse_args()?))
180            } else if attr.path().is_ident("prop_or_else") {
181                Ok(PropAttr::PropOrElse(attr.parse_args()?))
182            } else if attr.path().is_ident("prop_or_default") {
183                Ok(PropAttr::PropOrDefault)
184            } else {
185                unreachable!()
186            }
187        } else {
188            let ident = named_field.ident.as_ref().unwrap();
189            let wrapped_name = format_ident!("{}_wrapper", ident, span = Span::mixed_site());
190            Ok(PropAttr::Required { wrapped_name })
191        }
192    }
193}
194
195pub struct PropFieldCheck<'a> {
196    this: &'a PropField,
197    vis: &'a Visibility,
198    token: &'a GenericParam,
199    check_struct: Ident,
200    check_arg: GenericParam,
201}
202
203impl PropFieldCheck<'_> {
204    pub fn to_fake_prop_decl(&self) -> proc_macro2::TokenStream {
205        let Self { this, .. } = self;
206        if !this.is_required() {
207            return Default::default();
208        }
209        let mut prop_check_name = this.name.clone();
210        prop_check_name.set_span(Span::mixed_site());
211        quote! {
212            #[allow(non_camel_case_types)]
213            pub struct #prop_check_name;
214        }
215    }
216
217    pub fn to_stream(
218        &self,
219        type_generics: &mut Generics,
220        check_args: &mut Vec<GenericParam>,
221        prop_name_mod: &Ident,
222    ) -> proc_macro2::TokenStream {
223        let Self {
224            this,
225            vis,
226            token,
227            check_struct,
228            check_arg,
229        } = self;
230        if !this.is_required() {
231            return Default::default();
232        }
233        let mut prop_check_name = this.name.clone();
234        prop_check_name.set_span(Span::mixed_site());
235        check_args.push(check_arg.clone());
236        push_type_param(type_generics, check_arg.clone());
237        let where_clause = type_generics.make_where_clause();
238        where_clause.predicates.push(parse_quote! {
239            #token: ::yew::html::HasProp< #prop_name_mod :: #prop_check_name, #check_arg >
240        });
241
242        quote! {
243            #[doc(hidden)]
244            #[allow(non_camel_case_types)]
245            #vis struct #check_struct<How>(::std::marker::PhantomData<How>);
246
247            #[automatically_derived]
248            impl<B> ::yew::html::HasProp< #prop_name_mod :: #prop_check_name, #check_struct<B>>
249                for #check_struct<B> {}
250            #[automatically_derived]
251            impl<B, P, How> ::yew::html::HasProp<P, &dyn ::yew::html::HasProp<P, How>>
252                for #check_struct<B>
253                where B: ::yew::html::HasProp<P, How> {}
254
255        }
256    }
257}
258
259impl TryFrom<Field> for PropField {
260    type Error = Error;
261
262    fn try_from(field: Field) -> Result<Self> {
263        let extra_attrs = field
264            .attrs
265            .iter()
266            .filter(|a| should_preserve_attr(a))
267            .cloned()
268            .collect();
269
270        Ok(PropField {
271            attr: Self::attribute(&field)?,
272            extra_attrs,
273            ty: field.ty,
274            name: field.ident.unwrap(),
275        })
276    }
277}
278
279impl PartialOrd for PropField {
280    fn partial_cmp(&self, other: &PropField) -> Option<Ordering> {
281        Some(self.cmp(other))
282    }
283}
284
285impl Ord for PropField {
286    fn cmp(&self, other: &PropField) -> Ordering {
287        if self.name == other.name {
288            Ordering::Equal
289        } else if self.name == "children" {
290            Ordering::Greater
291        } else if other.name == "children" {
292            Ordering::Less
293        } else {
294            self.name.cmp(&other.name)
295        }
296    }
297}
298
299impl PartialEq for PropField {
300    fn eq(&self, other: &Self) -> bool {
301        self.name == other.name
302    }
303}