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

yew_macro/props/
prop_macro.rs

1use std::convert::TryInto;
2
3use proc_macro2::TokenStream;
4use quote::{quote_spanned, ToTokens};
5use syn::parse::{Parse, ParseStream};
6use syn::punctuated::Punctuated;
7use syn::spanned::Spanned;
8use syn::token::Brace;
9use syn::{Expr, Token, TypePath};
10
11use super::{ComponentProps, Prop, PropList, Props};
12use crate::html_tree::HtmlDashedName;
13
14/// Pop from `Punctuated` without leaving it in a state where it has trailing punctuation.
15fn pop_last_punctuated<T, P>(punctuated: &mut Punctuated<T, P>) -> Option<T> {
16    let value = punctuated.pop().map(|pair| pair.into_value());
17    // remove the 2nd last value and push it right back to remove the trailing punctuation
18    if let Some(pair) = punctuated.pop() {
19        punctuated.push_value(pair.into_value());
20    }
21    value
22}
23
24/// Check if the given type path looks like an associated `Properties` type.
25fn is_associated_properties(ty: &TypePath) -> bool {
26    let mut segments_it = ty.path.segments.iter();
27    if let Some(seg) = segments_it.next_back() {
28        // if the last segment is `Properties` ...
29        if seg.ident == "Properties" {
30            if let Some(seg) = segments_it.next_back() {
31                // ... and we can be reasonably sure that the previous segment is a component ...
32                if !crate::non_capitalized_ascii(&seg.ident.to_string()) {
33                    // ... then we assume that this is an associated type like
34                    // `Component::Properties`
35                    return true;
36                }
37            }
38        }
39    }
40
41    false
42}
43
44struct PropValue {
45    label: HtmlDashedName,
46    value: Expr,
47}
48impl Parse for PropValue {
49    fn parse(input: ParseStream) -> syn::Result<Self> {
50        let label = input.parse()?;
51        let value = if input.peek(Token![:]) {
52            let _colon_token: Token![:] = input.parse()?;
53            input.parse()?
54        } else {
55            syn::parse_quote!(#label)
56        };
57        Ok(Self { label, value })
58    }
59}
60
61impl From<PropValue> for Prop {
62    fn from(prop_value: PropValue) -> Prop {
63        let PropValue { label, value } = prop_value;
64        Prop {
65            label,
66            value,
67            directive: None,
68        }
69    }
70}
71
72struct PropsExpr {
73    ty: TypePath,
74    _brace_token: Brace,
75    fields: Punctuated<PropValue, Token![,]>,
76}
77impl Parse for PropsExpr {
78    fn parse(input: ParseStream) -> syn::Result<Self> {
79        let mut ty: TypePath = input.parse()?;
80
81        // if the type isn't already qualified (`<x as y>`) and it's an associated type
82        // (`MyComp::Properties`) ...
83        if ty.qself.is_none() && is_associated_properties(&ty) {
84            pop_last_punctuated(&mut ty.path.segments);
85            // .. transform it into a "qualified-self" type
86            ty = syn::parse2(quote_spanned! {ty.span()=>
87                <#ty as ::yew::html::Component>::Properties
88            })?;
89        }
90
91        let content;
92        let brace_token = syn::braced!(content in input);
93        let fields = content.parse_terminated(PropValue::parse, Token![,])?;
94        Ok(Self {
95            ty,
96            _brace_token: brace_token,
97            fields,
98        })
99    }
100}
101
102pub struct PropsMacroInput {
103    ty: TypePath,
104    props: ComponentProps,
105}
106impl Parse for PropsMacroInput {
107    fn parse(input: ParseStream) -> syn::Result<Self> {
108        let PropsExpr { ty, fields, .. } = input.parse()?;
109        let prop_list = PropList::new(fields.into_iter().map(Into::into).collect());
110        let props: Props = prop_list.try_into()?;
111        props.special.check_all(|prop| {
112            let label = &prop.label;
113            Err(syn::Error::new_spanned(
114                label,
115                "special props cannot be specified in the `props!` macro",
116            ))
117        })?;
118        Ok(Self {
119            ty,
120            props: props.try_into()?,
121        })
122    }
123}
124impl ToTokens for PropsMacroInput {
125    fn to_tokens(&self, tokens: &mut TokenStream) {
126        let Self { ty, props } = self;
127
128        tokens.extend(props.build_properties_tokens(ty, None::<TokenStream>))
129    }
130}