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