fix: more accurate derive Clone, PartialEq for generic components (#3968)

* convert to manual clone implements
* add test for generics
This commit is contained in:
Clouds authored and GitHub committed 2025-04-09 11:03:14 -07:00
1 parent 82534d389f
commit e0ecf0eb49
2 files changed
+105 -5

No files matched your search

+77 -5
View File
@@ -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<ItemImpl>) {
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::<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
@@ -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!() };
+28
View File
@@ -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,