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

yew_macro/derive_props/
mod.rs

1mod builder;
2mod field;
3mod generics;
4mod wrapper;
5
6use std::convert::TryInto;
7
8use builder::PropsBuilder;
9use field::PropField;
10use proc_macro2::{Ident, Span};
11use quote::{format_ident, quote, ToTokens};
12use syn::parse::{Parse, ParseStream, Result};
13use syn::punctuated::Pair;
14use syn::visit_mut::VisitMut;
15use syn::{
16    AngleBracketedGenericArguments, Attribute, ConstParam, DeriveInput, GenericArgument,
17    GenericParam, Generics, Path, PathArguments, PathSegment, Type, TypeParam, TypePath,
18    Visibility,
19};
20use wrapper::PropsWrapper;
21
22use self::field::PropAttr;
23use self::generics::to_arguments;
24
25pub struct DerivePropsInput {
26    vis: Visibility,
27    generics: Generics,
28    props_name: Ident,
29    prop_fields: Vec<PropField>,
30    preserved_attrs: Vec<Attribute>,
31}
32
33/// AST visitor that replaces all occurences of the keyword `Self` with `new_self`
34struct Normaliser<'ast> {
35    new_self: &'ast Ident,
36    generics: &'ast Generics,
37    /// `Option` for one-time initialisation
38    new_self_full: Option<PathSegment>,
39}
40
41impl<'ast> Normaliser<'ast> {
42    pub fn new(new_self: &'ast Ident, generics: &'ast Generics) -> Self {
43        Self {
44            new_self,
45            generics,
46            new_self_full: None,
47        }
48    }
49
50    fn get_new_self(&mut self) -> PathSegment {
51        self.new_self_full
52            .get_or_insert_with(|| {
53                PathSegment {
54                    ident: self.new_self.clone(),
55                    arguments: if self.generics.lt_token.is_some() {
56                        PathArguments::AngleBracketed(AngleBracketedGenericArguments {
57                            colon2_token: Some(Default::default()),
58                            lt_token: Default::default(),
59                            args: self
60                                .generics
61                                .params
62                                .pairs()
63                                .map(|pair| {
64                                    let (value, punct) = pair.cloned().into_tuple();
65                                    let value = match value {
66                                        GenericParam::Lifetime(param) => {
67                                            GenericArgument::Lifetime(param.lifetime)
68                                        }
69                                        GenericParam::Type(TypeParam { ident, .. })
70                                        | GenericParam::Const(ConstParam { ident, .. }) => {
71                                            GenericArgument::Type(Type::Path(TypePath {
72                                                qself: None,
73                                                path: ident.into(),
74                                            }))
75                                        }
76                                    };
77                                    Pair::new(value, punct)
78                                })
79                                .collect(),
80                            gt_token: Default::default(),
81                        })
82                    } else {
83                        // if no generics were defined for the struct
84                        PathArguments::None
85                    },
86                }
87            })
88            .clone()
89    }
90}
91
92impl VisitMut for Normaliser<'_> {
93    fn visit_path_mut(&mut self, path: &mut Path) {
94        if let Some(first) = path.segments.first_mut() {
95            if first.ident == "Self" {
96                *first = self.get_new_self();
97            }
98            syn::visit_mut::visit_path_mut(self, path)
99        }
100    }
101}
102
103/// Some attributes on the original struct are to be preserved and added to the builder struct,
104/// in order to avoid warnings (sometimes reported as errors) in the output.
105fn should_preserve_attr(attr: &Attribute) -> bool {
106    // #[cfg(...)]: does not usually appear in macro inputs, but rust-analyzer seems to generate it
107    // sometimes.              If not preserved, results in "no-such-field" errors generating
108    // the field setter for `build` #[allow(...)]: silences warnings from clippy, such as
109    // dead_code etc. #[deny(...)]: enable additional warnings from clippy
110    let path = attr.path();
111    path.is_ident("allow") || path.is_ident("deny") || path.is_ident("cfg")
112}
113
114impl Parse for DerivePropsInput {
115    fn parse(input: ParseStream) -> Result<Self> {
116        let input: DeriveInput = input.parse()?;
117        let prop_fields = match input.data {
118            syn::Data::Struct(data) => match data.fields {
119                syn::Fields::Named(fields) => {
120                    let mut prop_fields: Vec<PropField> = fields
121                        .named
122                        .into_iter()
123                        .map(|f| f.try_into())
124                        .collect::<Result<Vec<PropField>>>()?;
125
126                    // Alphabetize
127                    prop_fields.sort();
128
129                    prop_fields
130                }
131                syn::Fields::Unit => Vec::new(),
132                _ => unimplemented!("only structs are supported"),
133            },
134            _ => unimplemented!("only structs are supported"),
135        };
136
137        let preserved_attrs = input
138            .attrs
139            .iter()
140            .filter(|a| should_preserve_attr(a))
141            .cloned()
142            .collect();
143
144        Ok(Self {
145            vis: input.vis,
146            props_name: input.ident,
147            generics: input.generics,
148            prop_fields,
149            preserved_attrs,
150        })
151    }
152}
153
154impl DerivePropsInput {
155    /// Replaces all occurences of `Self` in the struct with the actual name of the struct.
156    /// Must be called before tokenising the struct.
157    pub fn normalise(&mut self) {
158        let mut normaliser = Normaliser::new(&self.props_name, &self.generics);
159        for field in &mut self.prop_fields {
160            normaliser.visit_type_mut(&mut field.ty);
161            if let PropAttr::PropOr(expr) | PropAttr::PropOrElse(expr) = &mut field.attr {
162                normaliser.visit_expr_mut(expr)
163            }
164        }
165    }
166}
167
168impl ToTokens for DerivePropsInput {
169    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
170        let Self {
171            generics,
172            props_name,
173            prop_fields,
174            preserved_attrs,
175            ..
176        } = self;
177
178        // The wrapper is a new struct which wraps required props in `Option`
179        let wrapper_name = format_ident!("{}Wrapper", props_name, span = Span::mixed_site());
180        let wrapper = PropsWrapper::new(&wrapper_name, generics, prop_fields, preserved_attrs);
181        tokens.extend(wrapper.into_token_stream());
182
183        // The builder will only build if all required props have been set
184        let builder_name = format_ident!("{}Builder", props_name, span = Span::mixed_site());
185        let check_all_props_name =
186            format_ident!("Check{}All", props_name, span = Span::mixed_site());
187        let builder = PropsBuilder::new(
188            &builder_name,
189            self,
190            &wrapper_name,
191            &check_all_props_name,
192            preserved_attrs,
193        );
194        let generic_args = to_arguments(generics);
195        tokens.extend(builder.into_token_stream());
196
197        // The properties trait has a `builder` method which creates the props builder
198        let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
199        let properties = quote! {
200            impl #impl_generics ::yew::html::Properties for #props_name #ty_generics #where_clause {
201                type Builder = #builder_name<#generic_args>;
202
203                fn builder() -> Self::Builder {
204                    #builder_name {
205                        wrapped: ::std::boxed::Box::new(::std::default::Default::default()),
206                    }
207                }
208            }
209        };
210        tokens.extend(properties);
211    }
212}