From b725e3e4415bc11298a6a9ed489fbc6e031081f3 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 29 Oct 2023 01:04:25 +0100 Subject: [PATCH 01/13] added for loops to html macro --- .../src/components/pagination.rs | 6 +- examples/function_router/src/pages/post.rs | 2 +- examples/router/src/components/pagination.rs | 6 +- examples/router/src/pages/post.rs | 2 +- .../yew-macro/src/html_tree/html_block.rs | 7 ++ packages/yew-macro/src/html_tree/html_for.rs | 83 +++++++++++++++++++ packages/yew-macro/src/html_tree/html_if.rs | 29 +------ .../yew-macro/src/html_tree/html_iterable.rs | 4 + packages/yew-macro/src/html_tree/html_node.rs | 11 ++- packages/yew-macro/src/html_tree/mod.rs | 72 +++++++++------- .../yew-macro/tests/html_macro/for-fail.rs | 8 ++ .../tests/html_macro/for-fail.stderr | 34 ++++++++ .../yew-macro/tests/html_macro/for-pass.rs | 58 +++++++++++++ .../tests/html_macro/iterable-fail.rs | 4 +- .../tests/html_macro/iterable-fail.stderr | 71 +++++++--------- .../tests/html_macro/iterable-pass.rs | 18 ++-- 16 files changed, 303 insertions(+), 112 deletions(-) create mode 100644 packages/yew-macro/src/html_tree/html_for.rs create mode 100644 packages/yew-macro/tests/html_macro/for-fail.rs create mode 100644 packages/yew-macro/tests/html_macro/for-fail.stderr create mode 100644 packages/yew-macro/tests/html_macro/for-pass.rs diff --git a/examples/function_router/src/components/pagination.rs b/examples/function_router/src/components/pagination.rs index d9b779e27c6..693bda5e72a 100644 --- a/examples/function_router/src/components/pagination.rs +++ b/examples/function_router/src/components/pagination.rs @@ -84,7 +84,11 @@ pub fn RenderLinks(props: &RenderLinksProps) -> Html { } } else { - html! { for range.map(|page| html! {}) } + html! { + for page in range { + + } + } } } diff --git a/examples/function_router/src/pages/post.rs b/examples/function_router/src/pages/post.rs index c542c433ae9..af3f66d036f 100644 --- a/examples/function_router/src/pages/post.rs +++ b/examples/function_router/src/pages/post.rs @@ -118,7 +118,7 @@ pub fn Post(props: &Props) -> Html { render_quote(quote) } }); - html! { for parts } + html! {{for parts}} }; let keywords = post diff --git a/examples/router/src/components/pagination.rs b/examples/router/src/components/pagination.rs index 2a51636ca9e..2ced795e76f 100644 --- a/examples/router/src/components/pagination.rs +++ b/examples/router/src/components/pagination.rs @@ -80,7 +80,11 @@ impl Pagination { } } else { - html! { for pages.map(|page| self.render_link(page, props)) } + html! { + for page in pages { + {self.render_link(page, props)} + } + } } } diff --git a/examples/router/src/pages/post.rs b/examples/router/src/pages/post.rs index 6d29957f470..7a0e52bc302 100644 --- a/examples/router/src/pages/post.rs +++ b/examples/router/src/pages/post.rs @@ -137,6 +137,6 @@ impl Post { self.render_quote(quote) } }); - html! { for parts } + html! {{for parts}} } } diff --git a/packages/yew-macro/src/html_tree/html_block.rs b/packages/yew-macro/src/html_tree/html_block.rs index 82580ab03e0..caa743df701 100644 --- a/packages/yew-macro/src/html_tree/html_block.rs +++ b/packages/yew-macro/src/html_tree/html_block.rs @@ -59,4 +59,11 @@ impl ToNodeIterator for HtmlBlock { Some(quote_spanned! {brace.span=> #new_tokens}) } + + fn is_singular(&self) -> bool { + match &self.content { + BlockContent::Node(node) => node.is_singular(), + BlockContent::Iterable(_) => false + } + } } diff --git a/packages/yew-macro/src/html_tree/html_for.rs b/packages/yew-macro/src/html_tree/html_for.rs new file mode 100644 index 00000000000..c34f2772420 --- /dev/null +++ b/packages/yew-macro/src/html_tree/html_for.rs @@ -0,0 +1,83 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::{quote, ToTokens}; +use syn::buffer::Cursor; +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; +use syn::{braced, Expr, Pat, Token}; + +use super::{HtmlChildrenTree, ToNodeIterator}; +use crate::PeekValue; + +pub struct HtmlFor { + for_token: Token![for], + pat: Pat, + in_token: Token![in], + 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 { + let for_token = input.parse()?; + let pat = Pat::parse_single(input)?; + let in_token = input.parse()?; + let iter = Expr::parse_without_eager_brace(input)?; + + let body_stream; + braced!(body_stream in input); + let body = HtmlChildrenTree::parse_delimited(&body_stream)?; + Ok(Self { + for_token, + pat, + in_token, + iter, + body, + }) + } +} + +impl ToTokens for HtmlFor { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + for_token, + pat, + in_token, + iter, + body, + } = self; + // TODO: call `__yew_v.reserve` if the amount of elements added per iteration can be + // pre-determined + let acc = Ident::new("__yew_v", iter.span()); + let optimisation = body.size_hint().map(|size| { + quote!( + #acc.reserve(#size); + ) + }); + 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(); + #for_token #pat #in_token #iter { + #optimisation + #(#body)* + } + ::yew::virtual_dom::VList::with_children(#acc, ::std::option::Option::None) + })) + } +} diff --git a/packages/yew-macro/src/html_tree/html_if.rs b/packages/yew-macro/src/html_tree/html_if.rs index 8b5481fc2b3..56c64dbcd6f 100644 --- a/packages/yew-macro/src/html_tree/html_if.rs +++ b/packages/yew-macro/src/html_tree/html_if.rs @@ -3,9 +3,9 @@ use quote::{quote_spanned, ToTokens}; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; -use syn::{Expr, Token}; +use syn::{parse_quote, Expr, Token}; -use super::{HtmlRootBraced, ToNodeIterator}; +use super::HtmlRootBraced; use crate::PeekValue; pub struct HtmlIf { @@ -69,13 +69,13 @@ impl Parse for HtmlIf { impl ToTokens for HtmlIf { fn to_tokens(&self, tokens: &mut TokenStream) { - let HtmlIf { + let Self { if_token, cond, then_branch, else_branch, } = self; - let default_else_branch = syn::parse_quote! { {} }; + let default_else_branch = parse_quote! { {} }; let else_branch = else_branch .as_ref() .map(|(_, branch)| branch) @@ -88,27 +88,6 @@ impl ToTokens for HtmlIf { } } -impl ToNodeIterator for HtmlIf { - fn to_node_iterator_stream(&self) -> Option { - let HtmlIf { - if_token, - cond, - then_branch, - else_branch, - } = self; - let default_else_branch = syn::parse_str("{}").unwrap(); - let else_branch = else_branch - .as_ref() - .map(|(_, branch)| branch) - .unwrap_or(&default_else_branch); - let new_tokens = quote_spanned! {if_token.span()=> - if #cond #then_branch else #else_branch - }; - - Some(quote_spanned! {if_token.span=> #new_tokens}) - } -} - pub enum HtmlRootBracedOrIf { Branch(HtmlRootBraced), If(HtmlIf), diff --git a/packages/yew-macro/src/html_tree/html_iterable.rs b/packages/yew-macro/src/html_tree/html_iterable.rs index 0f44b329804..5d6c3aa9bd3 100644 --- a/packages/yew-macro/src/html_tree/html_iterable.rs +++ b/packages/yew-macro/src/html_tree/html_iterable.rs @@ -58,4 +58,8 @@ impl ToNodeIterator for HtmlIterable { ::yew::utils::into_node_iter(#expr) }) } + + fn is_singular(&self) -> bool { + false + } } diff --git a/packages/yew-macro/src/html_tree/html_node.rs b/packages/yew-macro/src/html_tree/html_node.rs index 6b8022ba11a..18d1c62810c 100644 --- a/packages/yew-macro/src/html_tree/html_node.rs +++ b/packages/yew-macro/src/html_tree/html_node.rs @@ -57,8 +57,8 @@ impl ToTokens for HtmlNode { impl ToNodeIterator for HtmlNode { fn to_node_iterator_stream(&self) -> Option { match self { - HtmlNode::Literal(_) => None, - HtmlNode::Expression(expr) => { + Self::Literal(_) => None, + Self::Expression(expr) => { // NodeSeq turns both Into and Vec> into IntoIterator Some(quote_spanned! {expr.span().resolved_at(Span::call_site())=> ::std::convert::Into::<::yew::utils::NodeSeq<_, _>>::into(#expr) @@ -66,4 +66,11 @@ impl ToNodeIterator for HtmlNode { } } } + + fn is_singular(&self) -> bool { + match self { + Self::Literal(_) => true, + Self::Expression(_) => false + } + } } diff --git a/packages/yew-macro/src/html_tree/mod.rs b/packages/yew-macro/src/html_tree/mod.rs index 266ea31bfd8..a1fd415310e 100644 --- a/packages/yew-macro/src/html_tree/mod.rs +++ b/packages/yew-macro/src/html_tree/mod.rs @@ -12,6 +12,7 @@ mod html_block; mod html_component; mod html_dashed_name; mod html_element; +mod html_for; mod html_if; mod html_iterable; mod html_list; @@ -30,6 +31,7 @@ use html_node::HtmlNode; use tag::TagTokens; use self::html_block::BlockContent; +use self::html_for::HtmlFor; pub enum HtmlType { Block, @@ -37,6 +39,7 @@ pub enum HtmlType { List, Element, If, + For, Empty, } @@ -46,6 +49,7 @@ pub enum HtmlTree { List(Box), Element(Box), If(Box), + For(Box), Empty, } @@ -53,15 +57,15 @@ impl Parse for HtmlTree { fn parse(input: ParseStream) -> syn::Result { let html_type = Self::peek_html_type(input) .ok_or_else(|| input.error("expected a valid html element"))?; - let html_tree = match html_type { - HtmlType::Empty => HtmlTree::Empty, - HtmlType::Component => HtmlTree::Component(Box::new(input.parse()?)), - HtmlType::Element => HtmlTree::Element(Box::new(input.parse()?)), - HtmlType::Block => HtmlTree::Block(Box::new(input.parse()?)), - HtmlType::List => HtmlTree::List(Box::new(input.parse()?)), - HtmlType::If => HtmlTree::If(Box::new(input.parse()?)), - }; - Ok(html_tree) + Ok(match html_type { + HtmlType::Empty => Self::Empty, + HtmlType::Component => Self::Component(Box::new(input.parse()?)), + HtmlType::Element => Self::Element(Box::new(input.parse()?)), + HtmlType::Block => Self::Block(Box::new(input.parse()?)), + HtmlType::List => Self::List(Box::new(input.parse()?)), + HtmlType::If => Self::If(Box::new(input.parse()?)), + HtmlType::For => Self::For(Box::new(input.parse()?)), + }) } } @@ -72,17 +76,16 @@ impl HtmlTree { /// returns with the appropriate type. If invalid html tag, returns `None`. fn peek_html_type(input: ParseStream) -> Option { let input = input.fork(); // do not modify original ParseStream + let cursor = input.cursor(); if input.is_empty() { Some(HtmlType::Empty) - } else if input - .cursor() - .group(proc_macro2::Delimiter::Brace) - .is_some() - { + } else if HtmlBlock::peek(cursor).is_some() { Some(HtmlType::Block) - } else if HtmlIf::peek(input.cursor()).is_some() { + } else if HtmlIf::peek(cursor).is_some() { Some(HtmlType::If) + } else if HtmlFor::peek(cursor).is_some() { + Some(HtmlType::For) } else if input.peek(Token![<]) { let _lt: Token![<] = input.parse().ok()?; @@ -122,21 +125,21 @@ impl ToTokens for HtmlTree { fn to_tokens(&self, tokens: &mut TokenStream) { lint::lint_all(self); match self { - HtmlTree::Empty => tokens.extend(quote! { + Self::Empty => tokens.extend(quote! { <::yew::virtual_dom::VNode as ::std::default::Default>::default() }), - HtmlTree::Component(comp) => comp.to_tokens(tokens), - HtmlTree::Element(tag) => tag.to_tokens(tokens), - HtmlTree::List(list) => list.to_tokens(tokens), - HtmlTree::Block(block) => block.to_tokens(tokens), - HtmlTree::If(block) => block.to_tokens(tokens), + Self::Component(comp) => comp.to_tokens(tokens), + Self::Element(tag) => tag.to_tokens(tokens), + Self::List(list) => list.to_tokens(tokens), + Self::Block(block) => block.to_tokens(tokens), + Self::If(block) => block.to_tokens(tokens), + Self::For(block) => block.to_tokens(tokens), } } } pub enum HtmlRoot { Tree(HtmlTree), - Iterable(Box), Node(Box), } @@ -144,8 +147,6 @@ impl Parse for HtmlRoot { fn parse(input: ParseStream) -> syn::Result { let html_root = if HtmlTree::peek_html_type(input).is_some() { Self::Tree(input.parse()?) - } else if HtmlIterable::peek(input.cursor()).is_some() { - Self::Iterable(Box::new(input.parse()?)) } else { Self::Node(Box::new(input.parse()?)) }; @@ -168,7 +169,6 @@ impl ToTokens for HtmlRoot { match self { Self::Tree(tree) => tree.to_tokens(tokens), Self::Node(node) => node.to_tokens(tokens), - Self::Iterable(iterable) => iterable.to_tokens(tokens), } } } @@ -200,16 +200,27 @@ pub trait ToNodeIterator { /// each element. If the resulting iterator only ever yields a single item this function /// should return None instead. fn to_node_iterator_stream(&self) -> Option; + /// Returns a boolean indicating whether the node can only ever unfold into 1 node + /// Same as calling `.to_node_iterator_stream().is_none()`, + /// but doesn't actually construct any token stream + fn is_singular(&self) -> bool; } impl ToNodeIterator for HtmlTree { fn to_node_iterator_stream(&self) -> Option { match self { - HtmlTree::Block(block) => block.to_node_iterator_stream(), + Self::Block(block) => block.to_node_iterator_stream(), // everything else is just a single node. _ => None, } } + + fn is_singular(&self) -> bool { + match self { + Self::Block(block) => block.is_singular(), + _ => true, + } + } } pub struct HtmlChildrenTree(pub Vec); @@ -231,10 +242,7 @@ impl HtmlChildrenTree { // Check if each child represents a single node. // This is the case when no expressions are used. fn only_single_node_children(&self) -> bool { - self.0 - .iter() - .map(ToNodeIterator::to_node_iterator_stream) - .all(|s| s.is_none()) + self.0.iter().all(HtmlTree::is_singular) } pub fn to_build_vec_token_stream(&self) -> TokenStream { @@ -340,6 +348,10 @@ impl HtmlChildrenTree { }, } } + + pub fn size_hint(&self) -> Option { + self.only_single_node_children().then_some(self.0.len()) + } } impl ToTokens for HtmlChildrenTree { diff --git a/packages/yew-macro/tests/html_macro/for-fail.rs b/packages/yew-macro/tests/html_macro/for-fail.rs new file mode 100644 index 00000000000..4d35bb3fc57 --- /dev/null +++ b/packages/yew-macro/tests/html_macro/for-fail.rs @@ -0,0 +1,8 @@ +fn main() { + _ = ::yew::html!{for x}; + _ = ::yew::html!{for x in}; + _ = ::yew::html!{for x in 0 .. 10}; + _ = ::yew::html!{for (x, y) in 0 .. 10 { + {x} + }}; +} diff --git a/packages/yew-macro/tests/html_macro/for-fail.stderr b/packages/yew-macro/tests/html_macro/for-fail.stderr new file mode 100644 index 00000000000..698f028a54d --- /dev/null +++ b/packages/yew-macro/tests/html_macro/for-fail.stderr @@ -0,0 +1,34 @@ +error: unexpected end of input, expected `in` + --> tests/html_macro/for-fail.rs:2:9 + | +2 | _ = ::yew::html!{for x}; + | ^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `::yew::html` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected end of input, expected an expression + --> tests/html_macro/for-fail.rs:3:9 + | +3 | _ = ::yew::html!{for x in}; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `::yew::html` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected end of input, expected curly braces + --> tests/html_macro/for-fail.rs:4:9 + | +4 | _ = ::yew::html!{for x in 0 .. 10}; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `::yew::html` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> tests/html_macro/for-fail.rs:5:26 + | +5 | _ = ::yew::html!{for (x, y) in 0 .. 10 { + | ^^^^^^ ------- this is an iterator with items of type `{integer}` + | | + | expected integer, found tuple + | + = note: expected type `{integer}` + found tuple `(_, _)` diff --git a/packages/yew-macro/tests/html_macro/for-pass.rs b/packages/yew-macro/tests/html_macro/for-pass.rs new file mode 100644 index 00000000000..55f056bafc7 --- /dev/null +++ b/packages/yew-macro/tests/html_macro/for-pass.rs @@ -0,0 +1,58 @@ +#![no_implicit_prelude] + +// Shadow primitives +#[allow(non_camel_case_types)] +pub struct bool; +#[allow(non_camel_case_types)] +pub struct char; +#[allow(non_camel_case_types)] +pub struct f32; +#[allow(non_camel_case_types)] +pub struct f64; +#[allow(non_camel_case_types)] +pub struct i128; +#[allow(non_camel_case_types)] +pub struct i16; +#[allow(non_camel_case_types)] +pub struct i32; +#[allow(non_camel_case_types)] +pub struct i64; +#[allow(non_camel_case_types)] +pub struct i8; +#[allow(non_camel_case_types)] +pub struct isize; +#[allow(non_camel_case_types)] +pub struct str; +#[allow(non_camel_case_types)] +pub struct u128; +#[allow(non_camel_case_types)] +pub struct u16; +#[allow(non_camel_case_types)] +pub struct u32; +#[allow(non_camel_case_types)] +pub struct u64; +#[allow(non_camel_case_types)] +pub struct u8; +#[allow(non_camel_case_types)] +pub struct usize; + +fn main() { + _ = ::yew::html!{ + for i in 0 .. 10 { + {i} + } + }; + + struct Pair { + value1: &'static ::std::primitive::str, + value2: ::std::primitive::i32 + } + + _ = ::yew::html! { + for Pair { value1, value2 } in ::std::iter::Iterator::map(0 .. 10, |value2| Pair { value1: "Yew", value2 }) { + {value1} + {value2} + } + }; + +} diff --git a/packages/yew-macro/tests/html_macro/iterable-fail.rs b/packages/yew-macro/tests/html_macro/iterable-fail.rs index 97312a08ca6..1ad8c465f1e 100644 --- a/packages/yew-macro/tests/html_macro/iterable-fail.rs +++ b/packages/yew-macro/tests/html_macro/iterable-fail.rs @@ -3,14 +3,14 @@ use yew::prelude::*; fn compile_fail() { html! { for }; html! { for () }; - html! { for {()} }; + html! { {for ()} }; html! { for Vec::<()>::new().into_iter() }; let empty = Vec::<()>::new().into_iter(); html! { for empty }; let empty = Vec::<()>::new(); - html! { for empty.iter() }; + html! { {for empty.iter()} }; html! { <> diff --git a/packages/yew-macro/tests/html_macro/iterable-fail.stderr b/packages/yew-macro/tests/html_macro/iterable-fail.stderr index 54649e26bbe..a3f0fee3a36 100644 --- a/packages/yew-macro/tests/html_macro/iterable-fail.stderr +++ b/packages/yew-macro/tests/html_macro/iterable-fail.stderr @@ -1,62 +1,47 @@ -error: expected an expression after the keyword `for` - --> tests/html_macro/iterable-fail.rs:4:13 +error: unexpected end of input, expected one of: identifier, `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const` + --> tests/html_macro/iterable-fail.rs:4:5 | 4 | html! { for }; - | ^^^ - -error[E0277]: `()` is not an iterator - --> tests/html_macro/iterable-fail.rs:5:17 - | -5 | html! { for () }; - | ^^ `()` is not an iterator + | ^^^^^^^^^^^^^ | - = help: the trait `Iterator` is not implemented for `()` - = note: required because of the requirements on the impl of `IntoIterator` for `()` + = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: `()` is not an iterator - --> tests/html_macro/iterable-fail.rs:6:17 +error: unexpected end of input, expected `in` + --> tests/html_macro/iterable-fail.rs:5:5 | -6 | html! { for {()} }; - | ^^^^ `()` is not an iterator +5 | html! { for () }; + | ^^^^^^^^^^^^^^^^ | - = help: the trait `Iterator` is not implemented for `()` - = note: required because of the requirements on the impl of `IntoIterator` for `()` + = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: `()` doesn't implement `std::fmt::Display` - --> tests/html_macro/iterable-fail.rs:7:17 +error: expected `in` + --> tests/html_macro/iterable-fail.rs:7:33 | 7 | html! { for Vec::<()>::new().into_iter() }; - | ^^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = help: the trait `FromIterator` is implemented for `VNode` - = note: required because of the requirements on the impl of `ToString` for `()` - = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `FromIterator<()>` for `VNode` -note: required by a bound in `collect` + | ^ -error[E0277]: `()` doesn't implement `std::fmt::Display` - --> tests/html_macro/iterable-fail.rs:10:17 +error: unexpected end of input, expected `in` + --> tests/html_macro/iterable-fail.rs:10:5 | 10 | html! { for empty }; - | ^^^^^ `()` cannot be formatted with the default formatter + | ^^^^^^^^^^^^^^^^^^^ | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = help: the trait `FromIterator` is implemented for `VNode` - = note: required because of the requirements on the impl of `ToString` for `()` - = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `()` - = note: required because of the requirements on the impl of `FromIterator<()>` for `VNode` -note: required by a bound in `collect` + = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `()` is not an iterator + --> tests/html_macro/iterable-fail.rs:6:18 + | +6 | html! { {for ()} }; + | ^^ `()` is not an iterator + | + = help: the trait `Iterator` is not implemented for `()` + = note: required because of the requirements on the impl of `IntoIterator` for `()` error[E0277]: `()` doesn't implement `std::fmt::Display` - --> tests/html_macro/iterable-fail.rs:13:17 + --> tests/html_macro/iterable-fail.rs:13:18 | -13 | html! { for empty.iter() }; - | ^^^^^ `()` cannot be formatted with the default formatter +13 | html! { {for empty.iter()} }; + | ^^^^^ `()` cannot be formatted with the default formatter | = help: the trait `std::fmt::Display` is not implemented for `()` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead diff --git a/packages/yew-macro/tests/html_macro/iterable-pass.rs b/packages/yew-macro/tests/html_macro/iterable-pass.rs index 18eadf7b042..b3853b14f66 100644 --- a/packages/yew-macro/tests/html_macro/iterable-pass.rs +++ b/packages/yew-macro/tests/html_macro/iterable-pass.rs @@ -45,13 +45,19 @@ fn empty_iter() -> impl ::std::iter::Iterator { } fn main() { - ::yew::html! { for empty_iter() }; - ::yew::html! { for { empty_iter() } }; + ::yew::html! { {for empty_iter()} }; + ::yew::html! { {for { empty_iter() }} }; let empty = empty_vec(); - ::yew::html! { for empty }; + ::yew::html! { {for empty} }; - ::yew::html! { for empty_vec() }; - ::yew::html! { for ::std::iter::IntoIterator::into_iter(empty_vec()) }; - ::yew::html! { for ::std::iter::Iterator::map(0..3, |num| { ::yew::html! { { num } } }) }; + ::yew::html! {<> + {for empty_vec()} + }; + ::yew::html! {<> + {for ::std::iter::IntoIterator::into_iter(empty_vec())} + }; + ::yew::html! {<> + {for ::std::iter::Iterator::map(0..3, |num| { ::yew::html! { { num } } })} + }; } From 217636f9b44f8dc8f8fc4e5e7428db4af1fefc6a Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 29 Oct 2023 09:55:24 +0000 Subject: [PATCH 02/13] disallowed divrging expressions in for loops --- .../yew-macro/src/html_tree/html_block.rs | 2 +- packages/yew-macro/src/html_tree/html_for.rs | 76 +++++++++++++------ packages/yew-macro/src/html_tree/html_node.rs | 2 +- .../yew-macro/tests/html_macro/for-fail.rs | 3 + packages/yew/tests/suspense.rs | 1 - 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_block.rs b/packages/yew-macro/src/html_tree/html_block.rs index caa743df701..a6e6b314637 100644 --- a/packages/yew-macro/src/html_tree/html_block.rs +++ b/packages/yew-macro/src/html_tree/html_block.rs @@ -63,7 +63,7 @@ impl ToNodeIterator for HtmlBlock { fn is_singular(&self) -> bool { match &self.content { BlockContent::Node(node) => node.is_singular(), - BlockContent::Iterable(_) => false + BlockContent::Iterable(_) => false, } } } diff --git a/packages/yew-macro/src/html_tree/html_for.rs b/packages/yew-macro/src/html_tree/html_for.rs index c34f2772420..012dc305080 100644 --- a/packages/yew-macro/src/html_tree/html_for.rs +++ b/packages/yew-macro/src/html_tree/html_for.rs @@ -1,17 +1,52 @@ -use proc_macro2::{Ident, TokenStream}; +use std::iter::successors; + +use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree}; use quote::{quote, ToTokens}; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; -use syn::{braced, Expr, Pat, Token}; +use syn::token::{For, In}; +use syn::{braced, Expr, Pat}; use super::{HtmlChildrenTree, ToNodeIterator}; use crate::PeekValue; +/// Returns the location of a `break` or `continue` token, if found +fn find_divergence(cursor: Cursor) -> Option { + fn inner(stream: TokenStream) -> Option { + for token in stream { + match token { + TokenTree::Group(group) => { + if let res @ Some(_) = inner(group.stream()) { + return res; + } + } + TokenTree::Ident(ident) => { + if ident == "break" || ident == "continue" { + return Some(ident.span()); + } + } + TokenTree::Punct(_) | TokenTree::Literal(_) => (), + } + } + None + } + + for (token, _) in successors(cursor.token_tree(), |(_, cursor)| cursor.token_tree()) { + if let TokenTree::Group(group) = token { + if group.delimiter() == Delimiter::Brace { + if let res @ Some(_) = inner(group.stream()) { + return res; + } + } + } + } + + None +} + pub struct HtmlFor { - for_token: Token![for], pat: Pat, - in_token: Token![in], iter: Expr, body: HtmlChildrenTree, } @@ -25,35 +60,30 @@ impl PeekValue<()> for HtmlFor { impl Parse for HtmlFor { fn parse(input: ParseStream) -> syn::Result { - let for_token = input.parse()?; + For::parse(input)?; let pat = Pat::parse_single(input)?; - let in_token = input.parse()?; + In::parse(input)?; let iter = Expr::parse_without_eager_brace(input)?; let body_stream; braced!(body_stream in input); + + if let Some(span) = find_divergence(body_stream.cursor()) { + return Err(syn::Error::new( + span, + "diverging expression in the body of a for loop\n`break` or `continue` are not \ + allowed in `html!` for loops", + )); + } + let body = HtmlChildrenTree::parse_delimited(&body_stream)?; - Ok(Self { - for_token, - pat, - in_token, - iter, - body, - }) + Ok(Self { pat, iter, body }) } } impl ToTokens for HtmlFor { fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - for_token, - pat, - in_token, - iter, - body, - } = self; - // TODO: call `__yew_v.reserve` if the amount of elements added per iteration can be - // pre-determined + let Self { pat, iter, body } = self; let acc = Ident::new("__yew_v", iter.span()); let optimisation = body.size_hint().map(|size| { quote!( @@ -73,7 +103,7 @@ impl ToTokens for HtmlFor { }); tokens.extend(quote!({ let mut #acc = ::std::vec::Vec::<::yew::virtual_dom::VNode>::new(); - #for_token #pat #in_token #iter { + for #pat in #iter { #optimisation #(#body)* } diff --git a/packages/yew-macro/src/html_tree/html_node.rs b/packages/yew-macro/src/html_tree/html_node.rs index 18d1c62810c..45467619f5f 100644 --- a/packages/yew-macro/src/html_tree/html_node.rs +++ b/packages/yew-macro/src/html_tree/html_node.rs @@ -70,7 +70,7 @@ impl ToNodeIterator for HtmlNode { fn is_singular(&self) -> bool { match self { Self::Literal(_) => true, - Self::Expression(_) => false + Self::Expression(_) => false, } } } diff --git a/packages/yew-macro/tests/html_macro/for-fail.rs b/packages/yew-macro/tests/html_macro/for-fail.rs index 4d35bb3fc57..46f0e173428 100644 --- a/packages/yew-macro/tests/html_macro/for-fail.rs +++ b/packages/yew-macro/tests/html_macro/for-fail.rs @@ -5,4 +5,7 @@ fn main() { _ = ::yew::html!{for (x, y) in 0 .. 10 { {x} }}; + _ = ::yew::html!{for x in 0 .. 10 { + {break} + }}; } diff --git a/packages/yew/tests/suspense.rs b/packages/yew/tests/suspense.rs index 0596f3c6946..8a59ecf54da 100644 --- a/packages/yew/tests/suspense.rs +++ b/packages/yew/tests/suspense.rs @@ -812,7 +812,6 @@ async fn test_duplicate_suspension() { yew::Renderer::::with_root(gloo::utils::document().get_element_by_id("output").unwrap()) .render(); - sleep(Duration::from_millis(50)).await; let result = obtain_result(); assert_eq!(result.as_str(), "hello!"); From 910c7f0cc0903574b7ef6ffe0e175a52cf097a33 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 29 Oct 2023 10:19:01 +0000 Subject: [PATCH 03/13] updated the docs --- website/docs/concepts/html/lists.mdx | 35 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/website/docs/concepts/html/lists.mdx b/website/docs/concepts/html/lists.mdx index 6dca5da6c48..94340e386fa 100644 --- a/website/docs/concepts/html/lists.mdx +++ b/website/docs/concepts/html/lists.mdx @@ -7,13 +7,30 @@ import TabItem from '@theme/TabItem' ## Iterators -Yew supports two different syntaxes for building HTML from an iterator. +There are 3 ways to build HTML from iterators: - + +The main approach is to use for loops, the same for loops that already exist in Rust, but with 2 key differences: +1. Unlike standard for loops which can't return anything, for loops in `html!` are converted to a list of nodes; +2. Diverging expressions, i.e. `break`, `continue` are not allowed in the body of for loops in `html!`. -The first is to call `collect::()` on the final transform in your iterator, which returns a -list that Yew can display. +```rust +use yew::prelude::*; + +html! { + for i in 0 .. 10 { + {i} + } +} +``` + + + +An alternative is to use the `for` keyword, which is not native Rust syntax and instead is used by +the HTML macro to output the needed code to display the iterator. +This approach is better than the first one when the iterator is already computed and the only thing left to do +is to pass it to the macro. ```rust use yew::prelude::*; @@ -22,16 +39,16 @@ let items = (1..=10).collect::>(); html! {
    - { items.iter().collect::() } + { for items.iter() }
}; ```
- + -The alternative is to use the `for` keyword, which is not native Rust syntax and instead is used by -the HTML macro to output the needed code to display the iterator. +The last is to call `collect::()` on the final transform in your iterator, which returns a +list that Yew can display. ```rust use yew::prelude::*; @@ -40,7 +57,7 @@ let items = (1..=10).collect::>(); html! {
    - { for items.iter() } + { items.iter().collect::() }
}; ``` From 353259e84455c106f830ce08020d0b000f8d94ac Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 29 Oct 2023 10:30:59 +0000 Subject: [PATCH 04/13] updated the docs --- website/docs/concepts/html/lists.mdx | 2 +- .../versioned_docs/version-0.20/concepts/html/components.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/concepts/html/lists.mdx b/website/docs/concepts/html/lists.mdx index 94340e386fa..680020c78d2 100644 --- a/website/docs/concepts/html/lists.mdx +++ b/website/docs/concepts/html/lists.mdx @@ -22,7 +22,7 @@ html! { for i in 0 .. 10 { {i} } -} +}; ```
diff --git a/website/versioned_docs/version-0.20/concepts/html/components.mdx b/website/versioned_docs/version-0.20/concepts/html/components.mdx index 8e2eedb240c..6efbda0318c 100644 --- a/website/versioned_docs/version-0.20/concepts/html/components.mdx +++ b/website/versioned_docs/version-0.20/concepts/html/components.mdx @@ -154,7 +154,7 @@ fn List(props: &Props) -> Html { props.value = format!("item-{}", props.value); item }); - html! { for modified_children } + html! { modified_children } } html! { From 12460878dd946babb7353ad26639c85fe9a135ad Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 29 Oct 2023 10:39:25 +0000 Subject: [PATCH 05/13] fixed tests --- packages/yew-macro/tests/html_macro/for-fail.stderr | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/yew-macro/tests/html_macro/for-fail.stderr b/packages/yew-macro/tests/html_macro/for-fail.stderr index 698f028a54d..e7e89fe76a8 100644 --- a/packages/yew-macro/tests/html_macro/for-fail.stderr +++ b/packages/yew-macro/tests/html_macro/for-fail.stderr @@ -22,6 +22,13 @@ error: unexpected end of input, expected curly braces | = note: this error originates in the macro `::yew::html` (in Nightly builds, run with -Z macro-backtrace for more info) +error: diverging expression in the body of a for loop + `break` or `continue` are not allowed in `html!` for loops + --> tests/html_macro/for-fail.rs:9:16 + | +9 | {break} + | ^^^^^ + error[E0308]: mismatched types --> tests/html_macro/for-fail.rs:5:26 | From ff39ceffebf70d863a3678c12e04151e6daf5afe Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 29 Oct 2023 10:44:56 +0000 Subject: [PATCH 06/13] fix docs --- website/docs/advanced-topics/children.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/advanced-topics/children.mdx b/website/docs/advanced-topics/children.mdx index 80dae96ef3a..4a51f901438 100644 --- a/website/docs/advanced-topics/children.mdx +++ b/website/docs/advanced-topics/children.mdx @@ -137,7 +137,7 @@ fn List(props: &Props) -> Html { props.value = format!("item-{}", props.value); item }); - html! { for modified_children } + html! { modified_children } } html! { From eb02648c712d875e97fd00d98106ef4a215e7496 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 29 Oct 2023 10:58:57 +0000 Subject: [PATCH 07/13] fix docs --- website/docs/advanced-topics/children.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/advanced-topics/children.mdx b/website/docs/advanced-topics/children.mdx index 4a51f901438..17ae74eb23f 100644 --- a/website/docs/advanced-topics/children.mdx +++ b/website/docs/advanced-topics/children.mdx @@ -137,7 +137,7 @@ fn List(props: &Props) -> Html { props.value = format!("item-{}", props.value); item }); - html! { modified_children } + html! {{for modified_children}} } html! { From 31c6aa9a655490424716bc7143ad0488e347b7ab Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 29 Oct 2023 14:55:12 +0000 Subject: [PATCH 08/13] delegated divergence prohibition to rustc by using for_each instead of a for loop, added a check for duplicate keys, added keyed state compile-time inference --- .../yew-macro/src/html_tree/html_component.rs | 2 +- packages/yew-macro/src/html_tree/html_for.rs | 107 +++++++++--------- packages/yew-macro/src/html_tree/html_list.rs | 10 +- packages/yew-macro/src/html_tree/mod.rs | 31 +++++ packages/yew-macro/src/props/component.rs | 2 +- .../yew-macro/tests/html_macro/for-fail.rs | 15 ++- .../tests/html_macro/for-fail.stderr | 48 +++++--- .../yew-macro/tests/html_macro/for-pass.rs | 18 +++ packages/yew/src/virtual_dom/mod.rs | 2 + packages/yew/src/virtual_dom/vlist.rs | 13 ++- 10 files changed, 170 insertions(+), 78 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_component.rs b/packages/yew-macro/src/html_tree/html_component.rs index 58e58988325..0e2555dfbef 100644 --- a/packages/yew-macro/src/html_tree/html_component.rs +++ b/packages/yew-macro/src/html_tree/html_component.rs @@ -11,7 +11,7 @@ use crate::props::ComponentProps; pub struct HtmlComponent { ty: Type, - props: ComponentProps, + pub props: ComponentProps, children: HtmlChildrenTree, close: Option, } diff --git a/packages/yew-macro/src/html_tree/html_for.rs b/packages/yew-macro/src/html_tree/html_for.rs index 012dc305080..0bc20e2d95c 100644 --- a/packages/yew-macro/src/html_tree/html_for.rs +++ b/packages/yew-macro/src/html_tree/html_for.rs @@ -1,6 +1,4 @@ -use std::iter::successors; - -use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree}; +use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream}; @@ -9,40 +7,16 @@ use syn::token::{For, In}; use syn::{braced, Expr, Pat}; use super::{HtmlChildrenTree, ToNodeIterator}; +use crate::html_tree::HtmlTree; use crate::PeekValue; -/// Returns the location of a `break` or `continue` token, if found -fn find_divergence(cursor: Cursor) -> Option { - fn inner(stream: TokenStream) -> Option { - for token in stream { - match token { - TokenTree::Group(group) => { - if let res @ Some(_) = inner(group.stream()) { - return res; - } - } - TokenTree::Ident(ident) => { - if ident == "break" || ident == "continue" { - return Some(ident.span()); - } - } - TokenTree::Punct(_) | TokenTree::Literal(_) => (), - } - } - None - } - - for (token, _) in successors(cursor.token_tree(), |(_, cursor)| cursor.token_tree()) { - if let TokenTree::Group(group) = token { - if group.delimiter() == Delimiter::Brace { - if let res @ Some(_) = inner(group.stream()) { - return res; - } - } - } +/// 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, } - - None } pub struct HtmlFor { @@ -68,15 +42,21 @@ impl Parse for HtmlFor { let body_stream; braced!(body_stream in input); - if let Some(span) = find_divergence(body_stream.cursor()) { - return Err(syn::Error::new( - span, - "diverging expression in the body of a for loop\n`break` or `continue` are not \ - allowed in `html!` for loops", - )); - } - let body = HtmlChildrenTree::parse_delimited(&body_stream)?; + // TODO: reduce nesting by using if-let guards / let-else statements once MSRV is raised + for child in body.0.iter() { + if let HtmlTree::Element(element) = child { + if let Some(key) = &element.props.special.key { + 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 }) } } @@ -85,29 +65,48 @@ 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 optimisation = body.size_hint().map(|size| { + + let alloc_opt = body.size_hint().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::KnownFullyKeyed + ) + }, + 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); - ) + quote!( #acc.extend(#child) ) } else { - quote!( - #acc.push(::std::convert::Into::into(#child)); - ) + quote!( #acc.push(::std::convert::Into::into(#child)) ) } }); + tokens.extend(quote!({ let mut #acc = ::std::vec::Vec::<::yew::virtual_dom::VNode>::new(); - for #pat in #iter { - #optimisation - #(#body)* - } - ::yew::virtual_dom::VList::with_children(#acc, ::std::option::Option::None) + ::std::iter::Iterator::for_each( + ::std::iter::IntoIterator::into_iter(#iter), + |#pat| { #alloc_opt; #(#body);* } + ); + #vlist_gen })) } } diff --git a/packages/yew-macro/src/html_tree/html_list.rs b/packages/yew-macro/src/html_tree/html_list.rs index 301b533a92c..1be145d3a34 100644 --- a/packages/yew-macro/src/html_tree/html_list.rs +++ b/packages/yew-macro/src/html_tree/html_list.rs @@ -10,7 +10,7 @@ use crate::props::Prop; use crate::{Peek, PeekValue}; pub struct HtmlList { - open: HtmlListOpen, + pub open: HtmlListOpen, pub children: HtmlChildrenTree, close: HtmlListClose, } @@ -85,9 +85,9 @@ impl ToTokens for HtmlList { } } -struct HtmlListOpen { +pub struct HtmlListOpen { tag: TagTokens, - props: HtmlListProps, + pub props: HtmlListProps, } impl HtmlListOpen { fn to_spanned(&self) -> impl ToTokens { @@ -121,8 +121,8 @@ impl Parse for HtmlListOpen { } } -struct HtmlListProps { - key: Option, +pub struct HtmlListProps { + pub key: Option, } impl Parse for HtmlListProps { fn parse(input: ParseStream) -> syn::Result { diff --git a/packages/yew-macro/src/html_tree/mod.rs b/packages/yew-macro/src/html_tree/mod.rs index a1fd415310e..4f339a64b0c 100644 --- a/packages/yew-macro/src/html_tree/mod.rs +++ b/packages/yew-macro/src/html_tree/mod.rs @@ -352,6 +352,37 @@ impl HtmlChildrenTree { pub fn size_hint(&self) -> Option { self.only_single_node_children().then_some(self.0.len()) } + + pub fn fully_keyed(&self) -> Option { + for child in self.0.iter() { + match child { + HtmlTree::Block(block) => { + return if let BlockContent::Node(node) = &block.content { + matches!(&**node, HtmlNode::Literal(_)).then_some(false) + } else { + None + } + } + HtmlTree::Component(comp) => { + if comp.props.props.special.key.is_none() { + return Some(false); + } + } + HtmlTree::List(list) => { + if list.open.props.key.is_none() { + return Some(false); + } + } + HtmlTree::Element(element) => { + if element.props.special.key.is_none() { + return Some(false); + } + } + HtmlTree::If(_) | HtmlTree::For(_) | HtmlTree::Empty => return Some(false), + } + } + Some(true) + } } impl ToTokens for HtmlChildrenTree { diff --git a/packages/yew-macro/src/props/component.rs b/packages/yew-macro/src/props/component.rs index 3c0984e611c..f2eed2a5955 100644 --- a/packages/yew-macro/src/props/component.rs +++ b/packages/yew-macro/src/props/component.rs @@ -35,7 +35,7 @@ impl ToTokens for BaseExpr { } pub struct ComponentProps { - props: Props, + pub props: Props, base_expr: Option, } impl ComponentProps { diff --git a/packages/yew-macro/tests/html_macro/for-fail.rs b/packages/yew-macro/tests/html_macro/for-fail.rs index 46f0e173428..e40eece597b 100644 --- a/packages/yew-macro/tests/html_macro/for-fail.rs +++ b/packages/yew-macro/tests/html_macro/for-fail.rs @@ -1,3 +1,7 @@ +mod smth { + const KEY: u32 = 42; +} + fn main() { _ = ::yew::html!{for x}; _ = ::yew::html!{for x in}; @@ -5,7 +9,16 @@ fn main() { _ = ::yew::html!{for (x, y) in 0 .. 10 { {x} }}; - _ = ::yew::html!{for x in 0 .. 10 { + + _ = ::yew::html!{for _ in 0 .. 10 { {break} }}; + + _ = ::yew::html!{for _ in 0 .. 10 { +
+ }}; + + _ = ::yew::html!{for _ in 0 .. 10 { +
+ }}; } diff --git a/packages/yew-macro/tests/html_macro/for-fail.stderr b/packages/yew-macro/tests/html_macro/for-fail.stderr index e7e89fe76a8..0a2952dd620 100644 --- a/packages/yew-macro/tests/html_macro/for-fail.stderr +++ b/packages/yew-macro/tests/html_macro/for-fail.stderr @@ -1,41 +1,59 @@ error: unexpected end of input, expected `in` - --> tests/html_macro/for-fail.rs:2:9 + --> tests/html_macro/for-fail.rs:6:9 | -2 | _ = ::yew::html!{for x}; +6 | _ = ::yew::html!{for x}; | ^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the macro `::yew::html` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected end of input, expected an expression - --> tests/html_macro/for-fail.rs:3:9 + --> tests/html_macro/for-fail.rs:7:9 | -3 | _ = ::yew::html!{for x in}; +7 | _ = ::yew::html!{for x in}; | ^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the macro `::yew::html` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected end of input, expected curly braces - --> tests/html_macro/for-fail.rs:4:9 + --> tests/html_macro/for-fail.rs:8:9 | -4 | _ = ::yew::html!{for x in 0 .. 10}; +8 | _ = ::yew::html!{for x in 0 .. 10}; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the macro `::yew::html` (in Nightly builds, run with -Z macro-backtrace for more info) -error: diverging expression in the body of a for loop - `break` or `continue` are not allowed in `html!` for loops - --> tests/html_macro/for-fail.rs:9:16 - | -9 | {break} - | ^^^^^ +error: duplicate key for a node in a `for`-loop + this will create elements with duplicate keys if the loop iterates more than once + --> tests/html_macro/for-fail.rs:18:18 + | +18 |
+ | ^^^^^^^^^^^ + +error: duplicate key for a node in a `for`-loop + this will create elements with duplicate keys if the loop iterates more than once + --> tests/html_macro/for-fail.rs:22:19 + | +22 |
+ | ^^^^ + +error[E0267]: `break` inside of a closure + --> tests/html_macro/for-fail.rs:14:16 + | +13 | _ = ::yew::html!{for _ in 0 .. 10 { + | _________- +14 | | {break} + | | ^^^^^ cannot `break` inside of a closure +15 | | }}; + | |______- enclosing closure error[E0308]: mismatched types - --> tests/html_macro/for-fail.rs:5:26 + --> tests/html_macro/for-fail.rs:9:26 | -5 | _ = ::yew::html!{for (x, y) in 0 .. 10 { - | ^^^^^^ ------- this is an iterator with items of type `{integer}` +9 | _ = ::yew::html!{for (x, y) in 0 .. 10 { + | ^^^^^^ | | | expected integer, found tuple + | expected due to this | = note: expected type `{integer}` found tuple `(_, _)` diff --git a/packages/yew-macro/tests/html_macro/for-pass.rs b/packages/yew-macro/tests/html_macro/for-pass.rs index 55f056bafc7..d371a9d4e39 100644 --- a/packages/yew-macro/tests/html_macro/for-pass.rs +++ b/packages/yew-macro/tests/html_macro/for-pass.rs @@ -55,4 +55,22 @@ fn main() { } }; + fn rand_number() -> ::std::primitive::u32 { + 4 // chosen by fair dice roll. guaranteed to be random. + } + + _ = ::yew::html!{ + for _ in 0..5 { +
+ {{ + loop { + let a = rand_number(); + if a % 2 == 0 { + break a; + } + } + }} +
+ } + } } diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 7b5aac47f5b..6b27a4afad9 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -35,6 +35,8 @@ pub use self::listeners::*; pub use self::vcomp::{VChild, VComp}; #[doc(inline)] pub use self::vlist::VList; +#[doc(hidden)] +pub use self::vlist::FullyKeyedState; #[doc(inline)] pub use self::vnode::VNode; #[doc(inline)] diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 62dc5b14e0a..f1678750f1e 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -5,8 +5,9 @@ use std::rc::Rc; use super::{Key, VNode}; use crate::html::ImplicitClone; +#[doc(hidden)] #[derive(Clone, Copy, Debug, PartialEq)] -enum FullyKeyedState { +pub enum FullyKeyedState { KnownFullyKeyed, KnownMissingKeys, Unknown, @@ -82,6 +83,16 @@ impl VList { vlist } + #[doc(hidden)] + /// Used by `html!` to avoid calling `.recheck_fully_keyed()` when possible. + pub fn __macro_new(children: Vec, key: Option, fully_keyed: FullyKeyedState) -> Self { + VList { + children: Some(Rc::new(children)), + fully_keyed, + key, + } + } + // Returns a mutable reference to children, allocates the children if it hasn't been done. // // This method does not reassign key state. So it should only be used internally. From 1b1d8145004b38bfdb5ffa8bfdfa0a60ba2f2e39 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 29 Oct 2023 15:09:25 +0000 Subject: [PATCH 09/13] fixed formatting --- packages/yew/src/virtual_dom/mod.rs | 4 ++-- packages/yew/src/virtual_dom/vlist.rs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 6b27a4afad9..5db30a32f58 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -33,11 +33,11 @@ pub use self::key::Key; pub use self::listeners::*; #[doc(inline)] pub use self::vcomp::{VChild, VComp}; -#[doc(inline)] -pub use self::vlist::VList; #[doc(hidden)] pub use self::vlist::FullyKeyedState; #[doc(inline)] +pub use self::vlist::VList; +#[doc(inline)] pub use self::vnode::VNode; #[doc(inline)] pub use self::vportal::VPortal; diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index f1678750f1e..b8c01a36772 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -85,7 +85,11 @@ impl VList { #[doc(hidden)] /// Used by `html!` to avoid calling `.recheck_fully_keyed()` when possible. - pub fn __macro_new(children: Vec, key: Option, fully_keyed: FullyKeyedState) -> Self { + pub fn __macro_new( + children: Vec, + key: Option, + fully_keyed: FullyKeyedState, + ) -> Self { VList { children: Some(Rc::new(children)), fully_keyed, From 9a57be010a19180ba948bc962922415905ad549a Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 29 Oct 2023 19:21:56 +0000 Subject: [PATCH 10/13] fixed a bug, optimised fragments' creation --- packages/yew-macro/src/html_tree/html_for.rs | 8 ++------ packages/yew-macro/src/html_tree/html_list.rs | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_for.rs b/packages/yew-macro/src/html_tree/html_for.rs index 0bc20e2d95c..58256dfcf91 100644 --- a/packages/yew-macro/src/html_tree/html_for.rs +++ b/packages/yew-macro/src/html_tree/html_for.rs @@ -66,11 +66,7 @@ impl ToTokens for HtmlFor { let Self { pat, iter, body } = self; let acc = Ident::new("__yew_v", iter.span()); - let alloc_opt = body.size_hint().map(|size| { - quote!( - #acc.reserve(#size); - ) - }); + let alloc_opt = body.size_hint().map(|size| quote!( #acc.reserve(#size) )); let vlist_gen = match body.fully_keyed() { Some(true) => quote! { @@ -84,7 +80,7 @@ impl ToTokens for HtmlFor { ::yew::virtual_dom::VList::__macro_new( #acc, ::std::option::Option::None, - ::yew::virtual_dom::FullyKeyedState::KnownFullyKeyed + ::yew::virtual_dom::FullyKeyedState::KnownMissingKeys ) }, None => quote! { diff --git a/packages/yew-macro/src/html_tree/html_list.rs b/packages/yew-macro/src/html_tree/html_list.rs index 1be145d3a34..7b0a7cfdf94 100644 --- a/packages/yew-macro/src/html_tree/html_list.rs +++ b/packages/yew-macro/src/html_tree/html_list.rs @@ -71,16 +71,23 @@ impl ToTokens for HtmlList { quote! { ::std::option::Option::None } }; - let spanned = { + let span = { let open = open.to_spanned(); let close = close.to_spanned(); quote! { #open #close } - }; - - tokens.extend(quote_spanned! {spanned.span()=> - ::yew::virtual_dom::VNode::VList(::std::rc::Rc::new( + } + .span(); + + tokens.extend(match children.fully_keyed() { + Some(true) => quote_spanned!{span=> + ::yew::virtual_dom::VList::__macro_new(#children, #key, ::yew::virtual_dom::FullyKeyedState::KnownFullyKeyed) + }, + Some(false) => quote_spanned!{span=> + ::yew::virtual_dom::VList::__macro_new(#children, #key, ::yew::virtual_dom::FullyKeyedState::KnownMissingKeys) + }, + None => quote_spanned!{span=> ::yew::virtual_dom::VList::with_children(#children, #key) - )) + } }); } } From d0247bb68608de6c9e2dc77fa6d66d5f3d359379 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Mon, 30 Oct 2023 00:50:09 +0000 Subject: [PATCH 11/13] fixed tests --- packages/yew-macro/tests/html_macro/component-fail.stderr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/yew-macro/tests/html_macro/component-fail.stderr b/packages/yew-macro/tests/html_macro/component-fail.stderr index 440ffed4c64..e5c9f3c9465 100644 --- a/packages/yew-macro/tests/html_macro/component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-fail.stderr @@ -710,13 +710,13 @@ note: required by a bound in `ChildContainerPropertiesBuilder::children` | -------- required by a bound in this = note: this error originates in the macro `html` which comes from the expansion of the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `VChild: From` is not satisfied +error[E0277]: the trait bound `VChild: From` is not satisfied --> tests/html_macro/component-fail.rs:118:29 | 118 | html! { <> }; - | ^ the trait `From` is not implemented for `VChild` + | ^ the trait `From` is not implemented for `VChild` | - = note: required because of the requirements on the impl of `Into>` for `VNode` + = note: required because of the requirements on the impl of `Into>` for `yew::virtual_dom::VList` error[E0277]: the trait bound `VNode: IntoPropValue>>` is not satisfied --> tests/html_macro/component-fail.rs:119:14 From b3927ba8bc6c4ab585c1ef033cd8194359ee2a91 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Mon, 4 Dec 2023 15:16:38 +0000 Subject: [PATCH 12/13] removed an allocation optimisation for for-nodes generating only 1 node per iteration --- packages/yew-macro/src/html_tree/html_for.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/yew-macro/src/html_tree/html_for.rs b/packages/yew-macro/src/html_tree/html_for.rs index 58256dfcf91..1a7a345674f 100644 --- a/packages/yew-macro/src/html_tree/html_for.rs +++ b/packages/yew-macro/src/html_tree/html_for.rs @@ -66,7 +66,9 @@ impl ToTokens for HtmlFor { let Self { pat, iter, body } = self; let acc = Ident::new("__yew_v", iter.span()); - let alloc_opt = body.size_hint().map(|size| quote!( #acc.reserve(#size) )); + 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! { From a9a1c4947b8c592752e8808cffdaedc1e5e61d70 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Mon, 4 Dec 2023 15:22:28 +0000 Subject: [PATCH 13/13] fixed formatting --- packages/yew-macro/src/html_tree/html_for.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/yew-macro/src/html_tree/html_for.rs b/packages/yew-macro/src/html_tree/html_for.rs index 1a7a345674f..73e073b3a63 100644 --- a/packages/yew-macro/src/html_tree/html_for.rs +++ b/packages/yew-macro/src/html_tree/html_for.rs @@ -66,7 +66,8 @@ impl ToTokens for HtmlFor { let Self { pat, iter, body } = self; let acc = Ident::new("__yew_v", iter.span()); - let alloc_opt = body.size_hint() + 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) ));