mirror of https://github.com/yewstack/yew
Add support for required props
This commit is contained in:
parent
df47f20daa
commit
3fed651b42
|
@ -32,6 +32,7 @@ slab = "0.4"
|
|||
stdweb = "^0.4.16"
|
||||
toml = { version = "0.4", optional = true }
|
||||
yew-macro = { version = "0.8.0", path = "crates/macro" }
|
||||
yew-props-derive = { path = "crates/props-derive" }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
|
@ -54,6 +55,7 @@ cbor = ["serde_cbor"]
|
|||
[workspace]
|
||||
members = [
|
||||
"crates/macro",
|
||||
"crates/props-derive",
|
||||
"examples/counter",
|
||||
"examples/crm",
|
||||
"examples/custom_components",
|
||||
|
|
24
README.md
24
README.md
|
@ -204,11 +204,27 @@ Components live in an Angular-like scopes with **parent-to-child** *(properties)
|
|||
Properties are also pure Rust types with strict type-checking during the compilation.
|
||||
|
||||
```rust
|
||||
// my_button.rs
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Properties {
|
||||
pub hidden: bool,
|
||||
#[props(required)]
|
||||
pub color: Color,
|
||||
#[props(required)]
|
||||
pub onclick: Callback<()>,
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```rust
|
||||
// confirm_dialog.rs
|
||||
|
||||
html! {
|
||||
<nav class="menu">
|
||||
<MyButton color=Color::Red />
|
||||
<MyButton onclick=|_| ParentMsg::DoIt />
|
||||
</nav>
|
||||
<div class="confirm-dialog">
|
||||
<MyButton onclick=|_| DialogMsg::Cancel color=Color::Red hidden=false />
|
||||
<MyButton onclick=|_| DialogMsg::Submit color=Color::Blue />
|
||||
</div>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -29,6 +29,9 @@ cargo test --target=wasm32-unknown-unknown
|
|||
echo "Testing macro..."
|
||||
cargo test --test macro_test
|
||||
|
||||
echo "Testing props derive macro..."
|
||||
(cd crates/props-derive && cargo test)
|
||||
|
||||
check_example() {
|
||||
echo "Checking example [$2]"
|
||||
pushd $2 > /dev/null
|
||||
|
|
|
@ -25,5 +25,5 @@ proc-macro2 = "0.4"
|
|||
quote = "0.6"
|
||||
syn = { version = "^0.15.34", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
yew = { path = "../.." }
|
||||
[build-dependencies]
|
||||
autocfg = "0.1.3"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
extern crate autocfg;
|
||||
|
||||
pub fn main() {
|
||||
if autocfg::new().probe_rustc_version(1, 36) {
|
||||
println!("cargo:rustc-cfg=has_maybe_uninit");
|
||||
}
|
||||
}
|
|
@ -48,35 +48,72 @@ impl Parse for HtmlComponent {
|
|||
impl ToTokens for HtmlComponent {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let HtmlComponentInner { ty, props } = &self.0;
|
||||
let vcomp_props = Ident::new("__yew_vcomp_props", Span::call_site());
|
||||
let vcomp_scope = Ident::new("__yew_vcomp_scope", Span::call_site());
|
||||
let override_props = props.iter().map(|props| match props {
|
||||
Props::List(ListProps(vec_props)) => {
|
||||
|
||||
let validate_props = if let Some(Props::List(ListProps(vec_props))) = props {
|
||||
let prop_ref = Ident::new("__yew_prop_ref", Span::call_site());
|
||||
let check_props = vec_props.iter().map(|HtmlProp { label, .. }| {
|
||||
quote_spanned! { label.span()=> #vcomp_props.#label; }
|
||||
quote! { #prop_ref.#label; }
|
||||
});
|
||||
|
||||
// This is a hack to avoid allocating memory but still have a reference to a props
|
||||
// struct so that attributes can be checked against it
|
||||
|
||||
#[cfg(has_maybe_uninit)]
|
||||
let unallocated_prop_ref = quote! {
|
||||
let #prop_ref: <#ty as ::yew::html::Component>::Properties = unsafe { ::std::mem::MaybeUninit::uninit().assume_init() };
|
||||
};
|
||||
|
||||
#[cfg(not(has_maybe_uninit))]
|
||||
let unallocated_prop_ref = quote! {
|
||||
let #prop_ref: <#ty as ::yew::html::Component>::Properties = unsafe { ::std::mem::uninitialized() };
|
||||
};
|
||||
|
||||
quote! {
|
||||
#unallocated_prop_ref
|
||||
#(#check_props)*
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let init_props = if let Some(props) = props {
|
||||
match props {
|
||||
Props::List(ListProps(vec_props)) => {
|
||||
let set_props = vec_props.iter().map(|HtmlProp { label, value }| {
|
||||
quote_spanned! { value.span()=>
|
||||
#vcomp_props.#label = <::yew::virtual_dom::vcomp::VComp<_> as ::yew::virtual_dom::vcomp::Transformer<_, _, _>>::transform(#vcomp_scope.clone(), #value);
|
||||
.#label(<::yew::virtual_dom::vcomp::VComp<_> as ::yew::virtual_dom::vcomp::Transformer<_, _, _>>::transform(#vcomp_scope.clone(), #value))
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#(#check_props#set_props)*
|
||||
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder()
|
||||
#(#set_props)*
|
||||
.build()
|
||||
}
|
||||
}
|
||||
Props::With(WithProps(props)) => {
|
||||
quote_spanned! { props.span()=> #vcomp_props = #props; }
|
||||
Props::With(WithProps(props)) => quote! { #props },
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder().build()
|
||||
}
|
||||
};
|
||||
|
||||
let validate_comp = quote_spanned! { ty.span()=>
|
||||
struct __yew_validate_comp where #ty: ::yew::html::Component;
|
||||
};
|
||||
|
||||
tokens.extend(quote! {{
|
||||
// Validation nevers executes at runtime
|
||||
if false {
|
||||
#validate_comp
|
||||
#validate_props
|
||||
}
|
||||
});
|
||||
|
||||
tokens.extend(quote_spanned! { ty.span()=> {
|
||||
let #vcomp_scope: ::yew::virtual_dom::vcomp::ScopeHolder<_> = ::std::default::Default::default();
|
||||
let mut #vcomp_props: <#ty as ::yew::html::Component>::Properties = ::std::default::Default::default();
|
||||
#(#override_props)*
|
||||
::yew::virtual_dom::VNode::VComp(
|
||||
::yew::virtual_dom::VComp::new::<#ty>(#vcomp_props, #vcomp_scope)
|
||||
::yew::virtual_dom::VComp::new::<#ty>(#init_props, #vcomp_scope)
|
||||
)
|
||||
}});
|
||||
}
|
||||
|
@ -202,6 +239,14 @@ impl Parse for ListProps {
|
|||
}
|
||||
}
|
||||
|
||||
// alphabetize
|
||||
props.sort_by(|a, b| {
|
||||
a.label
|
||||
.to_string()
|
||||
.partial_cmp(&b.label.to_string())
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
Ok(ListProps(props))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "yew-props-derive"
|
||||
version = "0.7.0"
|
||||
edition = "2018"
|
||||
autotests = false
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[[test]]
|
||||
name = "tests"
|
||||
path = "tests/cases.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
||||
yew = { path = "../.." }
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "0.4"
|
||||
syn = "0.15"
|
||||
quote = "0.6"
|
|
@ -0,0 +1,344 @@
|
|||
#![recursion_limit = "128"]
|
||||
extern crate proc_macro;
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{quote, ToTokens};
|
||||
use std::iter;
|
||||
use syn::parse_macro_input;
|
||||
use syn::punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{
|
||||
DeriveInput, Error, GenericParam, Generics, Meta, MetaList, NestedMeta, Type, TypeParam,
|
||||
Visibility, WhereClause,
|
||||
};
|
||||
|
||||
struct PropField {
|
||||
ty: Type,
|
||||
name: Ident,
|
||||
wrapped_name: Option<Ident>,
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Properties, attributes(props))]
|
||||
pub fn derive(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let props_name = input.ident;
|
||||
let vis = input.vis;
|
||||
|
||||
let generics = input.generics;
|
||||
let generic_params = &generics.params;
|
||||
let generic_where = &generics.where_clause;
|
||||
let generic_idents = {
|
||||
let generic_idents = generics.params.iter().filter_map(|param| match param {
|
||||
GenericParam::Type(TypeParam { ident, .. }) => Some(quote! { #ident }),
|
||||
_ => unimplemented!(),
|
||||
});
|
||||
|
||||
quote! {#(#generic_idents),*}
|
||||
};
|
||||
|
||||
let named_fields = match input.data {
|
||||
syn::Data::Struct(data) => match data.fields {
|
||||
syn::Fields::Named(fields) => fields,
|
||||
_ => unimplemented!(),
|
||||
},
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
let prop_fields: Vec<PropField> = {
|
||||
let res: Result<Vec<PropField>, Error> = named_fields
|
||||
.named
|
||||
.into_iter()
|
||||
.map(|field| {
|
||||
Ok(PropField {
|
||||
wrapped_name: required_wrapper(&field)?,
|
||||
ty: field.ty,
|
||||
name: field.ident.unwrap(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
match res {
|
||||
Err(err) => return TokenStream::from(err.to_compile_error()),
|
||||
Ok(mut prop_fields) => {
|
||||
// Alphabetize
|
||||
prop_fields.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap());
|
||||
prop_fields
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Build Idents
|
||||
let step_name = Ident::new(&format!("{}Step", props_name), Span::call_site());
|
||||
let wrapped_name = Ident::new(&format!("Wrapped{}", props_name), Span::call_site());
|
||||
let builder_name = Ident::new(&format!("{}Builder", props_name), Span::call_site());
|
||||
let mut step_names: Vec<Ident> = prop_fields
|
||||
.iter()
|
||||
.filter(|field| field.wrapped_name.is_some())
|
||||
.map(|field| {
|
||||
Ident::new(
|
||||
&format!("{}_{}_is_required", props_name, field.name),
|
||||
Span::call_site(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
step_names.push(Ident::new(
|
||||
&format!("{}BuildStep", props_name),
|
||||
Span::call_site(),
|
||||
));
|
||||
|
||||
let start_step_name = &step_names[0];
|
||||
let build_step_name = &step_names[step_names.len() - 1];
|
||||
let all_step_names = &step_names;
|
||||
|
||||
let step_methods = step_methods(
|
||||
&vis,
|
||||
&generics,
|
||||
&generic_idents,
|
||||
&generic_where,
|
||||
&builder_name,
|
||||
&step_names,
|
||||
&prop_fields,
|
||||
);
|
||||
let wrapped_fields = wrapped_fields(prop_fields.iter());
|
||||
let wrapped_default_setters = wrapped_default_setters(prop_fields.iter());
|
||||
let prop_field_setters = prop_field_setters(prop_fields.iter());
|
||||
let step_name_impls = step_name_impls(&step_name, step_names.iter());
|
||||
let vis_repeat = iter::repeat(&vis);
|
||||
|
||||
let expanded = quote! {
|
||||
#(
|
||||
#[doc(hidden)]
|
||||
#vis_repeat struct #all_step_names;
|
||||
)*
|
||||
|
||||
#[doc(hidden)]
|
||||
#vis trait #step_name {}
|
||||
#(#step_name_impls)*
|
||||
|
||||
struct #wrapped_name#generics {
|
||||
#(#wrapped_fields)*
|
||||
}
|
||||
|
||||
impl#generics ::std::default::Default for #wrapped_name<#generic_idents> #generic_where {
|
||||
fn default() -> Self {
|
||||
#wrapped_name::<#generic_idents> {
|
||||
#(#wrapped_default_setters)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#vis struct #builder_name<P: #step_name, #generic_params> #generic_where {
|
||||
wrapped: ::std::boxed::Box<#wrapped_name<#generic_idents>>,
|
||||
_marker: ::std::marker::PhantomData<P>,
|
||||
}
|
||||
|
||||
impl #generics ::yew::html::Properties for #props_name<#generic_idents> #generic_where {
|
||||
type Builder = #builder_name<#start_step_name, #generic_idents>;
|
||||
|
||||
fn builder() -> Self::Builder {
|
||||
#builder_name {
|
||||
wrapped: ::std::boxed::Box::new(::std::default::Default::default()),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#(#step_methods)*
|
||||
|
||||
impl #generics #builder_name<#build_step_name, #generic_idents> #generic_where {
|
||||
#[doc(hidden)]
|
||||
#vis fn build(self) -> #props_name<#generic_idents> {
|
||||
#props_name::<#generic_idents> {
|
||||
#(#prop_field_setters)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
fn wrapped_fields<'a>(
|
||||
prop_fields: impl Iterator<Item = &'a PropField>,
|
||||
) -> impl Iterator<Item = impl ToTokens + 'a> {
|
||||
prop_fields.map(|pf| {
|
||||
let PropField { name, ty, .. } = &pf;
|
||||
if let Some(wrapped_name) = &pf.wrapped_name {
|
||||
quote! {
|
||||
#wrapped_name: ::std::option::Option<#ty>,
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name: #ty,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn wrapped_default_setters<'a>(
|
||||
prop_fields: impl Iterator<Item = &'a PropField>,
|
||||
) -> impl Iterator<Item = impl ToTokens + 'a> {
|
||||
prop_fields.map(|pf| {
|
||||
if let Some(wrapped_name) = &pf.wrapped_name {
|
||||
quote! {
|
||||
#wrapped_name: ::std::default::Default::default(),
|
||||
}
|
||||
} else {
|
||||
let name = &pf.name;
|
||||
quote! {
|
||||
#name: ::std::default::Default::default(),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn prop_field_setters<'a>(
|
||||
prop_fields: impl Iterator<Item = &'a PropField>,
|
||||
) -> impl Iterator<Item = impl ToTokens + 'a> {
|
||||
prop_fields.map(|pf| {
|
||||
let name = &pf.name;
|
||||
if let Some(wrapped_name) = &pf.wrapped_name {
|
||||
quote! {
|
||||
#name: self.wrapped.#wrapped_name.unwrap(),
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name: self.wrapped.#name,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn find_props_meta_list(field: &syn::Field) -> Option<MetaList> {
|
||||
let meta_list = field
|
||||
.attrs
|
||||
.iter()
|
||||
.find_map(|attr| match attr.parse_meta().ok()? {
|
||||
Meta::List(meta_list) => Some(meta_list),
|
||||
_ => None,
|
||||
})?;
|
||||
|
||||
if meta_list.ident == "props" {
|
||||
Some(meta_list)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn required_wrapper(named_field: &syn::Field) -> Result<Option<Ident>, Error> {
|
||||
let meta_list = if let Some(meta_list) = find_props_meta_list(named_field) {
|
||||
meta_list
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let expected_required = syn::Error::new(meta_list.span(), "expected `props(required)`");
|
||||
let first_nested = if let Some(first_nested) = meta_list.nested.first() {
|
||||
first_nested
|
||||
} else {
|
||||
return Err(expected_required);
|
||||
};
|
||||
|
||||
let word_ident = match first_nested {
|
||||
punctuated::Pair::End(NestedMeta::Meta(Meta::Word(ident))) => ident,
|
||||
_ => return Err(expected_required),
|
||||
};
|
||||
|
||||
if word_ident != "required" {
|
||||
return Err(expected_required);
|
||||
}
|
||||
|
||||
if let Some(ident) = &named_field.ident {
|
||||
Ok(Some(Ident::new(
|
||||
&format!("{}_wrapper", ident),
|
||||
Span::call_site(),
|
||||
)))
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn step_name_impls<'a>(
|
||||
step_trait_name: &'a Ident,
|
||||
step_names: impl Iterator<Item = &'a Ident>,
|
||||
) -> impl Iterator<Item = impl ToTokens + 'a> {
|
||||
step_names.map(move |name| {
|
||||
let trait_name = step_trait_name;
|
||||
quote! {
|
||||
impl #trait_name for #name {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn step_methods<'a>(
|
||||
vis: &'a Visibility,
|
||||
generics: &'a Generics,
|
||||
generic_idents: &'a proc_macro2::TokenStream,
|
||||
generic_where: &'a Option<WhereClause>,
|
||||
builder_name: &'a Ident,
|
||||
step_names: &'a [Ident],
|
||||
prop_fields: &'a [PropField],
|
||||
) -> proc_macro2::TokenStream {
|
||||
let mut prop_fields_index = 0;
|
||||
let mut token_stream = proc_macro2::TokenStream::new();
|
||||
|
||||
for (step, step_name) in step_names.iter().enumerate() {
|
||||
let mut optional_fields = Vec::new();
|
||||
let mut required_field = None;
|
||||
|
||||
if prop_fields_index >= prop_fields.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
while let Some(pf) = prop_fields.get(prop_fields_index) {
|
||||
prop_fields_index += 1;
|
||||
if pf.wrapped_name.is_some() {
|
||||
required_field = Some(pf);
|
||||
break;
|
||||
} else {
|
||||
optional_fields.push((&pf.name, &pf.ty));
|
||||
}
|
||||
}
|
||||
|
||||
let optional_prop_fn = optional_fields.into_iter().map(|(prop_name, prop_type)| {
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
#vis fn #prop_name(mut self, #prop_name: #prop_type) -> #builder_name<#step_name, #generic_idents> {
|
||||
self.wrapped.#prop_name = #prop_name;
|
||||
self
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let required_prop_fn = required_field.iter().map(|p| {
|
||||
let prop_name = &p.name;
|
||||
let prop_type = &p.ty;
|
||||
let wrapped_name = p.wrapped_name.as_ref().unwrap();
|
||||
let next_step_name = &step_names[step + 1];
|
||||
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
#vis fn #prop_name(mut self, #prop_name: #prop_type) -> #builder_name<#next_step_name, #generic_idents> {
|
||||
self.wrapped.#wrapped_name = ::std::option::Option::Some(#prop_name);
|
||||
#builder_name {
|
||||
wrapped: self.wrapped,
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
token_stream.extend(quote! {
|
||||
impl #generics #builder_name<#step_name, #generic_idents> #generic_where {
|
||||
#(#optional_prop_fn)*
|
||||
#(#required_prop_fn)*
|
||||
}
|
||||
});
|
||||
}
|
||||
token_stream
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#[test]
|
||||
fn tests() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.pass("tests/pass.rs");
|
||||
t.compile_fail("tests/fail.rs");
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
#![recursion_limit = "128"]
|
||||
|
||||
use yew::html::Properties;
|
||||
use yew_props_derive::Properties;
|
||||
|
||||
mod t1 {
|
||||
use super::*;
|
||||
struct Value;
|
||||
#[derive(Properties)]
|
||||
pub struct Props {
|
||||
// ERROR: optional params must implement default
|
||||
value: Value,
|
||||
}
|
||||
}
|
||||
|
||||
mod t2 {
|
||||
use super::*;
|
||||
#[derive(Properties)]
|
||||
pub struct Props {
|
||||
// ERROR: optional is not a tag
|
||||
#[props(optional)]
|
||||
value: String,
|
||||
}
|
||||
}
|
||||
|
||||
mod t3 {
|
||||
use super::*;
|
||||
#[derive(Properties)]
|
||||
pub struct Props {
|
||||
#[props(required)]
|
||||
value: String,
|
||||
}
|
||||
|
||||
fn required_props_should_be_set() {
|
||||
Props::builder().build();
|
||||
}
|
||||
}
|
||||
|
||||
mod t4 {
|
||||
use super::*;
|
||||
#[derive(Properties)]
|
||||
pub struct Props {
|
||||
b: i32,
|
||||
#[props(required)]
|
||||
a: i32,
|
||||
}
|
||||
|
||||
fn enforce_ordering() {
|
||||
Props::builder().b(1).a(2).build();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,34 @@
|
|||
error: expected `props(required)`
|
||||
--> $DIR/fail.rs:21:11
|
||||
|
|
||||
21 | #[props(optional)]
|
||||
| ^^^^^
|
||||
|
||||
error[E0277]: the trait bound `t1::Value: std::default::Default` is not satisfied
|
||||
--> $DIR/fail.rs:9:14
|
||||
|
|
||||
9 | #[derive(Properties)]
|
||||
| ^^^^^^^^^^ the trait `std::default::Default` is not implemented for `t1::Value`
|
||||
|
|
||||
= note: required by `std::default::Default::default`
|
||||
|
||||
error[E0599]: no method named `build` found for type `t3::PropsBuilder<t3::Props_value_is_required>` in the current scope
|
||||
--> $DIR/fail.rs:35:26
|
||||
|
|
||||
28 | #[derive(Properties)]
|
||||
| - method `build` not found for this
|
||||
...
|
||||
35 | Props::builder().build();
|
||||
| ^^^^^
|
||||
|
||||
error[E0599]: no method named `b` found for type `t4::PropsBuilder<t4::Props_a_is_required>` in the current scope
|
||||
--> $DIR/fail.rs:49:26
|
||||
|
|
||||
41 | #[derive(Properties)]
|
||||
| - method `b` not found for this
|
||||
...
|
||||
49 | Props::builder().b(1).a(2).build();
|
||||
| ^ help: there is a method with a similar name: `a`
|
||||
|
||||
Some errors have detailed explanations: E0277, E0599.
|
||||
For more information about an error, try `rustc --explain E0277`.
|
|
@ -0,0 +1,68 @@
|
|||
#![recursion_limit = "128"]
|
||||
|
||||
use yew::html::Properties;
|
||||
use yew_props_derive::Properties;
|
||||
|
||||
mod t1 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties)]
|
||||
pub struct Props<T: Default> {
|
||||
value: T,
|
||||
}
|
||||
|
||||
fn optional_prop_generics_should_work() {
|
||||
Props::<bool>::builder().build();
|
||||
Props::<bool>::builder().value(true).build();
|
||||
}
|
||||
}
|
||||
|
||||
mod t2 {
|
||||
use super::*;
|
||||
|
||||
struct Value;
|
||||
#[derive(Properties)]
|
||||
pub struct Props<T> {
|
||||
#[props(required)]
|
||||
value: T,
|
||||
}
|
||||
|
||||
fn required_prop_generics_should_work() {
|
||||
Props::<Value>::builder().value(Value).build();
|
||||
}
|
||||
}
|
||||
|
||||
mod t3 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties)]
|
||||
pub struct Props {
|
||||
#[props(required)]
|
||||
b: i32,
|
||||
a: i32,
|
||||
}
|
||||
|
||||
fn order_is_alphabetized() {
|
||||
Props::builder().b(1).build();
|
||||
Props::builder().a(1).b(2).build();
|
||||
}
|
||||
}
|
||||
|
||||
mod t4 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties)]
|
||||
pub struct Props<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
value: T,
|
||||
}
|
||||
|
||||
fn optional_prop_generics_should_work() {
|
||||
Props::<bool>::builder().build();
|
||||
Props::<bool>::builder().value(true).build();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -1,32 +1,23 @@
|
|||
use crate::button::Button;
|
||||
use yew::{html, Callback, Component, ComponentLink, Html, Renderable, ShouldRender};
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct Barrier {
|
||||
limit: u32,
|
||||
counter: u32,
|
||||
onsignal: Option<Callback<()>>,
|
||||
onsignal: Callback<()>,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
ChildClicked,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub limit: u32,
|
||||
pub onsignal: Option<Callback<()>>,
|
||||
#[props(required)]
|
||||
pub onsignal: Callback<()>,
|
||||
}
|
||||
|
||||
impl Default for Props {
|
||||
fn default() -> Self {
|
||||
Props {
|
||||
limit: 0,
|
||||
onsignal: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Component for Barrier {
|
||||
type Message = Msg;
|
||||
type Properties = Props;
|
||||
|
@ -44,13 +35,11 @@ impl Component for Barrier {
|
|||
Msg::ChildClicked => {
|
||||
self.counter += 1;
|
||||
if self.counter >= self.limit {
|
||||
if let Some(ref mut callback) = self.onsignal {
|
||||
callback.emit(());
|
||||
self.onsignal.emit(());
|
||||
self.counter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,19 @@
|
|||
use yew::{html, Callback, Component, ComponentLink, Html, Renderable, ShouldRender};
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct Button {
|
||||
title: String,
|
||||
onsignal: Option<Callback<()>>,
|
||||
onsignal: Callback<()>,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Clicked,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub title: String,
|
||||
pub onsignal: Option<Callback<()>>,
|
||||
}
|
||||
|
||||
impl Default for Props {
|
||||
fn default() -> Self {
|
||||
Props {
|
||||
title: "Send Signal".into(),
|
||||
onsignal: None,
|
||||
}
|
||||
}
|
||||
#[props(required)]
|
||||
pub onsignal: Callback<()>,
|
||||
}
|
||||
|
||||
impl Component for Button {
|
||||
|
@ -38,9 +30,7 @@ impl Component for Button {
|
|||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Clicked => {
|
||||
if let Some(ref mut callback) = self.onsignal {
|
||||
callback.emit(());
|
||||
}
|
||||
self.onsignal.emit(());
|
||||
}
|
||||
}
|
||||
false
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use yew::{html, Callback, Component, ComponentLink, Html, Renderable, ShouldRender};
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum Color {
|
||||
|
@ -7,31 +7,28 @@ pub enum Color {
|
|||
Blue,
|
||||
}
|
||||
|
||||
impl Default for Color {
|
||||
fn default() -> Self {
|
||||
Color::Green
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Counter {
|
||||
value: u32,
|
||||
color: Color,
|
||||
onclick: Option<Callback<u32>>,
|
||||
onclick: Callback<u32>,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Increase,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub initial: u32,
|
||||
pub color: Color,
|
||||
pub onclick: Option<Callback<u32>>,
|
||||
}
|
||||
|
||||
impl Default for Props {
|
||||
fn default() -> Self {
|
||||
Props {
|
||||
initial: 0,
|
||||
color: Color::Green,
|
||||
onclick: None,
|
||||
}
|
||||
}
|
||||
#[props(required)]
|
||||
pub onclick: Callback<u32>,
|
||||
}
|
||||
|
||||
impl Component for Counter {
|
||||
|
@ -50,9 +47,7 @@ impl Component for Counter {
|
|||
match msg {
|
||||
Msg::Increase => {
|
||||
self.value = self.value + 1;
|
||||
if let Some(ref onclick) = self.onclick {
|
||||
onclick.emit(self.value);
|
||||
}
|
||||
self.onclick.emit(self.value);
|
||||
}
|
||||
}
|
||||
true
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#![recursion_limit = "128"]
|
||||
|
||||
mod counter;
|
||||
mod button;
|
||||
mod barrier;
|
||||
mod button;
|
||||
mod counter;
|
||||
|
||||
use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender};
|
||||
use counter::{Counter, Color};
|
||||
use barrier::Barrier;
|
||||
use counter::{Color, Counter};
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct Model {
|
||||
with_barrier: bool,
|
||||
|
@ -19,8 +19,7 @@ pub enum Msg {
|
|||
ChildClicked(u32),
|
||||
}
|
||||
|
||||
impl Component for Model
|
||||
{
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
|
@ -41,17 +40,17 @@ impl Component for Model
|
|||
self.with_barrier = !self.with_barrier;
|
||||
true
|
||||
}
|
||||
Msg::ChildClicked(_value) => {
|
||||
false
|
||||
}
|
||||
Msg::ChildClicked(_value) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable<Model> for Model {
|
||||
fn view(&self) -> Html<Self> {
|
||||
let counter = |x| html! {
|
||||
<Counter initial=x color=&self.color onclick=Msg::ChildClicked/>
|
||||
let counter = |x| {
|
||||
html! {
|
||||
<Counter initial=x color=&self.color onclick=Msg::ChildClicked />
|
||||
}
|
||||
};
|
||||
html! {
|
||||
<div class="custom-components-example">
|
||||
|
|
|
@ -6,4 +6,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
yew = { path = "../.." }
|
||||
stdweb = "0.4.7"
|
||||
stdweb = "^0.4.16"
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
#![recursion_limit="128"]
|
||||
#![recursion_limit = "128"]
|
||||
#![deny(warnings)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate stdweb;
|
||||
|
||||
use yew::{html, Callback, Component, ComponentLink, Html, Renderable, ShouldRender};
|
||||
use stdweb::{_js_impl, js};
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct Model {
|
||||
payload: String,
|
||||
|
@ -18,16 +16,12 @@ pub enum Msg {
|
|||
AsyncPayload,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq, Clone)]
|
||||
pub struct Props {
|
||||
payload: String,
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = Props;
|
||||
type Properties = ();
|
||||
|
||||
fn create(Props { payload }: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
let payload = String::default();
|
||||
let debugged_payload = format!("{:?}", payload);
|
||||
Self {
|
||||
payload,
|
||||
|
@ -39,7 +33,15 @@ impl Component for Model {
|
|||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
use Msg::*;
|
||||
match msg {
|
||||
Payload(payload) => self.change(Self::Properties { payload }),
|
||||
Payload(payload) => {
|
||||
if payload != self.payload {
|
||||
self.debugged_payload = format!("{:?}", payload);
|
||||
self.payload = payload;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
AsyncPayload => {
|
||||
get_payload_later(self.link.send_back(Msg::Payload));
|
||||
false
|
||||
|
@ -47,14 +49,8 @@ impl Component for Model {
|
|||
}
|
||||
}
|
||||
|
||||
fn change(&mut self, Self::Properties { payload }: Self::Properties) -> ShouldRender {
|
||||
if payload == self.payload {
|
||||
fn change(&mut self, _: Self::Properties) -> ShouldRender {
|
||||
false
|
||||
} else {
|
||||
self.debugged_payload = format!("{:?}", payload);
|
||||
self.payload = payload;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +69,7 @@ impl Renderable<Model> for Model {
|
|||
{ "Get the payload later!" }
|
||||
</button>
|
||||
<p style="font-family: 'Monaco', monospace;">
|
||||
{ nbsp(self.debugged_payload.as_ref()) }
|
||||
{ nbsp(self.debugged_payload.as_str()) }
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ use std::rc::Rc;
|
|||
/// Callbacks should be used from JS callbacks or `setTimeout` calls.
|
||||
/// </aside>
|
||||
/// `Rc` wrapper used to make it clonable.
|
||||
#[must_use]
|
||||
pub struct Callback<IN>(Rc<dyn Fn(IN)>);
|
||||
|
||||
impl<IN, F: Fn(IN) + 'static> From<F> for Callback<IN> {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
use crate::callback::Callback;
|
||||
use crate::html::{ChangeData, Component, ComponentLink, Html, Renderable, ShouldRender};
|
||||
use crate::macros::html;
|
||||
use crate::macros::{html, Properties};
|
||||
|
||||
/// `Select` component.
|
||||
pub struct Select<T> {
|
||||
|
@ -31,7 +31,7 @@ pub enum Msg {
|
|||
}
|
||||
|
||||
/// Properties of `Select` component.
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct Props<T> {
|
||||
/// Initially selected value.
|
||||
pub selected: Option<T>,
|
||||
|
@ -40,18 +40,8 @@ pub struct Props<T> {
|
|||
/// Options are available to choose.
|
||||
pub options: Vec<T>,
|
||||
/// Callback to handle changes.
|
||||
pub onchange: Option<Callback<T>>,
|
||||
}
|
||||
|
||||
impl<T> Default for Props<T> {
|
||||
fn default() -> Self {
|
||||
Props {
|
||||
selected: None,
|
||||
disabled: false,
|
||||
options: Vec::new(),
|
||||
onchange: None,
|
||||
}
|
||||
}
|
||||
#[props(required)]
|
||||
pub onchange: Callback<T>,
|
||||
}
|
||||
|
||||
impl<T> Component for Select<T>
|
||||
|
@ -69,11 +59,9 @@ where
|
|||
match msg {
|
||||
Msg::Selected(value) => {
|
||||
if let Some(idx) = value {
|
||||
if let Some(ref mut callback) = self.props.onchange {
|
||||
let item = self.props.options.get(idx - 1).cloned();
|
||||
if let Some(value) = item {
|
||||
callback.emit(value);
|
||||
}
|
||||
self.props.onchange.emit(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
29
src/html.rs
29
src/html.rs
|
@ -23,10 +23,7 @@ pub trait Component: Sized + 'static {
|
|||
/// Control message type which `update` loop get.
|
||||
type Message: 'static;
|
||||
/// Properties type of component implementation.
|
||||
/// It sould be serializable because it's sent to dynamicaly created
|
||||
/// component (layed under `VComp`) and must be restored for a component
|
||||
/// with unknown type.
|
||||
type Properties: Clone + Default;
|
||||
type Properties: Properties;
|
||||
/// Initialization routine which could use a context.
|
||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self;
|
||||
/// Called everytime when a messages of `Msg` type received. It also takes a
|
||||
|
@ -40,6 +37,30 @@ pub trait Component: Sized + 'static {
|
|||
fn destroy(&mut self) {} // TODO Replace with `Drop`
|
||||
}
|
||||
|
||||
/// Trait for building properties for a component
|
||||
pub trait Properties {
|
||||
/// Builder that will be used to construct properties
|
||||
type Builder;
|
||||
|
||||
/// Entrypoint for building properties
|
||||
fn builder() -> Self::Builder;
|
||||
}
|
||||
|
||||
/// Builder for when a component has no properties
|
||||
pub struct EmptyBuilder;
|
||||
|
||||
impl Properties for () {
|
||||
type Builder = EmptyBuilder;
|
||||
|
||||
fn builder() -> Self::Builder {
|
||||
EmptyBuilder
|
||||
}
|
||||
}
|
||||
impl EmptyBuilder {
|
||||
/// Build empty properties
|
||||
pub fn build(self) {}
|
||||
}
|
||||
|
||||
/// Should be rendered relative to context and component environment.
|
||||
pub trait Renderable<COMP: Component> {
|
||||
/// Called by rendering loop.
|
||||
|
|
|
@ -74,6 +74,7 @@ pub use yew_macro::html;
|
|||
/// This module contains macros which implements html! macro and JSX-like templates
|
||||
pub mod macros {
|
||||
pub use crate::html;
|
||||
pub use yew_props_derive::Properties;
|
||||
}
|
||||
|
||||
pub mod agent;
|
||||
|
|
|
@ -150,12 +150,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, COMP, F, IN> Transformer<COMP, F, Option<Callback<IN>>> for VComp<COMP>
|
||||
impl<'a, COMP, F, IN> Transformer<COMP, F, Callback<IN>> for VComp<COMP>
|
||||
where
|
||||
COMP: Component + Renderable<COMP>,
|
||||
F: Fn(IN) -> COMP::Message + 'static,
|
||||
{
|
||||
fn transform(scope: ScopeHolder<COMP>, from: F) -> Option<Callback<IN>> {
|
||||
fn transform(scope: ScopeHolder<COMP>, from: F) -> Callback<IN> {
|
||||
let callback = move |arg| {
|
||||
let msg = from(arg);
|
||||
if let Some(ref mut sender) = *scope.borrow_mut() {
|
||||
|
@ -164,7 +164,7 @@ where
|
|||
panic!("unactivated callback, parent component have to activate it");
|
||||
}
|
||||
};
|
||||
Some(callback.into())
|
||||
callback.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
#![recursion_limit = "128"]
|
||||
|
||||
use yew::prelude::*;
|
||||
|
||||
fn compile_fail() {
|
||||
html! { <String /> };
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,9 @@
|
|||
error[E0277]: the trait bound `std::string::String: yew::html::Component` is not satisfied
|
||||
--> $DIR/html-component-fail-unimplemented.rs:6:14
|
||||
|
|
||||
6 | html! { <String /> };
|
||||
| ^^^^^^ the trait `yew::html::Component` is not implemented for `std::string::String`
|
||||
|
|
||||
= help: see issue #48214
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ChildProperties {
|
||||
pub string: String,
|
||||
#[props(required)]
|
||||
pub int: i32,
|
||||
}
|
||||
|
||||
|
@ -40,14 +41,11 @@ fn compile_fail() {
|
|||
html! { <ChildComponent invalid-prop-name=0 /> };
|
||||
html! { <ChildComponent unknown="unknown" /> };
|
||||
html! { <ChildComponent string= /> };
|
||||
html! { <ChildComponent string={} /> };
|
||||
html! { <ChildComponent string=3 /> };
|
||||
html! { <ChildComponent string={3} /> };
|
||||
html! { <ChildComponent int=1 string={} /> };
|
||||
html! { <ChildComponent int=1 string=3 /> };
|
||||
html! { <ChildComponent int=1 string={3} /> };
|
||||
html! { <ChildComponent int=0u32 /> };
|
||||
}
|
||||
|
||||
fn additional_fail() {
|
||||
html! { <String /> };
|
||||
html! { <ChildComponent string="abc" /> };
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -1,84 +1,93 @@
|
|||
error: expected component tag be of form `< .. />`
|
||||
--> $DIR/html-component-fail.rs:32:13
|
||||
--> $DIR/html-component-fail.rs:33:13
|
||||
|
|
||||
32 | html! { <ChildComponent> };
|
||||
33 | html! { <ChildComponent> };
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unexpected end of input, expected identifier
|
||||
--> $DIR/html-component-fail.rs:33:31
|
||||
--> $DIR/html-component-fail.rs:34:31
|
||||
|
|
||||
33 | html! { <ChildComponent:: /> };
|
||||
34 | html! { <ChildComponent:: /> };
|
||||
| ^
|
||||
|
||||
error: unexpected end of input, expected identifier
|
||||
--> $DIR/html-component-fail.rs:34:34
|
||||
--> $DIR/html-component-fail.rs:35:34
|
||||
|
|
||||
34 | html! { <ChildComponent with /> };
|
||||
35 | html! { <ChildComponent with /> };
|
||||
| ^
|
||||
|
||||
error: unexpected token
|
||||
--> $DIR/html-component-fail.rs:35:29
|
||||
--> $DIR/html-component-fail.rs:36:29
|
||||
|
|
||||
35 | html! { <ChildComponent props /> };
|
||||
36 | html! { <ChildComponent props /> };
|
||||
| ^^^^^
|
||||
|
||||
error: expected component tag be of form `< .. />`
|
||||
--> $DIR/html-component-fail.rs:36:13
|
||||
--> $DIR/html-component-fail.rs:37:13
|
||||
|
|
||||
36 | html! { <ChildComponent with props > };
|
||||
37 | html! { <ChildComponent with props > };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unexpected token
|
||||
--> $DIR/html-component-fail.rs:38:40
|
||||
--> $DIR/html-component-fail.rs:39:40
|
||||
|
|
||||
38 | html! { <ChildComponent with props () /> };
|
||||
39 | html! { <ChildComponent with props () /> };
|
||||
| ^^
|
||||
|
||||
error: expected identifier
|
||||
--> $DIR/html-component-fail.rs:39:29
|
||||
|
|
||||
39 | html! { <ChildComponent type=0 /> };
|
||||
| ^^^^
|
||||
|
||||
error: expected identifier
|
||||
--> $DIR/html-component-fail.rs:40:29
|
||||
|
|
||||
40 | html! { <ChildComponent invalid-prop-name=0 /> };
|
||||
40 | html! { <ChildComponent type=0 /> };
|
||||
| ^^^^
|
||||
|
||||
error: expected identifier
|
||||
--> $DIR/html-component-fail.rs:41:29
|
||||
|
|
||||
41 | html! { <ChildComponent invalid-prop-name=0 /> };
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unexpected end of input, expected expression
|
||||
--> $DIR/html-component-fail.rs:42:37
|
||||
--> $DIR/html-component-fail.rs:43:37
|
||||
|
|
||||
42 | html! { <ChildComponent string= /> };
|
||||
43 | html! { <ChildComponent string= /> };
|
||||
| ^
|
||||
|
||||
error[E0425]: cannot find value `blah` in this scope
|
||||
--> $DIR/html-component-fail.rs:37:34
|
||||
--> $DIR/html-component-fail.rs:38:34
|
||||
|
|
||||
37 | html! { <ChildComponent with blah /> };
|
||||
38 | html! { <ChildComponent with blah /> };
|
||||
| ^^^^ not found in this scope
|
||||
|
||||
error[E0609]: no field `unknown` on type `ChildProperties`
|
||||
--> $DIR/html-component-fail.rs:41:29
|
||||
--> $DIR/html-component-fail.rs:42:29
|
||||
|
|
||||
41 | html! { <ChildComponent unknown="unknown" /> };
|
||||
42 | html! { <ChildComponent unknown="unknown" /> };
|
||||
| ^^^^^^^ unknown field
|
||||
|
|
||||
= note: available fields are: `string`, `int`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/html-component-fail.rs:43:36
|
||||
error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder<ChildProperties_int_is_required>` in the current scope
|
||||
--> $DIR/html-component-fail.rs:42:29
|
||||
|
|
||||
43 | html! { <ChildComponent string={} /> };
|
||||
5 | #[derive(Properties, PartialEq)]
|
||||
| - method `unknown` not found for this
|
||||
...
|
||||
42 | html! { <ChildComponent unknown="unknown" /> };
|
||||
| ^^^^^^^
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/html-component-fail.rs:44:42
|
||||
|
|
||||
44 | html! { <ChildComponent int=1 string={} /> };
|
||||
| ^^ expected struct `std::string::String`, found ()
|
||||
|
|
||||
= note: expected type `std::string::String`
|
||||
found type `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/html-component-fail.rs:44:36
|
||||
--> $DIR/html-component-fail.rs:45:42
|
||||
|
|
||||
44 | html! { <ChildComponent string=3 /> };
|
||||
45 | html! { <ChildComponent int=1 string=3 /> };
|
||||
| ^
|
||||
| |
|
||||
| expected struct `std::string::String`, found integer
|
||||
|
@ -88,9 +97,9 @@ error[E0308]: mismatched types
|
|||
found type `{integer}`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/html-component-fail.rs:45:36
|
||||
--> $DIR/html-component-fail.rs:46:42
|
||||
|
|
||||
45 | html! { <ChildComponent string={3} /> };
|
||||
46 | html! { <ChildComponent int=1 string={3} /> };
|
||||
| ^^^
|
||||
| |
|
||||
| expected struct `std::string::String`, found integer
|
||||
|
@ -100,28 +109,23 @@ error[E0308]: mismatched types
|
|||
found type `{integer}`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/html-component-fail.rs:46:33
|
||||
--> $DIR/html-component-fail.rs:47:33
|
||||
|
|
||||
46 | html! { <ChildComponent int=0u32 /> };
|
||||
47 | html! { <ChildComponent int=0u32 /> };
|
||||
| ^^^^ expected i32, found u32
|
||||
help: you can convert an `u32` to `i32` and panic if the converted value wouldn't fit
|
||||
|
|
||||
46 | html! { <ChildComponent int=0u32.try_into().unwrap() /> };
|
||||
47 | html! { <ChildComponent int=0u32.try_into().unwrap() /> };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0277]: the trait bound `std::string::String: yew::html::Component` is not satisfied
|
||||
--> $DIR/html-component-fail.rs:50:14
|
||||
error[E0599]: no method named `string` found for type `ChildPropertiesBuilder<ChildProperties_int_is_required>` in the current scope
|
||||
--> $DIR/html-component-fail.rs:48:29
|
||||
|
|
||||
50 | html! { <String /> };
|
||||
| ^^^^^^ the trait `yew::html::Component` is not implemented for `std::string::String`
|
||||
5 | #[derive(Properties, PartialEq)]
|
||||
| - method `string` not found for this
|
||||
...
|
||||
48 | html! { <ChildComponent string="abc" /> };
|
||||
| ^^^^^^
|
||||
|
||||
error[E0277]: the trait bound `std::string::String: yew::html::Renderable<std::string::String>` is not satisfied
|
||||
--> $DIR/html-component-fail.rs:50:14
|
||||
|
|
||||
50 | html! { <String /> };
|
||||
| ^^^^^^ the trait `yew::html::Renderable<std::string::String>` is not implemented for `std::string::String`
|
||||
|
|
||||
= note: required by `yew::virtual_dom::vcomp::VComp::<COMP>::new`
|
||||
|
||||
Some errors have detailed explanations: E0277, E0308, E0425, E0609.
|
||||
For more information about an error, try `rustc --explain E0277`.
|
||||
Some errors have detailed explanations: E0308, E0425, E0599, E0609.
|
||||
For more information about an error, try `rustc --explain E0308`.
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
#[macro_use]
|
||||
mod helpers;
|
||||
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[derive(Properties, Default, PartialEq)]
|
||||
pub struct ChildProperties {
|
||||
pub string: String,
|
||||
#[props(required)]
|
||||
pub int: i32,
|
||||
pub vec: Vec<i32>,
|
||||
}
|
||||
|
@ -35,19 +36,19 @@ mod scoped {
|
|||
}
|
||||
|
||||
pass_helper! {
|
||||
html! { <ChildComponent /> };
|
||||
html! { <ChildComponent int=1 /> };
|
||||
|
||||
// backwards compat
|
||||
html! { <ChildComponent: /> };
|
||||
html! { <ChildComponent: int=1 /> };
|
||||
|
||||
html! {
|
||||
<>
|
||||
<ChildComponent />
|
||||
<scoped::ChildComponent />
|
||||
<ChildComponent int=1 />
|
||||
<scoped::ChildComponent int=1 />
|
||||
|
||||
// backwards compat
|
||||
<ChildComponent: />
|
||||
<scoped::ChildComponent: />
|
||||
<ChildComponent: int=1 />
|
||||
<scoped::ChildComponent: int=1 />
|
||||
</>
|
||||
};
|
||||
|
||||
|
@ -64,10 +65,10 @@ pass_helper! {
|
|||
|
||||
html! {
|
||||
<>
|
||||
<ChildComponent string="child" />
|
||||
<ChildComponent int=1 string="child" />
|
||||
<ChildComponent int=1 />
|
||||
<ChildComponent int={1+1} />
|
||||
<ChildComponent vec={vec![1]} />
|
||||
<ChildComponent int=1 vec={vec![1]} />
|
||||
<ChildComponent string={String::from("child")} int=1 />
|
||||
|
||||
// backwards compat
|
||||
|
@ -77,7 +78,7 @@ pass_helper! {
|
|||
|
||||
let name_expr = "child";
|
||||
html! {
|
||||
<ChildComponent string=name_expr />
|
||||
<ChildComponent int=1 string=name_expr />
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use yew::prelude::*;
|
||||
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct TestProperties {
|
||||
pub string: String,
|
||||
pub int: i32,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#[cfg(feature = "wasm-bindgen-test")]
|
||||
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
|
||||
use yew::macros::Properties;
|
||||
use yew::virtual_dom::VNode;
|
||||
use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender};
|
||||
|
||||
|
@ -8,21 +9,12 @@ wasm_bindgen_test_configure!(run_in_browser);
|
|||
|
||||
struct Comp;
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Properties)]
|
||||
struct Props {
|
||||
field_1: u32,
|
||||
field_2: u32,
|
||||
}
|
||||
|
||||
impl Default for Props {
|
||||
fn default() -> Self {
|
||||
Props {
|
||||
field_1: 0,
|
||||
field_2: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Comp {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
|
|
Loading…
Reference in New Issue