From 6866d0d3beb66680365d5ab8f27128c3848cd7e8 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Wed, 1 Nov 2023 11:04:07 +0100 Subject: [PATCH 01/21] Port yew-autoprops to yew-macro --- packages/yew-macro/src/autoprops.rs | 218 ++++++++++++++++++ packages/yew-macro/src/function_component.rs | 34 ++- packages/yew-macro/src/lib.rs | 12 + .../function_component_attr/autoprops-fail.rs | 66 ++++++ .../autoprops-fail.stderr | 83 +++++++ .../function_component_attr/autoprops-pass.rs | 102 ++++++++ .../bad-props-param-fail.stderr | 4 +- .../multiple-param-fail.stderr | 8 +- packages/yew/src/functional/mod.rs | 6 +- 9 files changed, 521 insertions(+), 12 deletions(-) create mode 100644 packages/yew-macro/src/autoprops.rs create mode 100644 packages/yew-macro/tests/function_component_attr/autoprops-fail.rs create mode 100644 packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr create mode 100644 packages/yew-macro/tests/function_component_attr/autoprops-pass.rs diff --git a/packages/yew-macro/src/autoprops.rs b/packages/yew-macro/src/autoprops.rs new file mode 100644 index 00000000000..5c0d21c14cb --- /dev/null +++ b/packages/yew-macro/src/autoprops.rs @@ -0,0 +1,218 @@ +use quote::quote; + +use crate::function_component::FunctionComponentName; + +#[derive(Clone)] +pub struct Autoprops { + item_fn: syn::ItemFn, + properties_name: syn::Ident, +} + +impl syn::parse::Parse for Autoprops { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let parsed: syn::Item = input.parse()?; + + let item_fn = match parsed { + syn::Item::Fn(m) => m, + item => { + return Err(syn::Error::new_spanned( + item, + "`autoprops` attribute can only be applied to functions", + )) + } + }; + + let syn::ItemFn { attrs, sig, .. } = &item_fn; + + let mut component_name = item_fn.sig.ident.clone(); + + attrs + .iter() + .find(|attr| { + match &attr.meta { + syn::Meta::Path(path) => { + if let Some(last_segment) = path.segments.last() { + if last_segment.ident == "function_component" { + return true; + } + } + } + syn::Meta::List(syn::MetaList { path, tokens, .. }) => { + if let Some(last_segment) = path.segments.last() { + if last_segment.ident == "function_component" { + if let Ok(attr) = + syn::parse2::(tokens.clone()) + { + if let Some(name) = attr.component_name { + component_name = name; + } + } + return true; + } + } + } + _ => {} + } + false + }) + .ok_or_else(|| { + syn::Error::new_spanned( + sig, + "could not find #[function_component] attribute in function declaration \ + (#[autoprops] must be place *before* #[function_component])", + ) + })?; + + for input in &sig.inputs { + match input { + syn::FnArg::Typed(syn::PatType { pat, .. }) => match pat.as_ref() { + syn::Pat::Wild(wild) => { + return Err(syn::Error::new_spanned( + wild, + "cannot use `_` as field name", + )); + } + _ => {} + }, + _ => {} + } + } + + let properties_name = syn::Ident::new( + &format!("{}Props", component_name), + proc_macro2::Span::call_site(), + ); + + Ok(Self { + properties_name, + item_fn, + }) + } +} + +impl Autoprops { + pub fn apply_args(&mut self, args: AutopropsArgs) { + if let Some(name) = args.properties_name { + self.properties_name = name; + } + } + + fn print_function_component(&self) -> proc_macro2::TokenStream { + let properties_name = &self.properties_name; + let syn::ItemFn { + attrs, + vis, + sig, + block, + } = &self.item_fn; + + let fn_name = &sig.ident; + let (impl_generics, type_generics, where_clause) = sig.generics.split_for_impl(); + let inputs = if sig.inputs.is_empty() { + quote! { (): &() } + } else { + // NOTE: function components currently don't accept receivers, we're just passing the + // information to the next macro to fail and give its own error message + let receivers = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Receiver(receiver) => Some(receiver), + _ => None, + }) + .collect::>(); + let args = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { pat, .. }) => Some(quote! { #pat }), + _ => None, + }) + .collect::>(); + quote! { #(#receivers,)* #properties_name { #(#args),* }: &#properties_name #type_generics } + }; + let clones = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { pat, ty, .. }) + if !matches!(**ty, syn::Type::Reference(_)) => + { + Some(quote! { let #pat = ::std::clone::Clone::clone(#pat); }) + } + _ => None, + }) + .collect::>(); + + quote! { + #(#attrs)* + #vis fn #fn_name #impl_generics (#inputs) -> ::yew::Html #where_clause { + #(#clones)* + #block + } + } + } + + fn print_properties_struct(&self) -> proc_macro2::TokenStream { + let properties_name = &self.properties_name; + let syn::ItemFn { vis, sig, .. } = &self.item_fn; + + if sig.inputs.is_empty() { + return quote! {}; + } + + let (impl_generics, _type_generics, where_clause) = sig.generics.split_for_impl(); + let fields = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { attrs, pat, ty, .. }) => match ty.as_ref() { + syn::Type::Reference(syn::TypeReference { elem, .. }) => { + Some(quote! { #(#attrs)* #pat: #elem, }) + } + _ => Some(quote! { #(#attrs)* #pat: #ty, }), + }, + _ => None, + }) + .collect::>(); + + quote! { + #[derive(::yew::Properties, ::std::cmp::PartialEq)] + #vis struct #properties_name #impl_generics #where_clause { + #(#fields)* + } + } + } +} + +impl quote::ToTokens for Autoprops { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let function_component = self.print_function_component(); + let properties_struct = self.print_properties_struct(); + + tokens.extend(quote! { + #function_component + #properties_struct + }) + } +} + +pub struct AutopropsArgs { + pub properties_name: Option, +} + +impl syn::parse::Parse for AutopropsArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + if input.is_empty() { + return Ok(Self { + properties_name: None, + }); + } + + let properties_name = input.parse()?; + + Ok(Self { + properties_name: Some(properties_name), + }) + } +} diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index d17b5b65155..aba7944db31 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -47,6 +47,32 @@ impl Parse for FunctionComponent { block, } = func; + if let Some(_attr) = attrs.iter().find(|attr| { + match &attr.meta { + syn::Meta::Path(path) => { + if let Some(last_segment) = path.segments.last() { + if last_segment.ident == "autoprops" { + return true; + } + } + } + syn::Meta::List(syn::MetaList { path, .. }) => { + if let Some(last_segment) = path.segments.last() { + if last_segment.ident == "autoprops" { + return true; + } + } + } + _ => {} + } + false + }) { + return Err(syn::Error::new_spanned( + sig, + "#[autoprops] must be placed *before* #[function_component]", + )); + } + if sig.generics.lifetimes().next().is_some() { return Err(syn::Error::new_spanned( sig.generics, @@ -111,7 +137,8 @@ impl Parse for FunctionComponent { } ty => { let msg = format!( - "expected a reference to a `Properties` type (try: `&{}`)", + "expected a reference to a `Properties` type \ + (try: `&{}` or use #[autoprops])", ty.to_token_stream() ); return Err(syn::Error::new_spanned(ty, msg)); @@ -133,7 +160,8 @@ impl Parse for FunctionComponent { let params: TokenStream = inputs.map(|it| it.to_token_stream()).collect(); return Err(syn::Error::new_spanned( params, - "function components can accept at most one parameter for the props", + "function components can accept at most one parameter for the props, \ + maybe you wanted to use #[autoprops]?", )); } @@ -393,7 +421,7 @@ impl FunctionComponent { } pub struct FunctionComponentName { - component_name: Option, + pub component_name: Option, } impl Parse for FunctionComponentName { diff --git a/packages/yew-macro/src/lib.rs b/packages/yew-macro/src/lib.rs index 5d7216d84a0..e72d42c37a2 100644 --- a/packages/yew-macro/src/lib.rs +++ b/packages/yew-macro/src/lib.rs @@ -48,6 +48,7 @@ //! //! Please refer to [https://github.com/yewstack/yew](https://github.com/yewstack/yew) for how to set this up. +mod autoprops; mod classes; mod derive_props; mod function_component; @@ -58,6 +59,7 @@ mod stringify; mod use_prepared_state; mod use_transitive_state; +use autoprops::{Autoprops, AutopropsArgs}; use derive_props::DerivePropsInput; use function_component::{function_component_impl, FunctionComponent, FunctionComponentName}; use hook::{hook_impl, HookFn}; @@ -148,6 +150,16 @@ pub fn function_component(attr: TokenStream, item: TokenStream) -> proc_macro::T .into() } +#[proc_macro_error::proc_macro_error] +#[proc_macro_attribute] +pub fn autoprops(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream { + let mut autoprops = parse_macro_input!(item as Autoprops); + let args = parse_macro_input!(attr as AutopropsArgs); + autoprops.apply_args(args); + + TokenStream::from(autoprops.into_token_stream()) +} + #[proc_macro_error::proc_macro_error] #[proc_macro_attribute] pub fn hook(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream { diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-fail.rs b/packages/yew-macro/tests/function_component_attr/autoprops-fail.rs new file mode 100644 index 00000000000..5bc8be54aad --- /dev/null +++ b/packages/yew-macro/tests/function_component_attr/autoprops-fail.rs @@ -0,0 +1,66 @@ +use yew::prelude::*; + +#[autoprops] +#[function_component] +fn CantAcceptReceiver(&self, b: bool) -> Html { + html! { +

{b}

+ } +} + +#[autoprops] +fn not_a_function_component(b: bool) -> Html { + html! { +

