Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions packages/yew-macro/src/html_tree/html_for.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::PeekValue;
use crate::html_tree::HtmlTree;

/// Determines if an expression is guaranteed to always return the same value anywhere.
fn is_contextless_pure(expr: &Expr) -> bool {
pub(super) fn is_contextless_pure(expr: &Expr) -> bool {
match expr {
Expr::Lit(_) => true,
Expr::Path(path) => path.path.get_ident().is_none(),
Expand Down Expand Up @@ -134,10 +134,9 @@ impl ToTokens for HtmlFor {
tokens.extend(quote!({
#deprecations
let mut #acc = ::std::vec::Vec::<::yew::virtual_dom::VNode>::new();
::std::iter::Iterator::for_each(
::std::iter::IntoIterator::into_iter(#iter),
|#pat| { #(#let_stmts)* #alloc_opt; #(#body);* }
);
for #pat in #iter {
#(#let_stmts)* #alloc_opt; #(#body);*
}
#vlist_gen
}))
}
Expand Down
145 changes: 145 additions & 0 deletions packages/yew-macro/src/html_tree/html_while.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use proc_macro2::{Ident, TokenStream};
use quote::{ToTokens, quote};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::token::While;
use syn::{Expr, Local, Stmt, Token, braced};

use super::{HtmlChildrenTree, ToNodeIterator};
use crate::PeekValue;
use crate::html_tree::HtmlTree;
use crate::html_tree::html_for::is_contextless_pure;

pub struct HtmlWhile {
cond: Box<Expr>,
let_stmts: Vec<Local>,
body: HtmlChildrenTree,
deprecations: TokenStream,
}

impl PeekValue<()> for HtmlWhile {
fn peek(cursor: Cursor) -> Option<()> {
let (ident, _) = cursor.ident()?;
(ident == "while").then_some(())
}
}

impl Parse for HtmlWhile {
fn parse(input: ParseStream) -> syn::Result<Self> {
While::parse(input)?;
let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);
match &*cond {
Expr::Block(syn::ExprBlock { block, .. }) if block.stmts.is_empty() => {
return Err(syn::Error::new(
cond.span(),
"missing condition for `while` expression",
));
}
_ => {}
}
if input.is_empty() {
return Err(syn::Error::new(
cond.span(),
"this `while` expression has a condition, but no block",
));
}

let body_stream;
braced!(body_stream in input);

let mut let_stmts = Vec::new();
while body_stream.peek(Token![let]) {
Comment thread
WorldSEnder marked this conversation as resolved.
Outdated
let stmt: Stmt = body_stream.parse()?;
match stmt {
Stmt::Local(local) => let_stmts.push(local),
_ => unreachable!("peeked Token![let] but parsed non-local statement"),
}
}

let body = HtmlChildrenTree::parse_delimited_with_nodes(&body_stream)?;
let deprecations = super::check_unnecessary_fragment(&body);
// TODO: more concise code by using if-let guards (MSRV 1.95)
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 `while`-loop\nthis will create elements with \
duplicate keys if the loop iterates more than once",
));
}
}
Ok(Self {
cond,
let_stmts,
body,
deprecations,
})
}
}

impl ToTokens for HtmlWhile {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
cond,
let_stmts,
body,
deprecations,
} = self;
let acc = Ident::new("__yew_v", cond.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| match child.to_node_iterator_stream() {
Some(child) => {
quote!( #acc.extend(#child) )
}
_ => {
quote!( #acc.push(::std::convert::Into::into(#child)) )
Comment thread
WorldSEnder marked this conversation as resolved.
Outdated
}
});

tokens.extend(quote!({
#deprecations
let mut #acc = ::std::vec::Vec::<::yew::virtual_dom::VNode>::new();
while #cond {
#(#let_stmts)* #alloc_opt; #(#body);*
}
#vlist_gen
}))
}
}
32 changes: 31 additions & 1 deletion packages/yew-macro/src/html_tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod html_iterable;
mod html_list;
mod html_match;
mod html_node;
mod html_while;
mod lint;
mod tag;

Expand All @@ -34,6 +35,7 @@ use tag::TagTokens;
use self::html_block::BlockContent;
use self::html_for::HtmlFor;
use self::html_match::HtmlMatch;
use self::html_while::HtmlWhile;

