diff --git a/docs/concepts/function-components/attribute.md b/docs/concepts/function-components/attribute.md index 4dd99078eae..aa7262df48b 100644 --- a/docs/concepts/function-components/attribute.md +++ b/docs/concepts/function-components/attribute.md @@ -3,14 +3,14 @@ title: #[function_component] description: The #[function_component] attribute --- - -The `#[function_component(_)]` turns a normal Rust function into a function component. -Functions with the attribute have to return `Html` and may take a single parameter for the type of props the component should accept. +`#[function_component(_)]` turns a normal Rust function into a function component. +Functions with the attribute have to return `Html` and may take a single parameter for the type of props the component should accept. The parameter type needs to be a reference to a type which implements `Properties` and `PartialEq` (ex. `props: &MyProps`). If the function doesn't have any parameters the resulting component doesn't accept any props. The attribute doesn't replace your original function with a component. You need to provide a name as an input to the attribute which will be the identifier of the component. Assuming you have a function called `chat_container` and you add the attribute `#[function_component(ChatContainer)]` you can use the component like this: + ```rust html! { } ``` @@ -19,6 +19,7 @@ html! { } + ```rust #[derive(Properties, Clone, PartialEq)] pub struct RenderedAtProps { @@ -37,6 +38,7 @@ pub fn rendered_at(props: &RenderedAtProps) -> Html { ``` + ```rust #[function_component(App)] fn app() -> Html { @@ -46,7 +48,7 @@ fn app() -> Html { let counter = Rc::clone(&counter); Callback::from(move |_| set_counter(*counter + 1)) }; - + html! {
@@ -58,4 +60,38 @@ fn app() -> Html { } } ``` + + +## Generic function components + +The `#[function_component(_)]` attribute also works with generic functions for creating generic components. + +```rust +#[derive(Properties, Clone, PartialEq)] +pub struct Props + where T: Clone + PartialEq +{ + data: T, +} + +#[function_component(MyGenericComponent)] +pub fn my_generic_component(props: &Props) -> Html + where T: Clone + PartialEq + Display +{ + html! { +

+ { props.data } +

