Add custom type for attribute values (#1994)

* Add custom type for attribute values

* fix tests/example

* Remove `PartialEq<String>` and it's usage

* `ReferenceCounted` -> `Rc`

* fucking fake

* please CI, just turn green
This commit is contained in:
Muhammad Hamza 2021-11-08 22:11:22 +05:00 committed by GitHub
parent 3ebd866d13
commit 2f47b09da4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 173 additions and 110 deletions

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
fake = "2.2" fake = "=2.4.1"
getrandom = { version = "0.2", features = ["js"] } getrandom = { version = "0.2", features = ["js"] }
instant = { version = "0.1", features = ["wasm-bindgen"] } instant = { version = "0.1", features = ["wasm-bindgen"] }
log = "0.4" log = "0.4"

View File

@ -1,6 +1,5 @@
use crate::{content, generator::Generated, Route}; use crate::{content, generator::Generated, Route};
use content::PostPart; use content::PostPart;
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::*; use yew_router::prelude::*;
@ -39,7 +38,7 @@ impl Component for Post {
html! { html! {
<> <>
<section class="hero is-medium is-light has-background"> <section class="hero is-medium is-light has-background">
<img class="hero-background is-transparent" src={Cow::Owned(post.meta.image_url.clone())} /> <img class="hero-background is-transparent" src={post.meta.image_url.clone()} />
<div class="hero-body"> <div class="hero-body">
<div class="container"> <div class="container">
<h1 class="title"> <h1 class="title">
@ -70,7 +69,7 @@ impl Post {
<article class="media block box my-6"> <article class="media block box my-6">
<figure class="media-left"> <figure class="media-left">
<p class="image is-64x64"> <p class="image is-64x64">
<img src={Cow::Owned(quote.author.image_url.clone())} loading="lazy" /> <img src={quote.author.image_url.clone()} loading="lazy" />
</p> </p>
</figure> </figure>
<div class="media-content"> <div class="media-content">
@ -90,7 +89,7 @@ impl Post {
fn render_section_hero(&self, section: &content::Section) -> Html { fn render_section_hero(&self, section: &content::Section) -> Html {
html! { html! {
<section class="hero is-dark has-background mt-6 mb-3"> <section class="hero is-dark has-background mt-6 mb-3">
<img class="hero-background is-transparent" src={Cow::Owned(section.image_url.clone())} loading="lazy" /> <img class="hero-background is-transparent" src={section.image_url.clone()} loading="lazy" />
<div class="hero-body"> <div class="hero-body">
<div class="container"> <div class="container">
<h2 class="subtitle">{ &section.title }</h2> <h2 class="subtitle">{ &section.title }</h2>

View File

@ -168,7 +168,7 @@ impl ToTokens for HtmlElement {
expr => Value::Dynamic(quote_spanned! {expr.span()=> expr => Value::Dynamic(quote_spanned! {expr.span()=>
if #expr { if #expr {
::std::option::Option::Some( ::std::option::Option::Some(
::std::borrow::Cow::<'static, ::std::primitive::str>::Borrowed(#key) ::yew::virtual_dom::AttrValue::Static(#key)
) )
} else { } else {
::std::option::Option::None ::std::option::Option::None

View File

@ -6,17 +6,17 @@ use syn::{Expr, Lit, LitStr};
/// Stringify a value at runtime. /// Stringify a value at runtime.
fn stringify_at_runtime(src: impl ToTokens) -> TokenStream { fn stringify_at_runtime(src: impl ToTokens) -> TokenStream {
quote_spanned! {src.span()=> quote_spanned! {src.span()=>
::std::convert::Into::<::std::borrow::Cow::<'static, ::std::primitive::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. /// This is deliberately not implemented for strings to preserve spans.
pub trait Stringify { pub trait Stringify {
/// Try to turn the value into a string literal. /// Try to turn the value into a string literal.
fn try_into_lit(&self) -> Option<LitStr>; fn try_into_lit(&self) -> Option<LitStr>;
/// Create `Cow<'static, str>` however possible. /// Create `AttrValue` however possible.
fn stringify(&self) -> TokenStream; fn stringify(&self) -> TokenStream;
/// Optimize literals to `&'static str`, otherwise keep the value as is. /// Optimize literals to `&'static str`, otherwise keep the value as is.
@ -71,7 +71,7 @@ impl Stringify for LitStr {
fn stringify(&self) -> TokenStream { fn stringify(&self) -> TokenStream {
quote_spanned! {self.span()=> quote_spanned! {self.span()=>
::std::borrow::Cow::<'static, ::std::primitive::str>::Borrowed(#self) ::yew::virtual_dom::AttrValue::Static(#self)
} }
} }
} }

View File

@ -342,8 +342,8 @@ error[E0277]: the trait bound `{integer}: IntoPropValue<String>` is not satisfie
| ^ the trait `IntoPropValue<String>` is not implemented for `{integer}` | ^ the trait `IntoPropValue<String>` is not implemented for `{integer}`
| |
= help: the following implementations were found: = help: the following implementations were found:
<&'static str as IntoPropValue<Cow<'static, str>>> <&'static str as IntoPropValue<AttrValue>>
<&'static str as IntoPropValue<Option<Cow<'static, str>>>> <&'static str as IntoPropValue<Option<AttrValue>>>
<&'static str as IntoPropValue<Option<String>>> <&'static str as IntoPropValue<Option<String>>>
<&'static str as IntoPropValue<String>> <&'static str as IntoPropValue<String>>
and 11 others and 11 others
@ -355,8 +355,8 @@ error[E0277]: the trait bound `{integer}: IntoPropValue<String>` is not satisfie
| ^ the trait `IntoPropValue<String>` is not implemented for `{integer}` | ^ the trait `IntoPropValue<String>` is not implemented for `{integer}`
| |
= help: the following implementations were found: = help: the following implementations were found:
<&'static str as IntoPropValue<Cow<'static, str>>> <&'static str as IntoPropValue<AttrValue>>
<&'static str as IntoPropValue<Option<Cow<'static, str>>>> <&'static str as IntoPropValue<Option<AttrValue>>>
<&'static str as IntoPropValue<Option<String>>> <&'static str as IntoPropValue<Option<String>>>
<&'static str as IntoPropValue<String>> <&'static str as IntoPropValue<String>>
and 11 others and 11 others

View File

@ -1,209 +1,209 @@
error: this opening tag has no corresponding closing tag error: this opening tag has no corresponding closing tag
--> $DIR/tests/html_macro/element-fail.rs:7:13 --> tests/html_macro/element-fail.rs:7:13
| |
7 | html! { <div> }; 7 | html! { <div> };
| ^^^^^ | ^^^^^
error: this opening tag has no corresponding closing tag error: this opening tag has no corresponding closing tag
--> $DIR/tests/html_macro/element-fail.rs:8:18 --> tests/html_macro/element-fail.rs:8:18
| |
8 | html! { <div><div> }; 8 | html! { <div><div> };
| ^^^^^ | ^^^^^
error: this opening tag has no corresponding closing tag error: this opening tag has no corresponding closing tag
--> $DIR/tests/html_macro/element-fail.rs:9:13 --> tests/html_macro/element-fail.rs:9:13
| |
9 | html! { <div><div></div> }; 9 | html! { <div><div></div> };
| ^^^^^ | ^^^^^
error: this closing tag has no corresponding opening tag error: this closing tag has no corresponding opening tag
--> $DIR/tests/html_macro/element-fail.rs:12:13 --> tests/html_macro/element-fail.rs:12:13
| |
12 | html! { </div> }; 12 | html! { </div> };
| ^^^^^^ | ^^^^^^
error: this closing tag has no corresponding opening tag error: this closing tag has no corresponding opening tag
--> $DIR/tests/html_macro/element-fail.rs:13:18 --> tests/html_macro/element-fail.rs:13:18
| |
13 | html! { <div></span></div> }; 13 | html! { <div></span></div> };
| ^^^^^^^ | ^^^^^^^
error: only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<></>`) error: only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<></>`)
--> $DIR/tests/html_macro/element-fail.rs:14:20 --> tests/html_macro/element-fail.rs:14:20
| |
14 | html! { <img /></img> }; 14 | html! { <img /></img> };
| ^^^^^^ | ^^^^^^
error: this closing tag has no corresponding opening tag error: this closing tag has no corresponding opening tag
--> $DIR/tests/html_macro/element-fail.rs:17:18 --> tests/html_macro/element-fail.rs:17:18
| |
17 | html! { <div></span> }; 17 | html! { <div></span> };
| ^^^^^^^ | ^^^^^^^
error: this closing tag has no corresponding opening tag error: this closing tag has no corresponding opening tag
--> $DIR/tests/html_macro/element-fail.rs:18:20 --> tests/html_macro/element-fail.rs:18:20
| |
18 | html! { <tag-a></tag-b> }; 18 | html! { <tag-a></tag-b> };
| ^^^^^^^^ | ^^^^^^^^
error: only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<></>`) error: only one root html element is allowed (hint: you can wrap multiple html elements in a fragment `<></>`)
--> $DIR/tests/html_macro/element-fail.rs:21:24 --> tests/html_macro/element-fail.rs:21:24
| |
21 | html! { <div></div><div></div> }; 21 | html! { <div></div><div></div> };
| ^^^^^^^^^^^ | ^^^^^^^^^^^
error: expected a valid html element error: expected a valid html element
--> $DIR/tests/html_macro/element-fail.rs:23:18 --> tests/html_macro/element-fail.rs:23:18
| |
23 | html! { <div>Invalid</div> }; 23 | html! { <div>Invalid</div> };
| ^^^^^^^ | ^^^^^^^
error: `attr` can only be specified once but is given here again error: `attr` can only be specified once but is given here again
--> $DIR/tests/html_macro/element-fail.rs:26:27 --> tests/html_macro/element-fail.rs:26:27
| |
26 | html! { <input attr=1 attr=2 /> }; 26 | html! { <input attr=1 attr=2 /> };
| ^^^^ | ^^^^
error: `value` can only be specified once but is given here again error: `value` can only be specified once but is given here again
--> $DIR/tests/html_macro/element-fail.rs:27:32 --> tests/html_macro/element-fail.rs:27:32
| |
27 | html! { <input value="123" value="456" /> }; 27 | html! { <input value="123" value="456" /> };
| ^^^^^ | ^^^^^
error: `kind` can only be specified once but is given here again error: `kind` can only be specified once but is given here again
--> $DIR/tests/html_macro/element-fail.rs:28:36 --> tests/html_macro/element-fail.rs:28:36
| |
28 | html! { <input kind="checkbox" kind="submit" /> }; 28 | html! { <input kind="checkbox" kind="submit" /> };
| ^^^^ | ^^^^
error: `checked` can only be specified once but is given here again error: `checked` can only be specified once but is given here again
--> $DIR/tests/html_macro/element-fail.rs:29:33 --> tests/html_macro/element-fail.rs:29:33
| |
29 | html! { <input checked=true checked=false /> }; 29 | html! { <input checked=true checked=false /> };
| ^^^^^^^ | ^^^^^^^
error: `disabled` can only be specified once but is given here again error: `disabled` can only be specified once but is given here again
--> $DIR/tests/html_macro/element-fail.rs:30:34 --> tests/html_macro/element-fail.rs:30:34
| |
30 | html! { <input disabled=true disabled=false /> }; 30 | html! { <input disabled=true disabled=false /> };
| ^^^^^^^^ | ^^^^^^^^
error: `selected` can only be specified once but is given here again error: `selected` can only be specified once but is given here again
--> $DIR/tests/html_macro/element-fail.rs:31:35 --> tests/html_macro/element-fail.rs:31:35
| |
31 | html! { <option selected=true selected=false /> }; 31 | html! { <option selected=true selected=false /> };
| ^^^^^^^^ | ^^^^^^^^
error: `class` can only be specified once but is given here again error: `class` can only be specified once but is given here again
--> $DIR/tests/html_macro/element-fail.rs:32:32 --> tests/html_macro/element-fail.rs:32:32
| |
32 | html! { <div class="first" class="second" /> }; 32 | html! { <div class="first" class="second" /> };
| ^^^^^ | ^^^^^
error: `ref` can only be specified once error: `ref` can only be specified once
--> $DIR/tests/html_macro/element-fail.rs:33:20 --> tests/html_macro/element-fail.rs:33:20
| |
33 | html! { <input ref={()} ref={()} /> }; 33 | html! { <input ref={()} ref={()} /> };
| ^^^ | ^^^
error: `ref` can only be specified once error: `ref` can only be specified once
--> $DIR/tests/html_macro/element-fail.rs:63:20 --> tests/html_macro/element-fail.rs:63:20
| |
63 | html! { <input ref={()} ref={()} /> }; 63 | html! { <input ref={()} ref={()} /> };
| ^^^ | ^^^
error: the tag `<input>` is a void element and cannot have children (hint: rewrite this as `<input/>`) error: the tag `<input>` is a void element and cannot have children (hint: rewrite this as `<input/>`)
--> $DIR/tests/html_macro/element-fail.rs:66:13 --> tests/html_macro/element-fail.rs:66:13
| |
66 | html! { <input type="text"></input> }; 66 | html! { <input type="text"></input> };
| ^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^
error: the tag `<iNpUt>` is a void element and cannot have children (hint: rewrite this as `<iNpUt/>`) error: the tag `<iNpUt>` is a void element and cannot have children (hint: rewrite this as `<iNpUt/>`)
--> $DIR/tests/html_macro/element-fail.rs:68:13 --> tests/html_macro/element-fail.rs:68:13
| |
68 | html! { <iNpUt type="text"></iNpUt> }; 68 | html! { <iNpUt type="text"></iNpUt> };
| ^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^
error: this dynamic tag is missing an expression block defining its value error: this dynamic tag is missing an expression block defining its value
--> $DIR/tests/html_macro/element-fail.rs:71:14 --> tests/html_macro/element-fail.rs:71:14
| |
71 | html! { <@></@> }; 71 | html! { <@></@> };
| ^ | ^
error: this dynamic tag is missing an expression block defining its value error: this dynamic tag is missing an expression block defining its value
--> $DIR/tests/html_macro/element-fail.rs:72:14 --> tests/html_macro/element-fail.rs:72:14
| |
72 | html! { <@/> }; 72 | html! { <@/> };
| ^ | ^
error: dynamic closing tags must not have a body (hint: replace it with just `</@>`) error: dynamic closing tags must not have a body (hint: replace it with just `</@>`)
--> $DIR/tests/html_macro/element-fail.rs:75:27 --> tests/html_macro/element-fail.rs:75:27
| |
75 | html! { <@{"test"}></@{"test"}> }; 75 | html! { <@{"test"}></@{"test"}> };
| ^^^^^^^^ | ^^^^^^^^
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/tests/html_macro/element-fail.rs:83:24 --> tests/html_macro/element-fail.rs:83:24
| |
83 | html! { <div class=("deprecated", "warning") /> }; 83 | html! { <div class=("deprecated", "warning") /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/tests/html_macro/element-fail.rs:84:24 --> tests/html_macro/element-fail.rs:84:24
| |
84 | html! { <input ref=() /> }; 84 | html! { <input ref=() /> };
| ^^ | ^^
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/tests/html_macro/element-fail.rs:85:24 --> tests/html_macro/element-fail.rs:85:24
| |
85 | html! { <input ref=() ref=() /> }; 85 | html! { <input ref=() ref=() /> };
| ^^ | ^^
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/tests/html_macro/element-fail.rs:86:28 --> tests/html_macro/element-fail.rs:86:28
| |
86 | html! { <input onfocus=Some(5) /> }; 86 | html! { <input onfocus=Some(5) /> };
| ^^^^^^^ | ^^^^^^^
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/tests/html_macro/element-fail.rs:87:27 --> tests/html_macro/element-fail.rs:87:27
| |
87 | html! { <input string=NotToString /> }; 87 | html! { <input string=NotToString /> };
| ^^^^^^^^^^^ | ^^^^^^^^^^^
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/tests/html_macro/element-fail.rs:88:22 --> tests/html_macro/element-fail.rs:88:22
| |
88 | html! { <a media=Some(NotToString) /> }; 88 | html! { <a media=Some(NotToString) /> };
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/tests/html_macro/element-fail.rs:89:21 --> tests/html_macro/element-fail.rs:89:21
| |
89 | html! { <a href=Some(5) /> }; 89 | html! { <a href=Some(5) /> };
| ^^^^^^^ | ^^^^^^^
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/tests/html_macro/element-fail.rs:90:25 --> tests/html_macro/element-fail.rs:90:25
| |
90 | html! { <input type=() /> }; 90 | html! { <input type=() /> };
| ^^ | ^^
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/tests/html_macro/element-fail.rs:91:26 --> tests/html_macro/element-fail.rs:91:26
| |
91 | html! { <input value=() /> }; 91 | html! { <input value=() /> };
| ^^ | ^^
error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/tests/html_macro/element-fail.rs:92:27 --> tests/html_macro/element-fail.rs:92:27
| |
92 | html! { <input string=NotToString /> }; 92 | html! { <input string=NotToString /> };
| ^^^^^^^^^^^ | ^^^^^^^^^^^
warning: use of deprecated function `compile_fail::deprecated_use_of_class`: the use of `(...)` with the attribute `class` is deprecated and will be removed in version 0.19. Use the `classes!` macro instead. warning: use of deprecated function `compile_fail::deprecated_use_of_class`: the use of `(...)` with the attribute `class` is deprecated and will be removed in version 0.19. Use the `classes!` macro instead.
--> $DIR/tests/html_macro/element-fail.rs:80:25 --> tests/html_macro/element-fail.rs:80:25
| |
80 | html! { <div class={("deprecated", "warning")} /> }; 80 | html! { <div class={("deprecated", "warning")} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^
@ -211,13 +211,13 @@ warning: use of deprecated function `compile_fail::deprecated_use_of_class`: the
= note: `#[warn(deprecated)]` on by default = note: `#[warn(deprecated)]` on by default
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/tests/html_macro/element-fail.rs:36:28 --> tests/html_macro/element-fail.rs:36:28
| |
36 | html! { <input checked=1 /> }; 36 | html! { <input checked=1 /> };
| ^ expected `bool`, found integer | ^ expected `bool`, found integer
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/tests/html_macro/element-fail.rs:37:29 --> tests/html_macro/element-fail.rs:37:29
| |
37 | html! { <input checked={Some(false)} /> }; 37 | html! { <input checked={Some(false)} /> };
| ^^^^^^^^^^^ expected `bool`, found enum `Option` | ^^^^^^^^^^^ expected `bool`, found enum `Option`
@ -226,13 +226,13 @@ error[E0308]: mismatched types
found enum `Option<bool>` found enum `Option<bool>`
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/tests/html_macro/element-fail.rs:38:29 --> tests/html_macro/element-fail.rs:38:29
| |
38 | html! { <input disabled=1 /> }; 38 | html! { <input disabled=1 /> };
| ^ expected `bool`, found integer | ^ expected `bool`, found integer
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/tests/html_macro/element-fail.rs:39:30 --> tests/html_macro/element-fail.rs:39:30
| |
39 | html! { <input disabled={Some(true)} /> }; 39 | html! { <input disabled={Some(true)} /> };
| ^^^^^^^^^^ expected `bool`, found enum `Option` | ^^^^^^^^^^ expected `bool`, found enum `Option`
@ -241,69 +241,69 @@ error[E0308]: mismatched types
found enum `Option<bool>` found enum `Option<bool>`
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/tests/html_macro/element-fail.rs:40:30 --> tests/html_macro/element-fail.rs:40:30
| |
40 | html! { <option selected=1 /> }; 40 | html! { <option selected=1 /> };
| ^ expected `bool`, found integer | ^ 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/tests/html_macro/element-fail.rs:43:26 --> tests/html_macro/element-fail.rs:43:26
| |
43 | html! { <input type={()} /> }; 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` = 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/tests/html_macro/element-fail.rs:44:27 --> tests/html_macro/element-fail.rs:44:27
| |
44 | html! { <input value={()} /> }; 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` = 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/tests/html_macro/element-fail.rs:45:22 --> tests/html_macro/element-fail.rs:45:22
| |
45 | html! { <a href={()} /> }; 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` = 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/tests/html_macro/element-fail.rs:46:28 --> tests/html_macro/element-fail.rs:46:28
| |
46 | html! { <input string={NotToString} /> }; 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` = 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/tests/html_macro/element-fail.rs:47:23 --> tests/html_macro/element-fail.rs:47:23
| |
47 | html! { <a media={Some(NotToString)} /> }; 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: = 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<&'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` = 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/tests/html_macro/element-fail.rs:48:22 --> tests/html_macro/element-fail.rs:48:22
| |
48 | html! { <a href={Some(5)} /> }; 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: = 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<&'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` = note: required by `into_prop_value`
error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `{integer}` error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `{integer}`
--> $DIR/tests/html_macro/element-fail.rs:51:28 --> tests/html_macro/element-fail.rs:51:28
| |
51 | html! { <input onclick=1 /> }; 51 | html! { <input onclick=1 /> };
| ^ expected an `Fn<(MouseEvent,)>` closure, found `{integer}` | ^ expected an `Fn<(MouseEvent,)>` closure, found `{integer}`
@ -323,7 +323,7 @@ error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `{integer}`
= note: required because of the requirements on the impl of `IntoEventCallback<MouseEvent>` for `{integer}` = note: required because of the requirements on the impl of `IntoEventCallback<MouseEvent>` for `{integer}`
error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<String>` error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<String>`
--> $DIR/tests/html_macro/element-fail.rs:52:29 --> tests/html_macro/element-fail.rs:52:29
| |
52 | html! { <input onclick={Callback::from(|a: String| ())} /> }; 52 | html! { <input onclick={Callback::from(|a: String| ())} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -346,7 +346,7 @@ error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<Strin
= note: required because of the requirements on the impl of `IntoEventCallback<MouseEvent>` for `yew::Callback<String>` = note: required because of the requirements on the impl of `IntoEventCallback<MouseEvent>` for `yew::Callback<String>`
error[E0277]: the trait bound `Option<{integer}>: IntoEventCallback<FocusEvent>` is not satisfied error[E0277]: the trait bound `Option<{integer}>: IntoEventCallback<FocusEvent>` is not satisfied
--> $DIR/tests/html_macro/element-fail.rs:53:29 --> tests/html_macro/element-fail.rs:53:29
| |
53 | html! { <input onfocus={Some(5)} /> }; 53 | html! { <input onfocus={Some(5)} /> };
| ^^^^^^^ the trait `IntoEventCallback<FocusEvent>` is not implemented for `Option<{integer}>` | ^^^^^^^ the trait `IntoEventCallback<FocusEvent>` is not implemented for `Option<{integer}>`
@ -367,7 +367,7 @@ error[E0277]: the trait bound `Option<{integer}>: IntoEventCallback<FocusEvent>`
<Option<yew::Callback<EVENT>> as IntoEventCallback<EVENT>> <Option<yew::Callback<EVENT>> as IntoEventCallback<EVENT>>
error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
--> $DIR/tests/html_macro/element-fail.rs:56:25 --> tests/html_macro/element-fail.rs:56:25
| |
56 | html! { <input ref={()} /> }; 56 | html! { <input ref={()} /> };
| ^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()` | ^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()`
@ -375,19 +375,19 @@ error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
= note: required by `into_prop_value` = note: required by `into_prop_value`
error[E0277]: the trait bound `Option<yew::NodeRef>: IntoPropValue<yew::NodeRef>` is not satisfied error[E0277]: the trait bound `Option<yew::NodeRef>: IntoPropValue<yew::NodeRef>` is not satisfied
--> $DIR/tests/html_macro/element-fail.rs:57:25 --> tests/html_macro/element-fail.rs:57:25
| |
57 | html! { <input ref={Some(NodeRef::default())} /> }; 57 | html! { <input ref={Some(NodeRef::default())} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `Option<yew::NodeRef>` | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `Option<yew::NodeRef>`
| |
= help: the following implementations were found: = 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<&'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` = note: required by `into_prop_value`
error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<String>` error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<String>`
--> $DIR/tests/html_macro/element-fail.rs:58:29 --> tests/html_macro/element-fail.rs:58:29
| |
58 | html! { <input onclick={Callback::from(|a: String| ())} /> }; 58 | html! { <input onclick={Callback::from(|a: String| ())} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -409,16 +409,16 @@ error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<Strin
= note: the trait bound `yew::Callback<String>: IntoEventCallback<MouseEvent>` is not satisfied = 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>` = 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/tests/html_macro/element-fail.rs:60:28 --> tests/html_macro/element-fail.rs:60:28
| |
60 | html! { <input string={NotToString} /> }; 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` = note: required by `into_prop_value`
error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
--> $DIR/tests/html_macro/element-fail.rs:62:25 --> tests/html_macro/element-fail.rs:62:25
| |
62 | html! { <input ref={()} /> }; 62 | html! { <input ref={()} /> };
| ^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()` | ^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()`
@ -426,7 +426,7 @@ error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
= note: required by `into_prop_value` = note: required by `into_prop_value`
error[E0277]: the trait bound `Cow<'static, str>: From<{integer}>` is not satisfied error[E0277]: the trait bound `Cow<'static, str>: From<{integer}>` is not satisfied
--> $DIR/tests/html_macro/element-fail.rs:77:15 --> tests/html_macro/element-fail.rs:77:15
| |
77 | html! { <@{55}></@> }; 77 | html! { <@{55}></@> };
| ^^^^ the trait `From<{integer}>` is not implemented for `Cow<'static, str>` | ^^^^ the trait `From<{integer}>` is not implemented for `Cow<'static, str>`

View File

@ -46,8 +46,7 @@ fn compile_pass() {
|| <::std::string::String as ::std::convert::From<&::std::primitive::str>>::from("test"); || <::std::string::String as ::std::convert::From<&::std::primitive::str>>::from("test");
let mut extra_tags_iter = ::std::iter::IntoIterator::into_iter(::std::vec!["a", "b"]); let mut extra_tags_iter = ::std::iter::IntoIterator::into_iter(::std::vec!["a", "b"]);
let cow_none: ::std::option::Option<::std::borrow::Cow<'static, ::std::primitive::str>> = let attr_val_none: ::std::option::Option<::yew::virtual_dom::AttrValue> = ::std::option::Option::None;
::std::option::Option::None;
::yew::html! { ::yew::html! {
<div> <div>
@ -100,11 +99,11 @@ fn compile_pass() {
} }
}/> }/>
<a href={::std::option::Option::Some(::std::borrow::Cow::Borrowed("http://google.com"))} media={::std::clone::Clone::clone(&cow_none)} /> <a href={::std::option::Option::Some(::yew::virtual_dom::AttrValue::Static("http://google.com"))} media={::std::clone::Clone::clone(&attr_val_none)} />
<track kind={::std::option::Option::Some(::std::borrow::Cow::Borrowed("subtitles"))} src={::std::clone::Clone::clone(&cow_none)} /> <track kind={::std::option::Option::Some(::yew::virtual_dom::AttrValue::Static("subtitles"))} src={::std::clone::Clone::clone(&attr_val_none)} />
<track kind={::std::option::Option::Some(::std::borrow::Cow::Borrowed("5"))} mixed="works" /> <track kind={::std::option::Option::Some(::yew::virtual_dom::AttrValue::Static("5"))} mixed="works" />
<input value={::std::option::Option::Some(::std::borrow::Cow::Borrowed("value"))} <input value={::std::option::Option::Some(::yew::virtual_dom::AttrValue::Static("value"))}
onblur={::std::option::Option::Some(<::yew::Callback<::yew::events::FocusEvent> as ::std::convert::From<_>>::from(|_| ()))} onblur={::std::option::Option::Some(<::yew::Callback<::yew::FocusEvent> as ::std::convert::From<_>>::from(|_| ()))}
/> />
</div> </div>
}; };

View File

@ -1,6 +1,7 @@
use super::IntoPropValue; use super::IntoPropValue;
use crate::virtual_dom::AttrValue; use crate::virtual_dom::AttrValue;
use indexmap::IndexSet; use indexmap::IndexSet;
use std::rc::Rc;
use std::{ use std::{
borrow::{Borrow, Cow}, borrow::{Borrow, Cow},
hint::unreachable_unchecked, hint::unreachable_unchecked,
@ -70,12 +71,12 @@ impl IntoPropValue<AttrValue> for Classes {
fn into_prop_value(mut self) -> AttrValue { fn into_prop_value(mut self) -> AttrValue {
if self.set.len() == 1 { if self.set.len() == 1 {
match self.set.pop() { match self.set.pop() {
Some(attr) => attr, Some(attr) => AttrValue::Rc(Rc::from(attr)),
// SAFETY: the collection is checked to be non-empty above // SAFETY: the collection is checked to be non-empty above
None => unsafe { unreachable_unchecked() }, None => unsafe { unreachable_unchecked() },
} }
} else { } else {
Cow::Owned(self.to_string()) AttrValue::Owned(self.to_string())
} }
} }
} }

View File

@ -1,4 +1,5 @@
use super::{Component, NodeRef, Scope}; use super::{Component, NodeRef, Scope};
use crate::virtual_dom::AttrValue;
use std::{borrow::Cow, rc::Rc}; use std::{borrow::Cow, rc::Rc};
/// Marker trait for types that the [`html!`] macro may clone implicitly. /// Marker trait for types that the [`html!`] macro may clone implicitly.
@ -83,8 +84,8 @@ macro_rules! impl_into_prop {
// implemented with literals in mind // implemented with literals in mind
impl_into_prop!(|value: &'static str| -> String { value.to_owned() }); 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: &'static str| -> AttrValue { AttrValue::Static(value) });
impl_into_prop!(|value: String| -> Cow<'static, str> { Cow::Owned(value) }); impl_into_prop!(|value: String| -> AttrValue { AttrValue::Owned(value) });
#[cfg(test)] #[cfg(test)]
mod test { mod test {
@ -94,7 +95,7 @@ mod test {
fn test_str() { fn test_str() {
let _: String = "foo".into_prop_value(); let _: String = "foo".into_prop_value();
let _: Option<String> = "foo".into_prop_value(); let _: Option<String> = "foo".into_prop_value();
let _: Cow<'static, str> = "foo".into_prop_value(); let _: AttrValue = "foo".into_prop_value();
let _: Option<Cow<'static, str>> = "foo".into_prop_value(); let _: Option<AttrValue> = "foo".into_prop_value();
} }
} }

View File

@ -17,7 +17,7 @@ pub mod vtext;
use crate::html::{AnyScope, NodeRef}; use crate::html::{AnyScope, NodeRef};
use indexmap::IndexMap; use indexmap::IndexMap;
use std::{borrow::Cow, collections::HashMap, hint::unreachable_unchecked, iter}; use std::{collections::HashMap, fmt, hint::unreachable_unchecked, iter};
use web_sys::{Element, Node}; use web_sys::{Element, Node};
#[doc(inline)] #[doc(inline)]
@ -34,9 +34,72 @@ pub use self::vnode::VNode;
pub use self::vtag::VTag; pub use self::vtag::VTag;
#[doc(inline)] #[doc(inline)]
pub use self::vtext::VText; pub use self::vtext::VText;
use std::fmt::Formatter;
use std::ops::Deref;
use std::rc::Rc;
/// Attribute value /// 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
Rc(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::Rc(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::Rc(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::Rc(s) => AttrValue::Rc(Rc::clone(s)),
}
}
}
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] /// Applies contained changes to DOM [Element]
trait Apply { trait Apply {
@ -628,7 +691,7 @@ mod benchmarks {
fn bench_diff_change_first() { fn bench_diff_change_first() {
let old = sample_values(); let old = sample_values();
let mut new = old.clone(); 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 dynamic = (make_dynamic(old.clone()), make_dynamic(new.clone()));
let map = (make_indexed_map(old), make_indexed_map(new)); let map = (make_indexed_map(old), make_indexed_map(new));
@ -670,7 +733,7 @@ mod benchmarks {
"danny", "the", "the", "calling", "glen", "glen", "down", "mountain", "", "danny", "the", "the", "calling", "glen", "glen", "down", "mountain", "",
] ]
.iter() .iter()
.map(|v| AttrValue::Borrowed(*v)) .map(|v| AttrValue::Static(*v))
.collect() .collect()
} }

View File

@ -44,7 +44,7 @@ impl<T: AccessValue> Apply for Value<T> {
match (&self.0, &ancestor.0) { match (&self.0, &ancestor.0) {
(Some(new), Some(_)) => { (Some(new), Some(_)) => {
// Refresh value from the DOM. It might have changed. // Refresh value from the DOM. It might have changed.
if new != &el.value() { if new.as_ref() != el.value() {
el.set_value(new); el.set_value(new);
} }
} }
@ -1075,7 +1075,7 @@ mod tests {
<@{"input"} value="World"/> <@{"input"} value="World"/>
}; };
let input_vtag = assert_vtag_mut(&mut input_el); let input_vtag = assert_vtag_mut(&mut input_el);
assert_eq!(input_vtag.value(), Some(&Cow::Borrowed("World"))); assert_eq!(input_vtag.value(), Some(&AttrValue::Static("World")));
assert!(!input_vtag.attributes.iter().any(|(k, _)| k == "value")); assert!(!input_vtag.attributes.iter().any(|(k, _)| k == "value"));
} }