hydro_lang/live_collections/
sliced.rs

1//! Utilities for transforming live collections via slicing.
2
3use super::boundedness::{Bounded, Unbounded};
4use crate::live_collections::boundedness::Boundedness;
5use crate::live_collections::keyed_singleton::BoundedValue;
6use crate::live_collections::stream::{Ordering, Retries};
7use crate::location::{Location, NoTick, Tick};
8use crate::nondet::NonDet;
9
10#[doc(hidden)]
11pub fn __sliced_wrap_invoke<A, B, O: Unslicable>(
12    a: A,
13    b: B,
14    f: impl FnOnce(A, B) -> O,
15) -> O::Unsliced {
16    let o_slice = f(a, b);
17    o_slice.unslice()
18}
19
20#[doc(hidden)]
21#[macro_export]
22macro_rules! __sliced_parse_uses__ {
23    // Parse immutable use statements: let name = use(expr, nondet);
24    (
25        @uses [$($uses:tt)*]
26        @states [$($states:tt)*]
27        let $name:ident = use $(::$style:ident)?($expr:expr, $nondet:expr); $($rest:tt)*
28    ) => {
29        $crate::__sliced_parse_uses__!(
30            @uses [$($uses)* { $name, ($($style)?), $expr, $nondet }]
31            @states [$($states)*]
32            $($rest)*
33        )
34    };
35
36    // Parse mutable state statements: let mut name = use::style::<Type>(args);
37    (
38        @uses [$($uses:tt)*]
39        @states [$($states:tt)*]
40        let mut $name:ident = use ::$style:ident $(::<$ty:ty>)? ($($args:expr)?); $($rest:tt)*
41    ) => {
42        $crate::__sliced_parse_uses__!(
43            @uses [$($uses)*]
44            @states [$($states)* { $name, $style, (($($ty)?), ($($args)?)) }]
45            $($rest)*
46        )
47    };
48
49    // Terminal case: no uses, only states
50    (
51        @uses []
52        @states [$({ $state_name:ident, $state_style:ident, $state_arg:tt })+]
53        $($body:tt)*
54    ) => {
55        {
56            // We need at least one use to get a tick, so panic if there are none
57            compile_error!("sliced! requires at least one `let name = use(...)` statement to determine the tick")
58        }
59    };
60
61    // Terminal case: uses with optional states
62    (
63        @uses [{ $first_name:ident, ($($first_style:ident)?), $first:expr, $nondet_first:expr } $({ $rest_name:ident, ($($rest_style:ident)?), $rest:expr, $nondet_expl:expr })*]
64        @states [$({ $state_name:ident, $state_style:ident, (($($state_ty:ty)?), ($($state_arg:expr)?)) })*]
65        $($body:tt)*
66    ) => {
67        {
68            let _ = $nondet_first;
69            $(let _ = $nondet_expl;)*
70
71            let __styled = (
72                $($crate::live_collections::sliced::style::$first_style)?($first),
73                $($($crate::live_collections::sliced::style::$rest_style)?($rest),)*
74            );
75
76            let __tick = $crate::live_collections::sliced::Slicable::preferred_tick(&__styled).unwrap_or_else(|| $crate::live_collections::sliced::Slicable::get_location(&__styled.0).tick());
77            let __backtraces = {
78                use $crate::compile::ir::backtrace::__macro_get_backtrace;
79                (
80                    $crate::macro_support::copy_span::copy_span!($first, {
81                        __macro_get_backtrace(1)
82                    }),
83                    $($crate::macro_support::copy_span::copy_span!($rest, {
84                        __macro_get_backtrace(1)
85                    }),)*
86                )
87            };
88            let __sliced = $crate::live_collections::sliced::Slicable::slice(__styled, &__tick, __backtraces, $nondet_first);
89            let (
90                $first_name,
91                $($rest_name,)*
92            ) = __sliced;
93
94            // Create all cycles and pack handles/values into tuples
95            let (__handles, __states) = $crate::live_collections::sliced::unzip_cycles((
96                $($crate::live_collections::sliced::style::$state_style$(::<$state_ty, _>)?(& __tick, $($state_arg)?),)*
97            ));
98
99            // Unpack mutable state values
100            let (
101                $(mut $state_name,)*
102            ) = __states;
103
104            // Execute the body
105            let __body_result = {
106                $($body)*
107            };
108
109            // Re-pack the final state values and complete cycles
110            let __final_states = (
111                $($state_name,)*
112            );
113            $crate::live_collections::sliced::complete_cycles(__handles, __final_states);
114
115            // Unslice the result
116            $crate::live_collections::sliced::Unslicable::unslice(__body_result)
117        }
118    };
119}
120
121#[macro_export]
122/// Transforms a live collection with a computation relying on a slice of another live collection.
123/// This is useful for reading a snapshot of an asynchronously updated collection while processing another
124/// collection, such as joining a stream with the latest values from a singleton.
125///
126/// # Syntax
127/// The `sliced!` macro takes in a closure-like syntax specifying the live collections to be sliced
128/// and the body of the transformation. Each `use` statement indicates a live collection to be sliced,
129/// along with a non-determinism explanation. Optionally, a style can be specified to control how the
130/// live collection is sliced (e.g., atomically). All `use` statements must appear before the body.
131///
132/// ```rust,ignore
133/// let stream = sliced! {
134///     let name1 = use(collection1, nondet!(/** explanation */));
135///     let name2 = use::atomic(collection2, nondet!(/** explanation */));
136///
137///     // arbitrary statements can follow
138///     let intermediate = name1.map(...);
139///     intermediate.cross_singleton(name2)
140/// };
141/// ```
142///
143/// # Stateful Computations
144/// The `sliced!` macro also supports stateful computations across iterations using `let mut` bindings
145/// with `use::state` or `use::state_null`. These create cycles that persist values between iterations.
146///
147/// - `use::state(|l| initial)`: Creates a cycle with an initial value. The closure receives
148///   the slice location and returns the initial state for the first iteration.
149/// - `use::state_null::<Type>()`: Creates a cycle that starts as null/empty on the first iteration.
150///
151/// The mutable binding can be reassigned in the body, and the final value will be passed to the
152/// next iteration.
153///
154/// ```rust,ignore
155/// let counter_stream = sliced! {
156///     let batch = use(input_stream, nondet!(/** explanation */));
157///     let mut counter = use::state(|l| l.singleton(q!(0)));
158///
159///     // Increment counter by the number of items in this batch
160///     let new_count = counter.clone().zip(batch.count())
161///         .map(q!(|(old, add)| old + add));
162///     counter = new_count.clone();
163///     new_count.into_stream()
164/// };
165/// ```
166macro_rules! __sliced__ {
167    ($($tt:tt)*) => {
168        $crate::__sliced_parse_uses__!(
169            @uses []
170            @states []
171            $($tt)*
172        )
173    };
174}
175
176pub use crate::__sliced__ as sliced;
177
178/// Marks this live collection as atomically-yielded, which means that the output outside
179/// `sliced` will be at an atomic location that is synchronous with respect to the body
180/// of the slice.
181pub fn yield_atomic<T>(t: T) -> style::Atomic<T> {
182    style::Atomic(t)
183}
184
185/// Styles for use with the `sliced!` macro.
186pub mod style {
187    use super::Slicable;
188    #[cfg(stageleft_runtime)]
189    use crate::forward_handle::{CycleCollection, CycleCollectionWithInitial};
190    use crate::forward_handle::{TickCycle, TickCycleHandle};
191    use crate::live_collections::boundedness::{Bounded, Unbounded};
192    use crate::live_collections::keyed_singleton::BoundedValue;
193    use crate::live_collections::sliced::Unslicable;
194    use crate::live_collections::stream::{Ordering, Retries, Stream};
195    use crate::location::tick::DeferTick;
196    use crate::location::{Location, NoTick, Tick};
197    use crate::nondet::NonDet;
198
199    /// Marks a live collection to be treated atomically during slicing.
200    pub struct Atomic<T>(pub T);
201
202    /// Wraps a live collection to be treated atomically during slicing.
203    pub fn atomic<T>(t: T) -> Atomic<T> {
204        Atomic(t)
205    }
206
207    /// Creates a stateful cycle with an initial value for use in `sliced!`.
208    ///
209    /// The initial value is computed from a closure that receives the location
210    /// for the body of the slice.
211    ///
212    /// The initial value is used on the first iteration, and subsequent iterations receive
213    /// the value assigned to the mutable binding at the end of the previous iteration.
214    #[cfg(stageleft_runtime)]
215    #[expect(
216        private_bounds,
217        reason = "only Hydro collections can implement CycleCollectionWithInitial"
218    )]
219    pub fn state<
220        'a,
221        S: CycleCollectionWithInitial<'a, TickCycle, Location = Tick<L>>,
222        L: Location<'a> + NoTick,
223    >(
224        tick: &Tick<L>,
225        initial_fn: impl FnOnce(&Tick<L>) -> S,
226    ) -> (TickCycleHandle<'a, S>, S) {
227        let initial = initial_fn(tick);
228        tick.cycle_with_initial(initial)
229    }
230
231    /// Creates a stateful cycle without an initial value for use in `sliced!`.
232    ///
233    /// On the first iteration, the state will be null/empty. Subsequent iterations receive
234    /// the value assigned to the mutable binding at the end of the previous iteration.
235    #[cfg(stageleft_runtime)]
236    #[expect(
237        private_bounds,
238        reason = "only Hydro collections can implement CycleCollection"
239    )]
240    pub fn state_null<
241        'a,
242        S: CycleCollection<'a, TickCycle, Location = Tick<L>> + DeferTick,
243        L: Location<'a> + NoTick,
244    >(
245        tick: &Tick<L>,
246    ) -> (TickCycleHandle<'a, S>, S) {
247        tick.cycle::<S>()
248    }
249
250    impl<'a, T, L: Location<'a> + NoTick, O: Ordering, R: Retries> Slicable<'a, L>
251        for Atomic<Stream<T, crate::location::Atomic<L>, Unbounded, O, R>>
252    {
253        type Slice = Stream<T, Tick<L>, Bounded, O, R>;
254        type Backtrace = crate::compile::ir::backtrace::Backtrace;
255
256        fn preferred_tick(&self) -> Option<Tick<L>> {
257            Some(self.0.location().tick.clone())
258        }
259
260        fn get_location(&self) -> &L {
261            panic!("Atomic location has no accessible inner location")
262        }
263
264        fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
265            assert_eq!(
266                self.0.location().tick.id(),
267                tick.id(),
268                "Mismatched tick for atomic slicing"
269            );
270
271            let out = self.0.batch_atomic(nondet);
272            out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
273            out
274        }
275    }
276
277    impl<'a, T, L: Location<'a> + NoTick, O: Ordering, R: Retries> Unslicable
278        for Atomic<Stream<T, Tick<L>, Bounded, O, R>>
279    {
280        type Unsliced = Stream<T, crate::location::Atomic<L>, Unbounded, O, R>;
281
282        fn unslice(self) -> Self::Unsliced {
283            self.0.all_ticks_atomic()
284        }
285    }
286
287    impl<'a, T, L: Location<'a> + NoTick> Slicable<'a, L>
288        for Atomic<crate::live_collections::Singleton<T, crate::location::Atomic<L>, Unbounded>>
289    {
290        type Slice = crate::live_collections::Singleton<T, Tick<L>, Bounded>;
291        type Backtrace = crate::compile::ir::backtrace::Backtrace;
292
293        fn preferred_tick(&self) -> Option<Tick<L>> {
294            Some(self.0.location().tick.clone())
295        }
296
297        fn get_location(&self) -> &L {
298            panic!("Atomic location has no accessible inner location")
299        }
300
301        fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
302            assert_eq!(
303                self.0.location().tick.id(),
304                tick.id(),
305                "Mismatched tick for atomic slicing"
306            );
307
308            let out = self.0.snapshot_atomic(nondet);
309            out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
310            out
311        }
312    }
313
314    impl<'a, T, L: Location<'a> + NoTick> Unslicable
315        for Atomic<crate::live_collections::Singleton<T, Tick<L>, Bounded>>
316    {
317        type Unsliced =
318            crate::live_collections::Singleton<T, crate::location::Atomic<L>, Unbounded>;
319
320        fn unslice(self) -> Self::Unsliced {
321            self.0.latest_atomic()
322        }
323    }
324
325    impl<'a, T, L: Location<'a> + NoTick> Slicable<'a, L>
326        for Atomic<crate::live_collections::Optional<T, crate::location::Atomic<L>, Unbounded>>
327    {
328        type Slice = crate::live_collections::Optional<T, Tick<L>, Bounded>;
329        type Backtrace = crate::compile::ir::backtrace::Backtrace;
330
331        fn preferred_tick(&self) -> Option<Tick<L>> {
332            Some(self.0.location().tick.clone())
333        }
334
335        fn get_location(&self) -> &L {
336            panic!("Atomic location has no accessible inner location")
337        }
338
339        fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
340            assert_eq!(
341                self.0.location().tick.id(),
342                tick.id(),
343                "Mismatched tick for atomic slicing"
344            );
345
346            let out = self.0.snapshot_atomic(nondet);
347            out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
348            out
349        }
350    }
351
352    impl<'a, T, L: Location<'a> + NoTick> Unslicable
353        for Atomic<crate::live_collections::Optional<T, Tick<L>, Bounded>>
354    {
355        type Unsliced = crate::live_collections::Optional<T, crate::location::Atomic<L>, Unbounded>;
356
357        fn unslice(self) -> Self::Unsliced {
358            self.0.latest_atomic()
359        }
360    }
361
362    impl<'a, K, V, L: Location<'a> + NoTick> Slicable<'a, L>
363        for Atomic<
364            crate::live_collections::KeyedSingleton<K, V, crate::location::Atomic<L>, Unbounded>,
365        >
366    {
367        type Slice = crate::live_collections::KeyedSingleton<K, V, Tick<L>, Bounded>;
368        type Backtrace = crate::compile::ir::backtrace::Backtrace;
369
370        fn preferred_tick(&self) -> Option<Tick<L>> {
371            Some(self.0.location().tick.clone())
372        }
373
374        fn get_location(&self) -> &L {
375            panic!("Atomic location has no accessible inner location")
376        }
377
378        fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
379            assert_eq!(
380                self.0.location().tick.id(),
381                tick.id(),
382                "Mismatched tick for atomic slicing"
383            );
384
385            let out = self.0.snapshot_atomic(nondet);
386            out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
387            out
388        }
389    }
390
391    impl<'a, K, V, L: Location<'a> + NoTick> Slicable<'a, L>
392        for Atomic<
393            crate::live_collections::KeyedSingleton<K, V, crate::location::Atomic<L>, BoundedValue>,
394        >
395    {
396        type Slice = crate::live_collections::KeyedSingleton<K, V, Tick<L>, Bounded>;
397        type Backtrace = crate::compile::ir::backtrace::Backtrace;
398
399        fn preferred_tick(&self) -> Option<Tick<L>> {
400            Some(self.0.location().tick.clone())
401        }
402
403        fn get_location(&self) -> &L {
404            panic!("Atomic location has no accessible inner location")
405        }
406
407        fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
408            assert_eq!(
409                self.0.location().tick.id(),
410                tick.id(),
411                "Mismatched tick for atomic slicing"
412            );
413
414            let out = self.0.batch_atomic(nondet);
415            out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
416            out
417        }
418    }
419}
420
421/// A trait for live collections which can be sliced into bounded versions at a tick.
422pub trait Slicable<'a, L: Location<'a>> {
423    /// The sliced version of this live collection.
424    type Slice;
425
426    /// The type of backtrace associated with this slice.
427    type Backtrace;
428
429    /// Gets the preferred tick to slice at. Used for atomic slicing.
430    fn preferred_tick(&self) -> Option<Tick<L>>;
431
432    /// Gets the location associated with this live collection.
433    fn get_location(&self) -> &L;
434
435    /// Slices this live collection at the given tick.
436    ///
437    /// # Non-Determinism
438    /// Slicing a live collection may involve non-determinism, such as choosing which messages
439    /// to include in a batch. The provided `nondet` parameter should be used to explain the impact
440    /// of this non-determinism on the program's behavior.
441    fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice;
442}
443
444/// A trait for live collections which can be yielded out of a slice back into their original form.
445pub trait Unslicable {
446    /// The unsliced version of this live collection.
447    type Unsliced;
448
449    /// Unslices a sliced live collection back into its original form.
450    fn unslice(self) -> Self::Unsliced;
451}
452
453/// A trait for unzipping a tuple of (handle, state) pairs into separate tuples.
454#[doc(hidden)]
455pub trait UnzipCycles {
456    /// The tuple of cycle handles.
457    type Handles;
458    /// The tuple of state values.
459    type States;
460
461    /// Unzips the cycles into handles and states.
462    fn unzip(self) -> (Self::Handles, Self::States);
463}
464
465/// Unzips a tuple of cycles into handles and states.
466#[doc(hidden)]
467pub fn unzip_cycles<T: UnzipCycles>(cycles: T) -> (T::Handles, T::States) {
468    cycles.unzip()
469}
470
471/// A trait for completing a tuple of cycle handles with their final state values.
472#[doc(hidden)]
473pub trait CompleteCycles<States> {
474    /// Completes all cycles with the provided state values.
475    fn complete(self, states: States);
476}
477
478/// Completes a tuple of cycle handles with their final state values.
479#[doc(hidden)]
480pub fn complete_cycles<H: CompleteCycles<S>, S>(handles: H, states: S) {
481    handles.complete(states);
482}
483
484impl<'a, L: Location<'a>> Slicable<'a, L> for () {
485    type Slice = ();
486    type Backtrace = ();
487
488    fn get_location(&self) -> &L {
489        unreachable!()
490    }
491
492    fn preferred_tick(&self) -> Option<Tick<L>> {
493        None
494    }
495
496    fn slice(self, _tick: &Tick<L>, __backtrace: Self::Backtrace, _nondet: NonDet) -> Self::Slice {}
497}
498
499impl Unslicable for () {
500    type Unsliced = ();
501
502    fn unslice(self) -> Self::Unsliced {}
503}
504
505macro_rules! impl_slicable_for_tuple {
506    ($($T:ident, $T_bt:ident, $idx:tt),+) => {
507        impl<'a, L: Location<'a>, $($T: Slicable<'a, L>),+> Slicable<'a, L> for ($($T,)+) {
508            type Slice = ($($T::Slice,)+);
509            type Backtrace = ($($T::Backtrace,)+);
510
511            fn get_location(&self) -> &L {
512                self.0.get_location()
513            }
514
515            fn preferred_tick(&self) -> Option<Tick<L>> {
516                let mut preferred: Option<Tick<L>> = None;
517                $(
518                    if let Some(tick) = self.$idx.preferred_tick() {
519                        preferred = Some(match preferred {
520                            Some(current) => {
521                                if $crate::location::Location::id(&current) == $crate::location::Location::id(&tick) {
522                                    current
523                                } else {
524                                    panic!("Mismatched preferred ticks for sliced collections")
525                                }
526                            },
527                            None => tick,
528                        });
529                    }
530                )+
531                preferred
532            }
533
534            #[expect(non_snake_case, reason = "macro codegen")]
535            fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
536                let ($($T,)+) = self;
537                let ($($T_bt,)+) = backtrace;
538                ($($T.slice(tick, $T_bt, nondet),)+)
539            }
540        }
541
542        impl<$($T: Unslicable),+> Unslicable for ($($T,)+) {
543            type Unsliced = ($($T::Unsliced,)+);
544
545            #[expect(non_snake_case, reason = "macro codegen")]
546            fn unslice(self) -> Self::Unsliced {
547                let ($($T,)+) = self;
548                ($($T.unslice(),)+)
549            }
550        }
551    };
552}
553
554#[cfg(stageleft_runtime)]
555impl_slicable_for_tuple!(S1, S1_bt, 0);
556#[cfg(stageleft_runtime)]
557impl_slicable_for_tuple!(S1, S1_bt, 0, S2, S2_bt, 1);
558#[cfg(stageleft_runtime)]
559impl_slicable_for_tuple!(S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2);
560#[cfg(stageleft_runtime)]
561impl_slicable_for_tuple!(S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3);
562#[cfg(stageleft_runtime)]
563impl_slicable_for_tuple!(
564    S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3, S5, S5_bt, 4
565); // 5 slices ought to be enough for anyone
566
567macro_rules! impl_cycles_for_tuple {
568    ($($H:ident, $S:ident, $idx:tt),*) => {
569        impl<$($H, $S),*> UnzipCycles for ($(($H, $S),)*) {
570            type Handles = ($($H,)*);
571            type States = ($($S,)*);
572
573            #[expect(clippy::allow_attributes, reason = "macro codegen")]
574            #[allow(non_snake_case, reason = "macro codegen")]
575            fn unzip(self) -> (Self::Handles, Self::States) {
576                let ($($H,)*) = self;
577                (
578                    ($($H.0,)*),
579                    ($($H.1,)*),
580                )
581            }
582        }
583
584        impl<$($H: crate::forward_handle::CompleteCycle<$S>, $S),*> CompleteCycles<($($S,)*)> for ($($H,)*) {
585            #[expect(clippy::allow_attributes, reason = "macro codegen")]
586            #[allow(non_snake_case, reason = "macro codegen")]
587            fn complete(self, states: ($($S,)*)) {
588                let ($($H,)*) = self;
589                let ($($S,)*) = states;
590                $($H.complete_next_tick($S);)*
591            }
592        }
593    };
594}
595
596#[cfg(stageleft_runtime)]
597impl_cycles_for_tuple!();
598#[cfg(stageleft_runtime)]
599impl_cycles_for_tuple!(H1, S1, 0);
600#[cfg(stageleft_runtime)]
601impl_cycles_for_tuple!(H1, S1, 0, H2, S2, 1);
602#[cfg(stageleft_runtime)]
603impl_cycles_for_tuple!(H1, S1, 0, H2, S2, 1, H3, S3, 2);
604#[cfg(stageleft_runtime)]
605impl_cycles_for_tuple!(H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3);
606#[cfg(stageleft_runtime)]
607impl_cycles_for_tuple!(H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3, H5, S5, 4);
608
609impl<'a, T, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries> Slicable<'a, L>
610    for super::Stream<T, L, B, O, R>
611{
612    type Slice = super::Stream<T, Tick<L>, Bounded, O, R>;
613    type Backtrace = crate::compile::ir::backtrace::Backtrace;
614
615    fn get_location(&self) -> &L {
616        self.location()
617    }
618
619    fn preferred_tick(&self) -> Option<Tick<L>> {
620        None
621    }
622
623    fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
624        let out = self.batch(tick, nondet);
625        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
626        out
627    }
628}
629
630impl<'a, T, L: Location<'a>, O: Ordering, R: Retries> Unslicable
631    for super::Stream<T, Tick<L>, Bounded, O, R>
632{
633    type Unsliced = super::Stream<T, L, Unbounded, O, R>;
634
635    fn unslice(self) -> Self::Unsliced {
636        self.all_ticks()
637    }
638}
639
640impl<'a, T, L: Location<'a>, B: Boundedness> Slicable<'a, L> for super::Singleton<T, L, B> {
641    type Slice = super::Singleton<T, Tick<L>, Bounded>;
642    type Backtrace = crate::compile::ir::backtrace::Backtrace;
643
644    fn get_location(&self) -> &L {
645        self.location()
646    }
647
648    fn preferred_tick(&self) -> Option<Tick<L>> {
649        None
650    }
651
652    fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
653        let out = self.snapshot(tick, nondet);
654        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
655        out
656    }
657}
658
659impl<'a, T, L: Location<'a>> Unslicable for super::Singleton<T, Tick<L>, Bounded> {
660    type Unsliced = super::Singleton<T, L, Unbounded>;
661
662    fn unslice(self) -> Self::Unsliced {
663        self.latest()
664    }
665}
666
667impl<'a, T, L: Location<'a>> Slicable<'a, L> for super::Optional<T, L, Unbounded> {
668    type Slice = super::Optional<T, Tick<L>, Bounded>;
669    type Backtrace = crate::compile::ir::backtrace::Backtrace;
670
671    fn get_location(&self) -> &L {
672        self.location()
673    }
674
675    fn preferred_tick(&self) -> Option<Tick<L>> {
676        None
677    }
678
679    fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
680        let out = self.snapshot(tick, nondet);
681        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
682        out
683    }
684}
685
686impl<'a, T, L: Location<'a>> Unslicable for super::Optional<T, Tick<L>, Bounded> {
687    type Unsliced = super::Optional<T, L, Unbounded>;
688
689    fn unslice(self) -> Self::Unsliced {
690        self.latest()
691    }
692}
693
694impl<'a, K, V, L: Location<'a>, O: Ordering, R: Retries> Slicable<'a, L>
695    for super::KeyedStream<K, V, L, Unbounded, O, R>
696{
697    type Slice = super::KeyedStream<K, V, Tick<L>, Bounded, O, R>;
698    type Backtrace = crate::compile::ir::backtrace::Backtrace;
699
700    fn get_location(&self) -> &L {
701        self.location()
702    }
703
704    fn preferred_tick(&self) -> Option<Tick<L>> {
705        None
706    }
707
708    fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
709        let out = self.batch(tick, nondet);
710        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
711        out
712    }
713}
714
715impl<'a, K, V, L: Location<'a>, O: Ordering, R: Retries> Unslicable
716    for super::KeyedStream<K, V, Tick<L>, Bounded, O, R>
717{
718    type Unsliced = super::KeyedStream<K, V, L, Unbounded, O, R>;
719
720    fn unslice(self) -> Self::Unsliced {
721        self.all_ticks()
722    }
723}
724
725impl<'a, K, V, L: Location<'a>> Slicable<'a, L> for super::KeyedSingleton<K, V, L, Unbounded> {
726    type Slice = super::KeyedSingleton<K, V, Tick<L>, Bounded>;
727    type Backtrace = crate::compile::ir::backtrace::Backtrace;
728
729    fn get_location(&self) -> &L {
730        self.location()
731    }
732
733    fn preferred_tick(&self) -> Option<Tick<L>> {
734        None
735    }
736
737    fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
738        let out = self.snapshot(tick, nondet);
739        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
740        out
741    }
742}
743
744impl<'a, K, V, L: Location<'a> + NoTick> Slicable<'a, L>
745    for super::KeyedSingleton<K, V, L, BoundedValue>
746{
747    type Slice = super::KeyedSingleton<K, V, Tick<L>, Bounded>;
748    type Backtrace = crate::compile::ir::backtrace::Backtrace;
749
750    fn get_location(&self) -> &L {
751        self.location()
752    }
753
754    fn preferred_tick(&self) -> Option<Tick<L>> {
755        None
756    }
757
758    fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace, nondet: NonDet) -> Self::Slice {
759        let out = self.batch(tick, nondet);
760        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
761        out
762    }
763}
764
765#[cfg(feature = "sim")]
766#[cfg(test)]
767mod tests {
768    use stageleft::q;
769
770    use super::sliced;
771    use crate::location::Location;
772    use crate::nondet::nondet;
773    use crate::prelude::FlowBuilder;
774
775    /// Test a counter using `use::state` with an initial singleton value.
776    /// Each input increments the counter, and we verify the output after each tick.
777
778    #[test]
779    fn sim_state_counter() {
780        let flow = FlowBuilder::new();
781        let node = flow.process::<()>();
782
783        let (input_send, input) = node.sim_input::<i32, _, _>();
784
785        let out_recv = sliced! {
786            let batch = use(input, nondet!(/** test */));
787            let mut counter = use::state(|l| l.singleton(q!(0)));
788
789            let new_count = counter.clone().zip(batch.count())
790                .map(q!(|(old, add)| old + add));
791            counter = new_count.clone();
792            new_count.into_stream()
793        }
794        .sim_output();
795
796        flow.sim().exhaustive(async || {
797            input_send.send(1);
798            assert_eq!(out_recv.next().await.unwrap(), 1);
799
800            input_send.send(1);
801            assert_eq!(out_recv.next().await.unwrap(), 2);
802
803            input_send.send(1);
804            assert_eq!(out_recv.next().await.unwrap(), 3);
805        });
806    }
807
808    /// Test `use::state_null` with an Optional that starts as None.
809    #[cfg(feature = "sim")]
810    #[test]
811    fn sim_state_null_optional() {
812        use crate::live_collections::Optional;
813        use crate::live_collections::boundedness::Bounded;
814        use crate::location::{Location, Tick};
815
816        let flow = FlowBuilder::new();
817        let node = flow.process::<()>();
818
819        let (input_send, input) = node.sim_input::<i32, _, _>();
820
821        let out_recv = sliced! {
822            let batch = use(input, nondet!(/** test */));
823            let mut prev = use::state_null::<Optional<i32, Tick<_>, Bounded>>();
824
825            // Output the previous value (or -1 if none)
826            let output = prev.clone().unwrap_or(prev.location().singleton(q!(-1)));
827            // Store the current batch's first value for next tick
828            prev = batch.first();
829            output.into_stream()
830        }
831        .sim_output();
832
833        flow.sim().exhaustive(async || {
834            input_send.send(10);
835            // First tick: prev is None, so output is -1
836            assert_eq!(out_recv.next().await.unwrap(), -1);
837
838            input_send.send(20);
839            // Second tick: prev is Some(10), so output is 10
840            assert_eq!(out_recv.next().await.unwrap(), 10);
841
842            input_send.send(30);
843            // Third tick: prev is Some(20), so output is 20
844            assert_eq!(out_recv.next().await.unwrap(), 20);
845        });
846    }
847}