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

yew_macro/html_tree/
html_for.rs

1use proc_macro2::{Ident, TokenStream};
2use quote::{quote, ToTokens};
3use syn::buffer::Cursor;
4use syn::parse::{Parse, ParseStream};
5use syn::spanned::Spanned;
6use syn::token::{For, In};
7use syn::{braced, Expr, Pat};
8
9use super::{HtmlChildrenTree, ToNodeIterator};
10use crate::html_tree::HtmlTree;
11use crate::PeekValue;
12
13/// Determines if an expression is guaranteed to always return the same value anywhere.
14fn is_contextless_pure(expr: &Expr) -> bool {
15    match expr {
16        Expr::Lit(_) => true,
17        Expr::Path(path) => path.path.get_ident().is_none(),
18        _ => false,
19    }
20}
21
22pub struct HtmlFor {
23    pat: Pat,
24    iter: Expr,
25    body: HtmlChildrenTree,
26}
27
28impl PeekValue<()> for HtmlFor {
29    fn peek(cursor: Cursor) -> Option<()> {
30        let (ident, _) = cursor.ident()?;
31        (ident == "for").then_some(())
32    }
33}
34
35impl Parse for HtmlFor {
36    fn parse(input: ParseStream) -> syn::Result<Self> {
37        For::parse(input)?;
38        let pat = Pat::parse_single(input)?;
39        In::parse(input)?;
40        let iter = Expr::parse_without_eager_brace(input)?;
41
42        let body_stream;
43        braced!(body_stream in input);
44
45        let body = HtmlChildrenTree::parse_delimited(&body_stream)?;
46        // TODO: more concise code by using if-let guards once MSRV is raised
47        for child in body.0.iter() {
48            let HtmlTree::Element(element) = child else {
49                continue;
50            };
51
52            let Some(key) = &element.props.special.key else {
53                continue;
54            };
55
56            if is_contextless_pure(&key.value) {
57                return Err(syn::Error::new(
58                    key.value.span(),
59                    "duplicate key for a node in a `for`-loop\nthis will create elements with \
60                     duplicate keys if the loop iterates more than once",
61                ));
62            }
63        }
64        Ok(Self { pat, iter, body })
65    }
66}
67
68impl ToTokens for HtmlFor {
69    fn to_tokens(&self, tokens: &mut TokenStream) {
70        let Self { pat, iter, body } = self;
71        let acc = Ident::new("__yew_v", iter.span());
72
73        let alloc_opt = body
74            .size_hint()
75            .filter(|&size| size > 1) // explicitly reserving space for 1 more element is redundant
76            .map(|size| quote!( #acc.reserve(#size) ));
77
78        let vlist_gen = match body.fully_keyed() {
79            Some(true) => quote! {
80                ::yew::virtual_dom::VList::__macro_new(
81                    #acc,
82                    ::std::option::Option::None,
83                    ::yew::virtual_dom::FullyKeyedState::KnownFullyKeyed
84                )
85            },
86            Some(false) => quote! {
87                ::yew::virtual_dom::VList::__macro_new(
88                    #acc,
89                    ::std::option::Option::None,
90                    ::yew::virtual_dom::FullyKeyedState::KnownMissingKeys
91                )
92            },
93            None => quote! {
94                ::yew::virtual_dom::VList::with_children(#acc, ::std::option::Option::None)
95            },
96        };
97
98        let body = body.0.iter().map(|child| {
99            if let Some(child) = child.to_node_iterator_stream() {
100                quote!( #acc.extend(#child) )
101            } else {
102                quote!( #acc.push(::std::convert::Into::into(#child)) )
103            }
104        });
105
106        tokens.extend(quote!({
107            let mut #acc = ::std::vec::Vec::<::yew::virtual_dom::VNode>::new();
108            ::std::iter::Iterator::for_each(
109                ::std::iter::IntoIterator::into_iter(#iter),
110                |#pat| { #alloc_opt; #(#body);* }
111            );
112            #vlist_gen
113        }))
114    }
115}