Allow taking Fn(EVENT) for callbacks too (#1989)

* Allow taking `Fn(EVENT)` for callbacks too

* fix bad test

* fmt

* add tests
This commit is contained in:
Muhammad Hamza 2021-08-09 20:53:49 +05:00 committed by GitHub
parent e474533daf
commit c6458f124a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 289 additions and 36 deletions

View File

@ -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

View File

@ -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
|
@ -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
|
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 | | }
| |_^
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
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
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
|
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
|
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
@ -227,11 +302,11 @@ 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
|
@ -244,9 +319,123 @@ error[E0277]: the trait bound `{integer}: IntoPropValue<Option<yew::Callback<Mou
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
|
| / 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 `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`

View File

@ -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)))
}
}

View File

@ -8,6 +8,7 @@ use web_sys::{
HtmlTextAreaElement as TextAreaElement, InputEvent,
};
use crate::Callback;
pub use events::*;
/// A type representing data from `oninput` event.
@ -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();
}
}