From cffa1fff1663b17f6b6c7754c45fcec4677bacd8 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Sat, 30 Aug 2025 19:53:18 +0300 Subject: [PATCH 01/15] Into for Signal from reactive closure --- Cargo.lock | 7 +- Cargo.toml | 4 + leptos_macro/src/component.rs | 43 ++++++--- leptos_macro/src/slot.rs | 24 +++-- reactive_graph/Cargo.toml | 1 + reactive_graph/src/signal.rs | 2 + reactive_graph/src/signal/into_signal.rs | 113 +++++++++++++++++++++++ reactive_graph/src/wrappers.rs | 16 ++++ 8 files changed, 186 insertions(+), 24 deletions(-) create mode 100644 reactive_graph/src/signal/into_signal.rs diff --git a/Cargo.lock b/Cargo.lock index 2af34ca4a5..d67430cc3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2895,6 +2895,7 @@ dependencies = [ "tokio", "tokio-test", "tracing", + "typed-builder", "web-sys", ] @@ -4272,8 +4273,7 @@ dependencies = [ [[package]] name = "typed-builder" version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d" +source = "git+https://github.com/zakstucke/rust-typed-builder.git?rev=617e7842d33f93ae5338f66d7881c06da91ddf6d#617e7842d33f93ae5338f66d7881c06da91ddf6d" dependencies = [ "typed-builder-macro", ] @@ -4281,8 +4281,7 @@ dependencies = [ [[package]] name = "typed-builder-macro" version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" +source = "git+https://github.com/zakstucke/rust-typed-builder.git?rev=617e7842d33f93ae5338f66d7881c06da91ddf6d#617e7842d33f93ae5338f66d7881c06da91ddf6d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a86392695b..0c0dcbd9e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,10 @@ members = [ ] exclude = ["benchmarks", "examples", "projects"] +[patch.crates-io] +typed-builder = { git = "https://github.com/zakstucke/rust-typed-builder.git", rev = "617e7842d33f93ae5338f66d7881c06da91ddf6d" } +typed-builder-macro = { git = "https://github.com/zakstucke/rust-typed-builder.git", rev = "617e7842d33f93ae5338f66d7881c06da91ddf6d" } + [workspace.package] edition = "2021" rust-version = "1.88" diff --git a/leptos_macro/src/component.rs b/leptos_macro/src/component.rs index 52e5f6149f..6bced311c8 100644 --- a/leptos_macro/src/component.rs +++ b/leptos_macro/src/component.rs @@ -1016,25 +1016,27 @@ struct PropOpt { name: Option, } -struct TypedBuilderOpts { +struct TypedBuilderOpts<'a> { default: bool, default_with_value: Option, strip_option: bool, into: bool, + ty: &'a Type, } -impl TypedBuilderOpts { - fn from_opts(opts: &PropOpt, is_ty_option: bool) -> Self { +impl<'a> TypedBuilderOpts<'a> { + fn from_opts(opts: &PropOpt, ty: &'a Type) -> Self { Self { default: opts.optional || opts.optional_no_strip || opts.attrs, default_with_value: opts.default.clone(), - strip_option: opts.strip_option || opts.optional && is_ty_option, + strip_option: opts.strip_option || opts.optional && is_option(ty), into: opts.into, + ty, } } } -impl TypedBuilderOpts { +impl TypedBuilderOpts<'_> { fn to_serde_tokens(&self) -> TokenStream { let default = if let Some(v) = &self.default_with_value { let v = v.to_token_stream().to_string(); @@ -1053,7 +1055,7 @@ impl TypedBuilderOpts { } } -impl ToTokens for TypedBuilderOpts { +impl ToTokens for TypedBuilderOpts<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let default = if let Some(v) = &self.default_with_value { let v = v.to_token_stream().to_string(); @@ -1071,7 +1073,12 @@ impl ToTokens for TypedBuilderOpts { }; let into = if self.into { - quote! { into, } + if is_signal(self.ty) { + let ty = self.ty; + quote! { transform_generics = "", transform = |value: impl ::leptos::prelude::IntoSignal<#ty, M>| value.into_signal(), } + } else { + quote! { into, } + } } else { quote! {} }; @@ -1107,8 +1114,7 @@ fn prop_builder_fields( ty, } = prop; - let builder_attrs = - TypedBuilderOpts::from_opts(prop_opts, is_option(ty)); + let builder_attrs = TypedBuilderOpts::from_opts(prop_opts, ty); let builder_docs = prop_to_doc(prop, PropDocStyle::Inline); @@ -1153,8 +1159,7 @@ fn prop_serializer_fields(vis: &Visibility, props: &[Prop]) -> TokenStream { ty, } = prop; - let builder_attrs = - TypedBuilderOpts::from_opts(prop_opts, is_option(ty)); + let builder_attrs = TypedBuilderOpts::from_opts(prop_opts, ty); let serde_attrs = builder_attrs.to_serde_tokens(); let PatIdent { ident, by_ref, .. } = &name; @@ -1242,6 +1247,22 @@ pub fn is_option(ty: &Type) -> bool { } } +pub fn is_signal(ty: &Type) -> bool { + if let Type::Path(TypePath { + path: Path { segments, .. }, + .. + }) = ty + { + if let [first] = &segments.iter().collect::>()[..] { + first.ident == "Signal" + } else { + false + } + } else { + false + } +} + pub fn unwrap_option(ty: &Type) -> Type { const STD_OPTION_MSG: &str = "make sure you're not shadowing the `std::option::Option` type that \ diff --git a/leptos_macro/src/slot.rs b/leptos_macro/src/slot.rs index 229afa05cc..953b544f9b 100644 --- a/leptos_macro/src/slot.rs +++ b/leptos_macro/src/slot.rs @@ -1,5 +1,6 @@ use crate::component::{ - convert_from_snake_case, drain_filter, is_option, unwrap_option, Docs, + convert_from_snake_case, drain_filter, is_option, is_signal, unwrap_option, + Docs, }; use attribute_derive::FromAttr; use proc_macro2::{Ident, TokenStream}; @@ -159,25 +160,27 @@ struct PropOpt { pub attrs: bool, } -struct TypedBuilderOpts { +struct TypedBuilderOpts<'a> { default: bool, default_with_value: Option, strip_option: bool, into: bool, + ty: &'a Type, } -impl TypedBuilderOpts { - pub fn from_opts(opts: &PropOpt, is_ty_option: bool) -> Self { +impl<'a> TypedBuilderOpts<'a> { + pub fn from_opts(opts: &PropOpt, ty: &'a Type) -> Self { Self { default: opts.optional || opts.optional_no_strip || opts.attrs, default_with_value: opts.default.clone(), - strip_option: opts.strip_option || opts.optional && is_ty_option, + strip_option: opts.strip_option || opts.optional && is_option(ty), into: opts.into, + ty, } } } -impl ToTokens for TypedBuilderOpts { +impl ToTokens for TypedBuilderOpts<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let default = if let Some(v) = &self.default_with_value { let v = v.to_token_stream().to_string(); @@ -195,7 +198,11 @@ impl ToTokens for TypedBuilderOpts { }; let into = if self.into { - quote! { into, } + if is_signal(self.ty) { + quote! { transform_generics = "", transform = |value: impl ::leptos::prelude::IntoSignal<::leptos::prelude::Signal, M>| value.into_signal(), } + } else { + quote! { into, } + } } else { quote! {} }; @@ -227,8 +234,7 @@ fn prop_builder_fields(vis: &Visibility, props: &[Prop]) -> TokenStream { ty, } = prop; - let builder_attrs = - TypedBuilderOpts::from_opts(prop_opts, is_option(ty)); + let builder_attrs = TypedBuilderOpts::from_opts(prop_opts, ty); let builder_docs = prop_to_doc(prop, PropDocStyle::Inline); diff --git a/reactive_graph/Cargo.toml b/reactive_graph/Cargo.toml index a073966e5c..c06f8fadf6 100644 --- a/reactive_graph/Cargo.toml +++ b/reactive_graph/Cargo.toml @@ -40,6 +40,7 @@ tokio = { features = [ ], workspace = true, default-features = true } tokio-test = { workspace = true, default-features = true } any_spawner = { workspace = true, features = ["futures-executor", "tokio"] } +typed-builder.workspace = true [build-dependencies] rustc_version = { workspace = true, default-features = true } diff --git a/reactive_graph/src/signal.rs b/reactive_graph/src/signal.rs index c3230fffa4..4d5de88b43 100644 --- a/reactive_graph/src/signal.rs +++ b/reactive_graph/src/signal.rs @@ -6,6 +6,7 @@ mod arc_rw; mod arc_trigger; mod arc_write; pub mod guards; +mod into_signal; mod mapped; mod read; mod rw; @@ -18,6 +19,7 @@ pub use arc_read::*; pub use arc_rw::*; pub use arc_trigger::*; pub use arc_write::*; +pub use into_signal::*; pub use mapped::*; pub use read::*; pub use rw::*; diff --git a/reactive_graph/src/signal/into_signal.rs b/reactive_graph/src/signal/into_signal.rs new file mode 100644 index 0000000000..bdda064d70 --- /dev/null +++ b/reactive_graph/src/signal/into_signal.rs @@ -0,0 +1,113 @@ +use crate::{ + owner::{LocalStorage, Storage, SyncStorage}, + wrappers::read::Signal, +}; + +#[doc(hidden)] +pub struct __IntoSignalMarker1; +#[doc(hidden)] +pub struct __IntoSignalMarker2; +#[doc(hidden)] +pub struct __IntoSignalMarker3; + +/// TODO docs +pub trait IntoSignal { + /// TODO docs + fn into_signal(self) -> T; +} + +impl>> IntoSignal, __IntoSignalMarker1> + for I +where + S: Storage, +{ + fn into_signal(self) -> Signal { + self.into() + } +} + +impl IntoSignal, __IntoSignalMarker2> for F +where + T: Send + Sync + 'static, + F: Fn() -> T + Send + Sync + 'static, +{ + fn into_signal(self) -> Signal { + Signal::derive(self) + } +} + +impl IntoSignal, __IntoSignalMarker2> for F +where + T: 'static, + F: Fn() -> T + 'static, +{ + fn into_signal(self) -> Signal { + Signal::derive_local(self) + } +} + +impl IntoSignal, __IntoSignalMarker3> for F +where + F: Fn() -> &'static str + Send + Sync + 'static, +{ + fn into_signal(self) -> Signal { + Signal::derive(move || self().to_string()) + } +} + +impl IntoSignal, __IntoSignalMarker3> for F +where + F: Fn() -> &'static str + 'static, +{ + fn into_signal(self) -> Signal { + Signal::derive_local(move || self().to_string()) + } +} + +#[cfg(test)] +mod tests { + + use typed_builder::TypedBuilder; + + use crate::{ + owner::LocalStorage, signal::into_signal::IntoSignal, + traits::GetUntracked, wrappers::read::Signal, + }; + + #[test] + fn text_into_signal_compiles() { + fn my_to_signal(val: impl IntoSignal) -> T { + val.into_signal() + } + + fn f_usize(_sig: Signal) {} + fn f_usize_local(_sig: Signal) {} + fn f_string(_sig: Signal) {} + fn f_string_local(_sig: Signal) {} + + f_usize(my_to_signal(2)); + f_usize(my_to_signal(|| 2)); + f_usize(my_to_signal(Signal::stored(2))); + + f_usize_local(my_to_signal(2)); + f_usize_local(my_to_signal(|| 2)); + f_usize_local(my_to_signal(Signal::stored_local(2))); + + f_string(my_to_signal("hello")); + f_string(my_to_signal(|| "hello")); + f_string(my_to_signal(Signal::stored("hello"))); + + f_string_local(my_to_signal("hello")); + f_string_local(my_to_signal(|| "hello")); + f_string_local(my_to_signal(Signal::stored_local("hello"))); + + #[derive(TypedBuilder)] + struct Foo { + #[builder(setter(transform_generics = "", transform = |value: impl IntoSignal, M>| value.into_signal()))] + sig: Signal, + } + + assert_eq!(Foo::builder().sig(2).build().sig.get_untracked(), 2); + assert_eq!(Foo::builder().sig(|| 2).build().sig.get_untracked(), 2); + } +} diff --git a/reactive_graph/src/wrappers.rs b/reactive_graph/src/wrappers.rs index b91a27b285..d1d21ba477 100644 --- a/reactive_graph/src/wrappers.rs +++ b/reactive_graph/src/wrappers.rs @@ -1049,6 +1049,13 @@ pub mod read { } } + impl From> for Signal { + #[track_caller] + fn from(value: Signal<&'static str, LocalStorage>) -> Self { + Signal::derive_local(move || value.read().to_string()) + } + } + impl From> for Signal { #[track_caller] fn from(value: Signal<&'static str>) -> Self { @@ -1077,6 +1084,15 @@ pub mod read { } } + impl From, LocalStorage>> + for Signal, LocalStorage> + { + #[track_caller] + fn from(value: Signal, LocalStorage>) -> Self { + Signal::derive_local(move || value.read().map(str::to_string)) + } + } + impl From>> for Signal, LocalStorage> { From 6e210594148218193e7a071a6e7c70c4193f1d15 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 17:28:07 +0000 Subject: [PATCH 02/15] [autofix.ci] apply automated fixes --- reactive_graph/src/signal/into_signal.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/reactive_graph/src/signal/into_signal.rs b/reactive_graph/src/signal/into_signal.rs index bdda064d70..4b35e0ef65 100644 --- a/reactive_graph/src/signal/into_signal.rs +++ b/reactive_graph/src/signal/into_signal.rs @@ -67,12 +67,11 @@ where #[cfg(test)] mod tests { - use typed_builder::TypedBuilder; - use crate::{ owner::LocalStorage, signal::into_signal::IntoSignal, traits::GetUntracked, wrappers::read::Signal, }; + use typed_builder::TypedBuilder; #[test] fn text_into_signal_compiles() { From 517bf3cafa7b272fb210b53e6e46ccce9a861eaf Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Sun, 31 Aug 2025 11:03:10 +0300 Subject: [PATCH 03/15] Make more robust, support single param Callback --- Cargo.lock | 1 + leptos/src/lib.rs | 2 +- leptos_macro/src/component.rs | 23 +---- leptos_macro/src/slot.rs | 9 +- leptos_macro/tests/component.rs | 68 +++++++++++-- reactive_graph/Cargo.toml | 1 + {leptos => reactive_graph}/src/callback.rs | 85 +++++++++++++++- reactive_graph/src/into_leptos_value.rs | 54 ++++++++++ reactive_graph/src/lib.rs | 10 +- reactive_graph/src/nightly.rs | 2 +- reactive_graph/src/signal.rs | 2 - reactive_graph/src/signal/into_signal.rs | 112 --------------------- reactive_graph/src/wrappers.rs | 61 ++++++++++- 13 files changed, 278 insertions(+), 152 deletions(-) rename {leptos => reactive_graph}/src/callback.rs (82%) create mode 100644 reactive_graph/src/into_leptos_value.rs delete mode 100644 reactive_graph/src/signal/into_signal.rs diff --git a/Cargo.lock b/Cargo.lock index d67430cc3b..f8a91ea8c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2884,6 +2884,7 @@ dependencies = [ "hydration_context", "indexmap", "or_poisoned", + "paste", "pin-project-lite", "rustc-hash 2.1.1", "rustc_version", diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index 274a6cf34a..d187647dd3 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -203,7 +203,7 @@ pub mod prelude { pub mod form; /// A standard way to wrap functions and closures to pass them to components. -pub mod callback; +pub use reactive_graph::callback; /// Types that can be passed as the `children` prop of a component. pub mod children; diff --git a/leptos_macro/src/component.rs b/leptos_macro/src/component.rs index 6bced311c8..74d9367da2 100644 --- a/leptos_macro/src/component.rs +++ b/leptos_macro/src/component.rs @@ -1073,9 +1073,10 @@ impl ToTokens for TypedBuilderOpts<'_> { }; let into = if self.into { - if is_signal(self.ty) { - let ty = self.ty; - quote! { transform_generics = "", transform = |value: impl ::leptos::prelude::IntoSignal<#ty, M>| value.into_signal(), } + // Transform cannot be used with strip_option, but otherwise use the IntoLeptosValue trait instead of into, to provide more From types than using into alone would. + if !self.strip_option { + let ty = &self.ty; + quote! { transform_generics = "<__IntoLeptosValueMarker>", transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| value.into_leptos_value(), } } else { quote! { into, } } @@ -1247,22 +1248,6 @@ pub fn is_option(ty: &Type) -> bool { } } -pub fn is_signal(ty: &Type) -> bool { - if let Type::Path(TypePath { - path: Path { segments, .. }, - .. - }) = ty - { - if let [first] = &segments.iter().collect::>()[..] { - first.ident == "Signal" - } else { - false - } - } else { - false - } -} - pub fn unwrap_option(ty: &Type) -> Type { const STD_OPTION_MSG: &str = "make sure you're not shadowing the `std::option::Option` type that \ diff --git a/leptos_macro/src/slot.rs b/leptos_macro/src/slot.rs index 953b544f9b..4911fba52c 100644 --- a/leptos_macro/src/slot.rs +++ b/leptos_macro/src/slot.rs @@ -1,6 +1,5 @@ use crate::component::{ - convert_from_snake_case, drain_filter, is_option, is_signal, unwrap_option, - Docs, + convert_from_snake_case, drain_filter, is_option, unwrap_option, Docs, }; use attribute_derive::FromAttr; use proc_macro2::{Ident, TokenStream}; @@ -198,8 +197,10 @@ impl ToTokens for TypedBuilderOpts<'_> { }; let into = if self.into { - if is_signal(self.ty) { - quote! { transform_generics = "", transform = |value: impl ::leptos::prelude::IntoSignal<::leptos::prelude::Signal, M>| value.into_signal(), } + // Transform cannot be used with strip_option, but otherwise use the IntoLeptosValue trait instead of into, to provide more From types than using into alone would. + if !self.strip_option { + let ty = &self.ty; + quote! { transform_generics = "<__IntoLeptosValueMarker>", transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| value.into_leptos_value(), } } else { quote! { into, } } diff --git a/leptos_macro/tests/component.rs b/leptos_macro/tests/component.rs index 0c6b62d994..348f097e70 100644 --- a/leptos_macro/tests/component.rs +++ b/leptos_macro/tests/component.rs @@ -120,6 +120,67 @@ fn returns_static_lifetime() { } } +#[component] +pub fn IntoLeptosValueTestComponent( + #[prop(into)] arg1: Signal, + #[prop(into)] arg2: Signal, + #[prop(into)] arg3: Signal, + #[prop(into)] arg4: Signal, + #[prop(into)] arg5: Signal, + #[prop(into)] arg6: Signal, + #[prop(into)] arg13: Callback<(), String>, + #[prop(into)] arg14: Callback, + #[prop(into)] arg15: Callback<(usize,), String>, + #[prop(into)] arg16: Callback<(usize, String), String>, + #[prop(into)] arg17: UnsyncCallback<(), String>, + #[prop(into)] arg18: UnsyncCallback, + #[prop(into)] arg19: UnsyncCallback<(usize,), String>, + #[prop(into)] arg20: UnsyncCallback<(usize, String), String>, +) -> impl IntoView { + move || { + view! { +
+

{arg1.get()}

+

{arg2.get()}

+

{arg3.get()}

+

{arg4.get()}

+

{arg5.get()}

+

{arg6.get()}

+

{arg13.run(())}

+

{arg14.run(1)}

+

{arg15.run((2,))}

+

{arg16.run((3, "three".into()))}

+

{arg17.run(())}

+

{arg18.run(1)}

+

{arg19.run((2,))}

+

{arg20.run((3, "three".into()))}

+
+ } + } +} + +#[test] +fn test_into_leptos_value() { + let _ = view! { + + }; +} + // an attempt to catch unhygienic macros regression mod macro_hygiene { // To ensure no relative module path to leptos inside macros. @@ -152,12 +213,7 @@ mod macro_hygiene { #[component] fn Component() -> impl IntoView { - view! { -
- {().into_any()} - {()} -
- } + view! {
{().into_any()} {()}
} } } } diff --git a/reactive_graph/Cargo.toml b/reactive_graph/Cargo.toml index c06f8fadf6..3d70a757e5 100644 --- a/reactive_graph/Cargo.toml +++ b/reactive_graph/Cargo.toml @@ -29,6 +29,7 @@ send_wrapper = { features = [ ], workspace = true, default-features = true } subsecond = { workspace = true, default-features = true, optional = true } indexmap = { workspace = true, default-features = true } +paste = { workspace = true, default-features = true } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] web-sys = { version = "0.3.77", features = ["console"] } diff --git a/leptos/src/callback.rs b/reactive_graph/src/callback.rs similarity index 82% rename from leptos/src/callback.rs rename to reactive_graph/src/callback.rs index 4daf12da9c..48c973b460 100644 --- a/leptos/src/callback.rs +++ b/reactive_graph/src/callback.rs @@ -5,7 +5,7 @@ //! # Usage //! Callbacks can be created manually from any function or closure, but the easiest way //! to create them is to use `#[prop(into)]]` when defining a component. -//! ``` +//! ```ignore //! use leptos::prelude::*; //! //! #[component] @@ -41,9 +41,10 @@ //! //! Use `SyncCallback` if the function is not `Sync` and `Send`. -use reactive_graph::{ +use crate::{ owner::{LocalStorage, StoredValue}, traits::{Dispose, WithValue}, + IntoLeptosValue, }; use std::{fmt, rc::Rc, sync::Arc}; @@ -151,7 +152,7 @@ impl_unsync_callable_from_fn!( /// Callbacks define a standard way to store functions and closures. /// /// # Example -/// ``` +/// ```ignore /// # use leptos::prelude::*; /// # use leptos::callback::{Callable, Callback}; /// #[component] @@ -241,6 +242,7 @@ impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12); impl Callback { /// Creates a new callback from the given function. + #[track_caller] pub fn new(fun: F) -> Self where F: Fn(In) -> Out + Send + Sync + 'static, @@ -262,11 +264,74 @@ impl Callback { } } +#[doc(hidden)] +pub struct __IntoLeptosValueMarkerCallbackSingleParam; + +#[doc(hidden)] +pub struct __IntoLeptosValueMarkerCallbackStrOutputToString; + +impl + IntoLeptosValue, __IntoLeptosValueMarkerCallbackSingleParam> + for F +where + F: Fn(I) -> O + Send + Sync + 'static, +{ + #[track_caller] + fn into_leptos_value(self) -> Callback { + Callback::new(self) + } +} + +impl + IntoLeptosValue< + UnsyncCallback, + __IntoLeptosValueMarkerCallbackSingleParam, + > for F +where + F: Fn(I) -> O + 'static, +{ + #[track_caller] + fn into_leptos_value(self) -> UnsyncCallback { + UnsyncCallback::new(self) + } +} + +impl + IntoLeptosValue< + Callback, + __IntoLeptosValueMarkerCallbackStrOutputToString, + > for F +where + F: Fn(I) -> &'static str + Send + Sync + 'static, +{ + #[track_caller] + fn into_leptos_value(self) -> Callback { + Callback::new(move |i| self(i).to_string()) + } +} + +impl + IntoLeptosValue< + UnsyncCallback, + __IntoLeptosValueMarkerCallbackStrOutputToString, + > for F +where + F: Fn(I) -> &'static str + 'static, +{ + #[track_caller] + fn into_leptos_value(self) -> UnsyncCallback { + UnsyncCallback::new(move |i| self(i).to_string()) + } +} + #[cfg(test)] mod tests { use super::Callable; - use crate::callback::{Callback, UnsyncCallback}; - use reactive_graph::traits::Dispose; + use crate::{ + callback::{Callback, UnsyncCallback}, + traits::Dispose, + IntoLeptosValue, + }; struct NoClone {} @@ -288,6 +353,11 @@ mod tests { let _callback: Callback<(), String> = (|| "test").into(); let _callback: Callback<(i32, String), String> = (|num, s| format!("{num} {s}")).into(); + // Single params should work without needing the (foo,) tuple using IntoLeptosValue: + let _callback: Callback = + (|_usize| "test").into_leptos_value(); + let _callback: Callback = + (|_usize| "test").into_leptos_value(); } #[test] @@ -295,6 +365,11 @@ mod tests { let _callback: UnsyncCallback<(), String> = (|| "test").into(); let _callback: UnsyncCallback<(i32, String), String> = (|num, s| format!("{num} {s}")).into(); + // Single params should work without needing the (foo,) tuple using IntoLeptosValue: + let _callback: UnsyncCallback = + (|_usize| "test").into_leptos_value(); + let _callback: UnsyncCallback = + (|_usize| "test").into_leptos_value(); } #[test] diff --git a/reactive_graph/src/into_leptos_value.rs b/reactive_graph/src/into_leptos_value.rs new file mode 100644 index 0000000000..4b2ae58803 --- /dev/null +++ b/reactive_graph/src/into_leptos_value.rs @@ -0,0 +1,54 @@ +#[doc(hidden)] +pub struct __IntoLeptosValueMarkerBaseCase; + +/// TODO docs +pub trait IntoLeptosValue { + /// TODO docs + fn into_leptos_value(self) -> T; +} + +// The base case, which allows anything which implements .into() to work: +impl IntoLeptosValue for I +where + I: Into, +{ + fn into_leptos_value(self) -> T { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{ + into_leptos_value::IntoLeptosValue, owner::LocalStorage, + traits::GetUntracked, wrappers::read::Signal, + }; + use typed_builder::TypedBuilder; + + #[test] + fn text_into_signal_compiles() { + let _: Signal = (|| 2).into_leptos_value(); + let _: Signal = 2.into_leptos_value(); + let _: Signal = (|| 2).into_leptos_value(); + let _: Signal = "str".into_leptos_value(); + let _: Signal = "str".into_leptos_value(); + + #[derive(TypedBuilder)] + struct Foo { + #[builder(setter(transform_generics = "", transform = |value: impl IntoLeptosValue, M>| value.into_leptos_value()))] + sig: Signal, + } + + assert_eq!(Foo::builder().sig(2).build().sig.get_untracked(), 2); + assert_eq!(Foo::builder().sig(|| 2).build().sig.get_untracked(), 2); + assert_eq!( + Foo::builder() + .sig(Signal::stored(2)) + .build() + .sig + .get_untracked(), + 2 + ); + } +} diff --git a/reactive_graph/src/lib.rs b/reactive_graph/src/lib.rs index fd1018961f..7a8e9e2484 100644 --- a/reactive_graph/src/lib.rs +++ b/reactive_graph/src/lib.rs @@ -90,6 +90,12 @@ pub mod traits; pub mod transition; pub mod wrappers; +mod into_leptos_value; +pub use into_leptos_value::*; + +/// A standard way to wrap functions and closures to pass them to components. +pub mod callback; + use computed::ScopedFuture; #[cfg(all(feature = "nightly", rustc_nightly))] @@ -97,7 +103,9 @@ mod nightly; /// Reexports frequently-used traits. pub mod prelude { - pub use crate::{owner::FromLocal, traits::*}; + pub use crate::{ + into_leptos_value::IntoLeptosValue, owner::FromLocal, traits::*, + }; } // TODO remove this, it's just useful while developing diff --git a/reactive_graph/src/nightly.rs b/reactive_graph/src/nightly.rs index d20e2d1883..271f2c84c8 100644 --- a/reactive_graph/src/nightly.rs +++ b/reactive_graph/src/nightly.rs @@ -151,7 +151,7 @@ impl_get_fn_traits_get_arena![ RwSignal, ArcMemo, ArcSignal, - Signal, + // Signal, MaybeSignal, Memo, MaybeProp diff --git a/reactive_graph/src/signal.rs b/reactive_graph/src/signal.rs index 4d5de88b43..c3230fffa4 100644 --- a/reactive_graph/src/signal.rs +++ b/reactive_graph/src/signal.rs @@ -6,7 +6,6 @@ mod arc_rw; mod arc_trigger; mod arc_write; pub mod guards; -mod into_signal; mod mapped; mod read; mod rw; @@ -19,7 +18,6 @@ pub use arc_read::*; pub use arc_rw::*; pub use arc_trigger::*; pub use arc_write::*; -pub use into_signal::*; pub use mapped::*; pub use read::*; pub use rw::*; diff --git a/reactive_graph/src/signal/into_signal.rs b/reactive_graph/src/signal/into_signal.rs deleted file mode 100644 index 4b35e0ef65..0000000000 --- a/reactive_graph/src/signal/into_signal.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::{ - owner::{LocalStorage, Storage, SyncStorage}, - wrappers::read::Signal, -}; - -#[doc(hidden)] -pub struct __IntoSignalMarker1; -#[doc(hidden)] -pub struct __IntoSignalMarker2; -#[doc(hidden)] -pub struct __IntoSignalMarker3; - -/// TODO docs -pub trait IntoSignal { - /// TODO docs - fn into_signal(self) -> T; -} - -impl>> IntoSignal, __IntoSignalMarker1> - for I -where - S: Storage, -{ - fn into_signal(self) -> Signal { - self.into() - } -} - -impl IntoSignal, __IntoSignalMarker2> for F -where - T: Send + Sync + 'static, - F: Fn() -> T + Send + Sync + 'static, -{ - fn into_signal(self) -> Signal { - Signal::derive(self) - } -} - -impl IntoSignal, __IntoSignalMarker2> for F -where - T: 'static, - F: Fn() -> T + 'static, -{ - fn into_signal(self) -> Signal { - Signal::derive_local(self) - } -} - -impl IntoSignal, __IntoSignalMarker3> for F -where - F: Fn() -> &'static str + Send + Sync + 'static, -{ - fn into_signal(self) -> Signal { - Signal::derive(move || self().to_string()) - } -} - -impl IntoSignal, __IntoSignalMarker3> for F -where - F: Fn() -> &'static str + 'static, -{ - fn into_signal(self) -> Signal { - Signal::derive_local(move || self().to_string()) - } -} - -#[cfg(test)] -mod tests { - - use crate::{ - owner::LocalStorage, signal::into_signal::IntoSignal, - traits::GetUntracked, wrappers::read::Signal, - }; - use typed_builder::TypedBuilder; - - #[test] - fn text_into_signal_compiles() { - fn my_to_signal(val: impl IntoSignal) -> T { - val.into_signal() - } - - fn f_usize(_sig: Signal) {} - fn f_usize_local(_sig: Signal) {} - fn f_string(_sig: Signal) {} - fn f_string_local(_sig: Signal) {} - - f_usize(my_to_signal(2)); - f_usize(my_to_signal(|| 2)); - f_usize(my_to_signal(Signal::stored(2))); - - f_usize_local(my_to_signal(2)); - f_usize_local(my_to_signal(|| 2)); - f_usize_local(my_to_signal(Signal::stored_local(2))); - - f_string(my_to_signal("hello")); - f_string(my_to_signal(|| "hello")); - f_string(my_to_signal(Signal::stored("hello"))); - - f_string_local(my_to_signal("hello")); - f_string_local(my_to_signal(|| "hello")); - f_string_local(my_to_signal(Signal::stored_local("hello"))); - - #[derive(TypedBuilder)] - struct Foo { - #[builder(setter(transform_generics = "", transform = |value: impl IntoSignal, M>| value.into_signal()))] - sig: Signal, - } - - assert_eq!(Foo::builder().sig(2).build().sig.get_untracked(), 2); - assert_eq!(Foo::builder().sig(|| 2).build().sig.get_untracked(), 2); - } -} diff --git a/reactive_graph/src/wrappers.rs b/reactive_graph/src/wrappers.rs index d1d21ba477..1e3988fdf9 100644 --- a/reactive_graph/src/wrappers.rs +++ b/reactive_graph/src/wrappers.rs @@ -17,7 +17,7 @@ pub mod read { traits::{ DefinedAt, Dispose, Get, Read, ReadUntracked, ReadValue, Track, }, - unwrap_signal, + unwrap_signal, IntoLeptosValue, }; use send_wrapper::SendWrapper; use std::{ @@ -1102,6 +1102,65 @@ pub mod read { } } + #[doc(hidden)] + pub struct __IntoLeptosValueMarkerSignalFromReactiveClosure; + #[doc(hidden)] + pub struct __IntoLeptosValueMarkerSignalStrOutputToString; + + impl + IntoLeptosValue< + Signal, + __IntoLeptosValueMarkerSignalFromReactiveClosure, + > for F + where + T: Send + Sync + 'static, + F: Fn() -> T + Send + Sync + 'static, + { + fn into_leptos_value(self) -> Signal { + Signal::derive(self) + } + } + + impl + IntoLeptosValue< + Signal, + __IntoLeptosValueMarkerSignalFromReactiveClosure, + > for F + where + T: 'static, + F: Fn() -> T + 'static, + { + fn into_leptos_value(self) -> Signal { + Signal::derive_local(self) + } + } + + impl + IntoLeptosValue< + Signal, + __IntoLeptosValueMarkerSignalStrOutputToString, + > for F + where + F: Fn() -> &'static str + Send + Sync + 'static, + { + fn into_leptos_value(self) -> Signal { + Signal::derive(move || self().to_string()) + } + } + + impl + IntoLeptosValue< + Signal, + __IntoLeptosValueMarkerSignalStrOutputToString, + > for F + where + F: Fn() -> &'static str + 'static, + { + fn into_leptos_value(self) -> Signal { + Signal::derive_local(move || self().to_string()) + } + } + #[allow(deprecated)] impl From> for Signal where From c688a010397151652a2c766e40ed7d927640c2ec Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Sun, 31 Aug 2025 12:02:59 +0300 Subject: [PATCH 04/15] ArcSignal --- leptos_macro/tests/component.rs | 18 +++++++++ reactive_graph/src/nightly.rs | 2 +- reactive_graph/src/wrappers.rs | 70 +++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/leptos_macro/tests/component.rs b/leptos_macro/tests/component.rs index 348f097e70..b4e7aa6099 100644 --- a/leptos_macro/tests/component.rs +++ b/leptos_macro/tests/component.rs @@ -136,6 +136,12 @@ pub fn IntoLeptosValueTestComponent( #[prop(into)] arg18: UnsyncCallback, #[prop(into)] arg19: UnsyncCallback<(usize,), String>, #[prop(into)] arg20: UnsyncCallback<(usize, String), String>, + #[prop(into)] arg21: ArcSignal, + #[prop(into)] arg22: ArcSignal, + #[prop(into)] arg23: ArcSignal, + #[prop(into)] arg24: ArcSignal, + #[prop(into)] arg25: ArcSignal, + #[prop(into)] arg26: ArcSignal, ) -> impl IntoView { move || { view! { @@ -154,6 +160,12 @@ pub fn IntoLeptosValueTestComponent(

