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 proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::*;
|
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
|
// Props declared, so we generate a props struct and then also attach the doc attributes to it
|
||||||
false => {
|
false => {
|
||||||
let doc = format!("Properties for the [`{}`] component.", &comp_fn.sig.ident);
|
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! {
|
quote! {
|
||||||
#[doc = #doc]
|
#[doc = #doc]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#props_struct
|
#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
|
/// We try our best to transfer over any declared doc attributes from the original function signature onto the
|
||||||
/// props struct fields.
|
/// props struct fields.
|
||||||
fn props_struct(&self) -> ItemStruct {
|
fn props_struct(&self) -> (ItemStruct, Vec<ItemImpl>) {
|
||||||
let ItemFn { vis, sig, .. } = &self.item_fn;
|
let ItemFn { vis, sig, .. } = &self.item_fn;
|
||||||
let Signature {
|
let Signature {
|
||||||
inputs,
|
inputs,
|
||||||
@@ -225,17 +228,56 @@ impl ComponentBody {
|
|||||||
..
|
..
|
||||||
} = sig;
|
} = 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 where_clause = &generics.where_clause;
|
||||||
let struct_fields = inputs.iter().map(move |f| make_prop_struct_field(f, vis));
|
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());
|
let struct_ident = Ident::new(&format!("{ident}Props"), ident.span());
|
||||||
|
|
||||||
parse_quote! {
|
let item_struct = parse_quote! {
|
||||||
#[derive(Props, Clone, PartialEq)]
|
#[derive(Props)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#vis struct #struct_ident #generics #where_clause {
|
#vis struct #struct_ident #generics #where_clause {
|
||||||
#(#struct_fields),*
|
#(#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
|
/// 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 {
|
fn rebind_mutability(f: &FnArg) -> TokenStream {
|
||||||
// There's no receivers (&self) allowed in the component body
|
// There's no receivers (&self) allowed in the component body
|
||||||
let FnArg::Typed(pt) = f else { unreachable!() };
|
let FnArg::Typed(pt) = f else { unreachable!() };
|
||||||
|
|||||||
@@ -30,6 +30,34 @@ fn generic_props_compile() {
|
|||||||
rsx! {}
|
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)]
|
#[derive(Props, Clone, PartialEq)]
|
||||||
struct TakesCloneManualProps<T: Clone + PartialEq + 'static> {
|
struct TakesCloneManualProps<T: Clone + PartialEq + 'static> {
|
||||||
value: T,
|
value: T,
|
||||||
|
|||||||
Reference in new issue
Block a user