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 pub fn is_required(&self) -> bool {
33 matches!(self.attr, PropAttr::Required { .. })
34 }
35
36 fn to_check_name(&self, props_name: &Ident) -> Ident {
38 format_ident!("Has{}{}", props_name, self.name, span = Span::mixed_site())
39 }
40
41 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 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 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 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 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 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 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}