Skip to content

Commit

Permalink
feat: Option<T> read-like traits & deprecate MaybeSignal (#3098)
Browse files Browse the repository at this point in the history
  • Loading branch information
zakstucke authored Oct 25, 2024
1 parent a437289 commit 396327b
Show file tree
Hide file tree
Showing 14 changed files with 664 additions and 59 deletions.
4 changes: 1 addition & 3 deletions examples/errors_axum/src/error_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ use leptos_axum::ResponseOptions;
// A basic function to display errors served by the error boundaries.
// Feel free to do more complicated things here than just displaying them.
#[component]
pub fn ErrorTemplate(
#[prop(into)] errors: MaybeSignal<Errors>,
) -> impl IntoView {
pub fn ErrorTemplate(#[prop(into)] errors: Signal<Errors>) -> impl IntoView {
// Get Errors from Signal
// Downcast lets us take a type that implements `std::error::Error`
let errors = Memo::new(move |_| {
Expand Down
10 changes: 5 additions & 5 deletions examples/slots/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ struct Then {
// the type with Option<...> and marking the option as #[prop(optional)].
#[slot]
struct ElseIf {
cond: MaybeSignal<bool>,
cond: Signal<bool>,
children: ChildrenFn,
}

Expand All @@ -22,7 +22,7 @@ struct Fallback {
// Slots are added to components like any other prop.
#[component]
fn SlotIf(
cond: MaybeSignal<bool>,
cond: Signal<bool>,
then: Then,
#[prop(default=vec![])] else_if: Vec<ElseIf>,
#[prop(optional)] fallback: Option<Fallback>,
Expand All @@ -43,9 +43,9 @@ fn SlotIf(
#[component]
pub fn App() -> impl IntoView {
let (count, set_count) = signal(0);
let is_even = MaybeSignal::derive(move || count.get() % 2 == 0);
let is_div5 = MaybeSignal::derive(move || count.get() % 5 == 0);
let is_div7 = MaybeSignal::derive(move || count.get() % 7 == 0);
let is_even = Signal::derive(move || count.get() % 2 == 0);
let is_div5 = Signal::derive(move || count.get() % 5 == 0);
let is_div7 = Signal::derive(move || count.get() % 7 == 0);

view! {
<button on:click=move |_| set_count.update(|value| *value += 1)>"+1"</button>
Expand Down
2 changes: 1 addition & 1 deletion examples/timer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub fn TimerDemo() -> impl IntoView {
pub fn use_interval<T, F>(interval_millis: T, f: F)
where
F: Fn() + Clone + 'static,
T: Into<MaybeSignal<u64>> + 'static,
T: Into<Signal<u64>> + 'static,
{
let interval_millis = interval_millis.into();
Effect::new(move |prev_handle: Option<IntervalHandle>| {
Expand Down
1 change: 1 addition & 0 deletions reactive_graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub mod owner;
#[cfg(feature = "serde")]
mod serde;
pub mod signal;
mod trait_options;
pub mod traits;
pub mod transition;
pub mod wrappers;
Expand Down
13 changes: 9 additions & 4 deletions reactive_graph/src/nightly.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[allow(deprecated)]
use crate::wrappers::read::{MaybeProp, MaybeSignal};
use crate::{
computed::{ArcMemo, Memo},
owner::Storage,
Expand All @@ -7,7 +9,7 @@ use crate::{
},
traits::{Get, Set},
wrappers::{
read::{ArcSignal, MaybeProp, MaybeSignal, Signal},
read::{ArcSignal, Signal, SignalTypes},
write::SignalSetter,
},
};
Expand Down Expand Up @@ -112,7 +114,8 @@ macro_rules! impl_get_fn_traits_get_arena {
($($ty:ident),*) => {
$(
#[cfg(feature = "nightly")]
impl<T, S> FnOnce<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> {
#[allow(deprecated)]
impl<T, S> FnOnce<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>> {
type Output = <Self as Get>::Value;

#[inline(always)]
Expand All @@ -122,15 +125,17 @@ macro_rules! impl_get_fn_traits_get_arena {
}

#[cfg(feature = "nightly")]
impl<T, S> FnMut<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> {
#[allow(deprecated)]
impl<T, S> FnMut<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>> {
#[inline(always)]
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
}
}

#[cfg(feature = "nightly")]
impl<T, S> Fn<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> {
#[allow(deprecated)]
impl<T, S> Fn<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>> {
#[inline(always)]
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()
Expand Down
18 changes: 8 additions & 10 deletions reactive_graph/src/serde.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#[allow(deprecated)]
use crate::wrappers::read::{MaybeProp, MaybeSignal};
use crate::{
computed::{ArcMemo, Memo},
owner::Storage,
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
traits::With,
wrappers::read::{MaybeProp, MaybeSignal, Signal, SignalTypes},
wrappers::read::{Signal, SignalTypes},
};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -73,6 +75,7 @@ impl<T: Serialize + 'static, St: Storage<T>> Serialize for ArcMemo<T, St> {
}
}

#[allow(deprecated)]
impl<T, St> Serialize for MaybeSignal<T, St>
where
T: Clone + Send + Sync + Serialize,
Expand All @@ -86,6 +89,7 @@ where
}
}

#[allow(deprecated)]
impl<T, St> Serialize for MaybeProp<T, St>
where
T: Send + Sync + Serialize,
Expand All @@ -96,15 +100,8 @@ where
S: serde::Serializer,
{
match &self.0 {
None | Some(MaybeSignal::Static(None)) => {
None::<T>.serialize(serializer)
}
Some(MaybeSignal::Static(Some(value))) => {
value.serialize(serializer)
}
Some(MaybeSignal::Dynamic(signal)) => {
signal.with(|value| value.serialize(serializer))
}
None => None::<T>.serialize(serializer),
Some(signal) => signal.with(|value| value.serialize(serializer)),
}
}
}
Expand Down Expand Up @@ -146,6 +143,7 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for ArcRwSignal<T> {
}
}

#[allow(deprecated)]
impl<'de, T: Deserialize<'de>, St> Deserialize<'de> for MaybeSignal<T, St>
where
St: Storage<T>,
Expand Down
238 changes: 238 additions & 0 deletions reactive_graph/src/trait_options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
use crate::{
traits::{
DefinedAt, Get, GetUntracked, Read, ReadUntracked, Track, With,
WithUntracked,
},
unwrap_signal,
};
use std::panic::Location;

impl<T> DefinedAt for Option<T>
where
T: DefinedAt,
{
fn defined_at(&self) -> Option<&'static Location<'static>> {
self.as_ref().map(DefinedAt::defined_at).unwrap_or(None)
}
}

impl<T> Track for Option<T>
where
T: Track,
{
fn track(&self) {
if let Some(signal) = self {
signal.track();
}
}
}

/// An alternative [`ReadUntracked`](crate) trait that works with `Option<Readable>` types.
pub trait ReadUntrackedOptional: Sized + DefinedAt {
/// The guard type that will be returned, which can be dereferenced to the value.
type Value;

/// Returns the guard, or `None` if the signal has already been disposed.
#[track_caller]
fn try_read_untracked(&self) -> Option<Self::Value>;

/// Returns the guard.
///
/// # Panics
/// Panics if you try to access a signal that has been disposed.
#[track_caller]
fn read_untracked(&self) -> Self::Value {
self.try_read_untracked()
.unwrap_or_else(unwrap_signal!(self))
}
}

impl<T> ReadUntrackedOptional for Option<T>
where
Self: DefinedAt,
T: ReadUntracked,
{
type Value = Option<<T as ReadUntracked>::Value>;

fn try_read_untracked(&self) -> Option<Self::Value> {
Some(if let Some(signal) = self {
Some(signal.try_read_untracked()?)
} else {
None
})
}
}

/// An alternative [`Read`](crate) trait that works with `Option<Readable>` types.
pub trait ReadOptional: DefinedAt {
/// The guard type that will be returned, which can be dereferenced to the value.
type Value;

/// Subscribes to the signal, and returns the guard, or `None` if the signal has already been disposed.
#[track_caller]
fn try_read(&self) -> Option<Self::Value>;

/// Subscribes to the signal, and returns the guard.
///
/// # Panics
/// Panics if you try to access a signal that has been disposed.
#[track_caller]
fn read(&self) -> Self::Value {
self.try_read().unwrap_or_else(unwrap_signal!(self))
}
}

impl<T> ReadOptional for Option<T>
where
Self: DefinedAt,
T: Read,
{
type Value = Option<<T as Read>::Value>;

fn try_read(&self) -> Option<Self::Value> {
Some(if let Some(readable) = self {
Some(readable.try_read()?)
} else {
None
})
}
}

/// An alternative [`WithUntracked`](crate) trait that works with `Option<Withable>` types.
pub trait WithUntrackedOptional: DefinedAt {
/// The type of the value contained in the signal.
type Value: ?Sized;

/// Applies the closure to the value, and returns the result,
/// or `None` if the signal has already been disposed.
#[track_caller]
fn try_with_untracked<U>(
&self,
fun: impl FnOnce(Option<&Self::Value>) -> U,
) -> Option<U>;

/// Applies the closure to the value, and returns the result.
///
/// # Panics
/// Panics if you try to access a signal that has been disposed.
#[track_caller]
fn with_untracked<U>(
&self,
fun: impl FnOnce(Option<&Self::Value>) -> U,
) -> U {
self.try_with_untracked(fun)
.unwrap_or_else(unwrap_signal!(self))
}
}

impl<T> WithUntrackedOptional for Option<T>
where
Self: DefinedAt,
T: WithUntracked,
<T as WithUntracked>::Value: Sized,
{
type Value = <T as WithUntracked>::Value;

fn try_with_untracked<U>(
&self,
fun: impl FnOnce(Option<&Self::Value>) -> U,
) -> Option<U> {
if let Some(signal) = self {
Some(signal.try_with_untracked(|val| fun(Some(val)))?)
} else {
Some(fun(None))
}
}
}

/// An alternative [`With`](crate) trait that works with `Option<Withable>` types.
pub trait WithOptional: DefinedAt {
/// The type of the value contained in the signal.
type Value: ?Sized;

/// Subscribes to the signal, applies the closure to the value, and returns the result,
/// or `None` if the signal has already been disposed.
#[track_caller]
fn try_with<U>(
&self,
fun: impl FnOnce(Option<&Self::Value>) -> U,
) -> Option<U>;

/// Subscribes to the signal, applies the closure to the value, and returns the result.
///
/// # Panics
/// Panics if you try to access a signal that has been disposed.
#[track_caller]
fn with<U>(&self, fun: impl FnOnce(Option<&Self::Value>) -> U) -> U {
self.try_with(fun).unwrap_or_else(unwrap_signal!(self))
}
}

impl<T> WithOptional for Option<T>
where
Self: DefinedAt,
T: With,
<T as With>::Value: Sized,
{
type Value = <T as With>::Value;

fn try_with<U>(
&self,
fun: impl FnOnce(Option<&Self::Value>) -> U,
) -> Option<U> {
if let Some(signal) = self {
Some(signal.try_with(|val| fun(Some(val)))?)
} else {
Some(fun(None))
}
}
}

impl<T> GetUntracked for Option<T>
where
Self: DefinedAt,
T: GetUntracked,
{
type Value = Option<<T as GetUntracked>::Value>;

fn try_get_untracked(&self) -> Option<Self::Value> {
Some(if let Some(signal) = self {
Some(signal.try_get_untracked()?)
} else {
None
})
}
}

impl<T> Get for Option<T>
where
Self: DefinedAt,
T: Get,
{
type Value = Option<<T as Get>::Value>;

fn try_get(&self) -> Option<Self::Value> {
Some(if let Some(signal) = self {
Some(signal.try_get()?)
} else {
None
})
}
}

/// Helper trait to implement flatten() on Option<&Option<T>>.
pub trait FlattenOptionRefOption {
/// The type of the value contained in the double option.
type Value;

/// Converts from `Option<&Option<T>>` to `Option<&T>`.
fn flatten(&self) -> Option<&Self::Value>;
}

impl<'a, T> FlattenOptionRefOption for Option<&'a Option<T>> {
type Value = T;

fn flatten(&self) -> Option<&'a T> {
self.map(Option::as_ref).flatten()
}
}
Loading

0 comments on commit 396327b

Please sign in to comment.