1use std::borrow::Cow;
2use std::rc::Rc;
34use crate::*;
56/// Represents a value that can be either static or dynamic.
7///
8/// This is useful for cases where you want to accept a value that can be either static or dynamic,
9/// such as in component props.
10///
11/// A [`MaybeDyn`] value can be created from a static value or a closure that returns the value by
12/// using the [`From`] trait.
13///
14/// # Creating a `MaybeDyn`
15///
16/// You can create a `MaybeDyn` from a static value by using the [`MaybeDyn::Static`] variant.
17/// However, most of the times, you probably want to use the implementation of the `From<U>` trait
18/// for `MaybeDyn<T>`.
19///
20/// This trait is already implemented globally for signals and closures that return `T`. However,
21/// we cannot provide a blanket implementation for all types `T` to convert into `MaybeDyn<T>`
22/// because of specialization. Instead, we can only implement it for specific types.
23#[derive(Clone)]
24pub enum MaybeDyn<T>
25where
26T: Into<Self> + 'static,
27{
28/// A static value.
29Static(T),
30/// A dynamic value backed by a signal.
31Signal(ReadSignal<T>),
32/// A derived dynamic value.
33Derived(Rc<dyn Fn() -> Self>),
34}
3536impl<T: Into<Self> + 'static> MaybeDyn<T> {
37/// Get the value by consuming itself. Unlike [`get_clone`](Self::get_clone), this method avoids
38 /// a clone if we are just storing a static value.
39pub fn evaluate(self) -> T
40where
41T: Clone,
42 {
43match self {
44Self::Static(value) => value,
45Self::Signal(signal) => signal.get_clone(),
46Self::Derived(f) => f().evaluate(),
47 }
48 }
4950/// Get the value by copying it.
51 ///
52 /// If the type does not implement [`Copy`], consider using [`get_clone`](Self::get_clone)
53 /// instead.
54pub fn get(&self) -> T
55where
56T: Copy,
57 {
58match self {
59Self::Static(value) => *value,
60Self::Signal(value) => value.get(),
61Self::Derived(f) => f().evaluate(),
62 }
63 }
6465/// Get the value by cloning it.
66 ///
67 /// If the type implements [`Copy`], consider using [`get`](Self::get) instead.
68pub fn get_clone(&self) -> T
69where
70T: Clone,
71 {
72match self {
73Self::Static(value) => value.clone(),
74Self::Signal(value) => value.get_clone(),
75Self::Derived(f) => f().evaluate(),
76 }
77 }
7879/// Track the reactive dependencies, if it is dynamic.
80pub fn track(&self) {
81match self {
82Self::Static(_) => {}
83Self::Signal(signal) => signal.track(),
84Self::Derived(f) => f().track(),
85 }
86 }
8788/// Tries to get the value statically or returns `None` if value is dynamic.
89pub fn as_static(&self) -> Option<&T> {
90match self {
91Self::Static(value) => Some(value),
92_ => None,
93 }
94 }
95}
9697impl<T: Into<Self>, U: Into<MaybeDyn<T>> + Clone> From<ReadSignal<U>> for MaybeDyn<T> {
98fn from(val: ReadSignal<U>) -> Self {
99// Check if U == T, i.e. ReadSignal<U> is actually a ReadSignal<T>.
100 //
101 // If so, we use a trick to convert the generic type to the concrete type. This should be
102 // optimized out by the compiler to be zero-cost.
103if let Some(val) =
104 (&mut Some(val) as &mut dyn std::any::Any).downcast_mut::<Option<ReadSignal<T>>>()
105 {
106 MaybeDyn::Signal(val.unwrap())
107 } else {
108 MaybeDyn::Derived(Rc::new(move || val.get_clone().into()))
109 }
110 }
111}
112113impl<T: Into<Self>, U: Into<MaybeDyn<T>> + Clone> From<Signal<U>> for MaybeDyn<T> {
114fn from(val: Signal<U>) -> Self {
115Self::from(*val)
116 }
117}
118119// TODO: add #[diagnostic::do_not_recommend] when it is stablised.
120impl<F, U, T: Into<Self>> From<F> for MaybeDyn<T>
121where
122F: Fn() -> U + 'static,
123 U: Into<MaybeDyn<T>>,
124{
125fn from(f: F) -> Self {
126 MaybeDyn::Derived(Rc::new(move || f().into()))
127 }
128}
129130/// A macro that makes it easy to write implementations for `Into<MaybeDyn<T>>`.
131///
132/// Because of Rust orphan rules, you can only implement `Into<MaybeDyn<T>>` for types that are
133/// defined in the current crate. To work around this limitation, the newtype pattern can be used.
134///
135/// # Example
136///
137/// ```
138/// # use sycamore_reactive::*;
139///
140/// struct MyType;
141///
142/// struct OtherType;
143///
144/// impl From<OtherType> for MyType {
145/// fn from(_: OtherType) -> Self {
146/// todo!();
147/// }
148/// }
149///
150/// // You can also list additional types that can be converted to `MaybeDyn<MyType>`.
151/// impl_into_maybe_dyn!(MyType; OtherType);
152/// ```
153#[macro_export]
154macro_rules! impl_into_maybe_dyn {
155 ($ty:ty $(; $($from:ty),*)?) => {
156impl From<$ty> for $crate::MaybeDyn<$ty> {
157fn from(val: $ty) -> Self {
158 MaybeDyn::Static(val)
159 }
160 }
161162$crate::impl_into_maybe_dyn_with_convert!($ty; Into::into $(; $($from),*)?);
163 };
164}
165166/// Create `From<U>` implementations for `MaybeDyn<T>` for a list of types.
167///
168/// Usually, you would use the [`impl_into_maybe_dyn!`] macro instead of this macro.
169#[macro_export]
170macro_rules! impl_into_maybe_dyn_with_convert {
171 ($ty:ty; $convert:expr $(; $($from:ty),*)?) => {
172 $(
173 $(
174impl From<$from> for $crate::MaybeDyn<$ty> {
175fn from(val: $from) -> Self {
176 MaybeDyn::Static($convert(val))
177 }
178 }
179 )*
180 )?
181};
182}
183184impl_into_maybe_dyn!(Cow<'static, str>; &'static str, String);
185impl_into_maybe_dyn_with_convert!(
186Option<Cow<'static, str>>; |x| Some(Into::into(x));
187 Cow<'static, str>, &'static str, String
188);
189impl_into_maybe_dyn_with_convert!(
190Option<Cow<'static, str>>; |x| Option::map(x, Into::into);
191Option<&'static str>, Option<String>
192);
193194impl_into_maybe_dyn!(bool);
195196impl_into_maybe_dyn!(f32);
197impl_into_maybe_dyn!(f64);
198199impl_into_maybe_dyn!(i8);
200impl_into_maybe_dyn!(i16);
201impl_into_maybe_dyn!(i32);
202impl_into_maybe_dyn!(i64);
203impl_into_maybe_dyn!(i128);
204impl_into_maybe_dyn!(isize);
205impl_into_maybe_dyn!(u8);
206impl_into_maybe_dyn!(u16);
207impl_into_maybe_dyn!(u32);
208impl_into_maybe_dyn!(u64);
209impl_into_maybe_dyn!(u128);
210impl_into_maybe_dyn!(usize);
211212impl<T> From<Option<T>> for MaybeDyn<Option<T>> {
213fn from(val: Option<T>) -> Self {
214 MaybeDyn::Static(val)
215 }
216}
217218impl<T> From<Vec<T>> for MaybeDyn<Vec<T>> {
219fn from(val: Vec<T>) -> Self {
220 MaybeDyn::Static(val)
221 }
222}
223224#[cfg(feature = "wasm-bindgen")]
225impl_into_maybe_dyn!(
226 wasm_bindgen::JsValue;
227&'static str, String, bool, f32, f64, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
228);
229230#[cfg(test)]
231mod tests {
232use super::*;
233234#[test]
235fn maybe_dyn_static() {
236let value = MaybeDyn::<i32>::from(123);
237assert!(value.as_static().is_some());
238assert_eq!(value.get(), 123);
239assert_eq!(value.get_clone(), 123);
240assert_eq!(value.evaluate(), 123);
241 }
242243#[test]
244fn maybe_dyn_signal() {
245let _ = create_root(move || {
246let signal = create_signal(123);
247let value = MaybeDyn::<i32>::from(signal);
248assert!(matches!(value, MaybeDyn::Signal(_)));
249250assert_eq!(value.get(), 123);
251assert_eq!(value.get_clone(), 123);
252assert_eq!(value.evaluate(), 123);
253 });
254 }
255256#[test]
257fn maybe_dyn_signal_from() {
258let _ = create_root(move || {
259let signal = create_signal("abc");
260let value = MaybeDyn::<Cow<'static, str>>::from(signal);
261assert!(matches!(value, MaybeDyn::Derived(_)));
262263assert_eq!(value.get_clone(), "abc");
264assert_eq!(value.evaluate(), "abc");
265 });
266 }
267268#[test]
269fn maybe_dyn_derived() {
270let value = MaybeDyn::<i32>::from(|| 123);
271assert!(value.as_static().is_none());
272assert_eq!(value.get(), 123);
273assert_eq!(value.get_clone(), 123);
274assert_eq!(value.evaluate(), 123);
275 }
276}