Skip to content
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
2 changes: 1 addition & 1 deletion packages/yew-macro/tests/html_macro/element-fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ fn compile_fail() {

// listener type mismatch
html! { <input onclick=1 /> };
html! { <input onclick={Callback::from(|a: String| ()) /> };
html! { <input onclick={Callback::from(|a: String| ())} /> };
html! { <input onfocus={Some(5)} /> };

// NodeRef type mismatch
Expand Down
255 changes: 222 additions & 33 deletions packages/yew-macro/tests/html_macro/element-fail.stderr
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
error: this file contains an unclosed delimiter
--> $DIR/element-fail.rs:95:14
|
5 | fn compile_fail() {
| - unclosed delimiter
...
52 | html! { <input onclick={Callback::from(|a: String| ()) /> };
| - this delimiter might not be properly closed...
...
93 | }
| - ...as it matches this but it has different indentation
94 |
95 | fn main() {}
| ^

error: this opening tag has no corresponding closing tag
--> $DIR/element-fail.rs:7:13
|
Expand Down Expand Up @@ -121,19 +106,109 @@ error: `ref` can only be specified once
33 | html! { <input ref={()} ref={()} /> };
| ^^^

error: unexpected end of input, expected token tree
--> $DIR/element-fail.rs:52:5
error: `ref` can only be specified once
--> $DIR/element-fail.rs:63:20
|
63 | html! { <input ref={()} ref={()} /> };
| ^^^

error: the tag `<input>` is a void element and cannot have children (hint: rewrite this as `<input/>`)
--> $DIR/element-fail.rs:66:13
|
66 | html! { <input type="text"></input> };
| ^^^^^^^^^^^^^^^^^^^

error: the tag `<iNpUt>` is a void element and cannot have children (hint: rewrite this as `<iNpUt/>`)
--> $DIR/element-fail.rs:68:13
|
68 | html! { <iNpUt type="text"></iNpUt> };
| ^^^^^^^^^^^^^^^^^^^

error: this dynamic tag is missing an expression block defining its value
--> $DIR/element-fail.rs:71:14
|
52 | / html! { <input onclick={Callback::from(|a: String| ()) /> };
53 | | html! { <input onfocus={Some(5)} /> };
54 | |
55 | | // NodeRef type mismatch
... |
92 | | html! { <input string=NotToString /> };
93 | | }
| |_^
71 | html! { <@></@> };
| ^

error: this dynamic tag is missing an expression block defining its value
--> $DIR/element-fail.rs:72:14
|
72 | html! { <@/> };
| ^

error: dynamic closing tags must not have a body (hint: replace it with just `</@>`)
--> $DIR/element-fail.rs:75:27
|
75 | html! { <@{"test"}></@{"test"}> };
| ^^^^^^^^

error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/element-fail.rs:83:24
|
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.
--> $DIR/element-fail.rs:84:24
|
84 | html! { <input ref=() /> };
| ^^

error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/element-fail.rs:85:24
|
85 | html! { <input ref=() ref=() /> };
| ^^

error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/element-fail.rs:86:28
|
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.
--> $DIR/element-fail.rs:87:27
|
87 | html! { <input string=NotToString /> };
| ^^^^^^^^^^^

error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/element-fail.rs:88:22
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
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.
--> $DIR/element-fail.rs:89:21
|
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.
--> $DIR/element-fail.rs:90:25
|
90 | html! { <input type=() /> };
| ^^

error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/element-fail.rs:91:26
|
91 | html! { <input value=() /> };
| ^^

error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression.
--> $DIR/element-fail.rs:92:27
|
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.
--> $DIR/element-fail.rs:80:25
|
80 | html! { <div class={("deprecated", "warning")} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(deprecated)]` on by default

error[E0308]: mismatched types
--> $DIR/element-fail.rs:36:28
Expand Down Expand Up @@ -227,11 +302,34 @@ error[E0277]: the trait bound `Option<{integer}>: IntoPropValue<Option<Cow<'stat
<Option<String> as IntoPropValue<Option<Cow<'static, str>>>>
= note: required by `into_prop_value`

error[E0277]: the trait bound `{integer}: IntoPropValue<Option<yew::Callback<MouseEvent>>>` is not satisfied
error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `{integer}`
--> $DIR/element-fail.rs:51:28
|
51 | html! { <input onclick=1 /> };
| ^ the trait `IntoPropValue<Option<yew::Callback<MouseEvent>>>` is not implemented for `{integer}`
| ^ expected an `Fn<(MouseEvent,)>` closure, found `{integer}`
|
::: $WORKSPACE/packages/yew/src/html/listener/events.rs
|
| / impl_action! {
3 | | onabort(name: "abort", event: Event) -> web_sys::Event => |_, event| { event }
4 | | onauxclick(name: "auxclick", event: MouseEvent) -> web_sys::MouseEvent => |_, event| { event }
5 | | onblur(name: "blur", event: FocusEvent) -> web_sys::FocusEvent => |_, event| { event }
... |
101 | | ontransitionstart(name: "transitionstart", event: TransitionEvent) -> web_sys::TransitionEvent => |_, event| { event }
102 | | }
| |_- required by this bound in `yew::html::onclick::Wrapper::__macro_new`
|
= help: the trait `Fn<(MouseEvent,)>` is not implemented 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>`
--> $DIR/element-fail.rs:52:29
|
52 | html! { <input onclick={Callback::from(|a: String| ())} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| expected an implementor of trait `IntoEventCallback<MouseEvent>`
| help: consider borrowing here: `&Callback::from(|a: String| ())`
|
::: $WORKSPACE/packages/yew/src/html/listener/events.rs
|
Expand All @@ -244,9 +342,100 @@ error[E0277]: the trait bound `{integer}: IntoPropValue<Option<yew::Callback<Mou
102 | | }
| |_- required by this bound in `yew::html::onclick::Wrapper::__macro_new`
|
= 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 `Option<{integer}>: IntoEventCallback<FocusEvent>` is not satisfied
--> $DIR/element-fail.rs:53:29
|
53 | html! { <input onfocus={Some(5)} /> };
| ^^^^^^^ the trait `IntoEventCallback<FocusEvent>` is not implemented for `Option<{integer}>`
|
::: $WORKSPACE/packages/yew/src/html/listener/events.rs
|
| / impl_action! {
3 | | onabort(name: "abort", event: Event) -> web_sys::Event => |_, event| { event }
4 | | onauxclick(name: "auxclick", event: MouseEvent) -> web_sys::MouseEvent => |_, event| { event }
5 | | onblur(name: "blur", event: FocusEvent) -> web_sys::FocusEvent => |_, event| { event }
... |
101 | | ontransitionstart(name: "transitionstart", event: TransitionEvent) -> web_sys::TransitionEvent => |_, event| { event }
102 | | }
| |_- required by this bound in `yew::html::onfocus::Wrapper::__macro_new`
|
= 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<Option<String>>>
<&'static str as IntoPropValue<String>>
and 11 others
<Option<T> as IntoEventCallback<EVENT>>
<Option<yew::Callback<EVENT>> as IntoEventCallback<EVENT>>

error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
--> $DIR/element-fail.rs:56:25
|
56 | html! { <input ref={()} /> };
| ^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()`
|
= note: required by `into_prop_value`

error[E0277]: the trait bound `Option<yew::NodeRef>: IntoPropValue<yew::NodeRef>` is not satisfied
--> $DIR/element-fail.rs:57:25
|
57 | html! { <input ref={Some(NodeRef::default())} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^ 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<String>>>
<Option<String> as IntoPropValue<Option<Cow<'static, str>>>>
= note: required by `into_prop_value`

error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback<String>`
--> $DIR/element-fail.rs:58:29
|
58 | html! { <input onclick={Callback::from(|a: String| ())} /> };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| expected an implementor of trait `IntoEventCallback<MouseEvent>`
| help: consider borrowing here: `&Callback::from(|a: String| ())`
|
::: $WORKSPACE/packages/yew/src/html/listener/events.rs
|
| / impl_action! {
3 | | onabort(name: "abort", event: Event) -> web_sys::Event => |_, event| { event }
4 | | onauxclick(name: "auxclick", event: MouseEvent) -> web_sys::MouseEvent => |_, event| { event }
5 | | onblur(name: "blur", event: FocusEvent) -> web_sys::FocusEvent => |_, event| { event }
... |
101 | | ontransitionstart(name: "transitionstart", event: TransitionEvent) -> web_sys::TransitionEvent => |_, event| { event }
102 | | }
| |_- required by this bound in `yew::html::onclick::Wrapper::__macro_new`
|
= 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
--> $DIR/element-fail.rs:60:28
|
60 | html! { <input string={NotToString} /> };
| ^^^^^^^^^^^ the trait `IntoPropValue<Option<Cow<'static, str>>>` is not implemented for `NotToString`
|
= note: required by `into_prop_value`

error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
--> $DIR/element-fail.rs:62:25
|
62 | html! { <input ref={()} /> };
| ^^ the trait `IntoPropValue<yew::NodeRef>` is not implemented for `()`
|
= note: required by `into_prop_value`

error[E0277]: the trait bound `Cow<'static, str>: From<{integer}>` is not satisfied
--> $DIR/element-fail.rs:77:15
|
77 | html! { <@{55}></@> };
| ^^^^ the trait `From<{integer}>` is not implemented for `Cow<'static, str>`
|
= help: the following implementations were found:
<Cow<'a, CStr> as From<&'a CStr>>
<Cow<'a, CStr> as From<&'a CString>>
<Cow<'a, CStr> as From<CString>>
<Cow<'a, OsStr> as From<&'a OsStr>>
and 11 others
= note: required because of the requirements on the impl of `Into<Cow<'static, str>>` for `{integer}`
= note: required by `into`
4 changes: 2 additions & 2 deletions packages/yew/src/html/listener/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ macro_rules! impl_action {

#[doc(hidden)]
#[inline]
pub fn __macro_new(callback: impl IntoPropValue<Option<Callback<Event>>>) -> Option<Rc<dyn Listener>> {
let callback = callback.into_prop_value()?;
pub fn __macro_new(callback: impl IntoEventCallback<Event>) -> Option<Rc<dyn Listener>> {
let callback = callback.into_event_callback()?;
Some(Rc::new(Self::new(callback)))
}
}
Expand Down
64 changes: 64 additions & 0 deletions packages/yew/src/html/listener/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use web_sys::{
HtmlTextAreaElement as TextAreaElement, InputEvent,
};

use crate::Callback;
pub use events::*;

/// A type representing data from `oninput` event.
Expand Down Expand Up @@ -84,3 +85,66 @@ fn onchange_handler(this: &Element) -> ChangeData {
}
}
}

/// A trait similar to `Into<T>` which allows conversion of a value into a [`Callback`].
/// This is used for event listeners.
pub trait IntoEventCallback<EVENT> {
/// Convert `self` to `Option<Callback<EVENT>>`
fn into_event_callback(self) -> Option<Callback<EVENT>>;
}

impl<EVENT> IntoEventCallback<EVENT> for Callback<EVENT> {
fn into_event_callback(self) -> Option<Callback<EVENT>> {
Some(self)
}
}

impl<EVENT> IntoEventCallback<EVENT> for &Callback<EVENT> {
fn into_event_callback(self) -> Option<Callback<EVENT>> {
Some(self.clone())
}
}

impl<EVENT> IntoEventCallback<EVENT> for Option<Callback<EVENT>> {
fn into_event_callback(self) -> Option<Callback<EVENT>> {
self
}
}

impl<T, EVENT> IntoEventCallback<EVENT> for T
where
T: Fn(EVENT) + 'static,
{
fn into_event_callback(self) -> Option<Callback<EVENT>> {
Some(Callback::from(self))
}
}

impl<T, EVENT> IntoEventCallback<EVENT> for Option<T>
where
T: Fn(EVENT) + 'static,
{
fn into_event_callback(self) -> Option<Callback<EVENT>> {
Some(Callback::from(self?))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn supported_into_event_callback_types() {
let f = |_: usize| ();
let cb = Callback::from(f);

// Callbacks
let _: Option<Callback<usize>> = cb.clone().into_event_callback();
let _: Option<Callback<usize>> = (&cb).into_event_callback();
let _: Option<Callback<usize>> = Some(cb).into_event_callback();

// Fns
let _: Option<Callback<usize>> = f.into_event_callback();
let _: Option<Callback<usize>> = Some(f).into_event_callback();
}
}