From 3e68a59abb8008099222ef5f7dae2a20bdf7a0ac Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Wed, 1 Nov 2023 23:27:58 +0300 Subject: [PATCH 01/11] Initial Implementation for Dynamic Prop Labels --- .../yew-macro/src/html_tree/html_element.rs | 61 ++++++--- packages/yew-macro/src/html_tree/html_list.rs | 2 +- packages/yew-macro/src/html_tree/lint/mod.rs | 7 +- packages/yew-macro/src/props/component.rs | 17 ++- packages/yew-macro/src/props/element.rs | 10 +- packages/yew-macro/src/props/prop.rs | 119 +++++++++++++++--- packages/yew-macro/src/props/prop_macro.rs | 8 +- 7 files changed, 172 insertions(+), 52 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_element.rs b/packages/yew-macro/src/html_tree/html_element.rs index ee588305de6..b492f3e0d6d 100644 --- a/packages/yew-macro/src/html_tree/html_element.rs +++ b/packages/yew-macro/src/html_tree/html_element.rs @@ -7,7 +7,7 @@ use syn::spanned::Spanned; use syn::{Expr, Ident, Lit, LitStr, Token}; use super::{HtmlChildrenTree, HtmlDashedName, TagTokens}; -use crate::props::{ElementProps, Prop, PropDirective}; +use crate::props::{ElementProps, Prop, PropDirective, PropLabel}; use crate::stringify::{Stringify, Value}; use crate::{is_ide_completion, non_capitalized_ascii, Peek, PeekValue}; @@ -138,6 +138,30 @@ impl ToTokens for HtmlElement { // other attributes let attributes = { + #[derive(Clone)] + enum Key { + Static(LitStr), + Dynamic(Expr), + } + + impl From<&PropLabel> for Key { + fn from(value: &PropLabel) -> Self { + match value { + PropLabel::Static(dashed_name) => Self::Static(dashed_name.to_lit_str()), + PropLabel::Dynamic(expr) => Self::Dynamic(expr.clone()), + } + } + } + + impl ToTokens for Key { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(match self { + Key::Static(dashed_name) => quote! { #dashed_name }, + Key::Dynamic(expr) => quote! { #expr }, + }); + } + } + let normal_attrs = attributes.iter().map( |Prop { label, @@ -146,7 +170,7 @@ impl ToTokens for HtmlElement { .. }| { ( - label.to_lit_str(), + Key::from(label), value.optimize_literals_tagged(), *directive, ) @@ -159,26 +183,30 @@ impl ToTokens for HtmlElement { directive, .. }| { - let key = label.to_lit_str(); + let key = Key::from(label); + let lit = match &key { + Key::Static(lit) => lit, + Key::Dynamic(_) => unreachable!(), + }; Some(( key.clone(), match value { Expr::Lit(e) => match &e.lit { Lit::Bool(b) => Value::Static(if b.value { - quote! { #key } + quote! { #lit } } else { return None; }), _ => Value::Dynamic(quote_spanned! {value.span()=> { ::yew::utils::__ensure_type::<::std::primitive::bool>(#value); - #key + #lit }}), }, expr => Value::Dynamic( quote_spanned! {expr.span().resolved_at(Span::call_site())=> if #expr { ::std::option::Option::Some( - ::yew::virtual_dom::AttrValue::Static(#key) + ::yew::virtual_dom::AttrValue::Static(#lit) ) } else { ::std::option::Option::None @@ -200,7 +228,7 @@ impl ToTokens for HtmlElement { None } else { Some(( - LitStr::new("class", lit.span()), + Key::Static(LitStr::new("class", lit.span())), Value::Static(quote! { #lit }), None, )) @@ -209,7 +237,7 @@ impl ToTokens for HtmlElement { None => { let expr = &classes.value; Some(( - LitStr::new("class", classes.label.span()), + Key::Static(LitStr::new("class", classes.label.span())), Value::Dynamic(quote! { ::std::convert::Into::<::yew::html::Classes>::into(#expr) }), @@ -219,13 +247,11 @@ impl ToTokens for HtmlElement { }); /// Try to turn attribute list into a `::yew::virtual_dom::Attributes::Static` - fn try_into_static( - src: &[(LitStr, Value, Option)], - ) -> Option { - if src - .iter() - .any(|(_, _, d)| matches!(d, Some(PropDirective::ApplyAsProperty(_)))) - { + fn try_into_static(src: &[(Key, Value, Option)]) -> Option { + if src.iter().any(|(k, _, d)| { + matches!(k, Key::Dynamic(_)) + || matches!(d, Some(PropDirective::ApplyAsProperty(_))) + }) { // don't try to make a static attribute list if there are any properties to // assign return None; @@ -255,7 +281,7 @@ impl ToTokens for HtmlElement { let attrs = normal_attrs .chain(boolean_attrs) .chain(class_attr) - .collect::)>>(); + .collect::)>>(); try_into_static(&attrs).unwrap_or_else(|| { let keys = attrs.iter().map(|(k, ..)| quote! { #k }); let values = attrs.iter().map(|(_, v, directive)| { @@ -289,7 +315,8 @@ impl ToTokens for HtmlElement { quote! { ::yew::virtual_dom::listeners::Listeners::None } } else { let listeners_it = listeners.iter().map(|Prop { label, value, .. }| { - let name = &label.name; + // TODO: consider making a `ListenerProp` that has dashed name's name and value + let name = &<&HtmlDashedName>::try_from(label).unwrap().name; quote! { ::yew::html::#name::Wrapper::__macro_new(#value) } diff --git a/packages/yew-macro/src/html_tree/html_list.rs b/packages/yew-macro/src/html_tree/html_list.rs index 301b533a92c..e3ada4b48f0 100644 --- a/packages/yew-macro/src/html_tree/html_list.rs +++ b/packages/yew-macro/src/html_tree/html_list.rs @@ -134,7 +134,7 @@ impl Parse for HtmlListProps { return Err(input.error("only a single `key` prop is allowed on a fragment")); } - if prop.label.to_ascii_lowercase_string() != "key" { + if !String::try_from(&prop.label).is_ok_and(|label| label.eq_ignore_ascii_case("key")) { return Err(syn::Error::new_spanned( prop.label, "fragments only accept the `key` prop", diff --git a/packages/yew-macro/src/html_tree/lint/mod.rs b/packages/yew-macro/src/html_tree/lint/mod.rs index bbb34f24648..7a3734988e1 100644 --- a/packages/yew-macro/src/html_tree/lint/mod.rs +++ b/packages/yew-macro/src/html_tree/lint/mod.rs @@ -45,10 +45,9 @@ where /// /// Attribute names are lowercased before being compared (so pass "href" for `name` and not "HREF"). fn get_attribute<'a>(props: &'a ElementProps, name: &str) -> Option<&'a Prop> { - props - .attributes - .iter() - .find(|item| item.label.eq_ignore_ascii_case(name)) + props.attributes.iter().find(|item| { + String::try_from(&item.label).is_ok_and(|label| label.eq_ignore_ascii_case(name)) + }) } /// Lints to check if anchor (``) tags have valid `href` attributes defined. diff --git a/packages/yew-macro/src/props/component.rs b/packages/yew-macro/src/props/component.rs index 3c0984e611c..099fac807dc 100644 --- a/packages/yew-macro/src/props/component.rs +++ b/packages/yew-macro/src/props/component.rs @@ -7,7 +7,7 @@ use syn::spanned::Spanned; use syn::token::DotDot; use syn::Expr; -use super::{Prop, Props, SpecialProps, CHILDREN_LABEL}; +use super::{Prop, PropLabel, Props, SpecialProps, CHILDREN_LABEL}; struct BaseExpr { pub dot_dot: DotDot, @@ -190,15 +190,12 @@ impl TryFrom for ComponentProps { fn validate(props: Props) -> Result { props.check_no_duplicates()?; - props.check_all(|prop| { - if !prop.label.extended.is_empty() { - Err(syn::Error::new_spanned( - &prop.label, - "expected a valid Rust identifier", - )) - } else { - Ok(()) - } + props.check_all(|prop| match &prop.label { + PropLabel::Static(dashed_name) if dashed_name.extended.is_empty() => Ok(()), + _ => Err(syn::Error::new_spanned( + &prop.label, + "expected a valid Rust identifier", + )), })?; Ok(props) diff --git a/packages/yew-macro/src/props/element.rs b/packages/yew-macro/src/props/element.rs index 17a90e3f9db..b9d99a8a872 100644 --- a/packages/yew-macro/src/props/element.rs +++ b/packages/yew-macro/src/props/element.rs @@ -19,14 +19,16 @@ impl Parse for ElementProps { fn parse(input: ParseStream) -> syn::Result { let mut props = input.parse::()?; - let listeners = - props.drain_filter(|prop| LISTENER_SET.contains(prop.label.to_string().as_str())); + let listeners = props.drain_filter(|prop| { + String::try_from(&prop.label).is_ok_and(|prop| LISTENER_SET.contains(prop.as_str())) + }); // Multiple listener attributes are allowed, but no others props.check_no_duplicates()?; - let booleans = - props.drain_filter(|prop| BOOLEAN_SET.contains(prop.label.to_string().as_str())); + let booleans = props.drain_filter(|prop| { + String::try_from(&prop.label).is_ok_and(|prop| BOOLEAN_SET.contains(prop.as_str())) + }); let classes = props.pop("class"); let value = props.pop("value"); diff --git a/packages/yew-macro/src/props/prop.rs b/packages/yew-macro/src/props/prop.rs index f1c0ad48079..b2f4285ad39 100644 --- a/packages/yew-macro/src/props/prop.rs +++ b/packages/yew-macro/src/props/prop.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use std::ops::{Deref, DerefMut}; use proc_macro2::{Spacing, Span, TokenStream, TokenTree}; -use quote::{quote, quote_spanned}; +use quote::{quote, quote_spanned, ToTokens}; use syn::parse::{Parse, ParseBuffer, ParseStream}; use syn::spanned::Spanned; use syn::token::Brace; @@ -16,9 +16,79 @@ pub enum PropDirective { ApplyAsProperty(Token![~]), } +pub enum PropLabel { + Static(HtmlDashedName), + Dynamic(Expr), +} + +impl From for PropLabel { + fn from(value: HtmlDashedName) -> Self { + Self::Static(value) + } +} + +impl TryFrom for HtmlDashedName { + type Error = (); + + fn try_from(value: PropLabel) -> Result { + use PropLabel::*; + match value { + Static(dashed_name) => Ok(dashed_name), + Dynamic(_) => Err(()), + } + } +} + +impl<'a> TryFrom<&'a PropLabel> for &'a HtmlDashedName { + type Error = (); + + fn try_from(value: &'a PropLabel) -> Result { + use PropLabel::*; + match value { + Static(dashed_name) => Ok(dashed_name), + Dynamic(_) => Err(()), + } + } +} + +impl TryFrom for String { + type Error = (); + + fn try_from(value: PropLabel) -> Result { + HtmlDashedName::try_from(value).map(|dashed_name| dashed_name.to_string()) + } +} + +impl TryFrom<&PropLabel> for String { + type Error = (); + + fn try_from(value: &PropLabel) -> Result { + <&HtmlDashedName>::try_from(value).map(|dashed_name| dashed_name.to_string()) + } +} + +impl PartialEq for PropLabel { + fn eq(&self, other: &PropLabel) -> bool { + match (self, other) { + (Self::Static(l), Self::Static(r)) => l == r, + // NOTE: Dynamic props may repeat + _ => false, + } + } +} + +impl ToTokens for PropLabel { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(match self { + PropLabel::Static(dashed_name) => quote! {#dashed_name}, + PropLabel::Dynamic(expr) => quote! {#expr}, // FIXME this probably wanted its group back + }); + } +} + pub struct Prop { pub directive: Option, - pub label: HtmlDashedName, + pub label: PropLabel, /// Punctuation between `label` and `value`. pub value: Expr, } @@ -30,7 +100,7 @@ impl Parse for Prop { .map(PropDirective::ApplyAsProperty) .ok(); if input.peek(Brace) { - Self::parse_shorthand_prop_assignment(input, directive) + Self::parse_shorthand_or_dynamic_prop_assignment(input, directive) } else { Self::parse_prop_assignment(input, directive) } @@ -39,16 +109,31 @@ impl Parse for Prop { /// Helpers for parsing props impl Prop { - /// Parse a prop using the shorthand syntax `{value}`, short for `value={value}` - /// This only allows for labels with no hyphens, as it would otherwise create - /// an ambiguity in the syntax - fn parse_shorthand_prop_assignment( + /// Parse a prop using the shorthand syntax `{value}`, short for `value={value}`, + /// or using the `{label}={value}` dynamic label syntax. + /// + /// Shorthand syntax only allows for labels with no hyphens, + /// as it would otherwise create an ambiguity in the syntax. + fn parse_shorthand_or_dynamic_prop_assignment( input: ParseStream, directive: Option, ) -> syn::Result { let value; let _brace = braced!(value in input); let expr = value.parse::()?; + + // dynamic here + if input.peek(Token![=]) { + input.parse::().unwrap(); + let value = parse_prop_value(input)?; + return Ok(Self { + label: PropLabel::Dynamic(expr), + value, + directive, + }); + } + // otherwise, shorthand + let label = if let Expr::Path(ExprPath { ref attrs, qself: None, @@ -72,7 +157,7 @@ impl Prop { }?; Ok(Self { - label, + label: label.into(), value: expr, directive, }) @@ -102,7 +187,7 @@ impl Prop { let value = parse_prop_value(input)?; Ok(Self { - label, + label: label.into(), value, directive, }) @@ -226,12 +311,16 @@ impl PropList { } fn position(&self, key: &str) -> Option { - self.0.iter().position(|it| it.label.to_string() == key) + self.0 + .iter() + .position(|it| String::try_from(&it.label).is_ok_and(|dashed_name| dashed_name == key)) } /// Get the first prop with the given key. pub fn get_by_label(&self, key: &str) -> Option<&Prop> { - self.0.iter().find(|it| it.label.to_string() == key) + self.0 + .iter() + .find(|it| String::try_from(&it.label).is_ok_and(|dashed_name| dashed_name == key)) } /// Pop the first prop with the given key. @@ -245,7 +334,8 @@ impl PropList { if prop.is_some() { if let Some(other_prop) = self.get_by_label(key) { return Err(syn::Error::new_spanned( - &other_prop.label, + // OK to unwrap since pop/get_by_label can be Some only if PropLabel::Static + &String::try_from(&other_prop.label).unwrap(), format!("`{key}` can only be specified once"), )); } @@ -289,10 +379,11 @@ impl PropList { pub fn check_no_duplicates(&self) -> syn::Result<()> { crate::join_errors(self.iter_duplicates().map(|prop| { syn::Error::new_spanned( - &prop.label, + // OK to unwrap since iter_duplicates iterates only over PropLabel::Static + &String::try_from(&prop.label).unwrap(), format!( "`{}` can only be specified once but is given here again", - prop.label + String::try_from(&prop.label).unwrap() ), ) })) diff --git a/packages/yew-macro/src/props/prop_macro.rs b/packages/yew-macro/src/props/prop_macro.rs index 2bddcebceb0..2ef67b54791 100644 --- a/packages/yew-macro/src/props/prop_macro.rs +++ b/packages/yew-macro/src/props/prop_macro.rs @@ -8,7 +8,7 @@ use syn::spanned::Spanned; use syn::token::Brace; use syn::{Expr, Token, TypePath}; -use super::{ComponentProps, Prop, PropList, Props}; +use super::{ComponentProps, Prop, PropLabel, PropList, Props}; use crate::html_tree::HtmlDashedName; /// Pop from `Punctuated` without leaving it in a state where it has trailing punctuation. @@ -45,6 +45,7 @@ struct PropValue { label: HtmlDashedName, value: Expr, } + impl Parse for PropValue { fn parse(input: ParseStream) -> syn::Result { let label = input.parse()?; @@ -62,7 +63,7 @@ impl From for Prop { fn from(prop_value: PropValue) -> Prop { let PropValue { label, value } = prop_value; Prop { - label, + label: PropLabel::Static(label), value, directive: None, } @@ -74,6 +75,7 @@ struct PropsExpr { _brace_token: Brace, fields: Punctuated, } + impl Parse for PropsExpr { fn parse(input: ParseStream) -> syn::Result { let mut ty: TypePath = input.parse()?; @@ -103,6 +105,7 @@ pub struct PropsMacroInput { ty: TypePath, props: ComponentProps, } + impl Parse for PropsMacroInput { fn parse(input: ParseStream) -> syn::Result { let PropsExpr { ty, fields, .. } = input.parse()?; @@ -121,6 +124,7 @@ impl Parse for PropsMacroInput { }) } } + impl ToTokens for PropsMacroInput { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { ty, props } = self; From b9a4a5b7ddec919c63e9cb00766af7ba20060125 Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Wed, 1 Nov 2023 23:40:34 +0300 Subject: [PATCH 02/11] Fixes per `cargo make lint` --- packages/yew-macro/src/props/prop.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yew-macro/src/props/prop.rs b/packages/yew-macro/src/props/prop.rs index b2f4285ad39..8f0eb9b9c7c 100644 --- a/packages/yew-macro/src/props/prop.rs +++ b/packages/yew-macro/src/props/prop.rs @@ -335,7 +335,7 @@ impl PropList { if let Some(other_prop) = self.get_by_label(key) { return Err(syn::Error::new_spanned( // OK to unwrap since pop/get_by_label can be Some only if PropLabel::Static - &String::try_from(&other_prop.label).unwrap(), + String::try_from(&other_prop.label).unwrap(), format!("`{key}` can only be specified once"), )); } @@ -380,7 +380,7 @@ impl PropList { crate::join_errors(self.iter_duplicates().map(|prop| { syn::Error::new_spanned( // OK to unwrap since iter_duplicates iterates only over PropLabel::Static - &String::try_from(&prop.label).unwrap(), + String::try_from(&prop.label).unwrap(), format!( "`{}` can only be specified once but is given here again", String::try_from(&prop.label).unwrap() From fcee6b252d97c69233807222925a3409c34c3b1d Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Thu, 2 Nov 2023 00:02:39 +0300 Subject: [PATCH 03/11] Rust feature-downgrade for compiling on 1.64.0 --- packages/yew-macro/src/html_tree/html_list.rs | 13 ++++++++----- packages/yew-macro/src/html_tree/lint/mod.rs | 10 +++++++--- packages/yew-macro/src/props/element.rs | 10 ++++++---- packages/yew-macro/src/props/prop.rs | 12 ++++++++---- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_list.rs b/packages/yew-macro/src/html_tree/html_list.rs index e3ada4b48f0..58d228239db 100644 --- a/packages/yew-macro/src/html_tree/html_list.rs +++ b/packages/yew-macro/src/html_tree/html_list.rs @@ -134,11 +134,14 @@ impl Parse for HtmlListProps { return Err(input.error("only a single `key` prop is allowed on a fragment")); } - if !String::try_from(&prop.label).is_ok_and(|label| label.eq_ignore_ascii_case("key")) { - return Err(syn::Error::new_spanned( - prop.label, - "fragments only accept the `key` prop", - )); + match String::try_from(&prop.label) { + Ok(label) if label.eq_ignore_ascii_case("key") => {} + _ => { + return Err(syn::Error::new_spanned( + prop.label, + "fragments only accept the `key` prop", + )) + } } Some(prop.value) diff --git a/packages/yew-macro/src/html_tree/lint/mod.rs b/packages/yew-macro/src/html_tree/lint/mod.rs index 7a3734988e1..d9ffc8e197e 100644 --- a/packages/yew-macro/src/html_tree/lint/mod.rs +++ b/packages/yew-macro/src/html_tree/lint/mod.rs @@ -45,9 +45,13 @@ where /// /// Attribute names are lowercased before being compared (so pass "href" for `name` and not "HREF"). fn get_attribute<'a>(props: &'a ElementProps, name: &str) -> Option<&'a Prop> { - props.attributes.iter().find(|item| { - String::try_from(&item.label).is_ok_and(|label| label.eq_ignore_ascii_case(name)) - }) + props + .attributes + .iter() + .find(|item| match String::try_from(&item.label) { + Ok(label) if label.eq_ignore_ascii_case(name) => true, + _ => false, + }) } /// Lints to check if anchor (``) tags have valid `href` attributes defined. diff --git a/packages/yew-macro/src/props/element.rs b/packages/yew-macro/src/props/element.rs index b9d99a8a872..0a141972fe8 100644 --- a/packages/yew-macro/src/props/element.rs +++ b/packages/yew-macro/src/props/element.rs @@ -19,15 +19,17 @@ impl Parse for ElementProps { fn parse(input: ParseStream) -> syn::Result { let mut props = input.parse::()?; - let listeners = props.drain_filter(|prop| { - String::try_from(&prop.label).is_ok_and(|prop| LISTENER_SET.contains(prop.as_str())) + let listeners = props.drain_filter(|prop| match String::try_from(&prop.label) { + Ok(prop) if LISTENER_SET.contains(prop.as_str()) => true, + _ => false, }); // Multiple listener attributes are allowed, but no others props.check_no_duplicates()?; - let booleans = props.drain_filter(|prop| { - String::try_from(&prop.label).is_ok_and(|prop| BOOLEAN_SET.contains(prop.as_str())) + let booleans = props.drain_filter(|prop| match String::try_from(&prop.label) { + Ok(prop) if BOOLEAN_SET.contains(prop.as_str()) => true, + _ => false, }); let classes = props.pop("class"); diff --git a/packages/yew-macro/src/props/prop.rs b/packages/yew-macro/src/props/prop.rs index 8f0eb9b9c7c..75621626abd 100644 --- a/packages/yew-macro/src/props/prop.rs +++ b/packages/yew-macro/src/props/prop.rs @@ -313,14 +313,18 @@ impl PropList { fn position(&self, key: &str) -> Option { self.0 .iter() - .position(|it| String::try_from(&it.label).is_ok_and(|dashed_name| dashed_name == key)) + .position(|it| match String::try_from(&it.label) { + Ok(dashed_name) if dashed_name == key => true, + _ => false, + }) } /// Get the first prop with the given key. pub fn get_by_label(&self, key: &str) -> Option<&Prop> { - self.0 - .iter() - .find(|it| String::try_from(&it.label).is_ok_and(|dashed_name| dashed_name == key)) + self.0.iter().find(|it| match String::try_from(&it.label) { + Ok(dashed_name) if dashed_name == key => true, + _ => false, + }) } /// Pop the first prop with the given key. From 764f4225fde0d67bfaa18fa1f353ff593cfab3ac Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Thu, 2 Nov 2023 00:08:08 +0300 Subject: [PATCH 04/11] Fix clippy --- packages/yew-macro/src/html_tree/lint/mod.rs | 13 ++++++------- packages/yew-macro/src/props/element.rs | 12 ++++++------ packages/yew-macro/src/props/prop.rs | 16 ++++++---------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/packages/yew-macro/src/html_tree/lint/mod.rs b/packages/yew-macro/src/html_tree/lint/mod.rs index d9ffc8e197e..cf166f9f680 100644 --- a/packages/yew-macro/src/html_tree/lint/mod.rs +++ b/packages/yew-macro/src/html_tree/lint/mod.rs @@ -45,13 +45,12 @@ where /// /// Attribute names are lowercased before being compared (so pass "href" for `name` and not "HREF"). fn get_attribute<'a>(props: &'a ElementProps, name: &str) -> Option<&'a Prop> { - props - .attributes - .iter() - .find(|item| match String::try_from(&item.label) { - Ok(label) if label.eq_ignore_ascii_case(name) => true, - _ => false, - }) + props.attributes.iter().find(|item| { + matches!( + String::try_from(&item.label), + Ok(label) if label.eq_ignore_ascii_case(name) + ) + }) } /// Lints to check if anchor (``) tags have valid `href` attributes defined. diff --git a/packages/yew-macro/src/props/element.rs b/packages/yew-macro/src/props/element.rs index 0a141972fe8..7ec68769c29 100644 --- a/packages/yew-macro/src/props/element.rs +++ b/packages/yew-macro/src/props/element.rs @@ -19,17 +19,17 @@ impl Parse for ElementProps { fn parse(input: ParseStream) -> syn::Result { let mut props = input.parse::()?; - let listeners = props.drain_filter(|prop| match String::try_from(&prop.label) { - Ok(prop) if LISTENER_SET.contains(prop.as_str()) => true, - _ => false, + let listeners = props.drain_filter(|prop| { + matches!(String::try_from(&prop.label), + Ok(prop) if LISTENER_SET.contains(prop.as_str())) }); // Multiple listener attributes are allowed, but no others props.check_no_duplicates()?; - let booleans = props.drain_filter(|prop| match String::try_from(&prop.label) { - Ok(prop) if BOOLEAN_SET.contains(prop.as_str()) => true, - _ => false, + let booleans = props.drain_filter(|prop| { + matches!(String::try_from(&prop.label), + Ok(prop) if BOOLEAN_SET.contains(prop.as_str())) }); let classes = props.pop("class"); diff --git a/packages/yew-macro/src/props/prop.rs b/packages/yew-macro/src/props/prop.rs index 75621626abd..5a1aa65f649 100644 --- a/packages/yew-macro/src/props/prop.rs +++ b/packages/yew-macro/src/props/prop.rs @@ -311,20 +311,16 @@ impl PropList { } fn position(&self, key: &str) -> Option { - self.0 - .iter() - .position(|it| match String::try_from(&it.label) { - Ok(dashed_name) if dashed_name == key => true, - _ => false, - }) + self.0.iter().position( + |it| matches!(String::try_from(&it.label), Ok(dashed_name) if dashed_name == key), + ) } /// Get the first prop with the given key. pub fn get_by_label(&self, key: &str) -> Option<&Prop> { - self.0.iter().find(|it| match String::try_from(&it.label) { - Ok(dashed_name) if dashed_name == key => true, - _ => false, - }) + self.0 + .iter() + .find(|it| matches!(String::try_from(&it.label), Ok(dashed_name) if dashed_name == key)) } /// Pop the first prop with the given key. From 3162b1dcdd4842e17f81e3fb57a31d7909e6a66c Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Thu, 2 Nov 2023 10:23:11 +0300 Subject: [PATCH 05/11] Fix erroneous changes for error spans --- packages/yew-macro/src/props/prop.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/yew-macro/src/props/prop.rs b/packages/yew-macro/src/props/prop.rs index 5a1aa65f649..f0641c2e477 100644 --- a/packages/yew-macro/src/props/prop.rs +++ b/packages/yew-macro/src/props/prop.rs @@ -334,8 +334,7 @@ impl PropList { if prop.is_some() { if let Some(other_prop) = self.get_by_label(key) { return Err(syn::Error::new_spanned( - // OK to unwrap since pop/get_by_label can be Some only if PropLabel::Static - String::try_from(&other_prop.label).unwrap(), + &other_prop.label, format!("`{key}` can only be specified once"), )); } @@ -379,8 +378,7 @@ impl PropList { pub fn check_no_duplicates(&self) -> syn::Result<()> { crate::join_errors(self.iter_duplicates().map(|prop| { syn::Error::new_spanned( - // OK to unwrap since iter_duplicates iterates only over PropLabel::Static - String::try_from(&prop.label).unwrap(), + &prop.label, format!( "`{}` can only be specified once but is given here again", String::try_from(&prop.label).unwrap() From 5861d6aea69bd15dfc7aa903c21f2c6eabcd5c9d Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Thu, 2 Nov 2023 12:48:51 +0300 Subject: [PATCH 06/11] Use IndexMap for dynamic labels Allows not only literals, e.g. "hx-on:click", but all expressions --- .../yew-macro/src/html_tree/html_element.rs | 68 ++++++++++++++++--- 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_element.rs b/packages/yew-macro/src/html_tree/html_element.rs index b492f3e0d6d..5c98251ae8a 100644 --- a/packages/yew-macro/src/html_tree/html_element.rs +++ b/packages/yew-macro/src/html_tree/html_element.rs @@ -4,7 +4,7 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; -use syn::{Expr, Ident, Lit, LitStr, Token}; +use syn::{Expr, ExprLit, Ident, Lit, LitStr, Token}; use super::{HtmlChildrenTree, HtmlDashedName, TagTokens}; use crate::props::{ElementProps, Prop, PropDirective, PropLabel}; @@ -253,7 +253,7 @@ impl ToTokens for HtmlElement { || matches!(d, Some(PropDirective::ApplyAsProperty(_))) }) { // don't try to make a static attribute list if there are any properties to - // assign + // assign or any labels are dynamic return None; } let mut kv = Vec::with_capacity(src.len()); @@ -278,13 +278,24 @@ impl ToTokens for HtmlElement { Some(quote! { ::yew::virtual_dom::Attributes::Static(&[#(#kv),*]) }) } - let attrs = normal_attrs - .chain(boolean_attrs) - .chain(class_attr) - .collect::)>>(); - try_into_static(&attrs).unwrap_or_else(|| { - let keys = attrs.iter().map(|(k, ..)| quote! { #k }); - let values = attrs.iter().map(|(_, v, directive)| { + /// Try to turn attribute list into a `::yew::virtual_dom::Attributes::Dynamic` + fn try_into_dynamic( + src: &[(Key, Value, Option)], + ) -> Option { + if src.iter().any(|(k, ..)| { + !matches!( + k, + Key::Dynamic(Expr::Lit(ExprLit { + lit: Lit::Str(_), + .. + })) | Key::Static(_) + ) + }) { + // use IndexMap if there are any dynamic-expr labels + return None; + } + let keys = src.iter().map(|(k, ..)| quote! { #k }); + let values = src.iter().map(|(_, v, directive)| { let value = match directive { Some(PropDirective::ApplyAsProperty(token)) => { quote_spanned!(token.span()=> ::std::option::Option::Some( @@ -302,11 +313,48 @@ impl ToTokens for HtmlElement { }; quote! { #value } }); - quote! { + Some(quote! { ::yew::virtual_dom::Attributes::Dynamic{ keys: &[#(#keys),*], values: ::std::boxed::Box::new([#(#values),*]), } + }) + } + + let attrs = normal_attrs + .chain(boolean_attrs) + .chain(class_attr) + .collect::)>>(); + try_into_static(&attrs).or_else(|| try_into_dynamic(&attrs)).unwrap_or_else(|| { + let results = attrs.iter() + .map(|(k, v, directive)| { + let value = match directive { + Some(PropDirective::ApplyAsProperty(token)) => { + quote_spanned!(token.span()=> ::std::option::Option::Some( + ::yew::virtual_dom::AttributeOrProperty::Property( + ::std::convert::Into::into(#v) + )) + ) + } + None => { + let value = wrap_attr_value(v); + quote! { + ::std::option::Option::map(#value, ::yew::virtual_dom::AttributeOrProperty::Attribute) + } + }, + }; + quote! { (::std::convert::Into::into(#k), #value) } + }); + quote! { + ::yew::virtual_dom::Attributes::IndexMap( + ::std::rc::Rc::new( + [#(#results),*] + .into_iter() + // FIXME verify if i understood it correctly + .filter_map(|(k, v)| v.map(|v| (k, v))) + .collect() + ) + ) } }) }; From 055ef6e58cc5dd96926cd1ee132274bb3f65439e Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Sun, 5 Nov 2023 19:42:05 +0300 Subject: [PATCH 07/11] Fix uncompilable iter chain (not elegant?) --- packages/yew-macro/src/html_tree/html_element.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_element.rs b/packages/yew-macro/src/html_tree/html_element.rs index 5c98251ae8a..4830a0a48e6 100644 --- a/packages/yew-macro/src/html_tree/html_element.rs +++ b/packages/yew-macro/src/html_tree/html_element.rs @@ -348,11 +348,13 @@ impl ToTokens for HtmlElement { quote! { ::yew::virtual_dom::Attributes::IndexMap( ::std::rc::Rc::new( - [#(#results),*] - .into_iter() - // FIXME verify if i understood it correctly - .filter_map(|(k, v)| v.map(|v| (k, v))) - .collect() + ::std::iter::Iterator::collect( + ::std::iter::Iterator::filter_map( + ::std::iter::IntoIterator::into_iter([#(#results),*]), + // FIXME verify if i understood it correctly + |(k, v)| v.map(|v| (k, v)) + ) + ) ) ) } From 2e8573125566efa772b9a3a9ad4846643257b74b Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Sun, 5 Nov 2023 19:42:51 +0300 Subject: [PATCH 08/11] Add tests --- .../tests/html_macro/dyn-prop-fail.rs | 67 +++++++++++++++++++ .../tests/html_macro/dyn-prop-fail.stderr | 33 +++++++++ .../tests/html_macro/dyn-prop-pass.rs | 53 +++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 packages/yew-macro/tests/html_macro/dyn-prop-fail.rs create mode 100644 packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr create mode 100644 packages/yew-macro/tests/html_macro/dyn-prop-pass.rs diff --git a/packages/yew-macro/tests/html_macro/dyn-prop-fail.rs b/packages/yew-macro/tests/html_macro/dyn-prop-fail.rs new file mode 100644 index 00000000000..d966d7f20b2 --- /dev/null +++ b/packages/yew-macro/tests/html_macro/dyn-prop-fail.rs @@ -0,0 +1,67 @@ +#![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; + +#[derive(::yew::Properties, ::std::cmp::PartialEq)] +pub struct SimpleProps { + pub test: ::std::string::String, +} + +pub struct Simple; +impl ::yew::Component for Simple { + type Message = (); + type Properties = SimpleProps; + + fn create(_ctx: &::yew::Context) -> Self { + ::std::unimplemented!() + } + + fn view(&self, _ctx: &::yew::Context) -> ::yew::Html { + ::std::unimplemented!() + } +} + +pub struct Fail; + +fn main() { + _ = ::yew::html! { }; + + let dyn_prop = || Fail; + _ = ::yew::html! { }; + + _ = ::yew::html! { } +} diff --git a/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr b/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr new file mode 100644 index 00000000000..f2d25f93c26 --- /dev/null +++ b/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr @@ -0,0 +1,33 @@ +error: expected a valid Rust identifier + --> tests/html_macro/dyn-prop-fail.rs:66:34 + | +66 | _ = ::yew::html! { } + | ^^^^^^ + +error[E0277]: the trait bound `implicit_clone::unsync::IString: From` is not satisfied + --> tests/html_macro/dyn-prop-fail.rs:61:9 + | +61 | _ = ::yew::html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From` is not implemented for `implicit_clone::unsync::IString` + | + = help: the following other types implement trait `From`: + > + >> + >> + > + = note: required because of the requirements on the impl of `Into` for `Fail` + = note: this error originates in the macro `::yew::html` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `implicit_clone::unsync::IString: From` is not satisfied + --> tests/html_macro/dyn-prop-fail.rs:64:9 + | +64 | _ = ::yew::html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From` is not implemented for `implicit_clone::unsync::IString` + | + = help: the following other types implement trait `From`: + > + >> + >> + > + = note: required because of the requirements on the impl of `Into` for `Fail` + = note: this error originates in the macro `::yew::html` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/packages/yew-macro/tests/html_macro/dyn-prop-pass.rs b/packages/yew-macro/tests/html_macro/dyn-prop-pass.rs new file mode 100644 index 00000000000..61d407a32e2 --- /dev/null +++ b/packages/yew-macro/tests/html_macro/dyn-prop-pass.rs @@ -0,0 +1,53 @@ +#![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() { + // basic example from https://htmx.org/attributes/hx-on/ + + // repeating attrs is not valid HTMX nor HTML, + // but valid html! (any checks can happen only during runtime) + + // literal + _ = ::yew::html! { }; + _ = ::yew::html! { }; + + // expr + let dyn_prop = || ::std::string::ToString::to_string("hx-on:click"); + _ = ::yew::html! { }; + _ = ::yew::html! { }; +} From 8f79f6f5e546181a0cc873faf884cbb5762e7100 Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Mon, 6 Nov 2023 19:55:49 +0300 Subject: [PATCH 09/11] Add direct-er literals --- packages/yew-macro/src/props/prop.rs | 45 +++++++++++++++++-- .../tests/html_macro/dyn-prop-fail.rs | 9 ++-- .../tests/html_macro/dyn-prop-fail.stderr | 26 +++++++++-- .../tests/html_macro/dyn-prop-pass.rs | 2 + 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/packages/yew-macro/src/props/prop.rs b/packages/yew-macro/src/props/prop.rs index f0641c2e477..3910aae1379 100644 --- a/packages/yew-macro/src/props/prop.rs +++ b/packages/yew-macro/src/props/prop.rs @@ -6,7 +6,7 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::parse::{Parse, ParseBuffer, ParseStream}; use syn::spanned::Spanned; use syn::token::Brace; -use syn::{braced, Block, Expr, ExprBlock, ExprMacro, ExprPath, ExprRange, Stmt, Token}; +use syn::{braced, Block, Expr, ExprBlock, ExprMacro, ExprPath, ExprRange, Stmt, Token, LitStr, parse_quote}; use crate::html_tree::HtmlDashedName; use crate::stringify::Stringify; @@ -27,6 +27,12 @@ impl From for PropLabel { } } +impl From for PropLabel { + fn from(value: LitStr) -> Self { + Self::Dynamic(parse_quote! { #value }) + } +} + impl TryFrom for HtmlDashedName { type Error = (); @@ -100,7 +106,9 @@ impl Parse for Prop { .map(PropDirective::ApplyAsProperty) .ok(); if input.peek(Brace) { - Self::parse_shorthand_or_dynamic_prop_assignment(input, directive) + Self::parse_shorthand_or_expr_dynamic_prop_assignment(input, directive) + } else if input.peek(LitStr) { + Self::parse_literal_dynamic_prop_assignment(input, directive) } else { Self::parse_prop_assignment(input, directive) } @@ -114,7 +122,7 @@ impl Prop { /// /// Shorthand syntax only allows for labels with no hyphens, /// as it would otherwise create an ambiguity in the syntax. - fn parse_shorthand_or_dynamic_prop_assignment( + fn parse_shorthand_or_expr_dynamic_prop_assignment( input: ParseStream, directive: Option, ) -> syn::Result { @@ -163,6 +171,37 @@ impl Prop { }) } + /// Parse a prop of the form `"label"={value}` + fn parse_literal_dynamic_prop_assignment( + input: ParseStream, + directive: Option, + ) -> syn::Result { + let label = input.parse::()?; + let equals = input.parse::().map_err(|_| { + let display = label.stringify(); + syn::Error::new_spanned( + &label, + format!( + "`{display}` doesn't have a value. (hint: set the value to `true` or `false` \ + for boolean attributes)" + ), + ) + })?; + if input.is_empty() { + return Err(syn::Error::new_spanned( + equals, + "expected an expression following this equals sign", + )); + } + + let value = parse_prop_value(input)?; + Ok(Self { + label: label.into(), + value, + directive, + }) + } + /// Parse a prop of the form `label={value}` fn parse_prop_assignment( input: ParseStream, diff --git a/packages/yew-macro/tests/html_macro/dyn-prop-fail.rs b/packages/yew-macro/tests/html_macro/dyn-prop-fail.rs index d966d7f20b2..fbd453add65 100644 --- a/packages/yew-macro/tests/html_macro/dyn-prop-fail.rs +++ b/packages/yew-macro/tests/html_macro/dyn-prop-fail.rs @@ -58,10 +58,13 @@ impl ::yew::Component for Simple { pub struct Fail; fn main() { - _ = ::yew::html! { }; - let dyn_prop = || Fail; + + _ = ::yew::html! { }; _ = ::yew::html! { }; - _ = ::yew::html! { } + _ = ::yew::html! { }; + _ = ::yew::html! { }; + _ = ::yew::html! { }; + _ = ::yew::html! { }; } diff --git a/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr b/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr index f2d25f93c26..335d25f840e 100644 --- a/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr +++ b/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr @@ -1,13 +1,31 @@ error: expected a valid Rust identifier - --> tests/html_macro/dyn-prop-fail.rs:66:34 + --> tests/html_macro/dyn-prop-fail.rs:66:32 | -66 | _ = ::yew::html! { } +66 | _ = ::yew::html! { }; + | ^^^^^^ + +error: expected a valid Rust identifier + --> tests/html_macro/dyn-prop-fail.rs:67:32 + | +67 | _ = ::yew::html! { }; + | ^^^^^^ + +error: expected a valid Rust identifier + --> tests/html_macro/dyn-prop-fail.rs:68:34 + | +68 | _ = ::yew::html! { }; | ^^^^^^ +error: expected a valid Rust identifier + --> tests/html_macro/dyn-prop-fail.rs:69:34 + | +69 | _ = ::yew::html! { }; + | ^^^^^^^^^^ + error[E0277]: the trait bound `implicit_clone::unsync::IString: From` is not satisfied - --> tests/html_macro/dyn-prop-fail.rs:61:9 + --> tests/html_macro/dyn-prop-fail.rs:63:9 | -61 | _ = ::yew::html! { }; +63 | _ = ::yew::html! { }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From` is not implemented for `implicit_clone::unsync::IString` | = help: the following other types implement trait `From`: diff --git a/packages/yew-macro/tests/html_macro/dyn-prop-pass.rs b/packages/yew-macro/tests/html_macro/dyn-prop-pass.rs index 61d407a32e2..4ffc3a25669 100644 --- a/packages/yew-macro/tests/html_macro/dyn-prop-pass.rs +++ b/packages/yew-macro/tests/html_macro/dyn-prop-pass.rs @@ -43,6 +43,8 @@ fn main() { // but valid html! (any checks can happen only during runtime) // literal + _ = ::yew::html! { }; + _ = ::yew::html! { }; _ = ::yew::html! { }; _ = ::yew::html! { }; From 6427346531cc4445ca475d261cedd213d318ca90 Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Mon, 6 Nov 2023 20:05:38 +0300 Subject: [PATCH 10/11] Better message for component prop label validation --- packages/yew-macro/src/props/component.rs | 2 +- packages/yew-macro/tests/html_macro/component-fail.stderr | 2 +- packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/yew-macro/src/props/component.rs b/packages/yew-macro/src/props/component.rs index 099fac807dc..9d06381cf53 100644 --- a/packages/yew-macro/src/props/component.rs +++ b/packages/yew-macro/src/props/component.rs @@ -194,7 +194,7 @@ fn validate(props: Props) -> Result { PropLabel::Static(dashed_name) if dashed_name.extended.is_empty() => Ok(()), _ => Err(syn::Error::new_spanned( &prop.label, - "expected a valid Rust identifier", + "components expect valid Rust identifiers for their property names", )), })?; diff --git a/packages/yew-macro/tests/html_macro/component-fail.stderr b/packages/yew-macro/tests/html_macro/component-fail.stderr index 440ffed4c64..3d960d2564e 100644 --- a/packages/yew-macro/tests/html_macro/component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-fail.stderr @@ -259,7 +259,7 @@ error: the property value must be either a literal or enclosed in braces. Consid 86 | html! { }; | ^^ -error: expected a valid Rust identifier +error: components expect valid Rust identifiers for their property names --> tests/html_macro/component-fail.rs:87:20 | 87 | html! { }; diff --git a/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr b/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr index 335d25f840e..ed5dfb60bfa 100644 --- a/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr +++ b/packages/yew-macro/tests/html_macro/dyn-prop-fail.stderr @@ -1,22 +1,22 @@ -error: expected a valid Rust identifier +error: components expect valid Rust identifiers for their property names --> tests/html_macro/dyn-prop-fail.rs:66:32 | 66 | _ = ::yew::html! { }; | ^^^^^^ -error: expected a valid Rust identifier +error: components expect valid Rust identifiers for their property names --> tests/html_macro/dyn-prop-fail.rs:67:32 | 67 | _ = ::yew::html! { }; | ^^^^^^ -error: expected a valid Rust identifier +error: components expect valid Rust identifiers for their property names --> tests/html_macro/dyn-prop-fail.rs:68:34 | 68 | _ = ::yew::html! { }; | ^^^^^^ -error: expected a valid Rust identifier +error: components expect valid Rust identifiers for their property names --> tests/html_macro/dyn-prop-fail.rs:69:34 | 69 | _ = ::yew::html! { }; From c69befccd94374d6bb5486d3b1eba3a862df32a5 Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Mon, 6 Nov 2023 20:07:26 +0300 Subject: [PATCH 11/11] fmt --- packages/yew-macro/src/props/prop.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/yew-macro/src/props/prop.rs b/packages/yew-macro/src/props/prop.rs index 3910aae1379..7a78cde7745 100644 --- a/packages/yew-macro/src/props/prop.rs +++ b/packages/yew-macro/src/props/prop.rs @@ -6,7 +6,10 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::parse::{Parse, ParseBuffer, ParseStream}; use syn::spanned::Spanned; use syn::token::Brace; -use syn::{braced, Block, Expr, ExprBlock, ExprMacro, ExprPath, ExprRange, Stmt, Token, LitStr, parse_quote}; +use syn::{ + braced, parse_quote, Block, Expr, ExprBlock, ExprMacro, ExprPath, ExprRange, LitStr, Stmt, + Token, +}; use crate::html_tree::HtmlDashedName; use crate::stringify::Stringify;