Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize vtag construction #1867

Merged
merged 3 commits into from
May 23, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
261 changes: 124 additions & 137 deletions packages/yew-macro/src/html_tree/html_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,61 +132,73 @@ impl ToTokens for HtmlElement {

// attributes with special treatment

let set_node_ref = node_ref.as_ref().map(|attr| {
let value = &attr.value;
quote! {
#vtag.node_ref = #value;
}
});
let set_key = key.as_ref().map(|attr| {
let value = &attr.value;
quote! {
#vtag.key = ::std::option::Option::Some(::std::convert::Into::<::yew::virtual_dom::Key>::into(#value));
}
});
let set_value = value.as_ref().map(|attr| {
let value = &attr.value;
if attr.question_mark.is_some() {
quote_spanned! {value.span()=>
if let ::std::option::Option::Some(__yew_v) = ::std::option::Option::as_ref(&(#value)) {
#vtag.set_value(__yew_v);
};
}
} else {
quote_spanned! {value.span()=>
#vtag.set_value(&(#value));
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;
quote! {
::std::option::Option::Some
(::std::convert::Into::<::yew::virtual_dom::Key>::into(#value))
}
}
});
let set_kind = kind.as_ref().map(|attr| {
let value = &attr.value;
if attr.question_mark.is_some() {
let sr = stringify::stringify_option_at_runtime(value);
quote_spanned! {value.span()=>
if let ::std::option::Option::Some(__yew_v) = #sr {
#vtag.set_kind(__yew_v);
};
})
.unwrap_or(quote! { ::std::option::Option::None });
let value = value
.as_ref()
.map(|attr| {
let value = &attr.value;
if attr.question_mark.is_some() {
quote_spanned! {value.span()=>
::std::option::Option::as_ref(&(#value))
.map(::std::string::ToString::to_string)
}
} else {
quote_spanned! {value.span()=>
::std::option::Option::Some(::std::string::ToString::to_string(&(#value)))
}
}
} else {
let sr = value.stringify();
quote_spanned! {value.span()=>
#vtag.set_kind(#sr);
})
.unwrap_or(quote! { ::std::option::Option::None });
let kind = kind
.as_ref()
.map(|attr| {
let value = &attr.value;
if attr.question_mark.is_some() {
let sr = stringify::stringify_option_at_runtime(value);
quote_spanned! {value.span()=>
::std::option::Option::map(
#sr,
|v| ::std::convert::Into::<::std::borrow::Cow<'static, str>>::into(v),
)
}
} else {
let sr = value.stringify();
quote_spanned! {value.span()=>
::std::option::Option::Some
(::std::convert::Into::<::std::borrow::Cow<'static, str>>::into(#sr))
}
}
}
});
let set_checked = checked.as_ref().map(|attr| {
let value = &attr.value;
quote_spanned! {value.span()=>
#vtag.set_checked(#value);
}
});
})
.unwrap_or(quote! { ::std::option::Option::None });
let checked = checked
.as_ref()
.map(|attr| {
let value = &attr.value;
quote_spanned! {value.span()=> #value}
})
.unwrap_or(quote! { false });

// normal attributes

let set_attributes = if attributes.is_empty() {
None
} else {
let attrs = attributes.iter().map(
let mut attributes = attributes
.iter()
.map(
|Prop {
label,
question_mark,
Expand All @@ -206,33 +218,22 @@ impl ToTokens for HtmlElement {
}
}
},
);
Some(quote! {
#vtag.attributes = ::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attrs),*]);
})
};

let push_booleans = if booleans.is_empty() {
None
} else {
let tokens = booleans
.iter()
.map(|Prop { label, value, .. }| {
let label_str = label.to_lit_str();
let sr = label.stringify();
quote_spanned! {value.span()=> {
if #value {
#vtag.__macro_push_attribute(#label_str, #sr);
} else {
#vtag.__macro_push_attribute_placeholder(#label_str);
};
}}
})
.collect::<TokenStream>();
Some(tokens)
};
)
.collect::<Vec<_>>();

attributes.extend(booleans.iter().map(|Prop { label, value, .. }| {
let label_str = label.to_lit_str();
let sr = label.stringify();
quote_spanned! {value.span()=> {
if #value {
::yew::virtual_dom::PositionalAttr::new(#label_str, #sr)
} else {
::yew::virtual_dom::PositionalAttr::new_placeholder(#label_str)
}
}}
}));

let push_classes = match classes {
match classes {
Some(ClassesForm::Tuple(classes)) => {
let span = classes.span();
let classes: Vec<_> = classes.elems.iter().collect();
Expand All @@ -250,49 +251,47 @@ impl ToTokens for HtmlElement {
};
};

Some(quote! {
attributes.push(quote! {{
let mut __yew_classes = ::yew::html::Classes::with_capacity(#n);
#(__yew_classes.push(#classes);)*

#deprecation_warning

if !__yew_classes.is_empty() {
#vtag.__macro_push_attribute("class", #sr);
::yew::virtual_dom::PositionalAttr::new("class", #sr)
} else {
#vtag.__macro_push_attribute_placeholder("class");
};
})
::yew::virtual_dom::PositionalAttr::new_placeholder("class")
}
}});
}
Some(ClassesForm::Single(classes)) => match classes.try_into_lit() {
Some(lit) => {
if lit.value().is_empty() {
None
} else {
if !lit.value().is_empty() {
let sr = lit.stringify();
Some(quote! {
#vtag.__macro_push_attribute("class", #sr);
})
attributes.push(quote! {
::yew::virtual_dom::PositionalAttr::new("class", #sr)
});
}
}
None => {
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);
if !__yew_classes.is_empty() {
#vtag.__macro_push_attribute("class", #sr);
::yew::virtual_dom::PositionalAttr::new("class", #sr)
} else {
#vtag.__macro_push_attribute_placeholder("class");
};
})
::yew::virtual_dom::PositionalAttr::new_placeholder("class")
}
}});
}
},
None => None,
None => (),
};

let add_listeners = if listeners.is_empty() {
None
let listeners = if listeners.is_empty() {
quote! { ::std::vec![] }
} else if listeners.iter().any(|attr| attr.question_mark.is_some()) {
let add_listeners = listeners
let listeners = listeners
.iter()
.map(
|Prop {
Expand All @@ -307,41 +306,29 @@ impl ToTokens for HtmlElement {
let ident = Ident::new("__yew_listener", name.span());
let listener = to_wrapped_listener(name, &ident);
quote_spanned! {value.span()=>
let #ident = ::std::option::Option::map(#value, |#ident| {
#listener
});
if let ::std::option::Option::Some(#ident) = #ident {
#vtag.add_listener(#ident);
};
::std::option::Option::map(#value, |#ident| #listener)
}
} else {
let listener = to_wrapped_listener(name, value);
quote_spanned! {value.span()=>
#vtag.add_listener(#listener);
}
quote_spanned! {value.span()=> Some(#listener)}
}
},
)
.collect();

Some(add_listeners)
.collect::<Vec<TokenStream>>();
quote! {{
use ::std::iter::{Iterator, IntoIterator};

::std::vec![#(#listeners),*]
.into_iter()
.filter_map(|l| l)
.collect::<::std::vec::Vec<::std::rc::Rc<dyn ::yew::virtual_dom::Listener>>>()
}}
} else {
let listeners_it = listeners
.iter()
.map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value));

Some(quote! {
#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);
})
.map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value))
.collect::<Vec<_>>();
quote! { ::std::vec![#(#listeners_it),*] }
};

// These are the runtime-checks exclusive to dynamic tags.
Expand Down Expand Up @@ -380,21 +367,21 @@ impl ToTokens for HtmlElement {

tokens.extend(quote_spanned! {name.span()=>
{
#[allow(unused_braces)]
let mut #vtag = ::yew::virtual_dom::VTag::new(#name_sr);

#set_node_ref
#set_key
#set_value
#set_kind
#set_checked

#set_attributes
#push_booleans
#push_classes

#add_listeners
#add_children
#[allow(clippy::redundant_clone, unused_braces)]
let mut #vtag = ::yew::virtual_dom::VTag::__new_complete(
#name_sr,
#node_ref,
#key,
#value,
#kind,
#checked,
::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attributes),*]),
#listeners,
::yew::virtual_dom::VList{
key: ::std::option::Option::None,
children: #children,
},
);

#dyn_tag_runtime_checks
#[allow(unused_braces)]
Expand All @@ -408,7 +395,7 @@ fn to_wrapped_listener(name: &Ident, value: impl ToTokens) -> TokenStream {
quote_spanned! {value.span()=>
::std::rc::Rc::new(::yew::html::#name::Wrapper::new(
<::yew::virtual_dom::VTag as ::yew::virtual_dom::Transformer<_, _>>::transform(#value),
))
)) as ::std::rc::Rc::<dyn ::yew::virtual_dom::Listener>
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/yew-macro/tests/html_macro/element-fail.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
= 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: required because of the requirements on the impl of `ToString` for `()`
= note: required by `to_string`

error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/element-fail.rs:30:21
Expand Down
4 changes: 2 additions & 2 deletions packages/yew/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
slab = "0.4"
thiserror = "1"
wasm-bindgen = "0.2.60"
wasm-bindgen = "0.2.74"
wasm-bindgen-futures = "0.4"
yew-macro = { version = "0.17.0", path = "../yew-macro" }

Expand Down Expand Up @@ -107,7 +107,7 @@ rustversion = "1.0"
serde_derive = "1"
ssri = "6.0.0"
trybuild = "1.0"
wasm-bindgen-test = "0.3.4"
wasm-bindgen-test = "0.3.24"

[features]
default = ["agent"]
Expand Down
Loading