{b}

+ } +} + +#[function_component(WrongAttrsOrder)] +#[autoprops] +fn wrong_attrs_order(b: bool) -> Html { + html! { +

{b}

+ } +} + +#[autoprops] +#[function_component(let)] +fn BadFunctionComponent(b: bool) -> Html { + html! { +

{b}

+ } +} + +#[derive(PartialEq)] +struct NotClonable(u32); + +#[autoprops] +#[function_component] +fn TypeIsNotClone(stuff: NotClonable) -> Html { + drop(stuff); + html! { +

+ } +} + +#[derive(Clone)] +struct NotPartialEq(u32); + +#[autoprops] +#[function_component] +fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { + drop(stuff); + html! { +

+ } +} + +#[autoprops] +#[function_component] +fn InvalidFieldName(_: u32) -> Html { + html! { +

+ } +} + +fn main() {} diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr b/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr new file mode 100644 index 00000000000..df2a783c24a --- /dev/null +++ b/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr @@ -0,0 +1,83 @@ +error: function components can't accept a receiver + --> tests/function_component_attr/autoprops-fail.rs:5:23 + | +5 | fn CantAcceptReceiver(&self, b: bool) -> Html { + | ^^^^^ + +error: could not find #[function_component] attribute in function declaration (#[autoprops] must be place *before* #[function_component]) + --> tests/function_component_attr/autoprops-fail.rs:12:1 + | +12 | fn not_a_function_component(b: bool) -> Html { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: #[autoprops] must be placed *before* #[function_component] + --> tests/function_component_attr/autoprops-fail.rs:20:1 + | +20 | fn wrong_attrs_order(b: bool) -> Html { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected identifier, found keyword `let` + --> tests/function_component_attr/autoprops-fail.rs:27:22 + | +27 | #[function_component(let)] + | ^^^ + +error: cannot use `_` as field name + --> tests/function_component_attr/autoprops-fail.rs:60:21 + | +60 | fn InvalidFieldName(_: u32) -> Html { + | ^ + +error[E0277]: the trait bound `NotClonable: Clone` is not satisfied + --> tests/function_component_attr/autoprops-fail.rs:39:19 + | +37 | #[autoprops] + | ------------ required by a bound introduced by this call +38 | #[function_component] +39 | fn TypeIsNotClone(stuff: NotClonable) -> Html { + | ^^^^^ the trait `Clone` is not implemented for `NotClonable` + | +help: consider annotating `NotClonable` with `#[derive(Clone)]` + | +35 | #[derive(Clone)] + | + +error[E0369]: binary operation `==` cannot be applied to type `NotPartialEq` + --> tests/function_component_attr/autoprops-fail.rs:51:23 + | +49 | #[autoprops] + | ------------ in this procedural macro expansion +50 | #[function_component] +51 | fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { + | ^^^^^^^^^^^^^^^^^^^ + | +note: an implementation of `PartialEq<_>` might be missing for `NotPartialEq` + --> tests/function_component_attr/autoprops-fail.rs:47:1 + | +47 | struct NotPartialEq(u32); + | ^^^^^^^^^^^^^^^^^^^ must implement `PartialEq<_>` + = note: this error originates in the derive macro `::std::cmp::PartialEq` which comes from the expansion of the attribute macro `autoprops` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `NotPartialEq` with `#[derive(PartialEq)]` + | +47 | #[derive(PartialEq)] + | + +error[E0369]: binary operation `!=` cannot be applied to type `NotPartialEq` + --> tests/function_component_attr/autoprops-fail.rs:51:23 + | +49 | #[autoprops] + | ------------ in this procedural macro expansion +50 | #[function_component] +51 | fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { + | ^^^^^^^^^^^^^^^^^^^ + | +note: an implementation of `PartialEq<_>` might be missing for `NotPartialEq` + --> tests/function_component_attr/autoprops-fail.rs:47:1 + | +47 | struct NotPartialEq(u32); + | ^^^^^^^^^^^^^^^^^^^ must implement `PartialEq<_>` + = note: this error originates in the derive macro `::std::cmp::PartialEq` which comes from the expansion of the attribute macro `autoprops` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `NotPartialEq` with `#[derive(PartialEq)]` + | +47 | #[derive(PartialEq)] + | diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs b/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs new file mode 100644 index 00000000000..06c48fdd7b1 --- /dev/null +++ b/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs @@ -0,0 +1,102 @@ +#![no_implicit_prelude] + +// Shadow primitives +#[allow(non_camel_case_types)] +pub struct bool; +#[allow(non_camel_case_types)] +pub struct char; +#[allow(non_camel_case_types)] +pub struct f32; +#[allow(non_camel_case_types)] +pub struct f64; +#[allow(non_camel_case_types)] +pub struct i128; +#[allow(non_camel_case_types)] +pub struct i16; +#[allow(non_camel_case_types)] +pub struct i32; +#[allow(non_camel_case_types)] +pub struct i64; +#[allow(non_camel_case_types)] +pub struct i8; +#[allow(non_camel_case_types)] +pub struct isize; +#[allow(non_camel_case_types)] +pub struct str; +#[allow(non_camel_case_types)] +pub struct u128; +#[allow(non_camel_case_types)] +pub struct u16; +#[allow(non_camel_case_types)] +pub struct u32; +#[allow(non_camel_case_types)] +pub struct u64; +#[allow(non_camel_case_types)] +pub struct u8; +#[allow(non_camel_case_types)] +pub struct usize; + +#[::yew::autoprops] +#[::yew::function_component] +fn CompUseFnName() -> ::yew::Html +{ + ::yew::html! { +

+ } +} + +#[::yew::autoprops] +#[::yew::function_component(CompNoProperties)] +fn comp_no_properties() -> ::yew::Html +{ + ::yew::html! { +

+ } +} + +#[::yew::autoprops] +#[::yew::function_component(CompNoGenerics)] +fn comp_no_generics(#[prop_or_default] b: ::std::primitive::bool, a: &::yew::AttrValue) -> ::yew::Html +{ + let _: ::std::primitive::bool = b; + let _: &::yew::AttrValue = a; + ::yew::html! { +

+ } +} + +#[::yew::autoprops] +#[::yew::function_component(CompGenerics)] +fn comp_generics(b: T1, a: &T2) -> ::yew::Html +where + T1: ::std::cmp::PartialEq + ::std::clone::Clone, + T2: ::std::cmp::PartialEq, +{ + let _: T1 = b; + let _: &T2 = a; + ::yew::html! { +

