Skip to content

Commit

Permalink
feat: support generic type parameters for component macro (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
ccbrown authored Oct 31, 2024
1 parent e412e99 commit 8b6d014
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 7 deletions.
93 changes: 86 additions & 7 deletions packages/iocraft-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -465,6 +525,25 @@ impl ToTokens for ParsedComponent {
/// }
/// }
/// ```
///
/// Components can also be generic:
///
/// ```
/// # use iocraft::prelude::*;
/// #[derive(Default, Props)]
/// struct MyGenericComponentProps<T> {
/// items: Vec<T>,
/// }
///
/// #[component]
/// fn MyGenericComponent<T: 'static>(
/// _props: &MyGenericComponentProps<T>,
/// ) -> impl Into<AnyElement<'static>> {
/// 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);
Expand Down
22 changes: 22 additions & 0 deletions packages/iocraft-macros/tests/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,25 @@ fn MyComponentWithHooks(_hooks: Hooks) -> impl Into<AnyElement<'static>> {
fn MyComponentWithHooksRef(_hooks: &mut Hooks) -> impl Into<AnyElement<'static>> {
element!(Box)
}

#[derive(Props)]
struct MyGenericProps<T, const U: usize> {
foo: [T; U],
}

#[component]
fn MyComponentWithGenericProps<T: 'static, const U: usize>(
_props: &mut MyGenericProps<T, U>,
) -> impl Into<AnyElement<'static>> {
element!(Box)
}

#[component]
fn MyComponentWithGenericPropsWhereClause<T, const U: usize>(
_props: &mut MyGenericProps<T, U>,
) -> impl Into<AnyElement<'static>>
where
T: 'static,
{
element!(Box)
}
27 changes: 27 additions & 0 deletions packages/iocraft-macros/tests/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ impl Component for MyContainer {
}
}

struct MyGenericComponent<T> {
_marker: std::marker::PhantomData<*const T>,
}

#[derive(Default, Props)]
struct MyGenericComponentProps<T> {
items: Vec<T>,
}

impl<T: 'static> Component for MyGenericComponent<T> {
type Props<'a> = MyGenericComponentProps<T>;

fn new(_props: &Self::Props<'_>) -> Self {
Self {
_marker: std::marker::PhantomData,
}
}
}

#[test]
fn minimal() {
let _: Element<MyComponent> = element!(MyComponent);
Expand Down Expand Up @@ -145,3 +164,11 @@ fn key() {
};
assert_eq!(e.props.children.len(), 1);
}

#[test]
fn generics() {
let e = element! {
MyGenericComponent<i32>(items: vec![0])
};
assert_eq!(vec![0], e.props.items);
}

0 comments on commit 8b6d014

Please sign in to comment.