+ } +} + +// used like this +html! { + data=123 /> +} +// or +html! { + data=foo /> +} +``` diff --git a/packages/yew-functional-macro/src/lib.rs b/packages/yew-functional-macro/src/lib.rs index eeb3355740b..902ba1f135a 100644 --- a/packages/yew-functional-macro/src/lib.rs +++ b/packages/yew-functional-macro/src/lib.rs @@ -1,15 +1,19 @@ use proc_macro2::TokenStream; use quote::{quote, quote_spanned, ToTokens}; use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; use syn::spanned::Spanned; +use syn::token::Comma; use syn::{ - parse_macro_input, Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility, + parse_macro_input, Attribute, Block, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type, + Visibility, }; struct FunctionComponent { block: Box, props_type: Box, arg: FnArg, + generics: Generics, vis: Visibility, attrs: Vec, name: Ident, @@ -29,10 +33,10 @@ impl Parse for FunctionComponent { block, } = func; - if !sig.generics.params.is_empty() { + if sig.generics.lifetimes().next().is_some() { return Err(syn::Error::new_spanned( sig.generics, - "function components can't contain generics", + "function components can't have generic lifetime parameters", )); } @@ -123,6 +127,7 @@ impl Parse for FunctionComponent { props_type: ty, block, arg, + generics: sig.generics, vis, attrs, name: sig.ident, @@ -176,12 +181,15 @@ fn function_component_impl( block, props_type, arg, + generics, vis, attrs, name: function_name, return_type, } = component; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + if function_name == component_name { return Err(syn::Error::new_spanned( component_name, @@ -191,12 +199,20 @@ fn function_component_impl( let ret_type = quote_spanned!(return_type.span()=> ::yew::html::Html); + let phantom_generics = generics + .type_params() + .map(|ty_param| ty_param.ident.clone()) // create a new Punctuated sequence without any type bounds + .collect::>(); + let quoted = quote! { #[doc(hidden)] #[allow(non_camel_case_types)] - #vis struct #function_name; + #[allow(unused_parens)] + #vis struct #function_name #impl_generics { + _marker: ::std::marker::PhantomData<(#phantom_generics)>, + } - impl ::yew_functional::FunctionProvider for #function_name { + impl #impl_generics ::yew_functional::FunctionProvider for #function_name #ty_generics #where_clause { type TProps = #props_type; fn run(#arg) -> #ret_type { @@ -205,7 +221,9 @@ fn function_component_impl( } #(#attrs)* - #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>; + #[allow(type_alias_bounds)] + #vis type #component_name #impl_generics = ::yew_functional::FunctionComponent<#function_name #ty_generics>; }; + Ok(quoted) } diff --git a/packages/yew-functional-macro/tests/function_attr/generic-fail.stderr b/packages/yew-functional-macro/tests/function_attr/generic-fail.stderr deleted file mode 100644 index 23bd5608388..00000000000 --- a/packages/yew-functional-macro/tests/function_attr/generic-fail.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: function components can't contain generics - --> $DIR/generic-fail.rs:10:14 - | -10 | const fn comp(props: &P) -> Html { - | ^^^^^^^^^^^^^^^ diff --git a/packages/yew-functional-macro/tests/function_attr/generic-fail.rs b/packages/yew-functional-macro/tests/function_attr/generic-lifetime-fail.rs similarity index 80% rename from packages/yew-functional-macro/tests/function_attr/generic-fail.rs rename to packages/yew-functional-macro/tests/function_attr/generic-lifetime-fail.rs index 483a32f5a86..cbf2921254f 100644 --- a/packages/yew-functional-macro/tests/function_attr/generic-fail.rs +++ b/packages/yew-functional-macro/tests/function_attr/generic-lifetime-fail.rs @@ -1,18 +1,19 @@ -use yew::prelude::*; -use yew_functional::function_component; - -#[derive(Clone, Properties, PartialEq)] -struct Props { - a: usize, -} - -#[function_component(Comp)] -const fn comp(props: &P) -> Html { - html! { -

- { props.a } -

- } -} - -fn main() {} +use yew::prelude::*; +use yew_functional::function_component; + +#[derive(Clone, Properties, PartialEq)] +struct Props { + a: usize, +} + +#[function_component(Comp)] +fn comp<'a>(props: &'a Props) -> Html { + + html! { +

+ { props.a } +

+ } +} + +fn main() {} diff --git a/packages/yew-functional-macro/tests/function_attr/generic-lifetime-fail.stderr b/packages/yew-functional-macro/tests/function_attr/generic-lifetime-fail.stderr new file mode 100644 index 00000000000..e934e01fe69 --- /dev/null +++ b/packages/yew-functional-macro/tests/function_attr/generic-lifetime-fail.stderr @@ -0,0 +1,5 @@ +error: function components can't have generic lifetime parameters + --> $DIR/generic-lifetime-fail.rs:10:8 + | +10 | fn comp<'a>(props: &'a Props) -> Html { + | ^^^^ diff --git a/packages/yew-functional-macro/tests/function_attr/generic-pass.rs b/packages/yew-functional-macro/tests/function_attr/generic-pass.rs new file mode 100644 index 00000000000..34eee7aa777 --- /dev/null +++ b/packages/yew-functional-macro/tests/function_attr/generic-pass.rs @@ -0,0 +1,40 @@ +#[derive(Clone, ::yew::Properties, PartialEq)] +struct Props { + a: usize, +} + +#[::yew_functional::function_component(Comp)] +fn comp

(_props: &P) -> ::yew::Html +where + P: ::yew::Properties + PartialEq, +{ + ::yew::html! { +

+ } +} + +#[::yew_functional::function_component(Comp1)] +fn comp1(_props: &()) -> ::yew::Html { + ::yew::html! { +

+ } +} + +// TODO: uncomment when min_const_generics are in stable and Rust version in CI is bumped +// #[::yew_functional::function_component(ConstGenerics)] +// fn const_generics() -> ::yew::Html { +// ::yew::html! { +//
+// { N } +//
+// } +// } + +fn compile_pass() { + ::yew::html! { a=10 /> }; + ::yew::html! { /> }; + + // ::yew::html! { }; +} + +fn main() {} diff --git a/packages/yew-functional-macro/tests/function_attr/generic-props-fail.rs b/packages/yew-functional-macro/tests/function_attr/generic-props-fail.rs new file mode 100644 index 00000000000..96b7c0bcb46 --- /dev/null +++ b/packages/yew-functional-macro/tests/function_attr/generic-props-fail.rs @@ -0,0 +1,34 @@ +use yew::prelude::*; +use yew_functional::function_component; + +#[derive(Clone, Properties, PartialEq)] +struct Props { + a: usize, +} + +#[function_component(Comp)] +fn comp