+ } +} + +#[::yew::autoprops] +#[::yew::function_component(ConstGenerics)] +fn const_generics(xs: [::std::primitive::u32; N]) -> ::yew::Html { + let _: [::std::primitive::u32; N] = xs; + ::yew::html! { +
+ { N } +
+ } +} + +fn compile_pass() { + ::yew::html! { }; + ::yew::html! { }; + ::yew::html! { }; + ::yew::html! { b=true a="foo" /> }; + + ::yew::html! { xs={[1_u32, 2_u32]} /> }; +} + +fn main() {} diff --git a/packages/yew-macro/tests/function_component_attr/bad-props-param-fail.stderr b/packages/yew-macro/tests/function_component_attr/bad-props-param-fail.stderr index 479b44a6154..21ce368ac29 100644 --- a/packages/yew-macro/tests/function_component_attr/bad-props-param-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/bad-props-param-fail.stderr @@ -1,5 +1,5 @@ -error: expected a reference to a `Properties` type (try: `&Props`) - --> $DIR/bad-props-param-fail.rs:9:16 +error: expected a reference to a `Properties` type (try: `&Props` or use #[autoprops]) + --> tests/function_component_attr/bad-props-param-fail.rs:9:16 | 9 | fn comp(props: Props) -> Html { | ^^^^^ diff --git a/packages/yew-macro/tests/function_component_attr/multiple-param-fail.stderr b/packages/yew-macro/tests/function_component_attr/multiple-param-fail.stderr index ca8bc2ed2da..a214dfe124e 100644 --- a/packages/yew-macro/tests/function_component_attr/multiple-param-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/multiple-param-fail.stderr @@ -1,11 +1,11 @@ -error: function components can accept at most one parameter for the props - --> $DIR/multiple-param-fail.rs:9:24 +error: function components can accept at most one parameter for the props, maybe you wanted to use #[autoprops]? + --> tests/function_component_attr/multiple-param-fail.rs:9:24 | 9 | fn comp(props: &Props, invalid: String) -> Html { | ^^^^^^^^^^^^^^^ -error: function components can accept at most one parameter for the props - --> $DIR/multiple-param-fail.rs:19:25 +error: function components can accept at most one parameter for the props, maybe you wanted to use #[autoprops]? + --> tests/function_component_attr/multiple-param-fail.rs:19:25 | 19 | fn comp3(props: &Props, invalid: String, another_invalid: u32) -> Html { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 6ba9f601f91..5486b786935 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -34,6 +34,8 @@ use crate::Properties; mod hooks; pub use hooks::*; +/// This attribute creates a user-defined hook from a normal Rust function. +pub use yew_macro::hook; /// This attribute creates a function component from a normal Rust function. /// /// Functions with this attribute **must** return `Html` and can optionally take an argument @@ -60,9 +62,7 @@ pub use hooks::*; /// } /// } /// ``` -pub use yew_macro::function_component; -/// This attribute creates a user-defined hook from a normal Rust function. -pub use yew_macro::hook; +pub use yew_macro::{autoprops, function_component}; type ReRender = Rc; From b5199be0b1b440e80a3483a35fbf9482f7d9745d Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Wed, 1 Nov 2023 11:08:50 +0100 Subject: [PATCH 02/21] clippy --- packages/yew-macro/src/autoprops.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/yew-macro/src/autoprops.rs b/packages/yew-macro/src/autoprops.rs index 5c0d21c14cb..95ac5bbefba 100644 --- a/packages/yew-macro/src/autoprops.rs +++ b/packages/yew-macro/src/autoprops.rs @@ -64,17 +64,13 @@ impl syn::parse::Parse for Autoprops { })?; for input in &sig.inputs { - match input { - syn::FnArg::Typed(syn::PatType { pat, .. }) => match pat.as_ref() { - syn::Pat::Wild(wild) => { - return Err(syn::Error::new_spanned( - wild, - "cannot use `_` as field name", - )); - } - _ => {} - }, - _ => {} + if let syn::FnArg::Typed(syn::PatType { pat, .. }) = input { + if let syn::Pat::Wild(wild) = pat.as_ref() { + return Err(syn::Error::new_spanned( + wild, + "cannot use `_` as field name", + )); + } } } From fd79b5844724d33d72efc7a4f894d0ff50ebe8de Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Wed, 1 Nov 2023 11:11:25 +0100 Subject: [PATCH 03/21] rustfmt nightly --- packages/yew-macro/src/autoprops.rs | 2 +- packages/yew-macro/src/function_component.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/yew-macro/src/autoprops.rs b/packages/yew-macro/src/autoprops.rs index 95ac5bbefba..faa02ae9462 100644 --- a/packages/yew-macro/src/autoprops.rs +++ b/packages/yew-macro/src/autoprops.rs @@ -59,7 +59,7 @@ impl syn::parse::Parse for Autoprops { syn::Error::new_spanned( sig, "could not find #[function_component] attribute in function declaration \ - (#[autoprops] must be place *before* #[function_component])", + (#[autoprops] must be place *before* #[function_component])", ) })?; diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index aba7944db31..d8bf190df25 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -137,8 +137,8 @@ impl Parse for FunctionComponent { } ty => { let msg = format!( - "expected a reference to a `Properties` type \ - (try: `&{}` or use #[autoprops])", + "expected a reference to a `Properties` type (try: `&{}` or use \ + #[autoprops])", ty.to_token_stream() ); return Err(syn::Error::new_spanned(ty, msg)); @@ -160,8 +160,8 @@ impl Parse for FunctionComponent { let params: TokenStream = inputs.map(|it| it.to_token_stream()).collect(); return Err(syn::Error::new_spanned( params, - "function components can accept at most one parameter for the props, \ - maybe you wanted to use #[autoprops]?", + "function components can accept at most one parameter for the props, maybe you \ + wanted to use #[autoprops]?", )); } From df251e31838ac6d4b49c6a161e8c6c801fa8b091 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Wed, 1 Nov 2023 11:19:57 +0100 Subject: [PATCH 04/21] Remove unnecessary unit when no props are provided --- packages/yew-macro/src/autoprops.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yew-macro/src/autoprops.rs b/packages/yew-macro/src/autoprops.rs index faa02ae9462..1d0c5019d2a 100644 --- a/packages/yew-macro/src/autoprops.rs +++ b/packages/yew-macro/src/autoprops.rs @@ -105,7 +105,7 @@ impl Autoprops { let fn_name = &sig.ident; let (impl_generics, type_generics, where_clause) = sig.generics.split_for_impl(); let inputs = if sig.inputs.is_empty() { - quote! { (): &() } + quote! {} } else { // NOTE: function components currently don't accept receivers, we're just passing the // information to the next macro to fail and give its own error message From 9f2f258cdb8ab4e03c01b004cb548b170993ead2 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Wed, 1 Nov 2023 11:23:28 +0100 Subject: [PATCH 05/21] Making sure field's attrs are transmitted to the Properties struct --- .../yew-macro/tests/function_component_attr/autoprops-pass.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs b/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs index 06c48fdd7b1..e0499072f00 100644 --- a/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs +++ b/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs @@ -93,6 +93,7 @@ fn const_generics(xs: [::std::primitive::u32; fn compile_pass() { ::yew::html! { }; ::yew::html! { }; + ::yew::html! { }; ::yew::html! { }; ::yew::html! { b=true a="foo" /> }; From 0e818ba7fb89c3f1a15c2710d8f0139b3cc03067 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Wed, 1 Nov 2023 11:34:57 +0100 Subject: [PATCH 06/21] Update doc --- .../function-components/properties.mdx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/website/docs/concepts/function-components/properties.mdx b/website/docs/concepts/function-components/properties.mdx index d331425ec92..c6e1c09002a 100644 --- a/website/docs/concepts/function-components/properties.mdx +++ b/website/docs/concepts/function-components/properties.mdx @@ -263,6 +263,28 @@ fn App() -> Html { } ``` +## Automatically generate properties (autoprops) + +In order to streamline your development process, you can also use the macro +`#[autoprops]` that will automatically generate the `Properties` struct for you. + +```rust +use yew::prelude::*; + +#[autoprops] +#[function_component] +fn Greetings( + #[prop_or("Hello".into())] + message: AttrValue, + #[prop_or("World".into())] + name: AttrValue, +) -> Html { + html! {<>{message}{name}} +} + +// The properties struct "GreetingsProps" will be generated automatically. +``` + ## Evaluation Order Props are evaluated in the order they're specified, as shown by the following example: @@ -296,7 +318,12 @@ These include, but are not limited to: **Why is this bad?** Interior mutability (such as with `RefCell`, `Mutex`, etc.) should _generally_ be avoided. It can cause problems with re-renders (Yew doesn't know when the state has changed) so you may have to manually force a render. Like all things, it has its place. Use it with caution. -3. You tell us. Did you run into an edge-case you wish you knew about earlier? Feel free to create an issue +3. Using `Vec` type instead of `IArray`.
+ **Why is this bad?** `Vec`, just like `String`, can also be expensive to clone. `IArray` is either + a reference-counted slice (`Rc`) or a `&'static [T]`, thus very cheap to clone.
+ **Note**: `IArray` can be imported from [implicit-clone](https://crates.io/crates/implicit-clone) + See that crate to learn more. +4. You tell us. Did you run into an edge-case you wish you knew about earlier? Feel free to create an issue or PR a fix to this documentation. ## yew-autoprops From 64dfc9f47c382bcb359e03dae91816f4093cd430 Mon Sep 17 00:00:00 2001 From: KirillSemyonkin Date: Wed, 1 Nov 2023 16:22:12 +0300 Subject: [PATCH 07/21] Fix error message typo --- packages/yew-macro/src/autoprops.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yew-macro/src/autoprops.rs b/packages/yew-macro/src/autoprops.rs index 1d0c5019d2a..cc28937606b 100644 --- a/packages/yew-macro/src/autoprops.rs +++ b/packages/yew-macro/src/autoprops.rs @@ -59,7 +59,7 @@ impl syn::parse::Parse for Autoprops { syn::Error::new_spanned( sig, "could not find #[function_component] attribute in function declaration \ - (#[autoprops] must be place *before* #[function_component])", + (#[autoprops] must be placed *before* #[function_component])", ) })?; From 98f62b5f2242139bfd1abbaf740eda9783cd476d Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 2 Nov 2023 10:03:17 +0100 Subject: [PATCH 08/21] update stderr --- .../tests/function_component_attr/autoprops-fail.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr b/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr index df2a783c24a..e09325262e0 100644 --- a/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr @@ -4,7 +4,7 @@ error: function components can't accept a receiver 5 | fn CantAcceptReceiver(&self, b: bool) -> Html { | ^^^^^ -error: could not find #[function_component] attribute in function declaration (#[autoprops] must be place *before* #[function_component]) +error: could not find #[function_component] attribute in function declaration (#[autoprops] must be placed *before* #[function_component]) --> tests/function_component_attr/autoprops-fail.rs:12:1 | 12 | fn not_a_function_component(b: bool) -> Html { From 7643e925f8ec312efcb7ba2dc02fe8e092972800 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 2 Nov 2023 10:51:47 +0100 Subject: [PATCH 09/21] Fix: allow generics not used in argument --- packages/yew-macro/src/autoprops.rs | 37 ++++++++++++++++--- .../function_component_attr/autoprops-pass.rs | 12 ++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/packages/yew-macro/src/autoprops.rs b/packages/yew-macro/src/autoprops.rs index cc28937606b..3847f4320af 100644 --- a/packages/yew-macro/src/autoprops.rs +++ b/packages/yew-macro/src/autoprops.rs @@ -104,9 +104,7 @@ impl Autoprops { let fn_name = &sig.ident; let (impl_generics, type_generics, where_clause) = sig.generics.split_for_impl(); - let inputs = if sig.inputs.is_empty() { - quote! {} - } else { + let inputs = if self.needs_a_properties_struct() { // NOTE: function components currently don't accept receivers, we're just passing the // information to the next macro to fail and give its own error message let receivers = sig @@ -125,7 +123,19 @@ impl Autoprops { _ => None, }) .collect::>(); - quote! { #(#receivers,)* #properties_name { #(#args),* }: &#properties_name #type_generics } + let phantom = sig.generics.type_params().next().is_some().then(|| { + quote! { + , phantom: _ + } + }); + quote! { + #(#receivers,)* #properties_name { + #(#args),* + #phantom + }: &#properties_name #type_generics + } + } else { + quote! {} }; let clones = sig .inputs @@ -153,7 +163,7 @@ impl Autoprops { let properties_name = &self.properties_name; let syn::ItemFn { vis, sig, .. } = &self.item_fn; - if sig.inputs.is_empty() { + if !self.needs_a_properties_struct() { return quote! {}; } @@ -171,14 +181,31 @@ impl Autoprops { _ => None, }) .collect::>(); + let type_params = sig + .generics + .type_params() + .map(|param| ¶m.ident) + .collect::>(); + let phantom = (!type_params.is_empty()).then(|| { + quote! { + #[prop_or_default] + phantom: ::std::marker::PhantomData <( #(#type_params),* )>, + } + }); quote! { #[derive(::yew::Properties, ::std::cmp::PartialEq)] #vis struct #properties_name #impl_generics #where_clause { #(#fields)* + #phantom } } } + + fn needs_a_properties_struct(&self) -> bool { + let syn::ItemFn { sig, .. } = &self.item_fn; + !sig.inputs.is_empty() || sig.generics.type_params().next().is_some() + } } impl quote::ToTokens for Autoprops { diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs b/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs index e0499072f00..90ca4d7170d 100644 --- a/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs +++ b/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs @@ -79,6 +79,18 @@ where } } +#[::yew::autoprops] +#[::yew::function_component(CompGenericsWithoutField)] +fn comp_generics_without_field(b: ::std::primitive::bool) -> ::yew::Html +where + T1: ::std::cmp::PartialEq, + T2: ::std::cmp::PartialEq, +{ + ::yew::html! { +

+ } +} + #[::yew::autoprops] #[::yew::function_component(ConstGenerics)] fn const_generics(xs: [::std::primitive::u32; N]) -> ::yew::Html { From ed70758510c03b69fecd8bc5812a7bcd95220678 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 2 Nov 2023 11:07:39 +0100 Subject: [PATCH 10/21] Replace derive PartialEq with manually built PartialEq --- packages/yew-macro/src/autoprops.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/yew-macro/src/autoprops.rs b/packages/yew-macro/src/autoprops.rs index 3847f4320af..c91af275e80 100644 --- a/packages/yew-macro/src/autoprops.rs +++ b/packages/yew-macro/src/autoprops.rs @@ -167,7 +167,7 @@ impl Autoprops { return quote! {}; } - let (impl_generics, _type_generics, where_clause) = sig.generics.split_for_impl(); + let (impl_generics, type_generics, where_clause) = sig.generics.split_for_impl(); let fields = sig .inputs .iter() @@ -192,13 +192,30 @@ impl Autoprops { phantom: ::std::marker::PhantomData <( #(#type_params),* )>, } }); + let fields_eq = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { attrs, pat, ty, .. }) => Some(quote! { + self.#pat == rhs.#pat && + }), + _ => None, + }) + .collect::>(); quote! { - #[derive(::yew::Properties, ::std::cmp::PartialEq)] + #[derive(::yew::Properties)] #vis struct #properties_name #impl_generics #where_clause { #(#fields)* #phantom } + + impl #impl_generics ::std::cmp::PartialEq for #properties_name #type_generics + #where_clause { + fn eq(&self, rhs: &Self) -> ::std::primitive::bool { + #(#fields_eq)* true + } + } } } From fe7d0b3caf3375f87f207d3f2c6db717929d714f Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 2 Nov 2023 11:13:25 +0100 Subject: [PATCH 11/21] Fix test & cleanup warnings --- packages/yew-macro/src/autoprops.rs | 2 +- .../tests/function_component_attr/autoprops-pass.rs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/yew-macro/src/autoprops.rs b/packages/yew-macro/src/autoprops.rs index c91af275e80..9a2592b5930 100644 --- a/packages/yew-macro/src/autoprops.rs +++ b/packages/yew-macro/src/autoprops.rs @@ -196,7 +196,7 @@ impl Autoprops { .inputs .iter() .filter_map(|arg| match arg { - syn::FnArg::Typed(syn::PatType { attrs, pat, ty, .. }) => Some(quote! { + syn::FnArg::Typed(syn::PatType { pat, .. }) => Some(quote! { self.#pat == rhs.#pat && }), _ => None, diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs b/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs index 90ca4d7170d..0ff0ab5d755 100644 --- a/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs +++ b/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs @@ -81,11 +81,7 @@ where #[::yew::autoprops] #[::yew::function_component(CompGenericsWithoutField)] -fn comp_generics_without_field(b: ::std::primitive::bool) -> ::yew::Html -where - T1: ::std::cmp::PartialEq, - T2: ::std::cmp::PartialEq, -{ +fn comp_generics_without_field(b: ::std::primitive::bool) -> ::yew::Html { ::yew::html! {

} From ee441f3fdb19e6a6bece56fa3ab8b8e58add0bfa Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 2 Nov 2023 11:14:34 +0100 Subject: [PATCH 12/21] Update stderr --- .../autoprops-fail.stderr | 29 ++----------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr b/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr index e09325262e0..0affcb69e53 100644 --- a/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr @@ -43,40 +43,17 @@ help: consider annotating `NotClonable` with `#[derive(Clone)]` | error[E0369]: binary operation `==` cannot be applied to type `NotPartialEq` - --> tests/function_component_attr/autoprops-fail.rs:51:23 + --> tests/function_component_attr/autoprops-fail.rs:49:1 | 49 | #[autoprops] - | ------------ in this procedural macro expansion -50 | #[function_component] -51 | fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | note: an implementation of `PartialEq<_>` might be missing for `NotPartialEq` --> tests/function_component_attr/autoprops-fail.rs:47:1 | 47 | struct NotPartialEq(u32); | ^^^^^^^^^^^^^^^^^^^ must implement `PartialEq<_>` - = note: this error originates in the derive macro `::std::cmp::PartialEq` which comes from the expansion of the attribute macro `autoprops` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider annotating `NotPartialEq` with `#[derive(PartialEq)]` - | -47 | #[derive(PartialEq)] - | - -error[E0369]: binary operation `!=` cannot be applied to type `NotPartialEq` - --> tests/function_component_attr/autoprops-fail.rs:51:23 - | -49 | #[autoprops] - | ------------ in this procedural macro expansion -50 | #[function_component] -51 | fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { - | ^^^^^^^^^^^^^^^^^^^ - | -note: an implementation of `PartialEq<_>` might be missing for `NotPartialEq` - --> tests/function_component_attr/autoprops-fail.rs:47:1 - | -47 | struct NotPartialEq(u32); - | ^^^^^^^^^^^^^^^^^^^ must implement `PartialEq<_>` - = note: this error originates in the derive macro `::std::cmp::PartialEq` which comes from the expansion of the attribute macro `autoprops` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `autoprops` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `NotPartialEq` with `#[derive(PartialEq)]` | 47 | #[derive(PartialEq)] From 63e29db6022fa47c8db914a4787cc0a784e16d3a Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Thu, 2 Nov 2023 11:18:54 +0100 Subject: [PATCH 13/21] Improve error message & update stderr --- packages/yew-macro/src/autoprops.rs | 7 ++++--- .../tests/function_component_attr/autoprops-fail.stderr | 7 +++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/yew-macro/src/autoprops.rs b/packages/yew-macro/src/autoprops.rs index 9a2592b5930..e90e5222dce 100644 --- a/packages/yew-macro/src/autoprops.rs +++ b/packages/yew-macro/src/autoprops.rs @@ -1,4 +1,5 @@ -use quote::quote; +use quote::{quote, quote_spanned}; +use syn::spanned::Spanned; use crate::function_component::FunctionComponentName; @@ -196,8 +197,8 @@ impl Autoprops { .inputs .iter() .filter_map(|arg| match arg { - syn::FnArg::Typed(syn::PatType { pat, .. }) => Some(quote! { - self.#pat == rhs.#pat && + syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => Some(quote_spanned! { + ty.span() => self.#pat == rhs.#pat && }), _ => None, }) diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr b/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr index 0affcb69e53..0d5941dc89a 100644 --- a/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr @@ -43,17 +43,16 @@ help: consider annotating `NotClonable` with `#[derive(Clone)]` | error[E0369]: binary operation `==` cannot be applied to type `NotPartialEq` - --> tests/function_component_attr/autoprops-fail.rs:49:1 + --> tests/function_component_attr/autoprops-fail.rs:51:30 | -49 | #[autoprops] - | ^^^^^^^^^^^^ +51 | fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { + | ^^^^^^^^^^^^ | note: an implementation of `PartialEq<_>` might be missing for `NotPartialEq` --> tests/function_component_attr/autoprops-fail.rs:47:1 | 47 | struct NotPartialEq(u32); | ^^^^^^^^^^^^^^^^^^^ must implement `PartialEq<_>` - = note: this error originates in the attribute macro `autoprops` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `NotPartialEq` with `#[derive(PartialEq)]` | 47 | #[derive(PartialEq)] From 373802615cef5079644b1dace50366fac34d0f38 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 3 Nov 2023 10:34:34 +0100 Subject: [PATCH 14/21] Update website/docs/concepts/function-components/properties.mdx --- website/docs/concepts/function-components/properties.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/concepts/function-components/properties.mdx b/website/docs/concepts/function-components/properties.mdx index c6e1c09002a..67ed59abab9 100644 --- a/website/docs/concepts/function-components/properties.mdx +++ b/website/docs/concepts/function-components/properties.mdx @@ -275,9 +275,9 @@ use yew::prelude::*; #[function_component] fn Greetings( #[prop_or("Hello".into())] - message: AttrValue, + message: &AttrValue, #[prop_or("World".into())] - name: AttrValue, + name: &AttrValue, ) -> Html { html! {<>{message}{name}} } From 080d6f294539f165f9f659db6c293648bab3c45b Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 17 Nov 2023 08:51:42 +0100 Subject: [PATCH 15/21] Remove macro, keep only doc --- packages/yew-macro/src/function_component.rs | 34 ++----------------- packages/yew-macro/src/lib.rs | 12 ------- .../bad-props-param-fail.stderr | 4 +-- .../multiple-param-fail.stderr | 8 ++--- packages/yew/src/functional/mod.rs | 6 ++-- 5 files changed, 12 insertions(+), 52 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index d8bf190df25..d17b5b65155 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -47,32 +47,6 @@ impl Parse for FunctionComponent { block, } = func; - if let Some(_attr) = attrs.iter().find(|attr| { - match &attr.meta { - syn::Meta::Path(path) => { - if let Some(last_segment) = path.segments.last() { - if last_segment.ident == "autoprops" { - return true; - } - } - } - syn::Meta::List(syn::MetaList { path, .. }) => { - if let Some(last_segment) = path.segments.last() { - if last_segment.ident == "autoprops" { - return true; - } - } - } - _ => {} - } - false - }) { - return Err(syn::Error::new_spanned( - sig, - "#[autoprops] must be placed *before* #[function_component]", - )); - } - if sig.generics.lifetimes().next().is_some() { return Err(syn::Error::new_spanned( sig.generics, @@ -137,8 +111,7 @@ impl Parse for FunctionComponent { } ty => { let msg = format!( - "expected a reference to a `Properties` type (try: `&{}` or use \ - #[autoprops])", + "expected a reference to a `Properties` type (try: `&{}`)", ty.to_token_stream() ); return Err(syn::Error::new_spanned(ty, msg)); @@ -160,8 +133,7 @@ impl Parse for FunctionComponent { let params: TokenStream = inputs.map(|it| it.to_token_stream()).collect(); return Err(syn::Error::new_spanned( params, - "function components can accept at most one parameter for the props, maybe you \ - wanted to use #[autoprops]?", + "function components can accept at most one parameter for the props", )); } @@ -421,7 +393,7 @@ impl FunctionComponent { } pub struct FunctionComponentName { - pub component_name: Option, + component_name: Option, } impl Parse for FunctionComponentName { diff --git a/packages/yew-macro/src/lib.rs b/packages/yew-macro/src/lib.rs index e72d42c37a2..5d7216d84a0 100644 --- a/packages/yew-macro/src/lib.rs +++ b/packages/yew-macro/src/lib.rs @@ -48,7 +48,6 @@ //! //! Please refer to [https://github.com/yewstack/yew](https://github.com/yewstack/yew) for how to set this up. -mod autoprops; mod classes; mod derive_props; mod function_component; @@ -59,7 +58,6 @@ mod stringify; mod use_prepared_state; mod use_transitive_state; -use autoprops::{Autoprops, AutopropsArgs}; use derive_props::DerivePropsInput; use function_component::{function_component_impl, FunctionComponent, FunctionComponentName}; use hook::{hook_impl, HookFn}; @@ -150,16 +148,6 @@ pub fn function_component(attr: TokenStream, item: TokenStream) -> proc_macro::T .into() } -#[proc_macro_error::proc_macro_error] -#[proc_macro_attribute] -pub fn autoprops(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream { - let mut autoprops = parse_macro_input!(item as Autoprops); - let args = parse_macro_input!(attr as AutopropsArgs); - autoprops.apply_args(args); - - TokenStream::from(autoprops.into_token_stream()) -} - #[proc_macro_error::proc_macro_error] #[proc_macro_attribute] pub fn hook(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream { diff --git a/packages/yew-macro/tests/function_component_attr/bad-props-param-fail.stderr b/packages/yew-macro/tests/function_component_attr/bad-props-param-fail.stderr index 21ce368ac29..479b44a6154 100644 --- a/packages/yew-macro/tests/function_component_attr/bad-props-param-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/bad-props-param-fail.stderr @@ -1,5 +1,5 @@ -error: expected a reference to a `Properties` type (try: `&Props` or use #[autoprops]) - --> tests/function_component_attr/bad-props-param-fail.rs:9:16 +error: expected a reference to a `Properties` type (try: `&Props`) + --> $DIR/bad-props-param-fail.rs:9:16 | 9 | fn comp(props: Props) -> Html { | ^^^^^ diff --git a/packages/yew-macro/tests/function_component_attr/multiple-param-fail.stderr b/packages/yew-macro/tests/function_component_attr/multiple-param-fail.stderr index a214dfe124e..ca8bc2ed2da 100644 --- a/packages/yew-macro/tests/function_component_attr/multiple-param-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/multiple-param-fail.stderr @@ -1,11 +1,11 @@ -error: function components can accept at most one parameter for the props, maybe you wanted to use #[autoprops]? - --> tests/function_component_attr/multiple-param-fail.rs:9:24 +error: function components can accept at most one parameter for the props + --> $DIR/multiple-param-fail.rs:9:24 | 9 | fn comp(props: &Props, invalid: String) -> Html { | ^^^^^^^^^^^^^^^ -error: function components can accept at most one parameter for the props, maybe you wanted to use #[autoprops]? - --> tests/function_component_attr/multiple-param-fail.rs:19:25 +error: function components can accept at most one parameter for the props + --> $DIR/multiple-param-fail.rs:19:25 | 19 | fn comp3(props: &Props, invalid: String, another_invalid: u32) -> Html { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 5486b786935..6ba9f601f91 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -34,8 +34,6 @@ use crate::Properties; mod hooks; pub use hooks::*; -/// This attribute creates a user-defined hook from a normal Rust function. -pub use yew_macro::hook; /// This attribute creates a function component from a normal Rust function. /// /// Functions with this attribute **must** return `Html` and can optionally take an argument @@ -62,7 +60,9 @@ pub use yew_macro::hook; /// } /// } /// ``` -pub use yew_macro::{autoprops, function_component}; +pub use yew_macro::function_component; +/// This attribute creates a user-defined hook from a normal Rust function. +pub use yew_macro::hook; type ReRender = Rc; From 60d9c4f2f9b44d23927fdd3ea5fef63390070ab5 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 17 Nov 2023 09:09:00 +0100 Subject: [PATCH 16/21] Improve doc to use AttrValue and &Props { ... } --- .../function-components/properties.mdx | 88 ++++++++++++------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/website/docs/concepts/function-components/properties.mdx b/website/docs/concepts/function-components/properties.mdx index 67ed59abab9..46cde42b4f6 100644 --- a/website/docs/concepts/function-components/properties.mdx +++ b/website/docs/concepts/function-components/properties.mdx @@ -60,14 +60,14 @@ pub struct Props { } #[function_component] -fn HelloWorld(props: &Props) -> Html { - html! { <>{"Am I loading? - "}{props.is_loading.clone()} } +fn HelloWorld(&Props { is_loading }: &Props) -> Html { + html! { <>{"Am I loading? - "}{is_loading} } } // Then supply the prop #[function_component] fn App() -> Html { - html! {} + html! { } } ``` @@ -91,7 +91,7 @@ fn HelloWorld() -> Html { // No props to supply #[function_component] fn App() -> Html { - html! {} + html! { } } ``` @@ -126,8 +126,8 @@ pub struct Props { } #[function_component] -fn HelloWorld(props: &Props) -> Html { - if props.is_loading.clone() { +fn HelloWorld(&Props { is_loading }: &Props) -> Html { + if is_loading { html! { "Loading" } } else { html! { "Hello world" } @@ -137,12 +137,12 @@ fn HelloWorld(props: &Props) -> Html { // Then use like this with default #[function_component] fn Case1() -> Html { - html! {} + html! { } } // Or no override the default #[function_component] fn Case2() -> Html { - html! {} + html! { } } ``` @@ -158,26 +158,32 @@ use yew::{function_component, html, Html, Properties}; #[derive(Properties, PartialEq)] pub struct Props { + #[prop_or_default] + pub is_loading: bool, // highlight-start - #[prop_or("Bob".to_string())] + #[prop_or(AttrValue::Static("Bob"))] // highlight-end - pub name: String, + pub name: AttrValue, } #[function_component] -fn HelloWorld(props: &Props) -> Html { - html! {<>{"Hello world"}{props.name.clone()}} +fn Hello(&Props { is_loading, ref name }: &Props) -> Html { + if is_loading { + html! { "Loading" } + } else { + html! { <>{"Hello "}{name} } + } } // Then use like this with default #[function_component] fn Case1() -> Html { - html! {} + html! { } } // Or no override the default #[function_component] fn Case2() -> Html { - html! {} + html! { } } ``` @@ -190,32 +196,38 @@ The function is called when no explicit value has been given for that attribute. ```rust use yew::{function_component, html, Html, Properties}; -fn create_default_name() -> String { - "Bob".to_string() +fn create_default_name() -> AttrValue { + AttrValue::Static("Bob") } #[derive(Properties, PartialEq)] pub struct Props { + #[prop_or_default] + pub is_loading: bool, // highlight-start #[prop_or_else(create_default_name)] // highlight-end - pub name: String, + pub name: AttrValue, } #[function_component] -fn HelloWorld(props: &Props) -> Html { - html! {<>{"Hello world"}{props.name.clone()}} +fn Hello(&Props { is_loading, ref name }: &Props) -> Html { + if is_loading { + html! { "Loading" } + } else { + html! { <>{"Hello "}{name} } + } } // Then use like this with default #[function_component] fn Case1() -> Html { - html! {} + html! { } } // Or no override the default #[function_component] fn Case2() -> Html { - html! {} + html! { } } ``` @@ -243,13 +255,19 @@ use yew::{function_component, html, Html, Properties, props, virtual_dom::AttrVa #[derive(Properties, PartialEq)] pub struct Props { - #[prop_or(AttrValue::from("Bob"))] + #[prop_or_default] + pub is_loading: bool, + #[prop_or(AttrValue::Static("Bob"))] pub name: AttrValue, } #[function_component] -fn HelloWorld(props: &Props) -> Html { - html! {<>{"Hello world"}{props.name.clone()}} +fn Hello(&Props { name }: &Props) -> Html { + if is_loading { + html! { "Loading" } + } else { + html! { <>{"Hello "}{name} } + } } #[function_component] @@ -259,14 +277,15 @@ fn App() -> Html { Props {} // Notice we did not need to specify name prop }; // highlight-end - html! {} + html! { } } ``` -## Automatically generate properties (autoprops) +## Automatically generate properties (yew-autoprops) In order to streamline your development process, you can also use the macro -`#[autoprops]` that will automatically generate the `Properties` struct for you. +`#[autoprops]` (from the crate `yew-autoprops`) that will automatically +generate the `Properties` struct for you. ```rust use yew::prelude::*; @@ -274,15 +293,24 @@ use yew::prelude::*; #[autoprops] #[function_component] fn Greetings( - #[prop_or("Hello".into())] + #[prop_or_default] + is_loading: bool, + #[prop_or(AttrValue::Static("Hello"))] message: &AttrValue, - #[prop_or("World".into())] + #[prop_or(AttrValue::Static("World"))] name: &AttrValue, ) -> Html { - html! {<>{message}{name}} + if is_loading { + html! { "Loading" } + } else { + html! { <>{message}{" "}{name} } + } } // The properties struct "GreetingsProps" will be generated automatically. +// +// `is_loading` will be passed as value to the components while `message` and +// `name` will use references because of the leading `&` in the definition. ``` ## Evaluation Order From 781edd627f52d0e18448584e7c4ee4ea23998573 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 17 Nov 2023 09:11:30 +0100 Subject: [PATCH 17/21] Failed my previous removal somehow --- packages/yew-macro/src/autoprops.rs | 259 ------------------ .../function_component_attr/autoprops-fail.rs | 66 ----- .../autoprops-fail.stderr | 59 ---- .../function_component_attr/autoprops-pass.rs | 111 -------- 4 files changed, 495 deletions(-) delete mode 100644 packages/yew-macro/src/autoprops.rs delete mode 100644 packages/yew-macro/tests/function_component_attr/autoprops-fail.rs delete mode 100644 packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr delete mode 100644 packages/yew-macro/tests/function_component_attr/autoprops-pass.rs diff --git a/packages/yew-macro/src/autoprops.rs b/packages/yew-macro/src/autoprops.rs deleted file mode 100644 index e90e5222dce..00000000000 --- a/packages/yew-macro/src/autoprops.rs +++ /dev/null @@ -1,259 +0,0 @@ -use quote::{quote, quote_spanned}; -use syn::spanned::Spanned; - -use crate::function_component::FunctionComponentName; - -#[derive(Clone)] -pub struct Autoprops { - item_fn: syn::ItemFn, - properties_name: syn::Ident, -} - -impl syn::parse::Parse for Autoprops { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let parsed: syn::Item = input.parse()?; - - let item_fn = match parsed { - syn::Item::Fn(m) => m, - item => { - return Err(syn::Error::new_spanned( - item, - "`autoprops` attribute can only be applied to functions", - )) - } - }; - - let syn::ItemFn { attrs, sig, .. } = &item_fn; - - let mut component_name = item_fn.sig.ident.clone(); - - attrs - .iter() - .find(|attr| { - match &attr.meta { - syn::Meta::Path(path) => { - if let Some(last_segment) = path.segments.last() { - if last_segment.ident == "function_component" { - return true; - } - } - } - syn::Meta::List(syn::MetaList { path, tokens, .. }) => { - if let Some(last_segment) = path.segments.last() { - if last_segment.ident == "function_component" { - if let Ok(attr) = - syn::parse2::(tokens.clone()) - { - if let Some(name) = attr.component_name { - component_name = name; - } - } - return true; - } - } - } - _ => {} - } - false - }) - .ok_or_else(|| { - syn::Error::new_spanned( - sig, - "could not find #[function_component] attribute in function declaration \ - (#[autoprops] must be placed *before* #[function_component])", - ) - })?; - - for input in &sig.inputs { - if let syn::FnArg::Typed(syn::PatType { pat, .. }) = input { - if let syn::Pat::Wild(wild) = pat.as_ref() { - return Err(syn::Error::new_spanned( - wild, - "cannot use `_` as field name", - )); - } - } - } - - let properties_name = syn::Ident::new( - &format!("{}Props", component_name), - proc_macro2::Span::call_site(), - ); - - Ok(Self { - properties_name, - item_fn, - }) - } -} - -impl Autoprops { - pub fn apply_args(&mut self, args: AutopropsArgs) { - if let Some(name) = args.properties_name { - self.properties_name = name; - } - } - - fn print_function_component(&self) -> proc_macro2::TokenStream { - let properties_name = &self.properties_name; - let syn::ItemFn { - attrs, - vis, - sig, - block, - } = &self.item_fn; - - let fn_name = &sig.ident; - let (impl_generics, type_generics, where_clause) = sig.generics.split_for_impl(); - let inputs = if self.needs_a_properties_struct() { - // NOTE: function components currently don't accept receivers, we're just passing the - // information to the next macro to fail and give its own error message - let receivers = sig - .inputs - .iter() - .filter_map(|arg| match arg { - syn::FnArg::Receiver(receiver) => Some(receiver), - _ => None, - }) - .collect::>(); - let args = sig - .inputs - .iter() - .filter_map(|arg| match arg { - syn::FnArg::Typed(syn::PatType { pat, .. }) => Some(quote! { #pat }), - _ => None, - }) - .collect::>(); - let phantom = sig.generics.type_params().next().is_some().then(|| { - quote! { - , phantom: _ - } - }); - quote! { - #(#receivers,)* #properties_name { - #(#args),* - #phantom - }: &#properties_name #type_generics - } - } else { - quote! {} - }; - let clones = sig - .inputs - .iter() - .filter_map(|arg| match arg { - syn::FnArg::Typed(syn::PatType { pat, ty, .. }) - if !matches!(**ty, syn::Type::Reference(_)) => - { - Some(quote! { let #pat = ::std::clone::Clone::clone(#pat); }) - } - _ => None, - }) - .collect::>(); - - quote! { - #(#attrs)* - #vis fn #fn_name #impl_generics (#inputs) -> ::yew::Html #where_clause { - #(#clones)* - #block - } - } - } - - fn print_properties_struct(&self) -> proc_macro2::TokenStream { - let properties_name = &self.properties_name; - let syn::ItemFn { vis, sig, .. } = &self.item_fn; - - if !self.needs_a_properties_struct() { - return quote! {}; - } - - let (impl_generics, type_generics, where_clause) = sig.generics.split_for_impl(); - let fields = sig - .inputs - .iter() - .filter_map(|arg| match arg { - syn::FnArg::Typed(syn::PatType { attrs, pat, ty, .. }) => match ty.as_ref() { - syn::Type::Reference(syn::TypeReference { elem, .. }) => { - Some(quote! { #(#attrs)* #pat: #elem, }) - } - _ => Some(quote! { #(#attrs)* #pat: #ty, }), - }, - _ => None, - }) - .collect::>(); - let type_params = sig - .generics - .type_params() - .map(|param| ¶m.ident) - .collect::>(); - let phantom = (!type_params.is_empty()).then(|| { - quote! { - #[prop_or_default] - phantom: ::std::marker::PhantomData <( #(#type_params),* )>, - } - }); - let fields_eq = sig - .inputs - .iter() - .filter_map(|arg| match arg { - syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => Some(quote_spanned! { - ty.span() => self.#pat == rhs.#pat && - }), - _ => None, - }) - .collect::>(); - - quote! { - #[derive(::yew::Properties)] - #vis struct #properties_name #impl_generics #where_clause { - #(#fields)* - #phantom - } - - impl #impl_generics ::std::cmp::PartialEq for #properties_name #type_generics - #where_clause { - fn eq(&self, rhs: &Self) -> ::std::primitive::bool { - #(#fields_eq)* true - } - } - } - } - - fn needs_a_properties_struct(&self) -> bool { - let syn::ItemFn { sig, .. } = &self.item_fn; - !sig.inputs.is_empty() || sig.generics.type_params().next().is_some() - } -} - -impl quote::ToTokens for Autoprops { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let function_component = self.print_function_component(); - let properties_struct = self.print_properties_struct(); - - tokens.extend(quote! { - #function_component - #properties_struct - }) - } -} - -pub struct AutopropsArgs { - pub properties_name: Option, -} - -impl syn::parse::Parse for AutopropsArgs { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - if input.is_empty() { - return Ok(Self { - properties_name: None, - }); - } - - let properties_name = input.parse()?; - - Ok(Self { - properties_name: Some(properties_name), - }) - } -} diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-fail.rs b/packages/yew-macro/tests/function_component_attr/autoprops-fail.rs deleted file mode 100644 index 5bc8be54aad..00000000000 --- a/packages/yew-macro/tests/function_component_attr/autoprops-fail.rs +++ /dev/null @@ -1,66 +0,0 @@ -use yew::prelude::*; - -#[autoprops] -#[function_component] -fn CantAcceptReceiver(&self, b: bool) -> Html { - html! { -

