Fix event handler drops (#2288)

* Try to fix event handler drops

* fix Option<EventHandler> parsing logic

* only move over props if both event handlers are some

* fix owner forwarding with props(extends)

* fix clippy

---------

Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
Matt Hunzinger 2024-04-10 12:23:45 -04:00 committed by GitHub
parent 994056e16d
commit 96b9baafff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -516,6 +516,8 @@ mod struct_info {
use syn::spanned::Spanned;
use syn::{Expr, Ident};
use crate::props::strip_option;
use super::field_info::{FieldBuilderAttr, FieldInfo};
use super::util::{
empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single,
@ -635,10 +637,6 @@ mod struct_info {
let event_handlers_fields: Vec<_> = self
.included_fields()
.filter(|f| looks_like_event_handler_type(f.ty))
.map(|f| {
let name = f.name;
quote!(#name)
})
.collect();
let regular_fields: Vec<_> = self
@ -650,12 +648,24 @@ mod struct_info {
})
.collect();
let move_event_handlers = quote! {
#(
// Update the event handlers
self.#event_handlers_fields.__set(new.#event_handlers_fields.__take());
)*
};
let move_event_handlers: TokenStream = event_handlers_fields.iter().map(|field| {
// If this is an optional event handler, we need to check if it's None before we try to update it
let optional = strip_option(field.ty).is_some();
let name = field.name;
if optional {
quote! {
// If the event handler is None, we don't need to update it
if let (Some(old_handler), Some(new_handler)) = (self.#name.as_mut(), new.#name.as_ref()) {
old_handler.__set(new_handler.__take());
}
}
} else {
quote! {
// Update the event handlers
self.#name.__set(new.#name.__take());
}
}
}).collect();
// If there are signals, we automatically try to memoize the signals
if !signal_fields.is_empty() {
@ -957,6 +967,11 @@ Finally, call `.build()` to create the instance of `{name}`.
quote!(#name: self.#name)
});
let forward_owner = self
.has_child_owned_fields()
.then(|| quote!(owner: self.owner))
.into_iter();
let extends_impl = field.builder_attr.extends.iter().map(|path| {
let name_str = path_to_single_string(path).unwrap();
let camel_name = name_str.to_case(Case::UpperCamel);
@ -994,6 +1009,7 @@ Finally, call `.build()` to create the instance of `{name}`.
);
#builder_name {
#(#forward_extended_fields,)*
#(#forward_owner,)*
fields: ( #(#reconstructing,)* ),
_phantom: self._phantom,
}
@ -1622,6 +1638,41 @@ fn extract_base_type_without_single_generic(ty: &Type) -> Option<syn::Path> {
Some(path_without_generics)
}
/// Returns the type inside the Option wrapper if it exists
fn strip_option(type_: &Type) -> Option<Type> {
if let Type::Path(ty) = &type_ {
let mut segments_iter = ty.path.segments.iter().peekable();
// Strip any leading std||core::option:: prefix
let allowed_segments: &[&[&str]] = &[&["std", "core"], &["option"]];
let mut allowed_segments_iter = allowed_segments.iter();
while let Some(segment) = segments_iter.peek() {
let Some(allowed_segments) = allowed_segments_iter.next() else {
break;
};
if !allowed_segments.contains(&segment.ident.to_string().as_str()) {
break;
}
segments_iter.next();
}
// The last segment should be Option
let option_segment = segments_iter.next()?;
if option_segment.ident == "Option" && segments_iter.next().is_none() {
// It should have a single generic argument
if let PathArguments::AngleBracketed(generic_arg) = &option_segment.arguments {
if let Some(syn::GenericArgument::Type(ty)) = generic_arg.args.first() {
return Some(ty.clone());
}
}
}
}
None
}
/// Remove the Option wrapper from a type
fn remove_option_wrapper(type_: Type) -> Type {
strip_option(&type_).unwrap_or(type_)
}
/// Check if a type should be owned by the child component after conversion
fn child_owned_type(ty: &Type) -> bool {
looks_like_signal_type(ty) || looks_like_event_handler_type(ty)
@ -1639,7 +1690,8 @@ fn looks_like_signal_type(ty: &Type) -> bool {
}
fn looks_like_event_handler_type(ty: &Type) -> bool {
match extract_base_type_without_single_generic(ty) {
let type_without_option = remove_option_wrapper(ty.clone());
match extract_base_type_without_single_generic(&type_without_option) {
Some(path_without_generics) => {
path_without_generics == parse_quote!(dioxus_core::prelude::EventHandler)
|| path_without_generics == parse_quote!(prelude::EventHandler)
@ -1648,3 +1700,44 @@ fn looks_like_event_handler_type(ty: &Type) -> bool {
None => false,
}
}
#[test]
fn test_looks_like_type() {
assert!(!looks_like_signal_type(&parse_quote!(
Option<ReadOnlySignal<i32>>
)));
assert!(looks_like_signal_type(&parse_quote!(ReadOnlySignal<i32>)));
assert!(looks_like_signal_type(
&parse_quote!(ReadOnlySignal<i32, SyncStorage>)
));
assert!(looks_like_signal_type(&parse_quote!(
ReadOnlySignal<Option<i32>, UnsyncStorage>
)));
assert!(looks_like_event_handler_type(&parse_quote!(
Option<EventHandler>
)));
assert!(looks_like_event_handler_type(&parse_quote!(
std::option::Option<EventHandler<i32>>
)));
assert!(looks_like_event_handler_type(&parse_quote!(
Option<EventHandler<MouseEvent>>
)));
assert!(looks_like_event_handler_type(&parse_quote!(
EventHandler<i32>
)));
assert!(looks_like_event_handler_type(&parse_quote!(EventHandler)));
}
#[test]
fn test_remove_option_wrapper() {
let type_without_option = remove_option_wrapper(parse_quote!(Option<i32>));
assert_eq!(type_without_option, parse_quote!(i32));
let type_without_option = remove_option_wrapper(parse_quote!(Option<Option<i32>>));
assert_eq!(type_without_option, parse_quote!(Option<i32>));
let type_without_option = remove_option_wrapper(parse_quote!(Option<Option<Option<i32>>>));
assert_eq!(type_without_option, parse_quote!(Option<Option<i32>>));
}