{arg18.run(1)}

{arg19.run((2,))}

{arg20.run((3, "three".into()))}

+

{arg21.get()}

+

{arg22.get()}

+

{arg23.get()}

+

{arg24.get()}

+

{arg25.get()}

+

{arg26.get()}

} } @@ -177,6 +189,12 @@ fn test_into_leptos_value() { arg18=|_n| "I was a callback static str!" arg19=|(_n,)| "I was a callback static str!" arg20=|(_n, _s)| "I was a callback static str!" + arg21=move || "I was a reactive closure!" + arg22="I was a basic str!" + arg23=ArcSignal::stored("I was already a signal!".to_string()) + arg24=move || 2 + arg25=3 + arg26=ArcSignal::stored(4) /> }; } diff --git a/reactive_graph/src/nightly.rs b/reactive_graph/src/nightly.rs index 271f2c84c8..e0511c96b8 100644 --- a/reactive_graph/src/nightly.rs +++ b/reactive_graph/src/nightly.rs @@ -150,7 +150,7 @@ impl_get_fn_traits_get_arena![ ReadSignal, RwSignal, ArcMemo, - ArcSignal, + // ArcSignal, // Signal, MaybeSignal, Memo, diff --git a/reactive_graph/src/wrappers.rs b/reactive_graph/src/wrappers.rs index 1e3988fdf9..c2a475df8d 100644 --- a/reactive_graph/src/wrappers.rs +++ b/reactive_graph/src/wrappers.rs @@ -324,6 +324,22 @@ pub mod read { } } + impl From<&'static str> for ArcSignal + where + S: Storage<&'static str> + Storage, + { + #[track_caller] + fn from(value: &'static str) -> Self { + Self { + inner: SignalTypes::Stored(ArcStoredValue::new( + value.to_string(), + )), + #[cfg(any(debug_assertions, leptos_debuginfo))] + defined_at: std::panic::Location::caller(), + } + } + } + impl DefinedAt for ArcSignal where S: Storage, @@ -1121,6 +1137,20 @@ pub mod read { } } + impl + IntoLeptosValue< + ArcSignal, + __IntoLeptosValueMarkerSignalFromReactiveClosure, + > for F + where + T: Send + Sync + 'static, + F: Fn() -> T + Send + Sync + 'static, + { + fn into_leptos_value(self) -> ArcSignal { + ArcSignal::derive(self) + } + } + impl IntoLeptosValue< Signal, @@ -1135,6 +1165,20 @@ pub mod read { } } + impl + IntoLeptosValue< + ArcSignal, + __IntoLeptosValueMarkerSignalFromReactiveClosure, + > for F + where + T: 'static, + F: Fn() -> T + 'static, + { + fn into_leptos_value(self) -> ArcSignal { + ArcSignal::derive_local(self) + } + } + impl IntoLeptosValue< Signal, @@ -1148,6 +1192,19 @@ pub mod read { } } + impl + IntoLeptosValue< + ArcSignal, + __IntoLeptosValueMarkerSignalStrOutputToString, + > for F + where + F: Fn() -> &'static str + Send + Sync + 'static, + { + fn into_leptos_value(self) -> ArcSignal { + ArcSignal::derive(move || self().to_string()) + } + } + impl IntoLeptosValue< Signal, @@ -1161,6 +1218,19 @@ pub mod read { } } + impl + IntoLeptosValue< + ArcSignal, + __IntoLeptosValueMarkerSignalStrOutputToString, + > for F + where + F: Fn() -> &'static str + 'static, + { + fn into_leptos_value(self) -> ArcSignal { + ArcSignal::derive_local(move || self().to_string()) + } + } + #[allow(deprecated)] impl From> for Signal where From cf9c3b179f00eb07fedee769adccb7efa7fbc3a8 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 09:35:32 +0000 Subject: [PATCH 05/15] [autofix.ci] apply automated fixes --- reactive_graph/src/nightly.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactive_graph/src/nightly.rs b/reactive_graph/src/nightly.rs index e0511c96b8..6e4cfbcbe5 100644 --- a/reactive_graph/src/nightly.rs +++ b/reactive_graph/src/nightly.rs @@ -9,7 +9,7 @@ use crate::{ }, traits::{Get, Set}, wrappers::{ - read::{ArcSignal, Signal, SignalTypes}, + read::SignalTypes, write::SignalSetter, }, }; From 4207e7ff41d152be8ec908f9251b4e51862080cb Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 10:08:13 +0000 Subject: [PATCH 06/15] [autofix.ci] apply automated fixes (attempt 2/3) --- reactive_graph/src/nightly.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/reactive_graph/src/nightly.rs b/reactive_graph/src/nightly.rs index 6e4cfbcbe5..0d72df7c5c 100644 --- a/reactive_graph/src/nightly.rs +++ b/reactive_graph/src/nightly.rs @@ -8,10 +8,7 @@ use crate::{ WriteSignal, }, traits::{Get, Set}, - wrappers::{ - read::SignalTypes, - write::SignalSetter, - }, + wrappers::{read::SignalTypes, write::SignalSetter}, }; macro_rules! impl_set_fn_traits { From f04975ee6525dbd19df2d430614492a1a417b79a Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Sun, 31 Aug 2025 15:44:09 +0300 Subject: [PATCH 07/15] CI --- reactive_graph/src/callback.rs | 31 +++++++++++++++++++++++++ reactive_graph/src/into_leptos_value.rs | 9 +++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/reactive_graph/src/callback.rs b/reactive_graph/src/callback.rs index 48c973b460..e04ddc5b24 100644 --- a/reactive_graph/src/callback.rs +++ b/reactive_graph/src/callback.rs @@ -329,6 +329,7 @@ mod tests { use super::Callable; use crate::{ callback::{Callback, UnsyncCallback}, + owner::Owner, traits::Dispose, IntoLeptosValue, }; @@ -337,12 +338,18 @@ mod tests { #[test] fn clone_callback() { + let owner = Owner::new(); + owner.set(); + let callback = Callback::new(move |_no_clone: NoClone| NoClone {}); let _cloned = callback; } #[test] fn clone_unsync_callback() { + let owner = Owner::new(); + owner.set(); + let callback = UnsyncCallback::new(move |_no_clone: NoClone| NoClone {}); let _cloned = callback; @@ -350,6 +357,9 @@ mod tests { #[test] fn runback_from() { + let owner = Owner::new(); + owner.set(); + let _callback: Callback<(), String> = (|| "test").into(); let _callback: Callback<(i32, String), String> = (|num, s| format!("{num} {s}")).into(); @@ -362,6 +372,9 @@ mod tests { #[test] fn sync_callback_from() { + let owner = Owner::new(); + owner.set(); + let _callback: UnsyncCallback<(), String> = (|| "test").into(); let _callback: UnsyncCallback<(i32, String), String> = (|num, s| format!("{num} {s}")).into(); @@ -374,6 +387,9 @@ mod tests { #[test] fn sync_callback_try_run() { + let owner = Owner::new(); + owner.set(); + let callback = Callback::new(move |arg| arg); assert_eq!(callback.try_run((0,)), Some((0,))); callback.dispose(); @@ -382,6 +398,9 @@ mod tests { #[test] fn unsync_callback_try_run() { + let owner = Owner::new(); + owner.set(); + let callback = UnsyncCallback::new(move |arg| arg); assert_eq!(callback.try_run((0,)), Some((0,))); callback.dispose(); @@ -390,6 +409,9 @@ mod tests { #[test] fn callback_matches_same() { + let owner = Owner::new(); + owner.set(); + let callback1 = Callback::new(|x: i32| x * 2); let callback2 = callback1; assert!(callback1.matches(&callback2)); @@ -397,6 +419,9 @@ mod tests { #[test] fn callback_matches_different() { + let owner = Owner::new(); + owner.set(); + let callback1 = Callback::new(|x: i32| x * 2); let callback2 = Callback::new(|x: i32| x + 1); assert!(!callback1.matches(&callback2)); @@ -404,6 +429,9 @@ mod tests { #[test] fn unsync_callback_matches_same() { + let owner = Owner::new(); + owner.set(); + let callback1 = UnsyncCallback::new(|x: i32| x * 2); let callback2 = callback1; assert!(callback1.matches(&callback2)); @@ -411,6 +439,9 @@ mod tests { #[test] fn unsync_callback_matches_different() { + let owner = Owner::new(); + owner.set(); + let callback1 = UnsyncCallback::new(|x: i32| x * 2); let callback2 = UnsyncCallback::new(|x: i32| x + 1); assert!(!callback1.matches(&callback2)); diff --git a/reactive_graph/src/into_leptos_value.rs b/reactive_graph/src/into_leptos_value.rs index 4b2ae58803..78affb6673 100644 --- a/reactive_graph/src/into_leptos_value.rs +++ b/reactive_graph/src/into_leptos_value.rs @@ -21,13 +21,18 @@ where mod tests { use crate::{ - into_leptos_value::IntoLeptosValue, owner::LocalStorage, - traits::GetUntracked, wrappers::read::Signal, + into_leptos_value::IntoLeptosValue, + owner::{LocalStorage, Owner}, + traits::GetUntracked, + wrappers::read::Signal, }; use typed_builder::TypedBuilder; #[test] fn text_into_signal_compiles() { + let owner = Owner::new(); + owner.set(); + let _: Signal = (|| 2).into_leptos_value(); let _: Signal = 2.into_leptos_value(); let _: Signal = (|| 2).into_leptos_value(); From 24d60d611cb32a68b5df41d574a84b4d60cc4811 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Sun, 31 Aug 2025 16:50:21 +0300 Subject: [PATCH 08/15] CI --- Cargo.lock | 4 ++-- Cargo.toml | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8a91ea8c4..729f62cb1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4274,7 +4274,7 @@ dependencies = [ [[package]] name = "typed-builder" version = "0.21.2" -source = "git+https://github.com/zakstucke/rust-typed-builder.git?rev=617e7842d33f93ae5338f66d7881c06da91ddf6d#617e7842d33f93ae5338f66d7881c06da91ddf6d" +source = "git+https://github.com/zakstucke/rust-typed-builder.git?rev=3bc55429a0036d0143ed96f9e48715def754f99d#3bc55429a0036d0143ed96f9e48715def754f99d" dependencies = [ "typed-builder-macro", ] @@ -4282,7 +4282,7 @@ dependencies = [ [[package]] name = "typed-builder-macro" version = "0.21.2" -source = "git+https://github.com/zakstucke/rust-typed-builder.git?rev=617e7842d33f93ae5338f66d7881c06da91ddf6d#617e7842d33f93ae5338f66d7881c06da91ddf6d" +source = "git+https://github.com/zakstucke/rust-typed-builder.git?rev=3bc55429a0036d0143ed96f9e48715def754f99d#3bc55429a0036d0143ed96f9e48715def754f99d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0c0dcbd9e2..15b4b46d06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,10 +39,6 @@ members = [ ] exclude = ["benchmarks", "examples", "projects"] -[patch.crates-io] -typed-builder = { git = "https://github.com/zakstucke/rust-typed-builder.git", rev = "617e7842d33f93ae5338f66d7881c06da91ddf6d" } -typed-builder-macro = { git = "https://github.com/zakstucke/rust-typed-builder.git", rev = "617e7842d33f93ae5338f66d7881c06da91ddf6d" } - [workspace.package] edition = "2021" rust-version = "1.88" @@ -83,7 +79,10 @@ itertools = { default-features = false, version = "0.14.0" } convert_case = { default-features = false, version = "0.8.0" } serde_json = { default-features = false, version = "1.0.143" } trybuild = { default-features = false, version = "1.0.110" } -typed-builder = { default-features = false, version = "0.21.2" } +# typed-builder = { default-features = false, version = "0.21.2" } +# typed-builder-macro = { default-features = false, version = "0.21.0" } +typed-builder = { git = "https://github.com/zakstucke/rust-typed-builder.git", rev = "3bc55429a0036d0143ed96f9e48715def754f99d" } +typed-builder-macro = { git = "https://github.com/zakstucke/rust-typed-builder.git", rev = "3bc55429a0036d0143ed96f9e48715def754f99d" } thiserror = { default-features = false, version = "2.0.16" } wasm-bindgen = { default-features = false, version = "0.2.100" } indexmap = { default-features = false, version = "2.11.0" } @@ -127,7 +126,6 @@ serial_test = { default-features = false, version = "3.2.0" } erased = { default-features = false, version = "0.1.2" } glib = { default-features = false, version = "0.20.12" } async-trait = { default-features = false, version = "0.1.89" } -typed-builder-macro = { default-features = false, version = "0.21.0" } linear-map = { default-features = false, version = "1.2.0" } anyhow = { default-features = false, version = "1.0.100" } walkdir = { default-features = false, version = "2.5.0" } From 3a43d04d7c7f581ddb4abc1b0ffaf6a0b5ccebea Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Mon, 1 Sep 2025 00:01:16 +0300 Subject: [PATCH 09/15] Support optional, nostrip etc --- leptos_macro/src/component.rs | 15 ++++-- leptos_macro/src/slot.rs | 15 ++++-- leptos_macro/src/view/component_builder.rs | 2 +- leptos_macro/tests/component.rs | 18 +++++++ reactive_graph/src/wrappers.rs | 58 ++++++++++++++++++++++ 5 files changed, 99 insertions(+), 9 deletions(-) diff --git a/leptos_macro/src/component.rs b/leptos_macro/src/component.rs index 74d9367da2..79149ef853 100644 --- a/leptos_macro/src/component.rs +++ b/leptos_macro/src/component.rs @@ -1066,19 +1066,26 @@ impl ToTokens for TypedBuilderOpts<'_> { quote! {} }; - let strip_option = if self.strip_option { + // If self.strip_option && self.into, then the strip_option will be represented as part of the transform closure. + let strip_option = if self.strip_option && !self.into { quote! { strip_option, } } else { quote! {} }; let into = if self.into { - // Transform cannot be used with strip_option, but otherwise use the IntoLeptosValue trait instead of into, to provide more From types than using into alone would. if !self.strip_option { let ty = &self.ty; - quote! { transform_generics = "<__IntoLeptosValueMarker>", transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| value.into_leptos_value(), } + quote! { + transform_generics = "<__IntoLeptosValueMarker>", + transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| value.into_leptos_value(), + } } else { - quote! { into, } + let ty = unwrap_option(self.ty); + quote! { + transform_generics = "<__IntoLeptosValueMarker>", + transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| Some(value.into_leptos_value()), + } } } else { quote! {} diff --git a/leptos_macro/src/slot.rs b/leptos_macro/src/slot.rs index 4911fba52c..8e5f7c0de3 100644 --- a/leptos_macro/src/slot.rs +++ b/leptos_macro/src/slot.rs @@ -190,19 +190,26 @@ impl ToTokens for TypedBuilderOpts<'_> { quote! {} }; - let strip_option = if self.strip_option { + // If self.strip_option && self.into, then the strip_option will be represented as part of the transform closure. + let strip_option = if self.strip_option && !self.into { quote! { strip_option, } } else { quote! {} }; let into = if self.into { - // Transform cannot be used with strip_option, but otherwise use the IntoLeptosValue trait instead of into, to provide more From types than using into alone would. if !self.strip_option { let ty = &self.ty; - quote! { transform_generics = "<__IntoLeptosValueMarker>", transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| value.into_leptos_value(), } + quote! { + transform_generics = "<__IntoLeptosValueMarker>", + transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| value.into_leptos_value(), + } } else { - quote! { into, } + let ty = unwrap_option(self.ty); + quote! { + transform_generics = "<__IntoLeptosValueMarker>", + transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| Some(value.into_leptos_value()), + } } } else { quote! {} diff --git a/leptos_macro/src/view/component_builder.rs b/leptos_macro/src/view/component_builder.rs index dda88f5ae0..196c5626df 100644 --- a/leptos_macro/src/view/component_builder.rs +++ b/leptos_macro/src/view/component_builder.rs @@ -90,7 +90,7 @@ pub(crate) fn component_to_tokens( if optional { optional_props.push(quote! { - props.#name = { #value }.map(Into::into); + props.#name = { #value }.map(::leptos::prelude::IntoLeptosValue::into_leptos_value); }) } else { required_props.push(quote! { diff --git a/leptos_macro/tests/component.rs b/leptos_macro/tests/component.rs index b4e7aa6099..cc63da64de 100644 --- a/leptos_macro/tests/component.rs +++ b/leptos_macro/tests/component.rs @@ -128,6 +128,7 @@ pub fn IntoLeptosValueTestComponent( #[prop(into)] arg4: Signal, #[prop(into)] arg5: Signal, #[prop(into)] arg6: Signal, + #[prop(into)] arg7: Signal>, #[prop(into)] arg13: Callback<(), String>, #[prop(into)] arg14: Callback, #[prop(into)] arg15: Callback<(usize,), String>, @@ -142,6 +143,12 @@ pub fn IntoLeptosValueTestComponent( #[prop(into)] arg24: ArcSignal, #[prop(into)] arg25: ArcSignal, #[prop(into)] arg26: ArcSignal, + #[prop(into)] arg27: ArcSignal>, + // Optionals: + #[prop(into, optional)] arg28: Option>, + #[prop(into, optional)] arg29_purposely_omitted: Option>, + #[prop(into, optional)] arg30: Option>, + #[prop(into, strip_option)] arg31: Option>, ) -> impl IntoView { move || { view! { @@ -152,6 +159,7 @@ pub fn IntoLeptosValueTestComponent(

{arg4.get()}

{arg5.get()}

{arg6.get()}

+

{arg7.get()}

{arg13.run(())}

{arg14.run(1)}

{arg15.run((2,))}

@@ -166,6 +174,11 @@ pub fn IntoLeptosValueTestComponent(

{arg24.get()}

{arg25.get()}

{arg26.get()}

+

{arg27.get()}

+

{arg28.get()}

+

{arg29_purposely_omitted.get()}

+

{arg30.get()}

+

{arg31.get()}

} } @@ -181,6 +194,7 @@ fn test_into_leptos_value() { arg4=move || 2 arg5=3 arg6=Signal::stored(4) + arg7=|| 2 arg13=|| "I was a callback static str!" arg14=|_n| "I was a callback static str!" arg15=|(_n,)| "I was a callback static str!" @@ -195,6 +209,10 @@ fn test_into_leptos_value() { arg24=move || 2 arg25=3 arg26=ArcSignal::stored(4) + arg27=|| 2 + arg28=|| 2 + nostrip:arg30=Some(|| 2) + arg31=|| 2 /> }; } diff --git a/reactive_graph/src/wrappers.rs b/reactive_graph/src/wrappers.rs index c2a475df8d..290db583ae 100644 --- a/reactive_graph/src/wrappers.rs +++ b/reactive_graph/src/wrappers.rs @@ -1122,6 +1122,8 @@ pub mod read { pub struct __IntoLeptosValueMarkerSignalFromReactiveClosure; #[doc(hidden)] pub struct __IntoLeptosValueMarkerSignalStrOutputToString; + #[doc(hidden)] + pub struct __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways; impl IntoLeptosValue< @@ -1231,6 +1233,62 @@ pub mod read { } } + impl + IntoLeptosValue< + Signal, SyncStorage>, + __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, + > for F + where + T: Send + Sync + 'static, + F: Fn() -> T + Send + Sync + 'static, + { + fn into_leptos_value(self) -> Signal, SyncStorage> { + Signal::derive(move || Some(self())) + } + } + + impl + IntoLeptosValue< + ArcSignal, SyncStorage>, + __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, + > for F + where + T: Send + Sync + 'static, + F: Fn() -> T + Send + Sync + 'static, + { + fn into_leptos_value(self) -> ArcSignal, SyncStorage> { + ArcSignal::derive(move || Some(self())) + } + } + + impl + IntoLeptosValue< + Signal, LocalStorage>, + __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, + > for F + where + T: 'static, + F: Fn() -> T + 'static, + { + fn into_leptos_value(self) -> Signal, LocalStorage> { + Signal::derive_local(move || Some(self())) + } + } + + impl + IntoLeptosValue< + ArcSignal, LocalStorage>, + __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, + > for F + where + T: 'static, + F: Fn() -> T + 'static, + { + fn into_leptos_value(self) -> ArcSignal, LocalStorage> { + ArcSignal::derive_local(move || Some(self())) + } + } + #[allow(deprecated)] impl From> for Signal where From 88996843661b36be4cae5f42d14fc10087ed7130 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Wed, 17 Sep 2025 11:29:30 +0300 Subject: [PATCH 10/15] Updates: --- Cargo.lock | 22 ++++++++++++++++++---- Cargo.toml | 6 ++---- leptos_macro/src/component.rs | 10 ++++++---- leptos_macro/src/slot.rs | 10 ++++++---- reactive_graph/src/into_leptos_value.rs | 13 +++++++++---- 5 files changed, 41 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 729f62cb1d..8cf5f7b135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4273,16 +4273,18 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.21.2" -source = "git+https://github.com/zakstucke/rust-typed-builder.git?rev=3bc55429a0036d0143ed96f9e48715def754f99d#3bc55429a0036d0143ed96f9e48715def754f99d" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398a3a3c918c96de527dc11e6e846cd549d4508030b8a33e1da12789c856b81a" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.21.2" -source = "git+https://github.com/zakstucke/rust-typed-builder.git?rev=3bc55429a0036d0143ed96f9e48715def754f99d#3bc55429a0036d0143ed96f9e48715def754f99d" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221" dependencies = [ "proc-macro2", "quote", @@ -4620,7 +4622,19 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ +<<<<<<< HEAD + "windows-sys 0.52.0", +||||||| parent of e8f56d4f (Updates:) + "windows-sys 0.61.0", +======= +<<<<<<< HEAD + "windows-sys 0.61.0", +||||||| parent of 97259a60 (Updates:) "windows-sys 0.52.0", +======= + "windows-sys 0.60.2", +>>>>>>> 97259a60 (Updates:) +>>>>>>> e8f56d4f (Updates:) ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 15b4b46d06..44a20ddacf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,10 +79,8 @@ itertools = { default-features = false, version = "0.14.0" } convert_case = { default-features = false, version = "0.8.0" } serde_json = { default-features = false, version = "1.0.143" } trybuild = { default-features = false, version = "1.0.110" } -# typed-builder = { default-features = false, version = "0.21.2" } -# typed-builder-macro = { default-features = false, version = "0.21.0" } -typed-builder = { git = "https://github.com/zakstucke/rust-typed-builder.git", rev = "3bc55429a0036d0143ed96f9e48715def754f99d" } -typed-builder-macro = { git = "https://github.com/zakstucke/rust-typed-builder.git", rev = "3bc55429a0036d0143ed96f9e48715def754f99d" } +typed-builder = { default-features = false, version = "0.22.0" } +typed-builder-macro = { default-features = false, version = "0.22.0" } thiserror = { default-features = false, version = "2.0.16" } wasm-bindgen = { default-features = false, version = "0.2.100" } indexmap = { default-features = false, version = "2.11.0" } diff --git a/leptos_macro/src/component.rs b/leptos_macro/src/component.rs index 79149ef853..7059609626 100644 --- a/leptos_macro/src/component.rs +++ b/leptos_macro/src/component.rs @@ -1077,14 +1077,16 @@ impl ToTokens for TypedBuilderOpts<'_> { if !self.strip_option { let ty = &self.ty; quote! { - transform_generics = "<__IntoLeptosValueMarker>", - transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| value.into_leptos_value(), + fn transform<__IntoLeptosValueMarker>(value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>) -> #ty { + value.into_leptos_value() + }, } } else { let ty = unwrap_option(self.ty); quote! { - transform_generics = "<__IntoLeptosValueMarker>", - transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| Some(value.into_leptos_value()), + fn transform<__IntoLeptosValueMarker>(value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>) -> Option<#ty> { + Some(value.into_leptos_value()) + }, } } } else { diff --git a/leptos_macro/src/slot.rs b/leptos_macro/src/slot.rs index 8e5f7c0de3..6d7e8342a9 100644 --- a/leptos_macro/src/slot.rs +++ b/leptos_macro/src/slot.rs @@ -201,14 +201,16 @@ impl ToTokens for TypedBuilderOpts<'_> { if !self.strip_option { let ty = &self.ty; quote! { - transform_generics = "<__IntoLeptosValueMarker>", - transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| value.into_leptos_value(), + fn transform<__IntoLeptosValueMarker>(value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>) -> #ty { + value.into_leptos_value() + }, } } else { let ty = unwrap_option(self.ty); quote! { - transform_generics = "<__IntoLeptosValueMarker>", - transform = |value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>| Some(value.into_leptos_value()), + fn transform<__IntoLeptosValueMarker>(value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>) -> Option<#ty> { + Some(value.into_leptos_value()) + }, } } } else { diff --git a/reactive_graph/src/into_leptos_value.rs b/reactive_graph/src/into_leptos_value.rs index 78affb6673..712e5c772a 100644 --- a/reactive_graph/src/into_leptos_value.rs +++ b/reactive_graph/src/into_leptos_value.rs @@ -1,9 +1,10 @@ #[doc(hidden)] pub struct __IntoLeptosValueMarkerBaseCase; -/// TODO docs +/// A helper trait that works like `Into` but uses a marker generic +/// to allow more `From` implementations than would be allowed with just `Into`. pub trait IntoLeptosValue { - /// TODO docs + /// Converts `self` into a `T`. fn into_leptos_value(self) -> T; } @@ -29,7 +30,7 @@ mod tests { use typed_builder::TypedBuilder; #[test] - fn text_into_signal_compiles() { + fn test_into_signal_compiles() { let owner = Owner::new(); owner.set(); @@ -41,7 +42,11 @@ mod tests { #[derive(TypedBuilder)] struct Foo { - #[builder(setter(transform_generics = "", transform = |value: impl IntoLeptosValue, M>| value.into_leptos_value()))] + #[builder(setter( + fn transform(value: impl IntoLeptosValue, M>) { + value.into_leptos_value() + } + ))] sig: Signal, } From 09d1c6dddbbd59decac670254721e0a3301a02c0 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Fri, 19 Sep 2025 20:44:27 +0300 Subject: [PATCH 11/15] Disable signal Fn impls on nightly --- leptos_macro/tests/component.rs | 139 ++++++++++++++---------- reactive_graph/src/callback.rs | 74 ++++--------- reactive_graph/src/into_leptos_value.rs | 3 + reactive_graph/src/nightly.rs | 4 +- reactive_graph/src/wrappers.rs | 41 ++++--- 5 files changed, 133 insertions(+), 128 deletions(-) diff --git a/leptos_macro/tests/component.rs b/leptos_macro/tests/component.rs index cc63da64de..f8720f38f1 100644 --- a/leptos_macro/tests/component.rs +++ b/leptos_macro/tests/component.rs @@ -120,8 +120,9 @@ fn returns_static_lifetime() { } } +#[cfg(not(feature = "nightly"))] #[component] -pub fn IntoLeptosValueTestComponent( +pub fn IntoLeptosValueTestComponentSignal( #[prop(into)] arg1: Signal, #[prop(into)] arg2: Signal, #[prop(into)] arg3: Signal, @@ -129,26 +130,18 @@ pub fn IntoLeptosValueTestComponent( #[prop(into)] arg5: Signal, #[prop(into)] arg6: Signal, #[prop(into)] arg7: Signal>, - #[prop(into)] arg13: Callback<(), String>, - #[prop(into)] arg14: Callback, - #[prop(into)] arg15: Callback<(usize,), String>, - #[prop(into)] arg16: Callback<(usize, String), String>, - #[prop(into)] arg17: UnsyncCallback<(), String>, - #[prop(into)] arg18: UnsyncCallback, - #[prop(into)] arg19: UnsyncCallback<(usize,), String>, - #[prop(into)] arg20: UnsyncCallback<(usize, String), String>, - #[prop(into)] arg21: ArcSignal, - #[prop(into)] arg22: ArcSignal, - #[prop(into)] arg23: ArcSignal, - #[prop(into)] arg24: ArcSignal, - #[prop(into)] arg25: ArcSignal, - #[prop(into)] arg26: ArcSignal, - #[prop(into)] arg27: ArcSignal>, + #[prop(into)] arg8: ArcSignal, + #[prop(into)] arg9: ArcSignal, + #[prop(into)] arg10: ArcSignal, + #[prop(into)] arg11: ArcSignal, + #[prop(into)] arg12: ArcSignal, + #[prop(into)] arg13: ArcSignal, + #[prop(into)] arg14: ArcSignal>, // Optionals: - #[prop(into, optional)] arg28: Option>, - #[prop(into, optional)] arg29_purposely_omitted: Option>, - #[prop(into, optional)] arg30: Option>, - #[prop(into, strip_option)] arg31: Option>, + #[prop(into, optional)] arg15: Option>, + #[prop(into, optional)] arg16_purposely_omitted: Option>, + #[prop(into, optional)] arg17: Option>, + #[prop(into, strip_option)] arg18: Option>, ) -> impl IntoView { move || { view! { @@ -160,34 +153,54 @@ pub fn IntoLeptosValueTestComponent(

{arg5.get()}

{arg6.get()}

{arg7.get()}

-

{arg13.run(())}

-

{arg14.run(1)}

-

{arg15.run((2,))}

-

{arg16.run((3, "three".into()))}

-

{arg17.run(())}

-

{arg18.run(1)}

-

{arg19.run((2,))}

-

{arg20.run((3, "three".into()))}

-

{arg21.get()}

-

{arg22.get()}

-

{arg23.get()}

-

{arg24.get()}

-

{arg25.get()}

-

{arg26.get()}

-

{arg27.get()}

-

{arg28.get()}

-

{arg29_purposely_omitted.get()}

-

{arg30.get()}

-

{arg31.get()}

+

{arg8.get()}

+

{arg9.get()}

+

{arg10.get()}

+

{arg11.get()}

+

{arg12.get()}

+

{arg13.get()}

+

{arg14.get()}

+

{arg15.get()}

+

{arg16_purposely_omitted.get()}

+

{arg17.get()}

+

{arg18.get()}

} } } +#[component] +pub fn IntoLeptosValueTestComponentCallback( + #[prop(into)] arg1: Callback<(), String>, + #[prop(into)] arg2: Callback, + #[prop(into)] arg3: Callback<(usize,), String>, + #[prop(into)] arg4: Callback<(usize, String), String>, + #[prop(into)] arg5: UnsyncCallback<(), String>, + #[prop(into)] arg6: UnsyncCallback, + #[prop(into)] arg7: UnsyncCallback<(usize,), String>, + #[prop(into)] arg8: UnsyncCallback<(usize, String), String>, +) -> impl IntoView { + move || { + view! { +
+

