diff --git a/packages/iocraft-macros/src/lib.rs b/packages/iocraft-macros/src/lib.rs index b51004d..7c39c54 100644 --- a/packages/iocraft-macros/src/lib.rs +++ b/packages/iocraft-macros/src/lib.rs @@ -12,8 +12,8 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, token::{Brace, Comma, Paren}, - DeriveInput, Error, Expr, FieldValue, FnArg, GenericParam, Ident, ItemFn, ItemStruct, Lifetime, - Lit, Member, Pat, Result, Token, Type, TypePath, + DeriveInput, Error, Expr, FieldValue, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct, + Lifetime, Lit, Member, Pat, Result, Token, Type, TypePath, WhereClause, WherePredicate, }; use uuid::Uuid; @@ -344,6 +344,62 @@ impl ToTokens for ParsedComponent { let block = &self.f.block; let output = &self.f.sig.output; let generics = &self.f.sig.generics; + let lifetime_generics = { + Generics { + params: generics + .params + .iter() + .filter(|param| matches!(param, GenericParam::Lifetime(_))) + .cloned() + .collect(), + where_clause: generics + .where_clause + .as_ref() + .map(|where_clause| WhereClause { + where_token: where_clause.where_token, + predicates: where_clause + .predicates + .iter() + .filter(|predicate| matches!(predicate, WherePredicate::Lifetime(_))) + .cloned() + .collect(), + }), + ..generics.clone() + } + }; + let (lifetime_impl_generics, _lifetime_ty_generics, lifetime_where_clause) = + lifetime_generics.split_for_impl(); + let type_generics = { + Generics { + params: generics + .params + .iter() + .filter(|param| !matches!(param, GenericParam::Lifetime(_))) + .cloned() + .collect(), + where_clause: generics + .where_clause + .as_ref() + .map(|where_clause| WhereClause { + where_token: where_clause.where_token, + predicates: where_clause + .predicates + .iter() + .filter(|predicate| !matches!(predicate, WherePredicate::Lifetime(_))) + .cloned() + .collect(), + }), + ..generics.clone() + } + }; + let (impl_generics, ty_generics, where_clause) = type_generics.split_for_impl(); + let ty_generic_names = type_generics.params.iter().filter_map(|param| match param { + GenericParam::Type(ty) => { + let name = &ty.ident; + Some(quote!(#name)) + } + _ => None, + }); let impl_args = &self.impl_args; let props_type_name = self @@ -354,17 +410,21 @@ impl ToTokens for ParsedComponent { tokens.extend(quote! { #(#attrs)* - #vis struct #name; + #vis struct #name #impl_generics { + _marker: std::marker::PhantomData<*const (#(#ty_generic_names),*)>, + } - impl #name { - fn implementation #generics (#args) #output #block + impl #impl_generics #name #ty_generics #where_clause { + fn implementation #lifetime_impl_generics (#args) #output #lifetime_where_clause #block } - impl ::iocraft::Component for #name { + impl #impl_generics ::iocraft::Component for #name #ty_generics #where_clause { type Props<'a> = #props_type_name; fn new(_props: &Self::Props<'_>) -> Self { - Self + Self{ + _marker: std::marker::PhantomData, + } } fn update(&mut self, props: &mut Self::Props<'_>, mut hooks: ::iocraft::Hooks, updater: &mut ::iocraft::ComponentUpdater) { @@ -465,6 +525,25 @@ impl ToTokens for ParsedComponent { /// } /// } /// ``` +/// +/// Components can also be generic: +/// +/// ``` +/// # use iocraft::prelude::*; +/// #[derive(Default, Props)] +/// struct MyGenericComponentProps { +/// items: Vec, +/// } +/// +/// #[component] +/// fn MyGenericComponent( +/// _props: &MyGenericComponentProps, +/// ) -> impl Into> { +/// element!(Box) +/// } +/// ``` +/// +/// However, note that generic type parameters must be `'static`. #[proc_macro_attribute] pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream { let component = parse_macro_input!(item as ParsedComponent); diff --git a/packages/iocraft-macros/tests/component.rs b/packages/iocraft-macros/tests/component.rs index 044df53..cb9e4f5 100644 --- a/packages/iocraft-macros/tests/component.rs +++ b/packages/iocraft-macros/tests/component.rs @@ -27,3 +27,25 @@ fn MyComponentWithHooks(_hooks: Hooks) -> impl Into> { fn MyComponentWithHooksRef(_hooks: &mut Hooks) -> impl Into> { element!(Box) } + +#[derive(Props)] +struct MyGenericProps { + foo: [T; U], +} + +#[component] +fn MyComponentWithGenericProps( + _props: &mut MyGenericProps, +) -> impl Into> { + element!(Box) +} + +#[component] +fn MyComponentWithGenericPropsWhereClause( + _props: &mut MyGenericProps, +) -> impl Into> +where + T: 'static, +{ + element!(Box) +} diff --git a/packages/iocraft-macros/tests/element.rs b/packages/iocraft-macros/tests/element.rs index 36b3c4a..3896c71 100644 --- a/packages/iocraft-macros/tests/element.rs +++ b/packages/iocraft-macros/tests/element.rs @@ -35,6 +35,25 @@ impl Component for MyContainer { } } +struct MyGenericComponent { + _marker: std::marker::PhantomData<*const T>, +} + +#[derive(Default, Props)] +struct MyGenericComponentProps { + items: Vec, +} + +impl Component for MyGenericComponent { + type Props<'a> = MyGenericComponentProps; + + fn new(_props: &Self::Props<'_>) -> Self { + Self { + _marker: std::marker::PhantomData, + } + } +} + #[test] fn minimal() { let _: Element = element!(MyComponent); @@ -145,3 +164,11 @@ fn key() { }; assert_eq!(e.props.children.len(), 1); } + +#[test] +fn generics() { + let e = element! { + MyGenericComponent(items: vec![0]) + }; + assert_eq!(vec![0], e.props.items); +}