pub enum HtmlType {
Block,
Expand All @@ -42,7 +44,10 @@ pub enum HtmlType {
Element,
If,
For,
While,
Match,
Break,
Continue,
Empty,
}

Expand All @@ -53,8 +58,11 @@ pub enum HtmlTree {
Element(Box<HtmlElement>),
If(Box<HtmlIf>),
For(Box<HtmlFor>),
While(Box<HtmlWhile>),
Match(Box<HtmlMatch>),
Node(Box<HtmlNode>),
Break(Token![break]),
Continue(Token![continue]),
Empty,
}

Expand All @@ -70,7 +78,10 @@ impl Parse for HtmlTree {
HtmlType::List => Self::List(Box::new(input.parse()?)),
HtmlType::If => Self::If(Box::new(input.parse()?)),
HtmlType::For => Self::For(Box::new(input.parse()?)),
HtmlType::While => Self::While(Box::new(input.parse()?)),
HtmlType::Match => Self::Match(Box::new(input.parse()?)),
HtmlType::Break => Self::Break(input.parse()?),
HtmlType::Continue => Self::Continue(input.parse()?),
})
}
}
Expand Down Expand Up @@ -102,8 +113,18 @@ impl HtmlTree {
Some(HtmlType::If)
} else if HtmlFor::peek(cursor).is_some() {
Some(HtmlType::For)
} else if HtmlWhile::peek(cursor).is_some() {
Some(HtmlType::While)
} else if HtmlMatch::peek(cursor).is_some() {
Some(HtmlType::Match)
} else if cursor.ident().map(|(i, _)| i == "break").unwrap_or(false) {
Some(HtmlType::Break)
} else if cursor
.ident()
.map(|(i, _)| i == "continue")
.unwrap_or(false)
{
Some(HtmlType::Continue)
} else if input.peek(Token![<]) {
let _lt: Token![<] = input.parse().ok()?;

Expand Down Expand Up @@ -152,8 +173,11 @@ impl ToTokens for HtmlTree {
Self::Block(block) => block.to_tokens(tokens),
Self::If(block) => block.to_tokens(tokens),
Self::For(block) => block.to_tokens(tokens),
Self::While(block) => block.to_tokens(tokens),
Self::Match(block) => block.to_tokens(tokens),
Self::Node(node) => node.to_tokens(tokens),
Self::Break(token) => token.to_tokens(tokens),
Self::Continue(token) => token.to_tokens(tokens),
}
}
}
Expand Down Expand Up @@ -447,7 +471,13 @@ impl HtmlChildrenTree {
HtmlNode::Expression(_) => None,
};
}
HtmlTree::If(_) | HtmlTree::For(_) | HtmlTree::Match(_) | HtmlTree::Empty => {
HtmlTree::If(_)
| HtmlTree::For(_)
| HtmlTree::While(_)
| HtmlTree::Match(_)
| HtmlTree::Break(_)
| HtmlTree::Continue(_)
| HtmlTree::Empty => {
return Some(false);
}
}
Expand Down
4 changes: 0 additions & 4 deletions packages/yew-macro/tests/html_macro/for-fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ fn main() {
<span>{x}</span>
}};

_ = ::yew::html!{for _ in 0 .. 10 {
<span>{break}</span>
}};

_ = ::yew::html!{for _ in 0 .. 10 {
<div key="duplicate" />
}};
Expand Down
21 changes: 5 additions & 16 deletions packages/yew-macro/tests/html_macro/for-fail.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,25 @@ error: unexpected end of input, expected curly braces

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
--> tests/html_macro/for-fail.rs:14:18
|
18 | <div key="duplicate" />
14 | <div key="duplicate" />
| ^^^^^^^^^^^

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
--> tests/html_macro/for-fail.rs:18:19
|
22 | <div key={smth::KEY} />
18 | <div key={smth::KEY} />
| ^^^^

error[E0267]: `break` inside of a closure
--> tests/html_macro/for-fail.rs:14:16
|
13 | _ = ::yew::html!{for _ in 0 .. 10 {
| _________-
14 | | <span>{break}</span>
| | ^^^^^ cannot `break` inside of a closure
15 | | }};
| |______- enclosing closure

error[E0308]: mismatched types
--> tests/html_macro/for-fail.rs:9:26
|
9 | _ = ::yew::html!{for (x, y) in 0 .. 10 {
| ^^^^^^
| ^^^^^^ ------- this is an iterator with items of type `{integer}`
| |
| expected integer, found `(_, _)`
| expected due to this
|
= note: expected type `{integer}`
found tuple `(_, _)`
20 changes: 20 additions & 0 deletions packages/yew-macro/tests/html_macro/for-pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,24 @@ fn main() {
<hr/>
}
};

// break in for loop
_ = ::yew::html!{
for i in 0..10 {
if i > 5 {
break
}
<span>{i}</span>
}
};

// continue in for loop
_ = ::yew::html!{
for i in 0..10 {
if i % 2 == 0 {
continue
}
<span>{i}</span>
}
};
}
17 changes: 17 additions & 0 deletions packages/yew-macro/tests/html_macro/while-fail.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
mod smth {
const KEY: u32 = 42;
}

fn main() {
_ = ::yew::html!{while};
_ = ::yew::html!{while true};
_ = ::yew::html!{while {} { <div/> }};

_ = ::yew::html!{while true {
<div key="duplicate" />
}};

_ = ::yew::html!{while true {
<div key={smth::KEY} />
}};
}
33 changes: 33 additions & 0 deletions packages/yew-macro/tests/html_macro/while-fail.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
error: unexpected end of input, expected an expression
--> tests/html_macro/while-fail.rs:6:9
|
6 | _ = ::yew::html!{while};
| ^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `::yew::html` (in Nightly builds, run with -Z macro-backtrace for more info)

error: this `while` expression has a condition, but no block
--> tests/html_macro/while-fail.rs:7:28
|
7 | _ = ::yew::html!{while true};
| ^^^^

error: missing condition for `while` expression
--> tests/html_macro/while-fail.rs:8:28
|
8 | _ = ::yew::html!{while {} { <div/> }};
| ^^

error: duplicate key for a node in a `while`-loop
this will create elements with duplicate keys if the loop iterates more than once
--> tests/html_macro/while-fail.rs:11:18
|
11 | <div key="duplicate" />
| ^^^^^^^^^^^

error: duplicate key for a node in a `while`-loop
this will create elements with duplicate keys if the loop iterates more than once
--> tests/html_macro/while-fail.rs:15:19
|
15 | <div key={smth::KEY} />
| ^^^^
Loading
Loading