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::{
9 Attribute, Error, Expr, Field, GenericArgument, GenericParam, Generics, PathArguments, Type,
10 Visibility, parse_quote,
11};
12
13use super::should_preserve_attr;
14use crate::derive_props::generics::push_type_param;
15
16fn is_option_type(ty: &Type) -> bool {
17 if let Type::Path(type_path) = ty {
18 if let Some(segment) = type_path.path.segments.last() {
19 if segment.ident == "Option" {
20 if let PathArguments::AngleBracketed(args) = &segment.arguments {
21 return args.args.len() == 1
22 && matches!(args.args.first(), Some(GenericArgument::Type(_)));
23 }
24 }
25 }
26 }
27 false
28}
29
30#[derive(PartialEq, Eq)]
31pub enum PropAttr {
32 Required { wrapped_name: Ident },
33 PropOr(Expr),
34 PropOrElse(Expr),
35 PropOrDefault,
36}
37
38#[derive(Eq)]
39pub struct PropField {
40 pub ty: Type,
41 name: Ident,
42 pub attr: PropAttr,
43 extra_attrs: Vec<Attribute>,
44}
45
46impl PropField {
47 pub fn is_required(&self) -> bool {
49 matches!(self.attr, PropAttr::Required { .. })
50 }
51
52 fn to_check_name(&self, props_name: &Ident) -> Ident {
54 format_ident!("Has{}{}", props_name, self.name, span = Span::mixed_site())
55 }
56
57 fn to_check_arg_name(&self, props_name: &Ident) -> GenericParam {
59 let ident = format_ident!("How{}{}", props_name, self.name, span = Span::mixed_site());
60 GenericParam::Type(ident.into())
61 }
62
63 fn wrapped_name(&self) -> &Ident {
65 match &self.attr {
66 PropAttr::Required { wrapped_name } => wrapped_name,
67 _ => &self.name,
68 }
69 }
70
71 pub fn to_field_check<'a>(
72 &'a self,
73 props_name: &'a Ident,
74 vis: &'a Visibility,
75 token: &'a GenericParam,
76 ) -> PropFieldCheck<'a> {
77 let check_struct = self.to_check_name(props_name);
78 let check_arg = self.to_check_arg_name(props_name);
79 PropFieldCheck {
80 this: self,
81 vis,
82 token,
83 check_struct,
84 check_arg,
85 }
86 }
87
88 pub fn to_field_setter(&self) -> proc_macro2::TokenStream {
90 let name = &self.name;
91 let setter = match &self.attr {
92 PropAttr::Required { wrapped_name } => {
93 quote! {
94 #name: ::std::option::Option::unwrap(this.wrapped.#wrapped_name),
95 }
96 }
97 PropAttr::PropOr(value) => {
98 quote_spanned! {value.span()=>
99 #name: ::std::option::Option::unwrap_or(this.wrapped.#name, #value),
100 }
101 }
102 PropAttr::PropOrElse(func) => {
103 quote_spanned! {func.span()=>
104 #name: ::std::option::Option::unwrap_or_else(this.wrapped.#name, #func),
105 }
106 }
107 PropAttr::PropOrDefault => {
108 quote! {
109 #name: ::std::option::Option::unwrap_or_default(this.wrapped.#name),
110 }
111 }
112 };
113 let extra_attrs = &self.extra_attrs;
114 quote! {
115 #( #extra_attrs )*
116 #setter
117 }
118 }
119
120 pub fn to_field_def(&self) -> proc_macro2::TokenStream {
122 let ty = &self.ty;
123 let extra_attrs = &self.extra_attrs;
124 let wrapped_name = self.wrapped_name();
125 quote! {
126 #( #extra_attrs )*
127 #wrapped_name: ::std::option::Option<#ty>,
128 }
129 }
130
131 pub fn to_default_setter(&self) -> proc_macro2::TokenStream {
133 let wrapped_name = self.wrapped_name();
134 let extra_attrs = &self.extra_attrs;
135 quote! {
136 #( #extra_attrs )*
137 #wrapped_name: ::std::option::Option::None,
138 }
139 }
140
141 pub fn to_build_step_fn(
143 &self,
144 vis: &Visibility,
145 props_name: &Ident,
146 ) -> proc_macro2::TokenStream {
147 let Self { name, ty, attr, .. } = self;
148 let token_ty = Ident::new("__YewTokenTy", Span::mixed_site());
149 let none_fn_name = format_ident!("{}_none", name, span = Span::mixed_site());
150 let build_fn = match attr {
151 PropAttr::Required { wrapped_name } => {
152 let check_struct = self.to_check_name(props_name);
153 let none_setter = if is_option_type(ty) {
154 quote! {
155 #[doc(hidden)]
156 #vis fn #none_fn_name<#token_ty>(
157 &mut self,
158 token: #token_ty,
159 ) -> #check_struct< #token_ty > {
160 self.wrapped.#wrapped_name = ::std::option::Option::Some(::std::option::Option::None);
161 #check_struct ( ::std::marker::PhantomData )
162 }
163 }
164 } else {
165 quote! {}
166 };
167 quote! {
168 #[doc(hidden)]
169 #vis fn #name<#token_ty>(
170 &mut self,
171 token: #token_ty,
172 value: impl ::yew::html::IntoPropValue<#ty>,
173 ) -> #check_struct< #token_ty > {
174 self.wrapped.#wrapped_name = ::std::option::Option::Some(value.into_prop_value());
175 #check_struct ( ::std::marker::PhantomData )
176 }
177
178 #none_setter
179 }
180 }
181 _ => {
182 let none_setter = if is_option_type(ty) {
183 quote! {
184 #[doc(hidden)]
185 #vis fn #none_fn_name<#token_ty>(
186 &mut self,
187 token: #token_ty,
188 ) -> #token_ty {
189 self.wrapped.#name = ::std::option::Option::Some(::std::option::Option::None);
190 token
191 }
192 }
193 } else {
194 quote! {}
195 };
196 quote! {
197 #[doc(hidden)]
198 #vis fn #name<#token_ty>(
199 &mut self,
200 token: #token_ty,
201 value: impl ::yew::html::IntoPropValue<#ty>,
202 ) -> #token_ty {
203 self.wrapped.#name = ::std::option::Option::Some(value.into_prop_value());
204 token
205 }
206
207 #none_setter
208 }
209 }
210 };
211 let extra_attrs = &self.extra_attrs;
212 quote! {
213 #( #extra_attrs )*
214 #build_fn
215 }
216 }
217
218 fn attribute(named_field: &Field) -> Result<PropAttr> {
220 let attr = named_field.attrs.iter().find(|attr| {
221 attr.path().is_ident("prop_or")
222 || attr.path().is_ident("prop_or_else")
223 || attr.path().is_ident("prop_or_default")
224 });
225
226 if let Some(attr) = attr {
227 if attr.path().is_ident("prop_or") {
228 Ok(PropAttr::PropOr(attr.parse_args()?))
229 } else if attr.path().is_ident("prop_or_else") {
230 Ok(PropAttr::PropOrElse(attr.parse_args()?))
231 } else if attr.path().is_ident("prop_or_default") {
232 Ok(PropAttr::PropOrDefault)
233 } else {
234 unreachable!()
235 }
236 } else {
237 let ident = named_field.ident.as_ref().unwrap();
238 let wrapped_name = format_ident!("{}_wrapper", ident, span = Span::mixed_site());
239 Ok(PropAttr::Required { wrapped_name })
240 }
241 }
242}
243
244pub struct PropFieldCheck<'a> {
245 this: &'a PropField,
246 vis: &'a Visibility,
247 token: &'a GenericParam,
248 check_struct: Ident,
249 check_arg: GenericParam,
250}
251
252impl PropFieldCheck<'_> {
253 pub fn to_fake_prop_decl(&self) -> proc_macro2::TokenStream {
254 let Self { this, .. } = self;
255 if !this.is_required() {
256 return Default::default();
257 }
258 let mut prop_check_name = this.name.clone();
259 prop_check_name.set_span(Span::mixed_site());
260 quote! {
261 #[allow(non_camel_case_types)]
262 pub struct #prop_check_name;
263 }
264 }
265
266 pub fn to_stream(
267 &self,
268 type_generics: &mut Generics,
269 check_args: &mut Vec<GenericParam>,
270 prop_name_mod: &Ident,
271 ) -> proc_macro2::TokenStream {
272 let Self {
273 this,
274 vis,
275 token,
276 check_struct,
277 check_arg,
278 } = self;
279 if !this.is_required() {
280 return Default::default();
281 }
282 let mut prop_check_name = this.name.clone();
283 prop_check_name.set_span(Span::mixed_site());
284 check_args.push(check_arg.clone());
285 push_type_param(type_generics, check_arg.clone());
286 let where_clause = type_generics.make_where_clause();
287 where_clause.predicates.push(parse_quote! {
288 #token: ::yew::html::HasProp< #prop_name_mod :: #prop_check_name, #check_arg >
289 });
290
291 quote! {
292 #[doc(hidden)]
293 #[allow(non_camel_case_types)]
294 #vis struct #check_struct<How>(::std::marker::PhantomData<How>);
295
296 #[automatically_derived]
297 #[diagnostic::do_not_recommend]
298 impl<B> ::yew::html::HasProp< #prop_name_mod :: #prop_check_name, #check_struct<B>>
299 for #check_struct<B> {}
300
301 #[automatically_derived]
302 #[diagnostic::do_not_recommend]
303 impl<B, P, How> ::yew::html::HasProp<P, &dyn ::yew::html::HasProp<P, How>>
304 for #check_struct<B>
305 where B: ::yew::html::HasProp<P, How> {}
306
307 }
308 }
309}
310
311impl TryFrom<Field> for PropField {
312 type Error = Error;
313
314 fn try_from(field: Field) -> Result<Self> {
315 let extra_attrs = field
316 .attrs
317 .iter()
318 .filter(|a| should_preserve_attr(a))
319 .cloned()
320 .collect();
321
322 Ok(PropField {
323 attr: Self::attribute(&field)?,
324 extra_attrs,
325 ty: field.ty,
326 name: field.ident.unwrap(),
327 })
328 }
329}
330
331impl PartialOrd for PropField {
332 fn partial_cmp(&self, other: &PropField) -> Option<Ordering> {
333 Some(self.cmp(other))
334 }
335}
336
337impl Ord for PropField {
338 fn cmp(&self, other: &PropField) -> Ordering {
339 if self.name == other.name {
340 Ordering::Equal
341 } else if self.name == "children" {
342 Ordering::Greater
343 } else if other.name == "children" {
344 Ordering::Less
345 } else {
346 self.name.cmp(&other.name)
347 }
348 }
349}
350
351impl PartialEq for PropField {
352 fn eq(&self, other: &Self) -> bool {
353 self.name == other.name
354 }
355}