(_props: &P) -> Html +where + P: Properties + PartialEq, +{ + html! { +

+ } +} + +struct MissingTypeBounds; + +fn compile_fail() { + // missing prop 'a' + html! { /> }; + + // invalid type parameter + html! { /> }; + // parameter doesn't match bounds + html! { /> }; + + // missing type param + html! { }; +} + +fn main() {} diff --git a/packages/yew-functional-macro/tests/function_attr/generic-props-fail.stderr b/packages/yew-functional-macro/tests/function_attr/generic-props-fail.stderr new file mode 100644 index 00000000000..1d1bfcd3dc1 --- /dev/null +++ b/packages/yew-functional-macro/tests/function_attr/generic-props-fail.stderr @@ -0,0 +1,58 @@ +error[E0412]: cannot find type `INVALID` in this scope + --> $DIR/generic-props-fail.rs:26:19 + | +21 | fn compile_fail() { + | - help: you might be missing a type parameter: `` +... +26 | html! { /> }; + | ^^^^^^^ not found in this scope + +error[E0599]: no method named `build` found for struct `PropsBuilder` in the current scope + --> $DIR/generic-props-fail.rs:23:14 + | +4 | #[derive(Clone, Properties, PartialEq)] + | ---------- method `build` not found for this +... +23 | html! { /> }; + | ^^^^ method not found in `PropsBuilder` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `build`, perhaps you need to implement it: + candidate #1: `proc_macro::bridge::server::TokenStreamBuilder` + +error[E0599]: no function or associated item named `new` found for struct `yew::virtual_dom::vcomp::VChild>>` in the current scope + --> $DIR/generic-props-fail.rs:28:14 + | +28 | html! { /> }; + | ^^^^ function or associated item not found in `yew::virtual_dom::vcomp::VChild>>` + | + ::: $WORKSPACE/packages/yew-functional/src/lib.rs + | + | pub struct FunctionComponent { + | ----------------------------------------------------------- doesn't satisfy `_: yew::html::component::Component` + | + = note: the method `new` exists but the following trait bounds were not satisfied: + `yew_functional::FunctionComponent>: yew::html::component::Component` + +error[E0277]: the trait bound `MissingTypeBounds: yew::html::component::properties::Properties` is not satisfied + --> $DIR/generic-props-fail.rs:28:14 + | +28 | html! { /> }; + | ^^^^ the trait `yew::html::component::properties::Properties` is not implemented for `MissingTypeBounds` + | + = note: required because of the requirements on the impl of `yew_functional::FunctionProvider` for `comp` + +error[E0277]: can't compare `MissingTypeBounds` with `MissingTypeBounds` + --> $DIR/generic-props-fail.rs:28:14 + | +28 | html! { /> }; + | ^^^^ no implementation for `MissingTypeBounds == MissingTypeBounds` + | + = help: the trait `std::cmp::PartialEq` is not implemented for `MissingTypeBounds` + = note: required because of the requirements on the impl of `yew_functional::FunctionProvider` for `comp` + +error[E0107]: wrong number of type arguments: expected 1, found 0 + --> $DIR/generic-props-fail.rs:31:14 + | +31 | html! { }; + | ^^^^ expected 1 type argument