|
@@ -8,10 +8,12 @@
|
|
|
|
|
|
use proc_macro2::TokenStream;
|
|
use proc_macro2::TokenStream;
|
|
|
|
|
|
-use syn::parse::Error;
|
|
|
|
|
|
+use syn::punctuated::Punctuated;
|
|
use syn::spanned::Spanned;
|
|
use syn::spanned::Spanned;
|
|
|
|
+use syn::{parse::Error, PathArguments};
|
|
|
|
|
|
use quote::quote;
|
|
use quote::quote;
|
|
|
|
+use syn::{parse_quote, Type};
|
|
|
|
|
|
pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
|
|
pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
|
|
let data = match &ast.data {
|
|
let data = match &ast.data {
|
|
@@ -515,6 +517,7 @@ mod struct_info {
|
|
use syn::{Expr, Ident};
|
|
use syn::{Expr, Ident};
|
|
|
|
|
|
use super::field_info::{FieldBuilderAttr, FieldInfo};
|
|
use super::field_info::{FieldBuilderAttr, FieldInfo};
|
|
|
|
+ use super::looks_like_signal_type;
|
|
use super::util::{
|
|
use super::util::{
|
|
empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single,
|
|
empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single,
|
|
modify_types_generics_hack, path_to_single_string, strip_raw_ident_prefix, type_tuple,
|
|
modify_types_generics_hack, path_to_single_string, strip_raw_ident_prefix, type_tuple,
|
|
@@ -576,6 +579,89 @@ mod struct_info {
|
|
generics
|
|
generics
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ fn has_signal_fields(&self) -> bool {
|
|
|
|
+ self.fields.iter().any(|f| looks_like_signal_type(f.ty))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn memoize_impl(&self) -> Result<TokenStream, Error> {
|
|
|
|
+ // First check if there are any Signal fields, if there are not, we can just use the partialEq impl
|
|
|
|
+ let has_signal_fields = self.has_signal_fields();
|
|
|
|
+
|
|
|
|
+ if has_signal_fields {
|
|
|
|
+ let signal_fields: Vec<_> = self
|
|
|
|
+ .included_fields()
|
|
|
|
+ .filter(|f| looks_like_signal_type(f.ty))
|
|
|
|
+ .map(|f| {
|
|
|
|
+ let name = f.name;
|
|
|
|
+ quote!(#name)
|
|
|
|
+ })
|
|
|
|
+ .collect();
|
|
|
|
+
|
|
|
|
+ Ok(quote! {
|
|
|
|
+ // First check if the fields are equal
|
|
|
|
+ let exactly_equal = self == new;
|
|
|
|
+ if exactly_equal {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If they are not, move over the signal fields and check if they are equal
|
|
|
|
+ #(
|
|
|
|
+ let mut #signal_fields = self.#signal_fields;
|
|
|
|
+ self.#signal_fields = new.#signal_fields;
|
|
|
|
+ )*
|
|
|
|
+
|
|
|
|
+ // Then check if the fields are equal again
|
|
|
|
+ let non_signal_fields_equal = self == new;
|
|
|
|
+
|
|
|
|
+ // If they are not equal, we still need to rerun the component
|
|
|
|
+ if !non_signal_fields_equal {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ trait NonPartialEq: Sized {
|
|
|
|
+ fn compare(&self, other: &Self) -> bool;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ impl<T> NonPartialEq for &&T {
|
|
|
|
+ fn compare(&self, other: &Self) -> bool {
|
|
|
|
+ false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ trait CanPartialEq: PartialEq {
|
|
|
|
+ fn compare(&self, other: &Self) -> bool;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ impl<T: PartialEq> CanPartialEq for T {
|
|
|
|
+ fn compare(&self, other: &Self) -> bool {
|
|
|
|
+ self == other
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If they are equal, we don't need to rerun the component we can just update the existing signals
|
|
|
|
+ #(
|
|
|
|
+ // Try to memo the signal
|
|
|
|
+ let field_eq = {
|
|
|
|
+ let old_value: &_ = &*#signal_fields.peek();
|
|
|
|
+ let new_value: &_ = &*new.#signal_fields.peek();
|
|
|
|
+ (&old_value).compare(&&new_value)
|
|
|
|
+ };
|
|
|
|
+ if !field_eq {
|
|
|
|
+ (#signal_fields).set(new.#signal_fields.take());
|
|
|
|
+ }
|
|
|
|
+ // Move the old value back
|
|
|
|
+ self.#signal_fields = #signal_fields;
|
|
|
|
+ )*
|
|
|
|
+
|
|
|
|
+ true
|
|
|
|
+ })
|
|
|
|
+ } else {
|
|
|
|
+ Ok(quote! {
|
|
|
|
+ self == new
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
pub fn builder_creation_impl(&self) -> Result<TokenStream, Error> {
|
|
pub fn builder_creation_impl(&self) -> Result<TokenStream, Error> {
|
|
let StructInfo {
|
|
let StructInfo {
|
|
ref vis,
|
|
ref vis,
|
|
@@ -584,12 +670,6 @@ mod struct_info {
|
|
..
|
|
..
|
|
} = *self;
|
|
} = *self;
|
|
|
|
|
|
- // we're generating stuff that goes into unsafe code here
|
|
|
|
- // we use the heuristic: are there *any* generic parameters?
|
|
|
|
- // If so, then they might have non-static lifetimes and we can't compare two generic things that *might borrow*
|
|
|
|
- // Therefore, we will generate code that shortcircuits the "comparison" in memoization
|
|
|
|
- let are_there_generics = !self.generics.params.is_empty();
|
|
|
|
-
|
|
|
|
let generics = self.generics.clone();
|
|
let generics = self.generics.clone();
|
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
let (_, b_initial_generics, _) = self.generics.split_for_impl();
|
|
let (_, b_initial_generics, _) = self.generics.split_for_impl();
|
|
@@ -669,20 +749,26 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|
.extend(predicates.predicates.clone());
|
|
.extend(predicates.predicates.clone());
|
|
}
|
|
}
|
|
|
|
|
|
- let can_memoize = match are_there_generics {
|
|
|
|
- true => quote! { false },
|
|
|
|
- false => quote! { self == other },
|
|
|
|
- };
|
|
|
|
|
|
+ let memoize = self.memoize_impl()?;
|
|
|
|
|
|
- let extend_fields = self.extend_fields().map(|f| {
|
|
|
|
- let name = f.name;
|
|
|
|
- let ty = f.ty;
|
|
|
|
- quote!(#name: #ty)
|
|
|
|
- });
|
|
|
|
- let extend_fields_value = self.extend_fields().map(|f| {
|
|
|
|
- let name = f.name;
|
|
|
|
- quote!(#name: Vec::new())
|
|
|
|
- });
|
|
|
|
|
|
+ let global_fields = self
|
|
|
|
+ .extend_fields()
|
|
|
|
+ .map(|f| {
|
|
|
|
+ let name = f.name;
|
|
|
|
+ let ty = f.ty;
|
|
|
|
+ quote!(#name: #ty)
|
|
|
|
+ })
|
|
|
|
+ .chain(self.has_signal_fields().then(|| quote!(owner: Owner)));
|
|
|
|
+ let global_fields_value = self
|
|
|
|
+ .extend_fields()
|
|
|
|
+ .map(|f| {
|
|
|
|
+ let name = f.name;
|
|
|
|
+ quote!(#name: Vec::new())
|
|
|
|
+ })
|
|
|
|
+ .chain(
|
|
|
|
+ self.has_signal_fields()
|
|
|
|
+ .then(|| quote!(owner: Owner::default())),
|
|
|
|
+ );
|
|
|
|
|
|
Ok(quote! {
|
|
Ok(quote! {
|
|
impl #impl_generics #name #ty_generics #where_clause {
|
|
impl #impl_generics #name #ty_generics #where_clause {
|
|
@@ -690,7 +776,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|
#[allow(dead_code, clippy::type_complexity)]
|
|
#[allow(dead_code, clippy::type_complexity)]
|
|
#vis fn builder() -> #builder_name #generics_with_empty {
|
|
#vis fn builder() -> #builder_name #generics_with_empty {
|
|
#builder_name {
|
|
#builder_name {
|
|
- #(#extend_fields_value,)*
|
|
|
|
|
|
+ #(#global_fields_value,)*
|
|
fields: #empties_tuple,
|
|
fields: #empties_tuple,
|
|
_phantom: ::core::default::Default::default(),
|
|
_phantom: ::core::default::Default::default(),
|
|
}
|
|
}
|
|
@@ -701,7 +787,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|
#builder_type_doc
|
|
#builder_type_doc
|
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
|
#vis struct #builder_name #b_generics {
|
|
#vis struct #builder_name #b_generics {
|
|
- #(#extend_fields,)*
|
|
|
|
|
|
+ #(#global_fields,)*
|
|
fields: #all_fields_param,
|
|
fields: #all_fields_param,
|
|
_phantom: (#( #phantom_generics ),*),
|
|
_phantom: (#( #phantom_generics ),*),
|
|
}
|
|
}
|
|
@@ -713,11 +799,10 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|
fn builder() -> Self::Builder {
|
|
fn builder() -> Self::Builder {
|
|
#name::builder()
|
|
#name::builder()
|
|
}
|
|
}
|
|
- fn memoize(&self, other: &Self) -> bool {
|
|
|
|
- #can_memoize
|
|
|
|
|
|
+ fn memoize(&mut self, new: &Self) -> bool {
|
|
|
|
+ #memoize
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
@@ -966,22 +1051,29 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|
let arg_type = field_type;
|
|
let arg_type = field_type;
|
|
// If the field is auto_into, we need to add a generic parameter to the builder for specialization
|
|
// If the field is auto_into, we need to add a generic parameter to the builder for specialization
|
|
let mut marker = None;
|
|
let mut marker = None;
|
|
- let (arg_type, arg_expr) =
|
|
|
|
- if field.builder_attr.auto_into || field.builder_attr.strip_option {
|
|
|
|
- let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site());
|
|
|
|
- marker = Some(marker_ident.clone());
|
|
|
|
- (
|
|
|
|
- quote!(impl dioxus_core::prelude::SuperInto<#arg_type, #marker_ident>),
|
|
|
|
- quote!(dioxus_core::prelude::SuperInto::super_into(#field_name)),
|
|
|
|
- )
|
|
|
|
- } else if field.builder_attr.from_displayable {
|
|
|
|
- (
|
|
|
|
- quote!(impl ::core::fmt::Display),
|
|
|
|
- quote!(#field_name.to_string()),
|
|
|
|
- )
|
|
|
|
- } else {
|
|
|
|
- (quote!(#arg_type), quote!(#field_name))
|
|
|
|
- };
|
|
|
|
|
|
+ let (arg_type, arg_expr) = if looks_like_signal_type(arg_type) {
|
|
|
|
+ let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site());
|
|
|
|
+ marker = Some(marker_ident.clone());
|
|
|
|
+ (
|
|
|
|
+ quote!(impl dioxus_core::prelude::SuperInto<#arg_type, #marker_ident>),
|
|
|
|
+ // If this looks like a signal type, we automatically convert it with SuperInto and use the props struct as the owner
|
|
|
|
+ quote!(with_owner(self.owner.clone(), move || dioxus_core::prelude::SuperInto::super_into(#field_name))),
|
|
|
|
+ )
|
|
|
|
+ } else if field.builder_attr.auto_into || field.builder_attr.strip_option {
|
|
|
|
+ let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site());
|
|
|
|
+ marker = Some(marker_ident.clone());
|
|
|
|
+ (
|
|
|
|
+ quote!(impl dioxus_core::prelude::SuperInto<#arg_type, #marker_ident>),
|
|
|
|
+ quote!(dioxus_core::prelude::SuperInto::super_into(#field_name)),
|
|
|
|
+ )
|
|
|
|
+ } else if field.builder_attr.from_displayable {
|
|
|
|
+ (
|
|
|
|
+ quote!(impl ::core::fmt::Display),
|
|
|
|
+ quote!(#field_name.to_string()),
|
|
|
|
+ )
|
|
|
|
+ } else {
|
|
|
|
+ (quote!(#arg_type), quote!(#field_name))
|
|
|
|
+ };
|
|
|
|
|
|
let repeated_fields_error_type_name = syn::Ident::new(
|
|
let repeated_fields_error_type_name = syn::Ident::new(
|
|
&format!(
|
|
&format!(
|
|
@@ -993,10 +1085,13 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|
);
|
|
);
|
|
let repeated_fields_error_message = format!("Repeated field {field_name}");
|
|
let repeated_fields_error_message = format!("Repeated field {field_name}");
|
|
|
|
|
|
- let forward_extended_fields = self.extend_fields().map(|f| {
|
|
|
|
- let name = f.name;
|
|
|
|
- quote!(#name: self.#name)
|
|
|
|
- });
|
|
|
|
|
|
+ let forward_fields = self
|
|
|
|
+ .extend_fields()
|
|
|
|
+ .map(|f| {
|
|
|
|
+ let name = f.name;
|
|
|
|
+ quote!(#name: self.#name)
|
|
|
|
+ })
|
|
|
|
+ .chain(self.has_signal_fields().then(|| quote!(owner: self.owner)));
|
|
|
|
|
|
Ok(quote! {
|
|
Ok(quote! {
|
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
|
@@ -1007,7 +1102,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|
let #field_name = (#arg_expr,);
|
|
let #field_name = (#arg_expr,);
|
|
let ( #(#descructuring,)* ) = self.fields;
|
|
let ( #(#descructuring,)* ) = self.fields;
|
|
#builder_name {
|
|
#builder_name {
|
|
- #(#forward_extended_fields,)*
|
|
|
|
|
|
+ #(#forward_fields,)*
|
|
fields: ( #(#reconstructing,)* ),
|
|
fields: ( #(#reconstructing,)* ),
|
|
_phantom: self._phantom,
|
|
_phantom: self._phantom,
|
|
}
|
|
}
|
|
@@ -1132,7 +1227,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|
note = #early_build_error_message
|
|
note = #early_build_error_message
|
|
)]
|
|
)]
|
|
pub fn build(self, _: #early_build_error_type_name) -> #name #ty_generics {
|
|
pub fn build(self, _: #early_build_error_type_name) -> #name #ty_generics {
|
|
- panic!();
|
|
|
|
|
|
+ panic!()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
@@ -1229,26 +1324,84 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|
// I’d prefer “a” or “an” to “its”, but determining which is grammatically
|
|
// I’d prefer “a” or “an” to “its”, but determining which is grammatically
|
|
// correct is roughly impossible.
|
|
// correct is roughly impossible.
|
|
let doc =
|
|
let doc =
|
|
- format!("Finalise the builder and create its [`{name}`] instance");
|
|
|
|
|
|
+ format!("Finalize the builder and create its [`{name}`] instance");
|
|
quote!(#[doc = #doc])
|
|
quote!(#[doc = #doc])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
quote!()
|
|
quote!()
|
|
};
|
|
};
|
|
- quote!(
|
|
|
|
- #[allow(dead_code, non_camel_case_types, missing_docs)]
|
|
|
|
- impl #impl_generics #builder_name #modified_ty_generics #where_clause {
|
|
|
|
- #doc
|
|
|
|
- pub fn build(self) -> #name #ty_generics {
|
|
|
|
- let ( #(#descructuring,)* ) = self.fields;
|
|
|
|
- #( #assignments )*
|
|
|
|
- #name {
|
|
|
|
- #( #field_names ),*
|
|
|
|
|
|
+
|
|
|
|
+ if self.has_signal_fields() {
|
|
|
|
+ let name = Ident::new(&format!("{}WithOwner", name), name.span());
|
|
|
|
+ let original_name = &self.name;
|
|
|
|
+ quote! {
|
|
|
|
+ #[doc(hidden)]
|
|
|
|
+ #[allow(dead_code, non_camel_case_types, missing_docs)]
|
|
|
|
+ #[derive(Clone)]
|
|
|
|
+ struct #name #ty_generics {
|
|
|
|
+ inner: #original_name #ty_generics,
|
|
|
|
+ owner: Owner,
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ impl #impl_generics PartialEq for #name #ty_generics #where_clause {
|
|
|
|
+ fn eq(&self, other: &Self) -> bool {
|
|
|
|
+ self.inner.eq(&other.inner)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ impl #impl_generics #name #ty_generics #where_clause {
|
|
|
|
+ /// Create a component from the props.
|
|
|
|
+ fn into_vcomponent<M: 'static>(
|
|
|
|
+ self,
|
|
|
|
+ render_fn: impl dioxus_core::prelude::ComponentFunction<#original_name #ty_generics, M>,
|
|
|
|
+ component_name: &'static str,
|
|
|
|
+ ) -> dioxus_core::VComponent {
|
|
|
|
+ use dioxus_core::prelude::ComponentFunction;
|
|
|
|
+ dioxus_core::VComponent::new(move |wrapper: Self| render_fn.rebuild(wrapper.inner), self, component_name)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ impl #impl_generics dioxus_core::prelude::Properties for #name #ty_generics #where_clause {
|
|
|
|
+ type Builder = ();
|
|
|
|
+ fn builder() -> Self::Builder {
|
|
|
|
+ unreachable!()
|
|
|
|
+ }
|
|
|
|
+ fn memoize(&mut self, new: &Self) -> bool {
|
|
|
|
+ self.inner.memoize(&new.inner)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[allow(dead_code, non_camel_case_types, missing_docs)]
|
|
|
|
+ impl #impl_generics #builder_name #modified_ty_generics #where_clause {
|
|
|
|
+ #doc
|
|
|
|
+ pub fn build(self) -> #name #ty_generics {
|
|
|
|
+ let ( #(#descructuring,)* ) = self.fields;
|
|
|
|
+ #( #assignments )*
|
|
|
|
+ #name {
|
|
|
|
+ inner: #original_name {
|
|
|
|
+ #( #field_names ),*
|
|
|
|
+ },
|
|
|
|
+ owner: self.owner,
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- )
|
|
|
|
|
|
+ } else {
|
|
|
|
+ quote!(
|
|
|
|
+ #[allow(dead_code, non_camel_case_types, missing_docs)]
|
|
|
|
+ impl #impl_generics #builder_name #modified_ty_generics #where_clause {
|
|
|
|
+ #doc
|
|
|
|
+ pub fn build(self) -> #name #ty_generics {
|
|
|
|
+ let ( #(#descructuring,)* ) = self.fields;
|
|
|
|
+ #( #assignments )*
|
|
|
|
+ #name {
|
|
|
|
+ #( #field_names ),*
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1374,3 +1527,47 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+fn looks_like_signal_type(ty: &Type) -> bool {
|
|
|
|
+ match ty {
|
|
|
|
+ Type::Path(ty) => {
|
|
|
|
+ if ty.qself.is_some() {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let path = &ty.path;
|
|
|
|
+
|
|
|
|
+ let mut path_segments_without_generics = Vec::new();
|
|
|
|
+
|
|
|
|
+ let mut generic_arg_count = 0;
|
|
|
|
+
|
|
|
|
+ for segment in &path.segments {
|
|
|
|
+ let mut segment = segment.clone();
|
|
|
|
+ match segment.arguments {
|
|
|
|
+ PathArguments::AngleBracketed(_) => generic_arg_count += 1,
|
|
|
|
+ PathArguments::Parenthesized(_) => {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ _ => {}
|
|
|
|
+ }
|
|
|
|
+ segment.arguments = syn::PathArguments::None;
|
|
|
|
+ path_segments_without_generics.push(segment);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If there is more than the type and the send/sync generic, it doesn't look like our signal
|
|
|
|
+ if generic_arg_count > 2 {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let path_without_generics = syn::Path {
|
|
|
|
+ leading_colon: None,
|
|
|
|
+ segments: Punctuated::from_iter(path_segments_without_generics),
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ path_without_generics == parse_quote!(dioxus_core::prelude::Signal)
|
|
|
|
+ || path_without_generics == parse_quote!(prelude::Signal)
|
|
|
|
+ || path_without_generics == parse_quote!(Signal)
|
|
|
|
+ }
|
|
|
|
+ _ => false,
|
|
|
|
+ }
|
|
|
|
+}
|