fix: more accurate derive Clone, PartialEq for generic components (#3968)
* convert to manual clone implements * add test for generics
This commit is contained in:
1 parent
82534d389f
commit
e0ecf0eb49
2 files changed
+105
-5
No files matched your search
@@ -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!() };
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user