{b}

- } -} - -#[autoprops] -fn not_a_function_component(b: bool) -> Html { - html! { -

{b}

- } -} - -#[function_component(WrongAttrsOrder)] -#[autoprops] -fn wrong_attrs_order(b: bool) -> Html { - html! { -

{b}

- } -} - -#[autoprops] -#[function_component(let)] -fn BadFunctionComponent(b: bool) -> Html { - html! { -

{b}

- } -} - -#[derive(PartialEq)] -struct NotClonable(u32); - -#[autoprops] -#[function_component] -fn TypeIsNotClone(stuff: NotClonable) -> Html { - drop(stuff); - html! { -

- } -} - -#[derive(Clone)] -struct NotPartialEq(u32); - -#[autoprops] -#[function_component] -fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { - drop(stuff); - html! { -

- } -} - -#[autoprops] -#[function_component] -fn InvalidFieldName(_: u32) -> Html { - html! { -

- } -} - -fn main() {} diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr b/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr deleted file mode 100644 index 0d5941dc89a..00000000000 --- a/packages/yew-macro/tests/function_component_attr/autoprops-fail.stderr +++ /dev/null @@ -1,59 +0,0 @@ -error: function components can't accept a receiver - --> tests/function_component_attr/autoprops-fail.rs:5:23 - | -5 | fn CantAcceptReceiver(&self, b: bool) -> Html { - | ^^^^^ - -error: could not find #[function_component] attribute in function declaration (#[autoprops] must be placed *before* #[function_component]) - --> tests/function_component_attr/autoprops-fail.rs:12:1 - | -12 | fn not_a_function_component(b: bool) -> Html { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: #[autoprops] must be placed *before* #[function_component] - --> tests/function_component_attr/autoprops-fail.rs:20:1 - | -20 | fn wrong_attrs_order(b: bool) -> Html { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: expected identifier, found keyword `let` - --> tests/function_component_attr/autoprops-fail.rs:27:22 - | -27 | #[function_component(let)] - | ^^^ - -error: cannot use `_` as field name - --> tests/function_component_attr/autoprops-fail.rs:60:21 - | -60 | fn InvalidFieldName(_: u32) -> Html { - | ^ - -error[E0277]: the trait bound `NotClonable: Clone` is not satisfied - --> tests/function_component_attr/autoprops-fail.rs:39:19 - | -37 | #[autoprops] - | ------------ required by a bound introduced by this call -38 | #[function_component] -39 | fn TypeIsNotClone(stuff: NotClonable) -> Html { - | ^^^^^ the trait `Clone` is not implemented for `NotClonable` - | -help: consider annotating `NotClonable` with `#[derive(Clone)]` - | -35 | #[derive(Clone)] - | - -error[E0369]: binary operation `==` cannot be applied to type `NotPartialEq` - --> tests/function_component_attr/autoprops-fail.rs:51:30 - | -51 | fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { - | ^^^^^^^^^^^^ - | -note: an implementation of `PartialEq<_>` might be missing for `NotPartialEq` - --> tests/function_component_attr/autoprops-fail.rs:47:1 - | -47 | struct NotPartialEq(u32); - | ^^^^^^^^^^^^^^^^^^^ must implement `PartialEq<_>` -help: consider annotating `NotPartialEq` with `#[derive(PartialEq)]` - | -47 | #[derive(PartialEq)] - | diff --git a/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs b/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs deleted file mode 100644 index 0ff0ab5d755..00000000000 --- a/packages/yew-macro/tests/function_component_attr/autoprops-pass.rs +++ /dev/null @@ -1,111 +0,0 @@ -#![no_implicit_prelude] - -// Shadow primitives -#[allow(non_camel_case_types)] -pub struct bool; -#[allow(non_camel_case_types)] -pub struct char; -#[allow(non_camel_case_types)] -pub struct f32; -#[allow(non_camel_case_types)] -pub struct f64; -#[allow(non_camel_case_types)] -pub struct i128; -#[allow(non_camel_case_types)] -pub struct i16; -#[allow(non_camel_case_types)] -pub struct i32; -#[allow(non_camel_case_types)] -pub struct i64; -#[allow(non_camel_case_types)] -pub struct i8; -#[allow(non_camel_case_types)] -pub struct isize; -#[allow(non_camel_case_types)] -pub struct str; -#[allow(non_camel_case_types)] -pub struct u128; -#[allow(non_camel_case_types)] -pub struct u16; -#[allow(non_camel_case_types)] -pub struct u32; -#[allow(non_camel_case_types)] -pub struct u64; -#[allow(non_camel_case_types)] -pub struct u8; -#[allow(non_camel_case_types)] -pub struct usize; - -#[::yew::autoprops] -#[::yew::function_component] -fn CompUseFnName() -> ::yew::Html -{ - ::yew::html! { -

