Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 77 additions & 5 deletions packages/core-macro/src/component.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::*;

Expand Down Expand Up @@ -55,11 +56,13 @@ impl ToTokens for ComponentBody {
// Props declared, so we generate a props struct and then also attach the doc attributes to it
false => {
let doc = format!("Properties for the [`{}`] component.", &comp_fn.sig.ident);
let props_struct = self.props_struct();
let (props_struct, props_impls) = self.props_struct();
quote! {
#[doc = #doc]
#[allow(missing_docs)]
#props_struct

#(#props_impls)*
}
}
};
Expand Down Expand Up @@ -216,7 +219,7 @@ impl ComponentBody {
///
/// We try our best to transfer over any declared doc attributes from the original function signature onto the
/// props struct fields.
fn props_struct(&self) -> ItemStruct {
fn props_struct(&self) -> (ItemStruct, Vec<ItemImpl>) {
let ItemFn { vis, sig, .. } = &self.item_fn;
let Signature {
inputs,
Expand All @@ -225,17 +228,56 @@ impl ComponentBody {
..
} = sig;

let generic_arguments = if !generics.params.is_empty() {
let generic_arguments = generics
.params
.iter()
.map(make_prop_struct_generics)
.collect::<Punctuated<_, Token![,]>>();
quote! { <#generic_arguments> }
} else {
quote! {}
};
let where_clause = &generics.where_clause;
let struct_fields = inputs.iter().map(move |f| make_prop_struct_field(f, vis));
let struct_field_idents = inputs
.iter()
.map(make_prop_struct_field_idents)
.collect::<Vec<_>>();
let struct_ident = Ident::new(&format!("{ident}Props"), ident.span());

parse_quote! {
#[derive(Props, Clone, PartialEq)]
let item_struct = parse_quote! {
#[derive(Props)]
#[allow(non_camel_case_types)]
#vis struct #struct_ident #generics #where_clause {
#(#struct_fields),*
}
}
};

let item_impl_clone = parse_quote! {
impl #generics ::core::clone::Clone for #struct_ident #generic_arguments #where_clause {
#[inline]
fn clone(&self) -> Self {
Self {
#(#struct_field_idents: ::core::clone::Clone::clone(&self.#struct_field_idents)),*
}
}
}
};

let item_impl_partial_eq = parse_quote! {
impl #generics ::core::cmp::PartialEq for #struct_ident #generic_arguments #where_clause {
#[inline]
fn eq(&self, other: &Self) -> bool {
#(
self.#struct_field_idents == other.#struct_field_idents &&
)*
true
}
}
};

(item_struct, vec![item_impl_clone, item_impl_partial_eq])
}

/// Convert a list of function arguments into a list of doc attributes for the props struct
Expand Down Expand Up @@ -494,6 +536,36 @@ fn make_prop_struct_field(f: &FnArg, vis: &Visibility) -> TokenStream {
}
}

/// Get ident from a function arg
fn make_prop_struct_field_idents(f: &FnArg) -> &Ident {
// There's no receivers (&self) allowed in the component body
let FnArg::Typed(pt) = f else { unreachable!() };

match pt.pat.as_ref() {
// rip off mutability
// todo: we actually don't want any of the extra bits of the field pattern
Pat::Ident(f) => &f.ident,
_ => unreachable!(),
}
}

fn make_prop_struct_generics(generics: &GenericParam) -> TokenStream {
match generics {
GenericParam::Type(ty) => {
let ident = &ty.ident;
quote! { #ident }
}
GenericParam::Lifetime(lifetime) => {
let lifetime = &lifetime.lifetime;
quote! { #lifetime }
}
GenericParam::Const(c) => {
let ident = &c.ident;
quote! { #ident }
}
}
}

fn rebind_mutability(f: &FnArg) -> TokenStream {
// There's no receivers (&self) allowed in the component body
let FnArg::Typed(pt) = f else { unreachable!() };
Expand Down
28 changes: 28 additions & 0 deletions packages/core-macro/tests/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,34 @@ fn generic_props_compile() {
rsx! {}
}

#[component]
fn TakesCloneArc<T: PartialEq + 'static>(value: std::sync::Arc<T>) -> Element {
rsx! {}
}

struct MyBox<T>(std::marker::PhantomData<T>);

impl<T: Display> Clone for MyBox<T> {
fn clone(&self) -> Self {
MyBox(std::marker::PhantomData)
}
}

impl<T: Display> PartialEq for MyBox<T> {
fn eq(&self, _: &Self) -> bool {
true
}
}

#[component]
#[allow(clippy::multiple_bound_locations)]
fn TakesCloneMyBox<T: 'static>(value: MyBox<T>) -> Element
where
T: Display,
{
rsx! {}
}

#[derive(Props, Clone, PartialEq)]
struct TakesCloneManualProps<T: Clone + PartialEq + 'static> {
value: T,
Expand Down