diff --git a/packages/core-macro/src/component.rs b/packages/core-macro/src/component.rs index 46410a0cd..235c1a38b 100644 --- a/packages/core-macro/src/component.rs +++ b/packages/core-macro/src/component.rs @@ -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::*; @@ -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)* } } }; @@ -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) { let ItemFn { vis, sig, .. } = &self.item_fn; let Signature { inputs, @@ -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::>(); + 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::>(); 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 @@ -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!() }; diff --git a/packages/core-macro/tests/generics.rs b/packages/core-macro/tests/generics.rs index 2547ab924..d83867493 100644 --- a/packages/core-macro/tests/generics.rs +++ b/packages/core-macro/tests/generics.rs @@ -30,6 +30,34 @@ fn generic_props_compile() { rsx! {} } + #[component] + fn TakesCloneArc(value: std::sync::Arc) -> Element { + rsx! {} + } + + struct MyBox(std::marker::PhantomData); + + impl Clone for MyBox { + fn clone(&self) -> Self { + MyBox(std::marker::PhantomData) + } + } + + impl PartialEq for MyBox { + fn eq(&self, _: &Self) -> bool { + true + } + } + + #[component] + #[allow(clippy::multiple_bound_locations)] + fn TakesCloneMyBox(value: MyBox) -> Element + where + T: Display, + { + rsx! {} + } + #[derive(Props, Clone, PartialEq)] struct TakesCloneManualProps { value: T,