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