- } -} - -#[::yew::autoprops] -#[::yew::function_component(CompNoProperties)] -fn comp_no_properties() -> ::yew::Html -{ - ::yew::html! { -

- } -} - -#[::yew::autoprops] -#[::yew::function_component(CompNoGenerics)] -fn comp_no_generics(#[prop_or_default] b: ::std::primitive::bool, a: &::yew::AttrValue) -> ::yew::Html -{ - let _: ::std::primitive::bool = b; - let _: &::yew::AttrValue = a; - ::yew::html! { -

- } -} - -#[::yew::autoprops] -#[::yew::function_component(CompGenerics)] -fn comp_generics(b: T1, a: &T2) -> ::yew::Html -where - T1: ::std::cmp::PartialEq + ::std::clone::Clone, - T2: ::std::cmp::PartialEq, -{ - let _: T1 = b; - let _: &T2 = a; - ::yew::html! { -

- } -} - -#[::yew::autoprops] -#[::yew::function_component(CompGenericsWithoutField)] -fn comp_generics_without_field(b: ::std::primitive::bool) -> ::yew::Html { - ::yew::html! { -

- } -} - -#[::yew::autoprops] -#[::yew::function_component(ConstGenerics)] -fn const_generics(xs: [::std::primitive::u32; N]) -> ::yew::Html { - let _: [::std::primitive::u32; N] = xs; - ::yew::html! { -
- { N } -
- } -} - -fn compile_pass() { - ::yew::html! { }; - ::yew::html! { }; - ::yew::html! { }; - ::yew::html! { }; - ::yew::html! { b=true a="foo" /> }; - - ::yew::html! { xs={[1_u32, 2_u32]} /> }; -} - -fn main() {} From 50f36664215c178c1e78447d0928f82404f43ba9 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 17 Nov 2023 09:20:34 +0100 Subject: [PATCH 18/21] Remark on the order of the macros --- website/docs/concepts/function-components/properties.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/concepts/function-components/properties.mdx b/website/docs/concepts/function-components/properties.mdx index 46cde42b4f6..d7af88a4447 100644 --- a/website/docs/concepts/function-components/properties.mdx +++ b/website/docs/concepts/function-components/properties.mdx @@ -290,6 +290,7 @@ generate the `Properties` struct for you. ```rust use yew::prelude::*; +// the #[autoprops] macro must appear BEFORE #[function_component], the order matters #[autoprops] #[function_component] fn Greetings( From 15a27ac28be6cf4f766b1f81beabdeffdc35e445 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Sun, 19 Nov 2023 11:16:14 +0100 Subject: [PATCH 19/21] Various fixes --- Cargo.lock | 12 ++++++++++++ tools/website-test/Cargo.toml | 1 + .../docs/concepts/function-components/properties.mdx | 11 ++++++----- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b560bd7eaef..a28642cc266 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3615,6 +3615,7 @@ dependencies = [ "weblog", "yew", "yew-agent", + "yew-autoprops", "yew-router", ] @@ -3876,6 +3877,17 @@ dependencies = [ "yew-agent", ] +[[package]] +name = "yew-autoprops" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0158e8f0ff40eaa82648dc0f7f33e9970157116deda097c73a16d247b8cc7f1e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "yew-macro" version = "0.21.0" diff --git a/tools/website-test/Cargo.toml b/tools/website-test/Cargo.toml index e24d135286e..3bd912a472a 100644 --- a/tools/website-test/Cargo.toml +++ b/tools/website-test/Cargo.toml @@ -17,6 +17,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" weblog = "0.3.0" yew = { path = "../../packages/yew/", features = ["ssr", "csr"] } +yew-autoprops = "0.3.0" yew-router = { path = "../../packages/yew-router/" } tokio = { version = "1.33.0", features = ["rt", "macros"] } diff --git a/website/docs/concepts/function-components/properties.mdx b/website/docs/concepts/function-components/properties.mdx index d7af88a4447..2927004e87f 100644 --- a/website/docs/concepts/function-components/properties.mdx +++ b/website/docs/concepts/function-components/properties.mdx @@ -154,7 +154,7 @@ For example, to default a boolean prop to `true`, use the attribute `#[prop_or(t is evaluated when the properties are constructed and no explicit value has been given. ```rust -use yew::{function_component, html, Html, Properties}; +use yew::prelude::*; #[derive(Properties, PartialEq)] pub struct Props { @@ -194,7 +194,7 @@ Call `function` to initialize the prop value. `function` should have the signatu The function is called when no explicit value has been given for that attribute. ```rust -use yew::{function_component, html, Html, Properties}; +use yew::prelude::*; fn create_default_name() -> AttrValue { AttrValue::Static("Bob") @@ -251,7 +251,7 @@ The macro uses the same syntax as a struct expression except that you can't use The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`). ```rust -use yew::{function_component, html, Html, Properties, props, virtual_dom::AttrValue}; +use yew::prelude::*; #[derive(Properties, PartialEq)] pub struct Props { @@ -262,7 +262,7 @@ pub struct Props { } #[function_component] -fn Hello(&Props { name }: &Props) -> Html { +fn Hello(&Props { is_loading, ref name }: &Props) -> Html { if is_loading { html! { "Loading" } } else { @@ -273,7 +273,7 @@ fn Hello(&Props { name }: &Props) -> Html { #[function_component] fn App() -> Html { // highlight-start - let pre_made_props = props! { + let pre_made_props = yew::props! { Props {} // Notice we did not need to specify name prop }; // highlight-end @@ -289,6 +289,7 @@ generate the `Properties` struct for you. ```rust use yew::prelude::*; +use yew_autoprops::autoprops; // the #[autoprops] macro must appear BEFORE #[function_component], the order matters #[autoprops] From f883b7c88f16e178d74dd55e94137fa6588d7a02 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 22 Dec 2023 07:27:37 +0100 Subject: [PATCH 20/21] Update dependencies --- Cargo.lock | 19 +++++++++++++++---- packages/yew/Cargo.toml | 2 +- tools/website-test/Cargo.toml | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a28642cc266..b015bc8386a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1675,13 +1675,24 @@ dependencies = [ [[package]] name = "implicit-clone" -version = "0.4.1" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3d77000817fd9e7db159e8d52ed9b5941a2cdbfbdc8ca646e59887ae2b2dd1" +checksum = "fc06a255cbf402a52ae399c05a518c68c569c916ea11423620111df576b9b9bb" dependencies = [ + "implicit-clone-derive", "indexmap 2.0.2", ] +[[package]] +name = "implicit-clone-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9311685eb9a34808bbb0608ad2fcab9ae216266beca5848613e95553ac914e3b" +dependencies = [ + "quote", + "syn 2.0.38", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -3879,9 +3890,9 @@ dependencies = [ [[package]] name = "yew-autoprops" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0158e8f0ff40eaa82648dc0f7f33e9970157116deda097c73a16d247b8cc7f1e" +checksum = "58865a8f2470a6ad839274e05c9627170d32930f39a2348f8c425880a6131792" dependencies = [ "proc-macro2", "quote", diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 89cd95b4c6c..27c6c545ef8 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -27,7 +27,7 @@ yew-macro = { version = "^0.21.0", path = "../yew-macro" } thiserror = "1.0" futures = { version = "0.3", default-features = false, features = ["std"] } html-escape = { version = "0.2.13", optional = true } -implicit-clone = { version = "0.4.1", features = ["map"] } +implicit-clone = { version = "0.4.8", features = ["map"] } base64ct = { version = "1.6.0", features = ["std"], optional = true } bincode = { version = "1.3.3", optional = true } serde = { version = "1", features = ["derive"] } diff --git a/tools/website-test/Cargo.toml b/tools/website-test/Cargo.toml index 3bd912a472a..a930210e718 100644 --- a/tools/website-test/Cargo.toml +++ b/tools/website-test/Cargo.toml @@ -17,7 +17,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" weblog = "0.3.0" yew = { path = "../../packages/yew/", features = ["ssr", "csr"] } -yew-autoprops = "0.3.0" +yew-autoprops = "0.4.1" yew-router = { path = "../../packages/yew-router/" } tokio = { version = "1.33.0", features = ["rt", "macros"] } From d9b1532923ffc88df4b1b92544f4b50ad7715f20 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Fri, 22 Dec 2023 07:48:18 +0100 Subject: [PATCH 21/21] update stderr --- .../tests/classes_macro/classes-fail.stderr | 12 +-- .../tests/html_macro/element-fail.stderr | 83 ++++++++++--------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/packages/yew-macro/tests/classes_macro/classes-fail.stderr b/packages/yew-macro/tests/classes_macro/classes-fail.stderr index 57cf0f21fb7..9d30a817b18 100644 --- a/packages/yew-macro/tests/classes_macro/classes-fail.stderr +++ b/packages/yew-macro/tests/classes_macro/classes-fail.stderr @@ -21,7 +21,7 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied >> > > - > + > >> >> > @@ -44,7 +44,7 @@ error[E0277]: the trait bound `Classes: From<{float}>` is not satisfied >> > > - > + > >> >> > @@ -67,7 +67,7 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied >> > > - > + > >> >> > @@ -93,7 +93,7 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied >> > > - > + > >> >> > @@ -119,7 +119,7 @@ error[E0277]: the trait bound `Classes: From` is not satisfied >> > > - > + > >> >> > @@ -145,7 +145,7 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied >> > > - > + > >> >> > diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr index 11f9e7d631a..bcc320a8975 100644 --- a/packages/yew-macro/tests/html_macro/element-fail.stderr +++ b/packages/yew-macro/tests/html_macro/element-fail.stderr @@ -396,76 +396,76 @@ note: function defined here | pub fn __ensure_type(_: T) {} | ^^^^^^^^^^^^^ -error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied +error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:43:26 | 43 | html! { }; - | ^^ the trait `IntoPropValue>` is not implemented for `()` + | ^^ the trait `IntoPropValue>` is not implemented for `()` | = help: the trait `IntoPropValue` is implemented for `()` -error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied +error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:44:27 | 44 | html! { }; - | ^^ the trait `IntoPropValue>` is not implemented for `()` + | ^^ the trait `IntoPropValue>` is not implemented for `()` | = help: the trait `IntoPropValue` is implemented for `()` -error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied +error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:45:22 | 45 | html! { }; - | ^^ the trait `IntoPropValue>` is not implemented for `()` + | ^^ the trait `IntoPropValue>` is not implemented for `()` | = help: the trait `IntoPropValue` is implemented for `()` -error[E0277]: the trait bound `NotToString: IntoPropValue>` is not satisfied +error[E0277]: the trait bound `NotToString: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:46:28 | 46 | html! { }; - | ^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `NotToString` + | ^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `NotToString` | = help: the following other types implement trait `IntoPropValue`: - <&'static [(K, V)] as IntoPropValue>> - <&'static [T] as IntoPropValue>> + <&'static [(K, V)] as IntoPropValue>> + <&'static [T] as IntoPropValue>> <&'static str as IntoPropValue> <&'static str as IntoPropValue>> - <&'static str as IntoPropValue>> + <&'static str as IntoPropValue>> <&'static str as IntoPropValue> - <&'static str as IntoPropValue> + <&'static str as IntoPropValue> <&String as IntoPropValue> and $N others -error[E0277]: the trait bound `Option: IntoPropValue>` is not satisfied +error[E0277]: the trait bound `Option: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:47:23 | 47 | html! { }; - | ^^^^ the trait `IntoPropValue>` is not implemented for `Option` + | ^^^^ the trait `IntoPropValue>` is not implemented for `Option` | = help: the following other types implement trait `IntoPropValue`: as IntoPropValue>> - as IntoPropValue>> - > as IntoPropValue>> + as IntoPropValue>> + > as IntoPropValue>> as IntoPropValue>>> - > as IntoPropValue>> - as IntoPropValue>> + > as IntoPropValue>> + as IntoPropValue>> > as IntoPropValue>>> as IntoPropValue> -error[E0277]: the trait bound `Option<{integer}>: IntoPropValue>` is not satisfied +error[E0277]: the trait bound `Option<{integer}>: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:48:22 | 48 | html! { }; - | ^^^^ the trait `IntoPropValue>` is not implemented for `Option<{integer}>` + | ^^^^ the trait `IntoPropValue>` is not implemented for `Option<{integer}>` | = help: the following other types implement trait `IntoPropValue`: as IntoPropValue>> - as IntoPropValue>> - > as IntoPropValue>> + as IntoPropValue>> + > as IntoPropValue>> as IntoPropValue>>> - > as IntoPropValue>> - as IntoPropValue>> + > as IntoPropValue>> + as IntoPropValue>> > as IntoPropValue>>> as IntoPropValue> @@ -567,11 +567,11 @@ error[E0277]: the trait bound `Option: IntoPropValue | = help: the following other types implement trait `IntoPropValue`: as IntoPropValue>> - as IntoPropValue>> - > as IntoPropValue>> + as IntoPropValue>> + > as IntoPropValue>> as IntoPropValue>>> - > as IntoPropValue>> - as IntoPropValue>> + > as IntoPropValue>> + as IntoPropValue>> > as IntoPropValue>>> as IntoPropValue> = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -603,20 +603,20 @@ note: required by a bound in `yew::html::onclick::Wrapper::__macro_new` | |_^ required by this bound in `yew::html::onclick::Wrapper::__macro_new` = note: this error originates in the macro `impl_action` which comes from the expansion of the macro `impl_short` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `NotToString: IntoPropValue>` is not satisfied +error[E0277]: the trait bound `NotToString: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:60:28 | 60 | html! { }; - | ^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `NotToString` + | ^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `NotToString` | = help: the following other types implement trait `IntoPropValue`: - <&'static [(K, V)] as IntoPropValue>> - <&'static [T] as IntoPropValue>> + <&'static [(K, V)] as IntoPropValue>> + <&'static [T] as IntoPropValue>> <&'static str as IntoPropValue> <&'static str as IntoPropValue>> - <&'static str as IntoPropValue>> + <&'static str as IntoPropValue>> <&'static str as IntoPropValue> - <&'static str as IntoPropValue> + <&'static str as IntoPropValue> <&String as IntoPropValue> and $N others @@ -629,15 +629,16 @@ error[E0277]: the trait bound `(): IntoPropValue` is not satisfied = help: the trait `IntoPropValue` is implemented for `()` = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `implicit_clone::unsync::IString: From<{integer}>` is not satisfied +error[E0277]: the trait bound `implicit_clone::unsync::string::IString: From<{integer}>` is not satisfied --> tests/html_macro/element-fail.rs:77:16 | 77 | html! { <@{55}> }; - | ^^ the trait `From<{integer}>` is not implemented for `implicit_clone::unsync::IString` + | ^^ the trait `From<{integer}>` is not implemented for `implicit_clone::unsync::string::IString` | = help: the following other types implement trait `From`: - > - >> - >> - > - = note: required because of the requirements on the impl of `Into` for `{integer}` + > + > + >> + >> + > + = note: required because of the requirements on the impl of `Into` for `{integer}`