From 98bd78de43676f8c3910e25b5418ef382ed7c73b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 15 Jan 2024 23:57:10 -0800 Subject: [PATCH] Fix issue with mutability in component macro --- examples/signals.rs | 5 ++++ examples/todomvc.rs | 12 +++----- .../inline_props.rs | 30 ++++++++++++++++--- .../router/src/components/history_buttons.rs | 4 +-- packages/signals/src/signal.rs | 18 ++++++----- 5 files changed, 47 insertions(+), 22 deletions(-) diff --git a/examples/signals.rs b/examples/signals.rs index 76136519d..3bba71cd7 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -17,7 +17,12 @@ fn app() -> Element { if running() { count += 1; } + + let val = running.read(); + tokio::time::sleep(Duration::from_millis(400)).await; + + println!("Running: {}", *val); } }); diff --git a/examples/todomvc.rs b/examples/todomvc.rs index 495c2adc4..87f38cb71 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -80,8 +80,7 @@ pub fn app() -> Element { } #[component] -pub fn TodoHeader(todos: Signal>) -> Element { - let mut todos = todos; +pub fn TodoHeader(mut todos: Signal>) -> Element { let mut draft = use_signal(|| "".to_string()); let mut todo_id = use_signal(|| 0); @@ -115,8 +114,7 @@ pub fn TodoHeader(todos: Signal>) -> Element { } #[component] -pub fn TodoEntry(todos: Signal>, id: u32) -> Element { - let mut todos = todos; +pub fn TodoEntry(mut todos: Signal>, id: u32) -> Element { let mut is_editing = use_signal(|| false); let checked = use_selector(move || todos.read().get(&id).unwrap().checked); let contents = use_selector(move || todos.read().get(&id).unwrap().contents.clone()); @@ -164,12 +162,10 @@ pub fn TodoEntry(todos: Signal>, id: u32) -> Element { #[component] pub fn ListFooter( - todos: Signal>, + mut todos: Signal>, active_todo_count: ReadOnlySignal, - filter: Signal, + mut filter: Signal, ) -> Element { - let mut todos = todos; - let mut filter = filter; let show_clear_completed = use_selector(move || todos.read().values().any(|todo| todo.checked)); rsx! { diff --git a/packages/core-macro/src/component_body_deserializers/inline_props.rs b/packages/core-macro/src/component_body_deserializers/inline_props.rs index 75d2c9c9d..c9a2ae847 100644 --- a/packages/core-macro/src/component_body_deserializers/inline_props.rs +++ b/packages/core-macro/src/component_body_deserializers/inline_props.rs @@ -46,12 +46,20 @@ fn get_props_struct(component_body: &ComponentBody) -> ItemStruct { .. } = sig; - // Skip first arg since that's the context let struct_fields = inputs.iter().map(move |f| { match f { FnArg::Receiver(_) => unreachable!(), // Unreachable because of ComponentBody parsing FnArg::Typed(pt) => { - let arg_pat = &pt.pat; // Pattern (identifier) + let arg_pat = match pt.pat.as_ref() { + // rip off mutability + Pat::Ident(f) => { + let mut f = f.clone(); + f.mutability = None; + quote! { #f } + } + a => quote! { #a }, + }; + let arg_colon = &pt.colon_token; let arg_ty = &pt.ty; // Type let arg_attrs = &pt.attrs; // Attributes @@ -247,7 +255,21 @@ fn get_function(component_body: &ComponentBody) -> ItemFn { // Skip first arg since that's the context let struct_field_names = inputs.iter().filter_map(|f| match f { FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters. - FnArg::Typed(pt) => Some(&pt.pat), + FnArg::Typed(pt) => { + let pat = &pt.pat; + + let mut pat = pat.clone(); + + // rip off mutability, but still write it out eventually + match pat.as_mut() { + Pat::Ident(ref mut pat_ident) => { + pat_ident.mutability = None; + } + _ => {} + } + + Some(quote!(mut #pat)) + } }); let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() { @@ -293,7 +315,7 @@ fn get_function(component_body: &ComponentBody) -> ItemFn { parse_quote! { #(#fn_attrs)* #(#props_docs)* - #asyncness #vis fn #fn_ident #fn_generics (__props: #struct_ident #generics_no_bounds) #fn_output + #asyncness #vis fn #fn_ident #fn_generics (mut __props: #struct_ident #generics_no_bounds) #fn_output #where_clause { let #struct_ident { #(#struct_field_names),* } = __props; diff --git a/packages/router/src/components/history_buttons.rs b/packages/router/src/components/history_buttons.rs index 19ae81270..d4fc86c49 100644 --- a/packages/router/src/components/history_buttons.rs +++ b/packages/router/src/components/history_buttons.rs @@ -57,7 +57,7 @@ pub struct HistoryButtonProps { #[component] pub fn GoBackButton( /// The props... - props: HistoryButtonProps, + mut props: HistoryButtonProps, ) -> Element { let HistoryButtonProps { children } = props; @@ -127,7 +127,7 @@ pub fn GoBackButton( #[component] pub fn GoForwardButton( /// Props... - props: HistoryButtonProps, + mut props: HistoryButtonProps, ) -> Element { let HistoryButtonProps { children } = props; diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 604ee805b..ab75a2861 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -245,7 +245,17 @@ impl Signal { GenerationalRef::map(inner, |v| &v.value) } + /// Get a mutable reference to the signal's value. + /// + /// If the signal has been dropped, this will panic. + #[track_caller] + pub fn write<'a>(&'a mut self) -> Write<'a, T> { + self.write_unchecked() + } + /// Write to the value through an immutable reference. + /// + /// This is public since it's useful in many scenarios, but we generally recommend mutation through [`Self::write`] instead. pub fn write_unchecked(&self) -> Write { let inner = self.inner.write(); let borrow = GenerationalRefMut::map(inner, |v| &mut v.value); @@ -255,14 +265,6 @@ impl Signal { } } - /// Get a mutable reference to the signal's value. - /// - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn write<'a>(&'a mut self) -> Write<'a, T> { - self.write_unchecked() - } - fn update_subscribers(&self) { { let inner = self.inner.read();