Skip to content

Commit

Permalink
Add custom type for attribute values
Browse files Browse the repository at this point in the history
  • Loading branch information
ranile committed Aug 25, 2021
1 parent 4e974da commit e9f847d
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 43 deletions.
2 changes: 1 addition & 1 deletion packages/yew-macro/src/html_tree/html_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ impl ToTokens for HtmlElement {
expr => Value::Dynamic(quote_spanned! {expr.span()=>
if #expr {
::std::option::Option::Some(
::std::borrow::Cow::<'static, str>::Borrowed(#key)
::yew::virtual_dom::AttrValue::Static(#key)
)
} else {
None
Expand Down
8 changes: 4 additions & 4 deletions packages/yew-macro/src/stringify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ use syn::{Expr, Lit, LitStr};
/// Stringify a value at runtime.
fn stringify_at_runtime(src: impl ToTokens) -> TokenStream {
quote_spanned! {src.span()=>
::std::convert::Into::<::std::borrow::Cow::<'static, str>>::into(#src)
::std::convert::Into::<::yew::virtual_dom::AttrValue>::into(#src)
}
}

/// Create `Cow<'static, str>` construction calls.
/// Create `AttrValue` construction calls.
///
/// This is deliberately not implemented for strings to preserve spans.
pub trait Stringify {
/// Try to turn the value into a string literal.
fn try_into_lit(&self) -> Option<LitStr>;
/// Create `Cow<'static, str>` however possible.
/// Create `AttrValue` however possible.
fn stringify(&self) -> TokenStream;

/// Optimize literals to `&'static str`, otherwise keep the value as is.
Expand Down Expand Up @@ -71,7 +71,7 @@ impl Stringify for LitStr {

fn stringify(&self) -> TokenStream {
quote_spanned! {self.span()=>
::std::borrow::Cow::<'static, str>::Borrowed(#self)
::yew::virtual_dom::AttrValue::Static(#self)
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/yew-macro/tests/html_macro/component-fail.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ error[E0277]: the trait bound `{integer}: IntoPropValue<String>` is not satisfie
| ^ the trait `IntoPropValue<String>` is not implemented for `{integer}`
|
= help: the following implementations were found:
<&'static str as IntoPropValue<Cow<'static, str>>>
<&'static str as IntoPropValue<Option<Cow<'static, str>>>>
<&'static str as IntoPropValue<AttrValue>>
<&'static str as IntoPropValue<Option<AttrValue>>>
<&'static str as IntoPropValue<Option<String>>>
<&'static str as IntoPropValue<String>>
and 11 others
Expand All @@ -249,8 +249,8 @@ error[E0277]: the trait bound `{integer}: IntoPropValue<String>` is not satisfie
| ^ the trait `IntoPropValue<String>` is not implemented for `{integer}`
|
= help: the following implementations were found:
<&'static str as IntoPropValue<Cow<'static, str>>>
<&'static str as IntoPropValue<Option<Cow<'static, str>>>>
<&'static str as IntoPropValue<AttrValue>>
<&'static str as IntoPropValue<Option<AttrValue>>>
<&'static str as IntoPropValue<Option<String>>>
<&'static str as IntoPropValue<String>>
and 11 others
Expand Down
40 changes: 20 additions & 20 deletions packages/yew-macro/tests/html_macro/element-fail.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -246,60 +246,60 @@ error[E0308]: mismatched types
40 | html! { <option selected=1 /> };
| ^ expected `bool`, found integer

error[E0277]: the trait bound `(): IntoPropValue<Option<Cow<'static, str>>>` is not satisfied
error[E0277]: the trait bound `(): IntoPropValue<Option<AttrValue>>` is not satisfied
--> $DIR/element-fail.rs:43:26
|
43 | html! { <input type={()} /> };
| ^^ the trait `IntoPropValue<Option<Cow<'static, str>>>` is not implemented for `()`
| ^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `()`
|
= note: required by `into_prop_value`

error[E0277]: the trait bound `(): IntoPropValue<Option<Cow<'static, str>>>` is not satisfied
error[E0277]: the trait bound `(): IntoPropValue<Option<AttrValue>>` is not satisfied
--> $DIR/element-fail.rs:44:27
|
44 | html! { <input value={()} /> };
| ^^ the trait `IntoPropValue<Option<Cow<'static, str>>>` is not implemented for `()`
| ^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `()`
|
= note: required by `into_prop_value`

error[E0277]: the trait bound `(): IntoPropValue<Option<Cow<'static, str>>>` is not satisfied
error[E0277]: the trait bound `(): IntoPropValue<Option<AttrValue>>` is not satisfied
--> $DIR/element-fail.rs:45:22
|
45 | html! { <a href={()} /> };
| ^^ the trait `IntoPropValue<Option<Cow<'static, str>>>` is not implemented for `()`
| ^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `()`
|
= note: required by `into_prop_value`

error[E0277]: the trait bound `NotToString: IntoPropValue<Option<Cow<'static, str>>>` is not satisfied
error[E0277]: the trait bound `NotToString: IntoPropValue<Option<AttrValue>>` is not satisfied
--> $DIR/element-fail.rs:46:28
|
46 | html! { <input string={NotToString} /> };
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<Cow<'static, str>>>` is not implemented for `NotToString`
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `NotToString`
|
= note: required by `into_prop_value`

error[E0277]: the trait bound `Option<NotToString>: IntoPropValue<Option<Cow<'static, str>>>` is not satisfied
error[E0277]: the trait bound `Option<NotToString>: IntoPropValue<Option<AttrValue>>` is not satisfied
--> $DIR/element-fail.rs:47:23
|
47 | html! { <a media={Some(NotToString)} /> };
| ^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<Option<Cow<'static, str>>>` is not implemented for `Option<NotToString>`
| ^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `Option<NotToString>`
|
= help: the following implementations were found:
<Option<&'static str> as IntoPropValue<Option<Cow<'static, str>>>>
<Option<&'static str> as IntoPropValue<Option<AttrValue>>>
<Option<&'static str> as IntoPropValue<Option<String>>>
<Option<String> as IntoPropValue<Option<Cow<'static, str>>>>
<Option<String> as IntoPropValue<Option<AttrValue>>>
= note: required by `into_prop_value`

error[E0277]: the trait bound `Option<{integer}>: IntoPropValue<Option<Cow<'static, str>>>` is not satisfied
error[E0277]: the trait bound `Option<{integer}>: IntoPropValue<Option<AttrValue>>` is not satisfied
--> $DIR/element-fail.rs:48:22
|
48 | html! { <a href={Some(5)} /> };
| ^^^^^^^ the trait `IntoPropValue<Option<Cow<'static, str>>>` is not implemented for `Option<{integer}>`
| ^^^^^^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `Option<{integer}>`
|
= help: the following implementations were found:
<Option<&'static str> as IntoPropValue<Option<Cow<'static, str>>>>
<Option<&'static str> as IntoPropValue<Option<AttrValue>>>
<Option<&'static str> as IntoPropValue<Option<String>>>
<Option<String> as IntoPropValue<Option<Cow<'static, str>>>>
<Option<String> as IntoPropValue<Option<AttrValue>>>
= note: required by `into_prop_value`

error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `{integer}`
Expand Down Expand Up @@ -381,9 +381,9 @@ error[E0277]: the trait bound `Option<yew::NodeRef>: IntoPropValue<yew::NodeRef>
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `Option<yew::NodeRef>`
|
= help: the following implementations were found:
<Option<&'static str> as IntoPropValue<Option<Cow<'static, str>>>>
<Option<&'static str> as IntoPropValue<Option<AttrValue>>>
<Option<&'static str> as IntoPropValue<Option<String>>>
<Option<String> as IntoPropValue<Option<Cow<'static, str>>>>
<Option<String> as IntoPropValue<Option<AttrValue>>>
= note: required by `into_prop_value`

error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<String>`
Expand All @@ -409,11 +409,11 @@ error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<Strin
= note: the trait bound `yew::Callback<String>: IntoEventCallback<MouseEvent>` is not satisfied
= note: required because of the requirements on the impl of `IntoEventCallback<MouseEvent>` for `yew::Callback<String>`

error[E0277]: the trait bound `NotToString: IntoPropValue<Option<Cow<'static, str>>>` is not satisfied
error[E0277]: the trait bound `NotToString: IntoPropValue<Option<AttrValue>>` is not satisfied
--> $DIR/element-fail.rs:60:28
|
60 | html! { <input string={NotToString} /> };
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<Cow<'static, str>>>` is not implemented for `NotToString`
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<AttrValue>>` is not implemented for `NotToString`
|
= note: required by `into_prop_value`

Expand Down
12 changes: 6 additions & 6 deletions packages/yew-macro/tests/html_macro/html-element-pass.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::borrow::Cow;
use yew::virtual_dom::AttrValue;
use yew::prelude::*;

fn compile_pass() {
Expand All @@ -8,7 +8,7 @@ fn compile_pass() {
let dyn_tag = || String::from("test");
let mut extra_tags_iter = vec!["a", "b"].into_iter();

let cow_none: Option<Cow<'static, str>> = None;
let attr_value_none: Option<AttrValue> = None;

html! {
<div>
Expand Down Expand Up @@ -61,10 +61,10 @@ fn compile_pass() {
}
}/>

<a href={Some(Cow::Borrowed("http://google.com"))} media={cow_none.clone()} />
<track kind={Some(Cow::Borrowed("subtitles"))} src={cow_none.clone()} />
<track kind={Some(Cow::Borrowed("5"))} mixed="works" />
<input value={Some(Cow::Borrowed("value"))} onblur={Some(Callback::from(|_| ()))} />
<a href={Some(AttrValue::Static("http://google.com"))} media={attr_value_none.clone()} />
<track kind={Some(AttrValue::Static("subtitles"))} src={attr_value_none.clone()} />
<track kind={Some(AttrValue::Static("5"))} mixed="works" />
<input value={Some(AttrValue::Static("value"))} onblur={Some(Callback::from(|_| ()))} />
</div>
};

Expand Down
5 changes: 3 additions & 2 deletions packages/yew/src/html/classes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::IntoPropValue;
use crate::virtual_dom::AttrValue;
use indexmap::IndexSet;
use std::rc::Rc;
use std::{
borrow::{Borrow, Cow},
hint::unreachable_unchecked,
Expand Down Expand Up @@ -70,12 +71,12 @@ impl IntoPropValue<AttrValue> for Classes {
fn into_prop_value(mut self) -> AttrValue {
if self.set.len() == 1 {
match self.set.pop() {
Some(attr) => attr,
Some(attr) => AttrValue::ReferenceCounted(Rc::from(attr)),
// SAFETY: the collection is checked to be non-empty above
None => unsafe { unreachable_unchecked() },
}
} else {
Cow::Owned(self.to_string())
AttrValue::Owned(self.to_string())
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions packages/yew/src/html/conversion.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{Component, NodeRef, Scope};
use crate::virtual_dom::AttrValue;
use std::{borrow::Cow, rc::Rc};

/// Marker trait for types that the [`html!`] macro may clone implicitly.
Expand Down Expand Up @@ -83,8 +84,8 @@ macro_rules! impl_into_prop {
// implemented with literals in mind
impl_into_prop!(|value: &'static str| -> String { value.to_owned() });

impl_into_prop!(|value: &'static str| -> Cow<'static, str> { Cow::Borrowed(value) });
impl_into_prop!(|value: String| -> Cow<'static, str> { Cow::Owned(value) });
impl_into_prop!(|value: &'static str| -> AttrValue { AttrValue::Static(value) });
impl_into_prop!(|value: String| -> AttrValue { AttrValue::Owned(value) });

#[cfg(test)]
mod test {
Expand Down
77 changes: 73 additions & 4 deletions packages/yew/src/virtual_dom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub mod vtext;
use crate::html::{AnyScope, NodeRef};
use gloo::events::EventListener;
use indexmap::IndexMap;
use std::{borrow::Cow, collections::HashMap, fmt, hint::unreachable_unchecked, iter};
use std::{collections::HashMap, fmt, hint::unreachable_unchecked, iter};
use web_sys::{Element, Node};

#[doc(inline)]
Expand All @@ -31,6 +31,9 @@ pub use self::vnode::VNode;
pub use self::vtag::VTag;
#[doc(inline)]
pub use self::vtext::VText;
use std::fmt::Formatter;
use std::ops::Deref;
use std::rc::Rc;

/// The `Listener` trait is an universal implementation of an event listener
/// which is used to bind Rust-listener to JS-listener (DOM).
Expand All @@ -48,7 +51,73 @@ impl fmt::Debug for dyn Listener {
}

/// Attribute value
pub type AttrValue = Cow<'static, str>;
#[derive(Eq, PartialEq, Debug)]
pub enum AttrValue {
/// String living for `'static`
Static(&'static str),
/// Owned string
Owned(String),
/// Reference counted string
ReferenceCounted(Rc<str>),
}

impl Deref for AttrValue {
type Target = str;

fn deref(&self) -> &Self::Target {
match self {
AttrValue::Static(s) => *s,
AttrValue::Owned(s) => s.as_str(),
AttrValue::ReferenceCounted(s) => &*s,
}
}
}

impl From<&'static str> for AttrValue {
fn from(s: &'static str) -> Self {
AttrValue::Static(s)
}
}

impl From<String> for AttrValue {
fn from(s: String) -> Self {
AttrValue::Owned(s)
}
}

impl From<Rc<str>> for AttrValue {
fn from(s: Rc<str>) -> Self {
AttrValue::ReferenceCounted(s)
}
}

impl Clone for AttrValue {
fn clone(&self) -> Self {
match self {
AttrValue::Static(s) => AttrValue::Static(s),
AttrValue::Owned(s) => AttrValue::Owned(s.clone()),
AttrValue::ReferenceCounted(s) => AttrValue::ReferenceCounted(Rc::clone(s)),
}
}
}

impl PartialEq<String> for AttrValue {
fn eq(&self, other: &String) -> bool {
self.as_ref() == other.as_str()
}
}

impl AsRef<str> for AttrValue {
fn as_ref(&self) -> &str {
&*self
}
}

impl fmt::Display for AttrValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}

/// Applies contained changes to DOM [Element]
trait Apply {
Expand Down Expand Up @@ -640,7 +709,7 @@ mod benchmarks {
fn bench_diff_change_first() {
let old = sample_values();
let mut new = old.clone();
new[0] = AttrValue::Borrowed("changed");
new[0] = AttrValue::Static("changed");

let dynamic = (make_dynamic(old.clone()), make_dynamic(new.clone()));
let map = (make_indexed_map(old), make_indexed_map(new));
Expand Down Expand Up @@ -682,7 +751,7 @@ mod benchmarks {
"danny", "the", "the", "calling", "glen", "glen", "down", "mountain", "",
]
.iter()
.map(|v| AttrValue::Borrowed(*v))
.map(|v| AttrValue::Static(*v))
.collect()
}

Expand Down

0 comments on commit e9f847d

Please sign in to comment.