{arg1.run(())}

+

{arg2.run(1)}

+

{arg3.run((2,))}

+

{arg4.run((3, "three".into()))}

+

{arg5.run(())}

+

{arg6.run(1)}

+

{arg7.run((2,))}

+

{arg8.run((3, "three".into()))}

+
+ } + } +} + +#[cfg(not(feature = "nightly"))] #[test] -fn test_into_leptos_value() { +fn test_into_leptos_value_signal() { let _ = view! { - + }; +} + +#[test] +fn test_into_leptos_value_callback() { + let _ = view! { + }; } diff --git a/reactive_graph/src/callback.rs b/reactive_graph/src/callback.rs index e04ddc5b24..534bf51743 100644 --- a/reactive_graph/src/callback.rs +++ b/reactive_graph/src/callback.rs @@ -2,42 +2,12 @@ //! for component properties, because they can be used to define optional callback functions, //! which generic props don’t support. //! -//! # Usage -//! Callbacks can be created manually from any function or closure, but the easiest way -//! to create them is to use `#[prop(into)]]` when defining a component. -//! ```ignore -//! use leptos::prelude::*; -//! -//! #[component] -//! fn MyComponent( -//! #[prop(into)] render_number: Callback<(i32,), String>, -//! ) -> impl IntoView { -//! view! { -//!
-//! {render_number.run((1,))} -//! // callbacks can be called multiple times -//! {render_number.run((42,))} -//!
-//! } -//! } -//! // you can pass a closure directly as `render_number` -//! fn test() -> impl IntoView { -//! view! { -//! -//! } -//! } -//! ``` -//! -//! *Notes*: -//! - The `render_number` prop can receive any type that implements `Fn(i32) -> String`. -//! - Callbacks are most useful when you want optional generic props. -//! - All callbacks implement the [`Callable`](leptos::callback::Callable) trait, and can be invoked with `my_callback.run(input)`. -//! - The callback types implement [`Copy`], so they can easily be moved into and out of other closures, just like signals. +//! The callback types implement [`Copy`], so they can easily be moved into and out of other closures, just like signals. //! //! # Types //! This modules implements 2 callback types: -//! - [`Callback`](leptos::callback::Callback) -//! - [`UnsyncCallback`](leptos::callback::UnsyncCallback) +//! - [`Callback`](reactive_graph::callback::Callback) +//! - [`UnsyncCallback`](reactive_graph::callback::UnsyncCallback) //! //! Use `SyncCallback` if the function is not `Sync` and `Send`. @@ -61,7 +31,16 @@ pub trait Callable { fn run(&self, input: In) -> Out; } -/// A callback type that is not required to be `Send + Sync`. +/// A callback type that is not required to be [`Send`] or [`Sync`]. +/// +/// # Example +/// ``` +/// # use reactive_graph::prelude::*; use reactive_graph::callback::*; let owner = reactive_graph::owner::Owner::new(); owner.set(); +/// let _: UnsyncCallback<()> = UnsyncCallback::new(|_| {}); +/// let _: UnsyncCallback<(i32, i32)> = (|_x: i32, _y: i32| {}).into(); +/// let cb: UnsyncCallback = UnsyncCallback::new(|x: i32| x.to_string()); +/// assert_eq!(cb.run(42), "42".to_string()); +/// ``` pub struct UnsyncCallback( StoredValue Out>, LocalStorage>, ); @@ -149,28 +128,15 @@ impl_unsync_callable_from_fn!( P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12 ); -/// Callbacks define a standard way to store functions and closures. +/// A callback type that is [`Send`] + [`Sync`]. /// /// # Example -/// ```ignore -/// # use leptos::prelude::*; -/// # use leptos::callback::{Callable, Callback}; -/// #[component] -/// fn MyComponent( -/// #[prop(into)] render_number: Callback<(i32,), String>, -/// ) -> impl IntoView { -/// view! { -///
-/// {render_number.run((42,))} -///
-/// } -/// } -/// -/// fn test() -> impl IntoView { -/// view! { -/// -/// } -/// } +/// ``` +/// # use reactive_graph::prelude::*; use reactive_graph::callback::*; let owner = reactive_graph::owner::Owner::new(); owner.set(); +/// let _: Callback<()> = Callback::new(|_| {}); +/// let _: Callback<(i32, i32)> = (|_x: i32, _y: i32| {}).into(); +/// let cb: Callback = Callback::new(|x: i32| x.to_string()); +/// assert_eq!(cb.run(42), "42".to_string()); /// ``` pub struct Callback( StoredValue Out + Send + Sync>>, diff --git a/reactive_graph/src/into_leptos_value.rs b/reactive_graph/src/into_leptos_value.rs index 712e5c772a..fb3c615a7b 100644 --- a/reactive_graph/src/into_leptos_value.rs +++ b/reactive_graph/src/into_leptos_value.rs @@ -34,8 +34,10 @@ mod tests { let owner = Owner::new(); owner.set(); + #[cfg(not(feature = "nightly"))] let _: Signal = (|| 2).into_leptos_value(); let _: Signal = 2.into_leptos_value(); + #[cfg(not(feature = "nightly"))] let _: Signal = (|| 2).into_leptos_value(); let _: Signal = "str".into_leptos_value(); let _: Signal = "str".into_leptos_value(); @@ -51,6 +53,7 @@ mod tests { } assert_eq!(Foo::builder().sig(2).build().sig.get_untracked(), 2); + #[cfg(not(feature = "nightly"))] assert_eq!(Foo::builder().sig(|| 2).build().sig.get_untracked(), 2); assert_eq!( Foo::builder() diff --git a/reactive_graph/src/nightly.rs b/reactive_graph/src/nightly.rs index 0d72df7c5c..21e070d8de 100644 --- a/reactive_graph/src/nightly.rs +++ b/reactive_graph/src/nightly.rs @@ -147,8 +147,8 @@ impl_get_fn_traits_get_arena![ ReadSignal, RwSignal, ArcMemo, - // ArcSignal, - // Signal, + ArcSignal, + Signal, MaybeSignal, Memo, MaybeProp diff --git a/reactive_graph/src/wrappers.rs b/reactive_graph/src/wrappers.rs index 290db583ae..e0e4a62b17 100644 --- a/reactive_graph/src/wrappers.rs +++ b/reactive_graph/src/wrappers.rs @@ -17,7 +17,7 @@ pub mod read { traits::{ DefinedAt, Dispose, Get, Read, ReadUntracked, ReadValue, Track, }, - unwrap_signal, IntoLeptosValue, + unwrap_signal, }; use send_wrapper::SendWrapper; use std::{ @@ -1118,15 +1118,19 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] #[doc(hidden)] pub struct __IntoLeptosValueMarkerSignalFromReactiveClosure; + #[cfg(not(feature = "nightly"))] #[doc(hidden)] pub struct __IntoLeptosValueMarkerSignalStrOutputToString; + #[cfg(not(feature = "nightly"))] #[doc(hidden)] pub struct __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways; + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< Signal, __IntoLeptosValueMarkerSignalFromReactiveClosure, > for F @@ -1139,8 +1143,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< ArcSignal, __IntoLeptosValueMarkerSignalFromReactiveClosure, > for F @@ -1153,8 +1158,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< Signal, __IntoLeptosValueMarkerSignalFromReactiveClosure, > for F @@ -1167,8 +1173,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< ArcSignal, __IntoLeptosValueMarkerSignalFromReactiveClosure, > for F @@ -1181,8 +1188,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< Signal, __IntoLeptosValueMarkerSignalStrOutputToString, > for F @@ -1194,8 +1202,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< ArcSignal, __IntoLeptosValueMarkerSignalStrOutputToString, > for F @@ -1207,8 +1216,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< Signal, __IntoLeptosValueMarkerSignalStrOutputToString, > for F @@ -1220,8 +1230,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< ArcSignal, __IntoLeptosValueMarkerSignalStrOutputToString, > for F @@ -1233,8 +1244,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< Signal, SyncStorage>, __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, > for F @@ -1247,8 +1259,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< ArcSignal, SyncStorage>, __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, > for F @@ -1261,8 +1274,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< Signal, LocalStorage>, __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, > for F @@ -1275,8 +1289,9 @@ pub mod read { } } + #[cfg(not(feature = "nightly"))] impl - IntoLeptosValue< + crate::IntoLeptosValue< ArcSignal, LocalStorage>, __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, > for F From 72c55aaca1d9cfdd39973a8aa92c554e4aba4b5a Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Fri, 19 Sep 2025 23:13:14 +0300 Subject: [PATCH 12/15] Fix --- reactive_graph/src/nightly.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/reactive_graph/src/nightly.rs b/reactive_graph/src/nightly.rs index 21e070d8de..d20e2d1883 100644 --- a/reactive_graph/src/nightly.rs +++ b/reactive_graph/src/nightly.rs @@ -8,7 +8,10 @@ use crate::{ WriteSignal, }, traits::{Get, Set}, - wrappers::{read::SignalTypes, write::SignalSetter}, + wrappers::{ + read::{ArcSignal, Signal, SignalTypes}, + write::SignalSetter, + }, }; macro_rules! impl_set_fn_traits { From 8f514196e018618f1c9ff48e0388e46e55aca7c1 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Sat, 20 Sep 2025 19:50:47 +0300 Subject: [PATCH 13/15] Fix conflicts --- Cargo.lock | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 8cf5f7b135..6cbd2258d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4622,19 +4622,33 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ +<<<<<<< HEAD <<<<<<< HEAD "windows-sys 0.52.0", ||||||| parent of e8f56d4f (Updates:) "windows-sys 0.61.0", ======= <<<<<<< HEAD +||||||| parent of 279abd38 (Fix conflicts) +<<<<<<< HEAD +======= +>>>>>>> 279abd38 (Fix conflicts) "windows-sys 0.61.0", +<<<<<<< HEAD ||||||| parent of 97259a60 (Updates:) "windows-sys 0.52.0", ======= "windows-sys 0.60.2", >>>>>>> 97259a60 (Updates:) >>>>>>> e8f56d4f (Updates:) +||||||| parent of 279abd38 (Fix conflicts) +||||||| parent of 97259a60 (Updates:) + "windows-sys 0.52.0", +======= + "windows-sys 0.60.2", +>>>>>>> 97259a60 (Updates:) +======= +>>>>>>> 279abd38 (Fix conflicts) ] [[package]] From 2994e6d38d97f129e9b2214b63c75c46a9f908a8 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Wed, 24 Sep 2025 10:23:54 +0300 Subject: [PATCH 14/15] IntoLeptosValue -> IntoReactiveValue --- leptos_macro/src/component.rs | 8 +- leptos_macro/src/slot.rs | 8 +- leptos_macro/src/view/component_builder.rs | 2 +- leptos_macro/tests/component.rs | 12 +-- reactive_graph/src/callback.rs | 46 +++++------ ...leptos_value.rs => into_reactive_value.rs} | 26 +++---- reactive_graph/src/lib.rs | 6 +- reactive_graph/src/wrappers.rs | 78 +++++++++---------- 8 files changed, 94 insertions(+), 92 deletions(-) rename reactive_graph/src/{into_leptos_value.rs => into_reactive_value.rs} (62%) diff --git a/leptos_macro/src/component.rs b/leptos_macro/src/component.rs index 7059609626..dbf9dc020b 100644 --- a/leptos_macro/src/component.rs +++ b/leptos_macro/src/component.rs @@ -1077,15 +1077,15 @@ impl ToTokens for TypedBuilderOpts<'_> { if !self.strip_option { let ty = &self.ty; quote! { - fn transform<__IntoLeptosValueMarker>(value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>) -> #ty { - value.into_leptos_value() + fn transform<__IntoReactiveValueMarker>(value: impl ::leptos::prelude::IntoReactiveValue<#ty, __IntoReactiveValueMarker>) -> #ty { + value.into_reactive_value() }, } } else { let ty = unwrap_option(self.ty); quote! { - fn transform<__IntoLeptosValueMarker>(value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>) -> Option<#ty> { - Some(value.into_leptos_value()) + fn transform<__IntoReactiveValueMarker>(value: impl ::leptos::prelude::IntoReactiveValue<#ty, __IntoReactiveValueMarker>) -> Option<#ty> { + Some(value.into_reactive_value()) }, } } diff --git a/leptos_macro/src/slot.rs b/leptos_macro/src/slot.rs index 6d7e8342a9..c4593818fe 100644 --- a/leptos_macro/src/slot.rs +++ b/leptos_macro/src/slot.rs @@ -201,15 +201,15 @@ impl ToTokens for TypedBuilderOpts<'_> { if !self.strip_option { let ty = &self.ty; quote! { - fn transform<__IntoLeptosValueMarker>(value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>) -> #ty { - value.into_leptos_value() + fn transform<__IntoReactiveValueMarker>(value: impl ::leptos::prelude::IntoReactiveValue<#ty, __IntoReactiveValueMarker>) -> #ty { + value.into_reactive_value() }, } } else { let ty = unwrap_option(self.ty); quote! { - fn transform<__IntoLeptosValueMarker>(value: impl ::leptos::prelude::IntoLeptosValue<#ty, __IntoLeptosValueMarker>) -> Option<#ty> { - Some(value.into_leptos_value()) + fn transform<__IntoReactiveValueMarker>(value: impl ::leptos::prelude::IntoReactiveValue<#ty, __IntoReactiveValueMarker>) -> Option<#ty> { + Some(value.into_reactive_value()) }, } } diff --git a/leptos_macro/src/view/component_builder.rs b/leptos_macro/src/view/component_builder.rs index 196c5626df..2f61988b3f 100644 --- a/leptos_macro/src/view/component_builder.rs +++ b/leptos_macro/src/view/component_builder.rs @@ -90,7 +90,7 @@ pub(crate) fn component_to_tokens( if optional { optional_props.push(quote! { - props.#name = { #value }.map(::leptos::prelude::IntoLeptosValue::into_leptos_value); + props.#name = { #value }.map(::leptos::prelude::IntoReactiveValue::into_reactive_value); }) } else { required_props.push(quote! { diff --git a/leptos_macro/tests/component.rs b/leptos_macro/tests/component.rs index f8720f38f1..d8d9e0d04e 100644 --- a/leptos_macro/tests/component.rs +++ b/leptos_macro/tests/component.rs @@ -122,7 +122,7 @@ fn returns_static_lifetime() { #[cfg(not(feature = "nightly"))] #[component] -pub fn IntoLeptosValueTestComponentSignal( +pub fn IntoReactiveValueTestComponentSignal( #[prop(into)] arg1: Signal, #[prop(into)] arg2: Signal, #[prop(into)] arg3: Signal, @@ -170,7 +170,7 @@ pub fn IntoLeptosValueTestComponentSignal( } #[component] -pub fn IntoLeptosValueTestComponentCallback( +pub fn IntoReactiveValueTestComponentCallback( #[prop(into)] arg1: Callback<(), String>, #[prop(into)] arg2: Callback, #[prop(into)] arg3: Callback<(usize,), String>, @@ -198,9 +198,9 @@ pub fn IntoLeptosValueTestComponentCallback( #[cfg(not(feature = "nightly"))] #[test] -fn test_into_leptos_value_signal() { +fn test_into_reactive_value_signal() { let _ = view! { - Callback { } #[doc(hidden)] -pub struct __IntoLeptosValueMarkerCallbackSingleParam; +pub struct __IntoReactiveValueMarkerCallbackSingleParam; #[doc(hidden)] -pub struct __IntoLeptosValueMarkerCallbackStrOutputToString; +pub struct __IntoReactiveValueMarkerCallbackStrOutputToString; impl - IntoLeptosValue, __IntoLeptosValueMarkerCallbackSingleParam> - for F + IntoReactiveValue< + Callback, + __IntoReactiveValueMarkerCallbackSingleParam, + > for F where F: Fn(I) -> O + Send + Sync + 'static, { #[track_caller] - fn into_leptos_value(self) -> Callback { + fn into_reactive_value(self) -> Callback { Callback::new(self) } } impl - IntoLeptosValue< + IntoReactiveValue< UnsyncCallback, - __IntoLeptosValueMarkerCallbackSingleParam, + __IntoReactiveValueMarkerCallbackSingleParam, > for F where F: Fn(I) -> O + 'static, { #[track_caller] - fn into_leptos_value(self) -> UnsyncCallback { + fn into_reactive_value(self) -> UnsyncCallback { UnsyncCallback::new(self) } } impl - IntoLeptosValue< + IntoReactiveValue< Callback, - __IntoLeptosValueMarkerCallbackStrOutputToString, + __IntoReactiveValueMarkerCallbackStrOutputToString, > for F where F: Fn(I) -> &'static str + Send + Sync + 'static, { #[track_caller] - fn into_leptos_value(self) -> Callback { + fn into_reactive_value(self) -> Callback { Callback::new(move |i| self(i).to_string()) } } impl - IntoLeptosValue< + IntoReactiveValue< UnsyncCallback, - __IntoLeptosValueMarkerCallbackStrOutputToString, + __IntoReactiveValueMarkerCallbackStrOutputToString, > for F where F: Fn(I) -> &'static str + 'static, { #[track_caller] - fn into_leptos_value(self) -> UnsyncCallback { + fn into_reactive_value(self) -> UnsyncCallback { UnsyncCallback::new(move |i| self(i).to_string()) } } @@ -297,7 +299,7 @@ mod tests { callback::{Callback, UnsyncCallback}, owner::Owner, traits::Dispose, - IntoLeptosValue, + IntoReactiveValue, }; struct NoClone {} @@ -329,11 +331,11 @@ mod tests { let _callback: Callback<(), String> = (|| "test").into(); let _callback: Callback<(i32, String), String> = (|num, s| format!("{num} {s}")).into(); - // Single params should work without needing the (foo,) tuple using IntoLeptosValue: + // Single params should work without needing the (foo,) tuple using IntoReactiveValue: let _callback: Callback = - (|_usize| "test").into_leptos_value(); + (|_usize| "test").into_reactive_value(); let _callback: Callback = - (|_usize| "test").into_leptos_value(); + (|_usize| "test").into_reactive_value(); } #[test] @@ -344,11 +346,11 @@ mod tests { let _callback: UnsyncCallback<(), String> = (|| "test").into(); let _callback: UnsyncCallback<(i32, String), String> = (|num, s| format!("{num} {s}")).into(); - // Single params should work without needing the (foo,) tuple using IntoLeptosValue: + // Single params should work without needing the (foo,) tuple using IntoReactiveValue: let _callback: UnsyncCallback = - (|_usize| "test").into_leptos_value(); + (|_usize| "test").into_reactive_value(); let _callback: UnsyncCallback = - (|_usize| "test").into_leptos_value(); + (|_usize| "test").into_reactive_value(); } #[test] diff --git a/reactive_graph/src/into_leptos_value.rs b/reactive_graph/src/into_reactive_value.rs similarity index 62% rename from reactive_graph/src/into_leptos_value.rs rename to reactive_graph/src/into_reactive_value.rs index fb3c615a7b..51da40850f 100644 --- a/reactive_graph/src/into_leptos_value.rs +++ b/reactive_graph/src/into_reactive_value.rs @@ -1,19 +1,19 @@ #[doc(hidden)] -pub struct __IntoLeptosValueMarkerBaseCase; +pub struct __IntoReactiveValueMarkerBaseCase; /// A helper trait that works like `Into` but uses a marker generic /// to allow more `From` implementations than would be allowed with just `Into`. -pub trait IntoLeptosValue { +pub trait IntoReactiveValue { /// Converts `self` into a `T`. - fn into_leptos_value(self) -> T; + fn into_reactive_value(self) -> T; } // The base case, which allows anything which implements .into() to work: -impl IntoLeptosValue for I +impl IntoReactiveValue for I where I: Into, { - fn into_leptos_value(self) -> T { + fn into_reactive_value(self) -> T { self.into() } } @@ -22,7 +22,7 @@ where mod tests { use crate::{ - into_leptos_value::IntoLeptosValue, + into_reactive_value::IntoReactiveValue, owner::{LocalStorage, Owner}, traits::GetUntracked, wrappers::read::Signal, @@ -35,18 +35,18 @@ mod tests { owner.set(); #[cfg(not(feature = "nightly"))] - let _: Signal = (|| 2).into_leptos_value(); - let _: Signal = 2.into_leptos_value(); + let _: Signal = (|| 2).into_reactive_value(); + let _: Signal = 2.into_reactive_value(); #[cfg(not(feature = "nightly"))] - let _: Signal = (|| 2).into_leptos_value(); - let _: Signal = "str".into_leptos_value(); - let _: Signal = "str".into_leptos_value(); + let _: Signal = (|| 2).into_reactive_value(); + let _: Signal = "str".into_reactive_value(); + let _: Signal = "str".into_reactive_value(); #[derive(TypedBuilder)] struct Foo { #[builder(setter( - fn transform(value: impl IntoLeptosValue, M>) { - value.into_leptos_value() + fn transform(value: impl IntoReactiveValue, M>) { + value.into_reactive_value() } ))] sig: Signal, diff --git a/reactive_graph/src/lib.rs b/reactive_graph/src/lib.rs index 7a8e9e2484..284a9f5f1c 100644 --- a/reactive_graph/src/lib.rs +++ b/reactive_graph/src/lib.rs @@ -90,8 +90,8 @@ pub mod traits; pub mod transition; pub mod wrappers; -mod into_leptos_value; -pub use into_leptos_value::*; +mod into_reactive_value; +pub use into_reactive_value::*; /// A standard way to wrap functions and closures to pass them to components. pub mod callback; @@ -104,7 +104,7 @@ mod nightly; /// Reexports frequently-used traits. pub mod prelude { pub use crate::{ - into_leptos_value::IntoLeptosValue, owner::FromLocal, traits::*, + into_reactive_value::IntoReactiveValue, owner::FromLocal, traits::*, }; } diff --git a/reactive_graph/src/wrappers.rs b/reactive_graph/src/wrappers.rs index e0e4a62b17..62ceaf28a6 100644 --- a/reactive_graph/src/wrappers.rs +++ b/reactive_graph/src/wrappers.rs @@ -1120,186 +1120,186 @@ pub mod read { #[cfg(not(feature = "nightly"))] #[doc(hidden)] - pub struct __IntoLeptosValueMarkerSignalFromReactiveClosure; + pub struct __IntoReactiveValueMarkerSignalFromReactiveClosure; #[cfg(not(feature = "nightly"))] #[doc(hidden)] - pub struct __IntoLeptosValueMarkerSignalStrOutputToString; + pub struct __IntoReactiveValueMarkerSignalStrOutputToString; #[cfg(not(feature = "nightly"))] #[doc(hidden)] - pub struct __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways; + pub struct __IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways; #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< Signal, - __IntoLeptosValueMarkerSignalFromReactiveClosure, + __IntoReactiveValueMarkerSignalFromReactiveClosure, > for F where T: Send + Sync + 'static, F: Fn() -> T + Send + Sync + 'static, { - fn into_leptos_value(self) -> Signal { + fn into_reactive_value(self) -> Signal { Signal::derive(self) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< ArcSignal, - __IntoLeptosValueMarkerSignalFromReactiveClosure, + __IntoReactiveValueMarkerSignalFromReactiveClosure, > for F where T: Send + Sync + 'static, F: Fn() -> T + Send + Sync + 'static, { - fn into_leptos_value(self) -> ArcSignal { + fn into_reactive_value(self) -> ArcSignal { ArcSignal::derive(self) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< Signal, - __IntoLeptosValueMarkerSignalFromReactiveClosure, + __IntoReactiveValueMarkerSignalFromReactiveClosure, > for F where T: 'static, F: Fn() -> T + 'static, { - fn into_leptos_value(self) -> Signal { + fn into_reactive_value(self) -> Signal { Signal::derive_local(self) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< ArcSignal, - __IntoLeptosValueMarkerSignalFromReactiveClosure, + __IntoReactiveValueMarkerSignalFromReactiveClosure, > for F where T: 'static, F: Fn() -> T + 'static, { - fn into_leptos_value(self) -> ArcSignal { + fn into_reactive_value(self) -> ArcSignal { ArcSignal::derive_local(self) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< Signal, - __IntoLeptosValueMarkerSignalStrOutputToString, + __IntoReactiveValueMarkerSignalStrOutputToString, > for F where F: Fn() -> &'static str + Send + Sync + 'static, { - fn into_leptos_value(self) -> Signal { + fn into_reactive_value(self) -> Signal { Signal::derive(move || self().to_string()) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< ArcSignal, - __IntoLeptosValueMarkerSignalStrOutputToString, + __IntoReactiveValueMarkerSignalStrOutputToString, > for F where F: Fn() -> &'static str + Send + Sync + 'static, { - fn into_leptos_value(self) -> ArcSignal { + fn into_reactive_value(self) -> ArcSignal { ArcSignal::derive(move || self().to_string()) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< Signal, - __IntoLeptosValueMarkerSignalStrOutputToString, + __IntoReactiveValueMarkerSignalStrOutputToString, > for F where F: Fn() -> &'static str + 'static, { - fn into_leptos_value(self) -> Signal { + fn into_reactive_value(self) -> Signal { Signal::derive_local(move || self().to_string()) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< ArcSignal, - __IntoLeptosValueMarkerSignalStrOutputToString, + __IntoReactiveValueMarkerSignalStrOutputToString, > for F where F: Fn() -> &'static str + 'static, { - fn into_leptos_value(self) -> ArcSignal { + fn into_reactive_value(self) -> ArcSignal { ArcSignal::derive_local(move || self().to_string()) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< Signal, SyncStorage>, - __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, + __IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways, > for F where T: Send + Sync + 'static, F: Fn() -> T + Send + Sync + 'static, { - fn into_leptos_value(self) -> Signal, SyncStorage> { + fn into_reactive_value(self) -> Signal, SyncStorage> { Signal::derive(move || Some(self())) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< ArcSignal, SyncStorage>, - __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, + __IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways, > for F where T: Send + Sync + 'static, F: Fn() -> T + Send + Sync + 'static, { - fn into_leptos_value(self) -> ArcSignal, SyncStorage> { + fn into_reactive_value(self) -> ArcSignal, SyncStorage> { ArcSignal::derive(move || Some(self())) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< Signal, LocalStorage>, - __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, + __IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways, > for F where T: 'static, F: Fn() -> T + 'static, { - fn into_leptos_value(self) -> Signal, LocalStorage> { + fn into_reactive_value(self) -> Signal, LocalStorage> { Signal::derive_local(move || Some(self())) } } #[cfg(not(feature = "nightly"))] impl - crate::IntoLeptosValue< + crate::IntoReactiveValue< ArcSignal, LocalStorage>, - __IntoLeptosValueMarkerOptionalSignalFromReactiveClosureAlways, + __IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways, > for F where T: 'static, F: Fn() -> T + 'static, { - fn into_leptos_value(self) -> ArcSignal, LocalStorage> { + fn into_reactive_value(self) -> ArcSignal, LocalStorage> { ArcSignal::derive_local(move || Some(self())) } } From 13bba18599661caa107562630efbf675895b96fb Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Thu, 25 Sep 2025 17:51:47 +0300 Subject: [PATCH 15/15] Re-enable for nightly --- Cargo.lock | 32 ++---------- leptos_macro/tests/component.rs | 2 - reactive_graph/src/into_reactive_value.rs | 3 -- reactive_graph/src/lib.rs | 2 + reactive_graph/src/wrappers.rs | 60 ++++++++++++----------- 5 files changed, 36 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6cbd2258d9..ec125a52a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1057,7 +1057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.0", ] [[package]] @@ -3146,7 +3146,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.0", ] [[package]] @@ -3815,7 +3815,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.0", ] [[package]] @@ -4622,33 +4622,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ -<<<<<<< HEAD -<<<<<<< HEAD - "windows-sys 0.52.0", -||||||| parent of e8f56d4f (Updates:) - "windows-sys 0.61.0", -======= -<<<<<<< HEAD -||||||| parent of 279abd38 (Fix conflicts) -<<<<<<< HEAD -======= ->>>>>>> 279abd38 (Fix conflicts) "windows-sys 0.61.0", -<<<<<<< HEAD -||||||| parent of 97259a60 (Updates:) - "windows-sys 0.52.0", -======= - "windows-sys 0.60.2", ->>>>>>> 97259a60 (Updates:) ->>>>>>> e8f56d4f (Updates:) -||||||| parent of 279abd38 (Fix conflicts) -||||||| parent of 97259a60 (Updates:) - "windows-sys 0.52.0", -======= - "windows-sys 0.60.2", ->>>>>>> 97259a60 (Updates:) -======= ->>>>>>> 279abd38 (Fix conflicts) ] [[package]] diff --git a/leptos_macro/tests/component.rs b/leptos_macro/tests/component.rs index d8d9e0d04e..c1980a33ed 100644 --- a/leptos_macro/tests/component.rs +++ b/leptos_macro/tests/component.rs @@ -120,7 +120,6 @@ fn returns_static_lifetime() { } } -#[cfg(not(feature = "nightly"))] #[component] pub fn IntoReactiveValueTestComponentSignal( #[prop(into)] arg1: Signal, @@ -196,7 +195,6 @@ pub fn IntoReactiveValueTestComponentCallback( } } -#[cfg(not(feature = "nightly"))] #[test] fn test_into_reactive_value_signal() { let _ = view! { diff --git a/reactive_graph/src/into_reactive_value.rs b/reactive_graph/src/into_reactive_value.rs index 51da40850f..f4b0d2a652 100644 --- a/reactive_graph/src/into_reactive_value.rs +++ b/reactive_graph/src/into_reactive_value.rs @@ -34,10 +34,8 @@ mod tests { let owner = Owner::new(); owner.set(); - #[cfg(not(feature = "nightly"))] let _: Signal = (|| 2).into_reactive_value(); let _: Signal = 2.into_reactive_value(); - #[cfg(not(feature = "nightly"))] let _: Signal = (|| 2).into_reactive_value(); let _: Signal = "str".into_reactive_value(); let _: Signal = "str".into_reactive_value(); @@ -53,7 +51,6 @@ mod tests { } assert_eq!(Foo::builder().sig(2).build().sig.get_untracked(), 2); - #[cfg(not(feature = "nightly"))] assert_eq!(Foo::builder().sig(|| 2).build().sig.get_untracked(), 2); assert_eq!( Foo::builder() diff --git a/reactive_graph/src/lib.rs b/reactive_graph/src/lib.rs index 284a9f5f1c..8361e4a435 100644 --- a/reactive_graph/src/lib.rs +++ b/reactive_graph/src/lib.rs @@ -70,6 +70,8 @@ #![cfg_attr(all(feature = "nightly", rustc_nightly), feature(unboxed_closures))] #![cfg_attr(all(feature = "nightly", rustc_nightly), feature(fn_traits))] +#![cfg_attr(all(feature = "nightly", rustc_nightly), feature(auto_traits))] +#![cfg_attr(all(feature = "nightly", rustc_nightly), feature(negative_impls))] #![deny(missing_docs)] use std::{fmt::Arguments, future::Future}; diff --git a/reactive_graph/src/wrappers.rs b/reactive_graph/src/wrappers.rs index 62ceaf28a6..b09fb8dae6 100644 --- a/reactive_graph/src/wrappers.rs +++ b/reactive_graph/src/wrappers.rs @@ -1118,17 +1118,30 @@ pub mod read { } } - #[cfg(not(feature = "nightly"))] + #[cfg(not(all(feature = "nightly", rustc_nightly)))] + /// On nightly signal types implement `Fn()`, + /// so must use negative trait bounds to prevent these implementations for Fn() types + /// causing "multiple impl errors" with the signal types themselves. + trait NotSignalMarker {} + #[cfg(not(all(feature = "nightly", rustc_nightly)))] + impl NotSignalMarker for T {} + + #[cfg(all(feature = "nightly", rustc_nightly))] + auto trait NotSignalMarker {} + + #[cfg(all(feature = "nightly", rustc_nightly))] + impl !NotSignalMarker for Signal {} + + #[cfg(all(feature = "nightly", rustc_nightly))] + impl !NotSignalMarker for ArcSignal {} + #[doc(hidden)] pub struct __IntoReactiveValueMarkerSignalFromReactiveClosure; - #[cfg(not(feature = "nightly"))] #[doc(hidden)] pub struct __IntoReactiveValueMarkerSignalStrOutputToString; - #[cfg(not(feature = "nightly"))] #[doc(hidden)] pub struct __IntoReactiveValueMarkerOptionalSignalFromReactiveClosureAlways; - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< Signal, @@ -1136,14 +1149,13 @@ pub mod read { > for F where T: Send + Sync + 'static, - F: Fn() -> T + Send + Sync + 'static, + F: Fn() -> T + NotSignalMarker + Send + Sync + 'static, { fn into_reactive_value(self) -> Signal { Signal::derive(self) } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< ArcSignal, @@ -1151,14 +1163,13 @@ pub mod read { > for F where T: Send + Sync + 'static, - F: Fn() -> T + Send + Sync + 'static, + F: Fn() -> T + NotSignalMarker + Send + Sync + 'static, { fn into_reactive_value(self) -> ArcSignal { ArcSignal::derive(self) } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< Signal, @@ -1166,14 +1177,13 @@ pub mod read { > for F where T: 'static, - F: Fn() -> T + 'static, + F: Fn() -> T + NotSignalMarker + 'static, { fn into_reactive_value(self) -> Signal { Signal::derive_local(self) } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< ArcSignal, @@ -1181,70 +1191,65 @@ pub mod read { > for F where T: 'static, - F: Fn() -> T + 'static, + F: Fn() -> T + NotSignalMarker + 'static, { fn into_reactive_value(self) -> ArcSignal { ArcSignal::derive_local(self) } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< Signal, __IntoReactiveValueMarkerSignalStrOutputToString, > for F where - F: Fn() -> &'static str + Send + Sync + 'static, + F: Fn() -> &'static str + NotSignalMarker + Send + Sync + 'static, { fn into_reactive_value(self) -> Signal { - Signal::derive(move || self().to_string()) + Signal::derive(self).into() } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< ArcSignal, __IntoReactiveValueMarkerSignalStrOutputToString, > for F where - F: Fn() -> &'static str + Send + Sync + 'static, + F: Fn() -> &'static str + NotSignalMarker + Send + Sync + 'static, { fn into_reactive_value(self) -> ArcSignal { ArcSignal::derive(move || self().to_string()) } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< Signal, __IntoReactiveValueMarkerSignalStrOutputToString, > for F where - F: Fn() -> &'static str + 'static, + F: Fn() -> &'static str + NotSignalMarker + 'static, { fn into_reactive_value(self) -> Signal { - Signal::derive_local(move || self().to_string()) + Signal::derive_local(self).into() } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< ArcSignal, __IntoReactiveValueMarkerSignalStrOutputToString, > for F where - F: Fn() -> &'static str + 'static, + F: Fn() -> &'static str + NotSignalMarker + 'static, { fn into_reactive_value(self) -> ArcSignal { ArcSignal::derive_local(move || self().to_string()) } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< Signal, SyncStorage>, @@ -1252,14 +1257,13 @@ pub mod read { > for F where T: Send + Sync + 'static, - F: Fn() -> T + Send + Sync + 'static, + F: Fn() -> T + NotSignalMarker + Send + Sync + 'static, { fn into_reactive_value(self) -> Signal, SyncStorage> { Signal::derive(move || Some(self())) } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< ArcSignal, SyncStorage>, @@ -1267,14 +1271,13 @@ pub mod read { > for F where T: Send + Sync + 'static, - F: Fn() -> T + Send + Sync + 'static, + F: Fn() -> T + NotSignalMarker + Send + Sync + 'static, { fn into_reactive_value(self) -> ArcSignal, SyncStorage> { ArcSignal::derive(move || Some(self())) } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< Signal, LocalStorage>, @@ -1282,14 +1285,13 @@ pub mod read { > for F where T: 'static, - F: Fn() -> T + 'static, + F: Fn() -> T + NotSignalMarker + 'static, { fn into_reactive_value(self) -> Signal, LocalStorage> { Signal::derive_local(move || Some(self())) } } - #[cfg(not(feature = "nightly"))] impl crate::IntoReactiveValue< ArcSignal, LocalStorage>, @@ -1297,7 +1299,7 @@ pub mod read { > for F where T: 'static, - F: Fn() -> T + 'static, + F: Fn() -> T + NotSignalMarker + 'static, { fn into_reactive_value(self) -> ArcSignal, LocalStorage> { ArcSignal::derive_local(move || Some(self()))