Optimize vtag construction (#1867)

* yew-macro: optimize VTag construction in html! macro

* vtag: remove inlining suggestions

* vtag: clean up doc comment
This commit is contained in:
bakape 2021-05-24 00:33:25 +03:00 committed by GitHub
parent 97f67fa77d
commit 7cac4d4756
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 166 additions and 145 deletions

View File

@ -132,61 +132,73 @@ impl ToTokens for HtmlElement {
// attributes with special treatment // attributes with special treatment
let set_node_ref = node_ref.as_ref().map(|attr| { let node_ref = node_ref
.as_ref()
.map(|attr| {
let value = &attr.value;
quote! { #value }
})
.unwrap_or(quote! { ::std::default::Default::default() });
let key = key
.as_ref()
.map(|attr| {
let value = &attr.value; let value = &attr.value;
quote! { quote! {
#vtag.node_ref = #value; ::std::option::Option::Some
(::std::convert::Into::<::yew::virtual_dom::Key>::into(#value))
} }
}); })
let set_key = key.as_ref().map(|attr| { .unwrap_or(quote! { ::std::option::Option::None });
let value = &attr.value; let value = value
quote! { .as_ref()
#vtag.key = ::std::option::Option::Some(::std::convert::Into::<::yew::virtual_dom::Key>::into(#value)); .map(|attr| {
}
});
let set_value = value.as_ref().map(|attr| {
let value = &attr.value; let value = &attr.value;
if attr.question_mark.is_some() { if attr.question_mark.is_some() {
quote_spanned! {value.span()=> quote_spanned! {value.span()=>
if let ::std::option::Option::Some(__yew_v) = ::std::option::Option::as_ref(&(#value)) { ::std::option::Option::as_ref(&(#value))
#vtag.set_value(__yew_v); .map(::std::string::ToString::to_string)
};
} }
} else { } else {
quote_spanned! {value.span()=> quote_spanned! {value.span()=>
#vtag.set_value(&(#value)); ::std::option::Option::Some(::std::string::ToString::to_string(&(#value)))
} }
} }
}); })
let set_kind = kind.as_ref().map(|attr| { .unwrap_or(quote! { ::std::option::Option::None });
let kind = kind
.as_ref()
.map(|attr| {
let value = &attr.value; let value = &attr.value;
if attr.question_mark.is_some() { if attr.question_mark.is_some() {
let sr = stringify::stringify_option_at_runtime(value); let sr = stringify::stringify_option_at_runtime(value);
quote_spanned! {value.span()=> quote_spanned! {value.span()=>
if let ::std::option::Option::Some(__yew_v) = #sr { ::std::option::Option::map(
#vtag.set_kind(__yew_v); #sr,
}; |v| ::std::convert::Into::<::std::borrow::Cow<'static, str>>::into(v),
)
} }
} else { } else {
let sr = value.stringify(); let sr = value.stringify();
quote_spanned! {value.span()=> quote_spanned! {value.span()=>
#vtag.set_kind(#sr); ::std::option::Option::Some
(::std::convert::Into::<::std::borrow::Cow<'static, str>>::into(#sr))
} }
} }
}); })
let set_checked = checked.as_ref().map(|attr| { .unwrap_or(quote! { ::std::option::Option::None });
let checked = checked
.as_ref()
.map(|attr| {
let value = &attr.value; let value = &attr.value;
quote_spanned! {value.span()=> quote_spanned! {value.span()=> #value}
#vtag.set_checked(#value); })
} .unwrap_or(quote! { false });
});
// normal attributes // normal attributes
let set_attributes = if attributes.is_empty() { let mut attributes = attributes
None .iter()
} else { .map(
let attrs = attributes.iter().map(
|Prop { |Prop {
label, label,
question_mark, question_mark,
@ -206,33 +218,22 @@ impl ToTokens for HtmlElement {
} }
} }
}, },
); )
Some(quote! { .collect::<Vec<_>>();
#vtag.attributes = ::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attrs),*]);
})
};
let push_booleans = if booleans.is_empty() { attributes.extend(booleans.iter().map(|Prop { label, value, .. }| {
None
} else {
let tokens = booleans
.iter()
.map(|Prop { label, value, .. }| {
let label_str = label.to_lit_str(); let label_str = label.to_lit_str();
let sr = label.stringify(); let sr = label.stringify();
quote_spanned! {value.span()=> { quote_spanned! {value.span()=> {
if #value { if #value {
#vtag.__macro_push_attribute(#label_str, #sr); ::yew::virtual_dom::PositionalAttr::new(#label_str, #sr)
} else { } else {
#vtag.__macro_push_attribute_placeholder(#label_str); ::yew::virtual_dom::PositionalAttr::new_placeholder(#label_str)
}; }
}} }}
}) }));
.collect::<TokenStream>();
Some(tokens)
};
let push_classes = match classes { match classes {
Some(ClassesForm::Tuple(classes)) => { Some(ClassesForm::Tuple(classes)) => {
let span = classes.span(); let span = classes.span();
let classes: Vec<_> = classes.elems.iter().collect(); let classes: Vec<_> = classes.elems.iter().collect();
@ -250,49 +251,47 @@ impl ToTokens for HtmlElement {
}; };
}; };
Some(quote! { attributes.push(quote! {{
let mut __yew_classes = ::yew::html::Classes::with_capacity(#n); let mut __yew_classes = ::yew::html::Classes::with_capacity(#n);
#(__yew_classes.push(#classes);)* #(__yew_classes.push(#classes);)*
#deprecation_warning #deprecation_warning
if !__yew_classes.is_empty() { if !__yew_classes.is_empty() {
#vtag.__macro_push_attribute("class", #sr); ::yew::virtual_dom::PositionalAttr::new("class", #sr)
} else { } else {
#vtag.__macro_push_attribute_placeholder("class"); ::yew::virtual_dom::PositionalAttr::new_placeholder("class")
}; }
}) }});
} }
Some(ClassesForm::Single(classes)) => match classes.try_into_lit() { Some(ClassesForm::Single(classes)) => match classes.try_into_lit() {
Some(lit) => { Some(lit) => {
if lit.value().is_empty() { if !lit.value().is_empty() {
None
} else {
let sr = lit.stringify(); let sr = lit.stringify();
Some(quote! { attributes.push(quote! {
#vtag.__macro_push_attribute("class", #sr); ::yew::virtual_dom::PositionalAttr::new("class", #sr)
}) });
} }
} }
None => { None => {
let sr = stringify::stringify_at_runtime(quote! { __yew_classes }); let sr = stringify::stringify_at_runtime(quote! { __yew_classes });
Some(quote! { attributes.push(quote! {{
let __yew_classes = ::std::convert::Into::<::yew::html::Classes>::into(#classes); let __yew_classes = ::std::convert::Into::<::yew::html::Classes>::into(#classes);
if !__yew_classes.is_empty() { if !__yew_classes.is_empty() {
#vtag.__macro_push_attribute("class", #sr); ::yew::virtual_dom::PositionalAttr::new("class", #sr)
} else { } else {
#vtag.__macro_push_attribute_placeholder("class"); ::yew::virtual_dom::PositionalAttr::new_placeholder("class")
}; }
}) }});
} }
}, },
None => None, None => (),
}; };
let add_listeners = if listeners.is_empty() { let listeners = if listeners.is_empty() {
None quote! { ::std::vec![] }
} else if listeners.iter().any(|attr| attr.question_mark.is_some()) { } else if listeners.iter().any(|attr| attr.question_mark.is_some()) {
let add_listeners = listeners let listeners = listeners
.iter() .iter()
.map( .map(
|Prop { |Prop {
@ -307,41 +306,29 @@ impl ToTokens for HtmlElement {
let ident = Ident::new("__yew_listener", name.span()); let ident = Ident::new("__yew_listener", name.span());
let listener = to_wrapped_listener(name, &ident); let listener = to_wrapped_listener(name, &ident);
quote_spanned! {value.span()=> quote_spanned! {value.span()=>
let #ident = ::std::option::Option::map(#value, |#ident| { ::std::option::Option::map(#value, |#ident| #listener)
#listener
});
if let ::std::option::Option::Some(#ident) = #ident {
#vtag.add_listener(#ident);
};
} }
} else { } else {
let listener = to_wrapped_listener(name, value); let listener = to_wrapped_listener(name, value);
quote_spanned! {value.span()=> quote_spanned! {value.span()=> Some(#listener)}
#vtag.add_listener(#listener);
}
} }
}, },
) )
.collect(); .collect::<Vec<TokenStream>>();
quote! {{
use ::std::iter::{Iterator, IntoIterator};
Some(add_listeners) ::std::vec![#(#listeners),*]
.into_iter()
.filter_map(|l| l)
.collect::<::std::vec::Vec<::std::rc::Rc<dyn ::yew::virtual_dom::Listener>>>()
}}
} else { } else {
let listeners_it = listeners let listeners_it = listeners
.iter() .iter()
.map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value)); .map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value))
.collect::<Vec<_>>();
Some(quote! { quote! { ::std::vec![#(#listeners_it),*] }
#vtag.add_listeners(::std::vec![#(#listeners_it),*]);
})
};
let add_children = if children.is_empty() {
None
} else {
Some(quote! {
#[allow(clippy::redundant_clone, unused_braces)]
#vtag.add_children(#children);
})
}; };
// These are the runtime-checks exclusive to dynamic tags. // These are the runtime-checks exclusive to dynamic tags.
@ -380,21 +367,21 @@ impl ToTokens for HtmlElement {
tokens.extend(quote_spanned! {name.span()=> tokens.extend(quote_spanned! {name.span()=>
{ {
#[allow(unused_braces)] #[allow(clippy::redundant_clone, unused_braces)]
let mut #vtag = ::yew::virtual_dom::VTag::new(#name_sr); let mut #vtag = ::yew::virtual_dom::VTag::__new_complete(
#name_sr,
#set_node_ref #node_ref,
#set_key #key,
#set_value #value,
#set_kind #kind,
#set_checked #checked,
::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attributes),*]),
#set_attributes #listeners,
#push_booleans ::yew::virtual_dom::VList{
#push_classes key: ::std::option::Option::None,
children: #children,
#add_listeners },
#add_children );
#dyn_tag_runtime_checks #dyn_tag_runtime_checks
#[allow(unused_braces)] #[allow(unused_braces)]
@ -408,7 +395,7 @@ fn to_wrapped_listener(name: &Ident, value: impl ToTokens) -> TokenStream {
quote_spanned! {value.span()=> quote_spanned! {value.span()=>
::std::rc::Rc::new(::yew::html::#name::Wrapper::new( ::std::rc::Rc::new(::yew::html::#name::Wrapper::new(
<::yew::virtual_dom::VTag as ::yew::virtual_dom::Transformer<_, _>>::transform(#value), <::yew::virtual_dom::VTag as ::yew::virtual_dom::Transformer<_, _>>::transform(#value),
)) )) as ::std::rc::Rc::<dyn ::yew::virtual_dom::Listener>
} }
} }

View File

@ -200,6 +200,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
= help: the trait `std::fmt::Display` is not implemented for `()` = 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 = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required because of the requirements on the impl of `ToString` for `()` = note: required because of the requirements on the impl of `ToString` for `()`
= note: required by `to_string`
error[E0277]: `()` doesn't implement `std::fmt::Display` error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/element-fail.rs:30:21 --> $DIR/element-fail.rs:30:21

View File

@ -28,7 +28,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
slab = "0.4" slab = "0.4"
thiserror = "1" thiserror = "1"
wasm-bindgen = "0.2.60" wasm-bindgen = "0.2.74"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
yew-macro = { version = "0.17.0", path = "../yew-macro" } yew-macro = { version = "0.17.0", path = "../yew-macro" }
@ -107,7 +107,7 @@ rustversion = "1.0"
serde_derive = "1" serde_derive = "1"
ssri = "6.0.0" ssri = "6.0.0"
trybuild = "1.0" trybuild = "1.0"
wasm-bindgen-test = "0.3.4" wasm-bindgen-test = "0.3.24"
[features] [features]
default = ["agent"] default = ["agent"]

View File

@ -103,23 +103,56 @@ impl Clone for VTag {
impl VTag { impl VTag {
/// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM). /// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM).
pub fn new(tag: impl Into<Cow<'static, str>>) -> Self { pub fn new(tag: impl Into<Cow<'static, str>>) -> Self {
Self::__new_complete(
tag,
Default::default(),
Default::default(),
None,
None,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
)
}
/// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM).
///
/// Unlike `new()`, this sets all the public fields of `VTag` in one call. This allows the
/// compiler to inline property and child list construction in the html! macro. This enables
/// higher instruction parallelism by reducing data dependency and avoids `memcpy` of Vtag
/// fields amd child `VTag`s.
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub fn __new_complete(
tag: impl Into<Cow<'static, str>>,
node_ref: NodeRef,
key: Option<Key>,
value: Option<String>,
kind: Option<Cow<'static, str>>,
checked: bool,
// at bottom for more readable macro-expanded coded
attributes: Attributes,
listeners: Listeners,
children: VList,
) -> Self {
let tag: Cow<'static, str> = tag.into(); let tag: Cow<'static, str> = tag.into();
let element_type = ElementType::from_tag(&tag); let element_type = ElementType::from_tag(&tag);
VTag { VTag {
tag, tag,
element_type, element_type,
reference: None, reference: None,
attributes: Attributes::new(), attributes,
listeners: Vec::new(), listeners,
captured: Vec::new(), captured: Vec::new(),
children: VList::new(), children,
node_ref: NodeRef::default(), node_ref,
key: None, key,
value: None, value,
kind: None, kind,
// In HTML node `checked` attribute sets `defaultChecked` parameter, // In HTML node `checked` attribute sets `defaultChecked` parameter,
// but we use own field to control real `checked` parameter // but we use own field to control real `checked` parameter
checked: false, checked,
} }
} }