Skip to main content

hydro_lang/live_collections/stream/
mod.rs

1//! Definitions for the [`Stream`] live collection.
2
3use std::cell::RefCell;
4use std::future::Future;
5use std::hash::Hash;
6use std::marker::PhantomData;
7use std::ops::Deref;
8use std::rc::Rc;
9
10use stageleft::{IntoQuotedMut, QuotedWithContext, QuotedWithContextWithProps, q, quote_type};
11
12use super::OperatorContext;
13use super::boundedness::{Bounded, Boundedness, IsBounded, Unbounded};
14use super::keyed_singleton::KeyedSingleton;
15use super::keyed_stream::{Generate, KeyedStream};
16use super::optional::Optional;
17use super::singleton::Singleton;
18#[cfg(feature = "tokio")]
19use super::sliced::sliced;
20use crate::compile::builder::{CycleId, FlowState};
21use crate::compile::ir::{
22    CollectionKind, HydroIrOpMetadata, HydroNode, HydroRoot, SharedNode, StreamOrder, StreamRetry,
23};
24#[cfg(stageleft_runtime)]
25use crate::forward_handle::{CycleCollection, CycleCollectionWithInitial, ReceiverComplete};
26use crate::forward_handle::{ForwardRef, TickCycle};
27use crate::live_collections::batch_atomic::BatchAtomic;
28use crate::live_collections::singleton::SingletonBound;
29#[cfg(stageleft_runtime)]
30use crate::location::dynamic::{DynLocation, LocationId};
31use crate::location::tick::{Atomic, DeferTick};
32use crate::location::{Location, Tick, TopLevel, check_matching_location};
33use crate::manual_expr::ManualExpr;
34use crate::nondet::{NonDet, nondet};
35use crate::prelude::manual_proof;
36use crate::properties::{
37    AggFuncAlgebra, ApplyMonotoneStream, NotProved, StreamMapFuncAlgebra, ValidCommutativityFor,
38    ValidIdempotenceFor, ValidMutBorrowCommutativityFor, ValidMutBorrowIdempotenceFor,
39    ValidMutCommutativityFor, ValidMutIdempotenceFor,
40};
41
42pub mod networking;
43
44/// A trait implemented by valid ordering markers ([`TotalOrder`] and [`NoOrder`]).
45#[sealed::sealed]
46pub trait Ordering:
47    MinOrder<Self, Min = Self> + MinOrder<TotalOrder, Min = Self> + MinOrder<NoOrder, Min = NoOrder>
48{
49    /// The [`StreamOrder`] corresponding to this type.
50    const ORDERING_KIND: StreamOrder;
51}
52
53/// Marks the stream as being totally ordered, which means that there are
54/// no sources of non-determinism (other than intentional ones) that will
55/// affect the order of elements.
56pub enum TotalOrder {}
57
58#[sealed::sealed]
59impl Ordering for TotalOrder {
60    const ORDERING_KIND: StreamOrder = StreamOrder::TotalOrder;
61}
62
63/// Marks the stream as having no order, which means that the order of
64/// elements may be affected by non-determinism.
65///
66/// This restricts certain operators, such as `fold` and `reduce`, to only
67/// be used with commutative aggregation functions.
68pub enum NoOrder {}
69
70#[sealed::sealed]
71impl Ordering for NoOrder {
72    const ORDERING_KIND: StreamOrder = StreamOrder::NoOrder;
73}
74
75/// Marker trait for an [`Ordering`] that is available when `Self` is a weaker guarantee than
76/// `Other`, which means that a stream with `Other` guarantees can be safely converted to
77/// have `Self` guarantees instead.
78#[sealed::sealed]
79pub trait WeakerOrderingThan<Other: ?Sized>: Ordering {}
80#[sealed::sealed]
81impl<O, O2: Ordering> WeakerOrderingThan<O2> for O where O: Ordering + MinOrder<O2, Min = O> {}
82
83/// Helper trait for determining the weakest of two orderings.
84#[sealed::sealed]
85pub trait MinOrder<Other: ?Sized> {
86    /// The weaker of the two orderings.
87    type Min: Ordering;
88}
89
90#[sealed::sealed]
91impl<O: Ordering> MinOrder<O> for TotalOrder {
92    type Min = O;
93}
94
95#[sealed::sealed]
96impl<O: Ordering> MinOrder<O> for NoOrder {
97    type Min = NoOrder;
98}
99
100/// A trait implemented by valid retries markers ([`ExactlyOnce`] and [`AtLeastOnce`]).
101#[sealed::sealed]
102pub trait Retries:
103    MinRetries<Self, Min = Self>
104    + MinRetries<ExactlyOnce, Min = Self>
105    + MinRetries<AtLeastOnce, Min = AtLeastOnce>
106{
107    /// The [`StreamRetry`] corresponding to this type.
108    const RETRIES_KIND: StreamRetry;
109}
110
111/// Marks the stream as having deterministic message cardinality, with no
112/// possibility of duplicates.
113pub enum ExactlyOnce {}
114
115#[sealed::sealed]
116impl Retries for ExactlyOnce {
117    const RETRIES_KIND: StreamRetry = StreamRetry::ExactlyOnce;
118}
119
120/// Marks the stream as having non-deterministic message cardinality, which
121/// means that duplicates may occur, but messages will not be dropped.
122pub enum AtLeastOnce {}
123
124#[sealed::sealed]
125impl Retries for AtLeastOnce {
126    const RETRIES_KIND: StreamRetry = StreamRetry::AtLeastOnce;
127}
128
129/// Marker trait for a [`Retries`] that is available when `Self` is a weaker guarantee than
130/// `Other`, which means that a stream with `Other` guarantees can be safely converted to
131/// have `Self` guarantees instead.
132#[sealed::sealed]
133pub trait WeakerRetryThan<Other: ?Sized>: Retries {}
134#[sealed::sealed]
135impl<R, R2: Retries> WeakerRetryThan<R2> for R where R: Retries + MinRetries<R2, Min = R> {}
136
137/// Helper trait for determining the weakest of two retry guarantees.
138#[sealed::sealed]
139pub trait MinRetries<Other: ?Sized> {
140    /// The weaker of the two retry guarantees.
141    type Min: Retries + WeakerRetryThan<Self> + WeakerRetryThan<Other>;
142}
143
144#[sealed::sealed]
145impl<R: Retries> MinRetries<R> for ExactlyOnce {
146    type Min = R;
147}
148
149#[sealed::sealed]
150impl<R: Retries> MinRetries<R> for AtLeastOnce {
151    type Min = AtLeastOnce;
152}
153
154#[sealed::sealed]
155#[diagnostic::on_unimplemented(
156    message = "the input stream must be totally ordered (`TotalOrder`), but has order `{Self}`",
157    label = "required here",
158    note = "strengthen the order upstream, or consider a different API",
159    note = "to intentionally process the stream by observing a non-deterministic (shuffled) order of elements, use `.assume_ordering`; this introduces non-determinism, so avoid it unless necessary"
160)]
161/// Marker trait that is implemented for the [`TotalOrder`] ordering guarantee.
162pub trait IsOrdered: Ordering {}
163
164#[sealed::sealed]
165#[diagnostic::do_not_recommend]
166impl IsOrdered for TotalOrder {}
167
168#[sealed::sealed]
169#[diagnostic::on_unimplemented(
170    message = "the input stream must be exactly-once (`ExactlyOnce`), but has retries `{Self}`",
171    label = "required here",
172    note = "strengthen the retries guarantee upstream, or consider a different API",
173    note = "to intentionally process the stream by observing non-deterministic (randomly duplicated) retries, use `.assume_retries`; this introduces non-determinism, so avoid it unless necessary"
174)]
175/// Marker trait that is implemented for the [`ExactlyOnce`] retries guarantee.
176pub trait IsExactlyOnce: Retries {}
177
178#[sealed::sealed]
179#[diagnostic::do_not_recommend]
180impl IsExactlyOnce for ExactlyOnce {}
181
182/// Streaming sequence of elements with type `Type`.
183///
184/// This live collection represents a growing sequence of elements, with new elements being
185/// asynchronously appended to the end of the sequence. This can be used to model the arrival
186/// of network input, such as API requests, or streaming ingestion.
187///
188/// By default, all streams have deterministic ordering and each element is materialized exactly
189/// once. But streams can also capture non-determinism via the `Order` and `Retries` type
190/// parameters. When the ordering / retries guarantee is relaxed, fewer APIs will be available
191/// on the stream. For example, if the stream is unordered, you cannot invoke [`Stream::first`].
192///
193/// Type Parameters:
194/// - `Type`: the type of elements in the stream
195/// - `Loc`: the location where the stream is being materialized
196/// - `Bound`: the boundedness of the stream, which is either [`Bounded`] or [`Unbounded`]
197/// - `Order`: the ordering of the stream, which is either [`TotalOrder`] or [`NoOrder`]
198///   (default is [`TotalOrder`])
199/// - `Retries`: the retry guarantee of the stream, which is either [`ExactlyOnce`] or
200///   [`AtLeastOnce`] (default is [`ExactlyOnce`])
201pub struct Stream<
202    Type,
203    Loc,
204    Bound: Boundedness = Unbounded,
205    Order: Ordering = TotalOrder,
206    Retry: Retries = ExactlyOnce,
207> {
208    pub(crate) location: Loc,
209    pub(crate) ir_node: Rc<RefCell<HydroNode>>,
210    pub(crate) flow_state: FlowState,
211
212    _phantom: PhantomData<(Type, Loc, Bound, Order, Retry)>,
213}
214
215impl<T, L, B: Boundedness, O: Ordering, R: Retries> Drop for Stream<T, L, B, O, R> {
216    fn drop(&mut self) {
217        let ir_node = self.ir_node.replace(HydroNode::Placeholder);
218        if !matches!(ir_node, HydroNode::Placeholder) && !ir_node.is_shared_with_others() {
219            self.flow_state.borrow_mut().try_push_root(HydroRoot::Null {
220                input: Box::new(ir_node),
221                op_metadata: HydroIrOpMetadata::new(),
222            });
223        }
224    }
225}
226
227impl<'a, T, L, O: Ordering, R: Retries> From<Stream<T, L, Bounded, O, R>>
228    for Stream<T, L, Unbounded, O, R>
229where
230    L: Location<'a>,
231{
232    fn from(stream: Stream<T, L, Bounded, O, R>) -> Stream<T, L, Unbounded, O, R> {
233        let new_meta = stream
234            .location
235            .new_node_metadata(Stream::<T, L, Unbounded, O, R>::collection_kind());
236
237        let flow_state = stream.flow_state.clone();
238        Stream {
239            location: stream.location.clone(),
240            ir_node: super::tracked_ir_node(
241                &flow_state,
242                HydroNode::Cast {
243                    inner: Box::new(stream.ir_node.replace(HydroNode::Placeholder)),
244                    metadata: new_meta,
245                },
246            ),
247            flow_state,
248            _phantom: PhantomData,
249        }
250    }
251}
252
253impl<'a, T, L, B: Boundedness, R: Retries> From<Stream<T, L, B, TotalOrder, R>>
254    for Stream<T, L, B, NoOrder, R>
255where
256    L: Location<'a>,
257{
258    fn from(stream: Stream<T, L, B, TotalOrder, R>) -> Stream<T, L, B, NoOrder, R> {
259        stream.weaken_ordering()
260    }
261}
262
263impl<'a, T, L, B: Boundedness, O: Ordering> From<Stream<T, L, B, O, ExactlyOnce>>
264    for Stream<T, L, B, O, AtLeastOnce>
265where
266    L: Location<'a>,
267{
268    fn from(stream: Stream<T, L, B, O, ExactlyOnce>) -> Stream<T, L, B, O, AtLeastOnce> {
269        stream.weaken_retries()
270    }
271}
272
273impl<'a, T, L, O: Ordering, R: Retries> DeferTick for Stream<T, Tick<L>, Bounded, O, R>
274where
275    L: Location<'a>,
276{
277    fn defer_tick(self) -> Self {
278        Stream::defer_tick(self)
279    }
280}
281
282impl<'a, T, L, O: Ordering, R: Retries> CycleCollection<'a, TickCycle>
283    for Stream<T, Tick<L>, Bounded, O, R>
284where
285    L: Location<'a>,
286{
287    type Location = Tick<L>;
288
289    fn create_source(cycle_id: CycleId, location: Tick<L>) -> Self {
290        Stream::new(
291            location.clone(),
292            HydroNode::CycleSource {
293                cycle_id,
294                metadata: location.new_node_metadata(Self::collection_kind()),
295            },
296        )
297    }
298}
299
300impl<'a, T, L, O: Ordering, R: Retries> CycleCollectionWithInitial<'a, TickCycle>
301    for Stream<T, Tick<L>, Bounded, O, R>
302where
303    L: Location<'a>,
304{
305    type Location = Tick<L>;
306
307    fn location(&self) -> &Self::Location {
308        self.location()
309    }
310
311    fn create_source_with_initial(cycle_id: CycleId, initial: Self, location: Tick<L>) -> Self {
312        let from_previous_tick: Stream<T, Tick<L>, Bounded, O, R> = Stream::new(
313            location.clone(),
314            HydroNode::DeferTick {
315                input: Box::new(HydroNode::CycleSource {
316                    cycle_id,
317                    metadata: location.new_node_metadata(Self::collection_kind()),
318                }),
319                metadata: location.new_node_metadata(Self::collection_kind()),
320            },
321        );
322
323        from_previous_tick.chain(initial.filter_if(location.optional_first_tick(q!(())).is_some()))
324    }
325}
326
327impl<'a, T, L, O: Ordering, R: Retries> ReceiverComplete<'a, TickCycle>
328    for Stream<T, Tick<L>, Bounded, O, R>
329where
330    L: Location<'a>,
331{
332    fn complete(self, cycle_id: CycleId, expected_location: LocationId) {
333        assert_eq!(
334            Location::id(&self.location),
335            expected_location,
336            "locations do not match"
337        );
338        self.location
339            .flow_state()
340            .borrow_mut()
341            .push_root(HydroRoot::CycleSink {
342                cycle_id,
343                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
344                op_metadata: HydroIrOpMetadata::new(),
345            });
346    }
347}
348
349impl<'a, T, L, B: Boundedness, O: Ordering, R: Retries> CycleCollection<'a, ForwardRef>
350    for Stream<T, L, B, O, R>
351where
352    L: Location<'a>,
353{
354    type Location = L;
355
356    fn create_source(cycle_id: CycleId, location: L) -> Self {
357        Stream::new(
358            location.clone(),
359            HydroNode::CycleSource {
360                cycle_id,
361                metadata: location.new_node_metadata(Self::collection_kind()),
362            },
363        )
364    }
365}
366
367impl<'a, T, L, B: Boundedness, O: Ordering, R: Retries> ReceiverComplete<'a, ForwardRef>
368    for Stream<T, L, B, O, R>
369where
370    L: Location<'a>,
371{
372    fn complete(self, cycle_id: CycleId, expected_location: LocationId) {
373        assert_eq!(
374            Location::id(&self.location),
375            expected_location,
376            "locations do not match"
377        );
378        self.location
379            .flow_state()
380            .borrow_mut()
381            .push_root(HydroRoot::CycleSink {
382                cycle_id,
383                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
384                op_metadata: HydroIrOpMetadata::new(),
385            });
386    }
387}
388
389impl<'a, T, L, B: Boundedness, O: Ordering, R: Retries> Clone for Stream<T, L, B, O, R>
390where
391    T: Clone,
392    L: Location<'a>,
393{
394    fn clone(&self) -> Self {
395        if !matches!(self.ir_node.borrow().deref(), HydroNode::Tee { .. }) {
396            let orig_ir_node = self.ir_node.replace(HydroNode::Placeholder);
397            *self.ir_node.borrow_mut() = HydroNode::Tee {
398                inner: SharedNode(Rc::new(RefCell::new(orig_ir_node))),
399                metadata: self.location.new_node_metadata(Self::collection_kind()),
400            };
401        }
402
403        let HydroNode::Tee { inner, metadata } = &*self.ir_node.borrow() else {
404            unreachable!()
405        };
406        Stream {
407            location: self.location.clone(),
408            flow_state: self.flow_state.clone(),
409            ir_node: super::tracked_ir_node(
410                &self.flow_state,
411                HydroNode::Tee {
412                    inner: SharedNode(inner.0.clone()),
413                    metadata: metadata.clone(),
414                },
415            ),
416            _phantom: PhantomData,
417        }
418    }
419}
420
421impl<'a, T, L, B: Boundedness, O: Ordering, R: Retries> Stream<T, L, B, O, R>
422where
423    L: Location<'a>,
424{
425    pub(crate) fn new(location: L, ir_node: HydroNode) -> Self {
426        debug_assert_eq!(ir_node.metadata().location_id, Location::id(&location));
427        debug_assert_eq!(ir_node.metadata().collection_kind, Self::collection_kind());
428
429        let flow_state = location.flow_state().clone();
430        let ir_node = super::tracked_ir_node(&flow_state, ir_node);
431        Stream {
432            location,
433            flow_state,
434            ir_node,
435            _phantom: PhantomData,
436        }
437    }
438
439    /// Returns the [`Location`] where this stream is being materialized.
440    pub fn location(&self) -> &L {
441        &self.location
442    }
443
444    /// Creates a shared reference handle to this stream's handoff buffer that can be captured
445    /// inside `q!()` closures. The handle resolves to `&Vec<T>` at runtime.
446    ///
447    /// The stream must be bounded, otherwise reading it would be non-deterministic.
448    pub fn by_ref(&self) -> crate::handoff_ref::StreamRef<'a, '_, T, L, B>
449    where
450        B: IsBounded,
451    {
452        crate::handoff_ref::StreamRef::new(&self.ir_node)
453    }
454
455    /// Returns a mutable reference handle to this stream's handoff buffer that can be captured
456    /// inside `q!()` closures. The handle resolves to `&mut Vec<T>` at runtime.
457    pub fn by_mut(&self) -> crate::handoff_ref::StreamMut<'a, '_, T, L, B>
458    where
459        B: IsBounded,
460    {
461        crate::handoff_ref::StreamMut::new(&self.ir_node)
462    }
463
464    /// Weakens the consistency of this live collection to not guarantee any consistency across
465    /// cluster members (if this collection is on a cluster).
466    pub fn weaken_consistency(self) -> Stream<T, L::DropConsistency, B, O, R>
467    where
468        L: Location<'a>,
469    {
470        if L::consistency()
471            .is_none_or(|c| c == crate::location::dynamic::ClusterConsistency::NoConsistency)
472        {
473            // already no consistency
474            Stream::new(
475                self.location.drop_consistency(),
476                self.ir_node.replace(HydroNode::Placeholder),
477            )
478        } else {
479            Stream::new(
480                self.location.drop_consistency(),
481                HydroNode::Cast {
482                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
483                    metadata: self.location.drop_consistency().new_node_metadata(Stream::<
484                        T,
485                        L::DropConsistency,
486                        B,
487                        O,
488                        R,
489                    >::collection_kind(
490                    )),
491                },
492            )
493        }
494    }
495
496    /// Casts this live collection to have the consistency guarantees specified in the given
497    /// location type parameter. The developer must ensure that the strengthened consistency
498    /// is actually guaranteed, via the proof field (see [`crate::prelude::manual_proof`]).
499    pub fn assert_has_consistency_of<L2: Location<'a, DropConsistency = L::DropConsistency>>(
500        self,
501        _proof: impl crate::properties::ConsistencyProof,
502    ) -> Stream<T, L2, B, O, R>
503    where
504        L: Location<'a>,
505    {
506        if L::consistency() == L2::consistency() {
507            Stream::new(
508                self.location.with_consistency_of(),
509                self.ir_node.replace(HydroNode::Placeholder),
510            )
511        } else {
512            Stream::new(
513                self.location.with_consistency_of(),
514                HydroNode::AssertIsConsistent {
515                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
516                    trusted: false,
517                    metadata: self
518                        .location
519                        .clone()
520                        .with_consistency_of::<L2>()
521                        .new_node_metadata(Stream::<T, L2, B, O, R>::collection_kind()),
522                },
523            )
524        }
525    }
526
527    pub(crate) fn assert_has_consistency_of_trusted<
528        L2: Location<'a, DropConsistency = L::DropConsistency>,
529    >(
530        self,
531        _proof: impl crate::properties::ConsistencyProof,
532    ) -> Stream<T, L2, B, O, R>
533    where
534        L: Location<'a>,
535    {
536        if L::consistency() == L2::consistency() {
537            Stream::new(
538                self.location.with_consistency_of(),
539                self.ir_node.replace(HydroNode::Placeholder),
540            )
541        } else {
542            Stream::new(
543                self.location.with_consistency_of(),
544                HydroNode::AssertIsConsistent {
545                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
546                    trusted: true,
547                    metadata: self
548                        .location
549                        .clone()
550                        .with_consistency_of::<L2>()
551                        .new_node_metadata(Stream::<T, L2, B, O, R>::collection_kind()),
552                },
553            )
554        }
555    }
556
557    pub(crate) fn collection_kind() -> CollectionKind {
558        CollectionKind::Stream {
559            bound: B::BOUND_KIND,
560            order: O::ORDERING_KIND,
561            retry: R::RETRIES_KIND,
562            element_type: quote_type::<T>().into(),
563        }
564    }
565
566    /// Produces a stream based on invoking `f` on each element.
567    /// If you do not want to modify the stream and instead only want to view
568    /// each item use [`Stream::inspect`] instead.
569    ///
570    /// If the input stream is unordered **and** `f` mutably captures state (such as a
571    /// [`Singleton::by_mut`](crate::live_collections::singleton::Singleton::by_mut)
572    /// reference), `f` must be proven **commutative**: processing any two elements in
573    /// either order must leave the mutably-captured state in the same final value *and*
574    /// produce the same multiset of outputs. In particular, outputs must not expose the
575    /// processing order (e.g. emitting a running total is not commutative even though
576    /// addition is).
577    ///
578    /// Similarly, if the input stream has retries (`AtLeastOnce`) **and** `f` mutably
579    /// captures state, `f` must be proven **idempotent**: processing an element twice in
580    /// a row must leave the mutably-captured state in the same final value as processing
581    /// it once *and* return the same output both times. In particular, outputs must not
582    /// expose whether an element is a retry (e.g. emitting "is this a new maximum?" is not
583    /// idempotent even though `max` is).
584    ///
585    /// # Example
586    /// ```rust
587    /// # #[cfg(feature = "deploy")] {
588    /// # use hydro_lang::prelude::*;
589    /// # use futures::StreamExt;
590    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
591    /// let words = process.source_iter(q!(vec!["hello", "world"]));
592    /// words.map(q!(|x| x.to_uppercase()))
593    /// # }, |mut stream| async move {
594    /// # for w in vec!["HELLO", "WORLD"] {
595    /// #     assert_eq!(stream.next().await.unwrap(), w);
596    /// # }
597    /// # }));
598    /// # }
599    /// ```
600    pub fn map<U, F, C, I, const WAS_MUT: bool>(
601        self,
602        f: impl IntoQuotedMut<
603            'a,
604            F,
605            OperatorContext<L, B>,
606            StreamMapFuncAlgebra<T, B, C, I, L::SimHookScope>,
607        >,
608    ) -> Stream<U, L, B, O, R>
609    where
610        F: FnMut(T) -> U + 'a,
611        C: ValidMutCommutativityFor<F, T, U, O, WAS_MUT>,
612        I: ValidMutIdempotenceFor<F, T, U, R, WAS_MUT>,
613    {
614        let f = crate::handoff_ref::with_ref_capture(|| {
615            let (expr, proof) =
616                f.splice_fnmut1_ctx_props(&OperatorContext::<L, B>::new(&self.location));
617            proof.register_proof(&expr);
618            expr.into()
619        });
620        Stream::new(
621            self.location.clone(),
622            HydroNode::Map {
623                f,
624                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
625                metadata: self
626                    .location
627                    .new_node_metadata(Stream::<U, L, B, O, R>::collection_kind()),
628            },
629        )
630    }
631
632    /// For each item `i` in the input stream, transform `i` using `f` and then treat the
633    /// result as an [`Iterator`] to produce items one by one. The implementation for [`Iterator`]
634    /// for the output type `U` must produce items in a **deterministic** order.
635    ///
636    /// For example, `U` could be a `Vec`, but not a `HashSet`. If the order of the items in `U` is
637    /// not deterministic, use [`Stream::flat_map_unordered`] instead.
638    ///
639    /// # Example
640    /// ```rust
641    /// # #[cfg(feature = "deploy")] {
642    /// # use hydro_lang::prelude::*;
643    /// # use futures::StreamExt;
644    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
645    /// process
646    ///     .source_iter(q!(vec![vec![1, 2], vec![3, 4]]))
647    ///     .flat_map_ordered(q!(|x| x))
648    /// # }, |mut stream| async move {
649    /// // 1, 2, 3, 4
650    /// # for w in (1..5) {
651    /// #     assert_eq!(stream.next().await.unwrap(), w);
652    /// # }
653    /// # }));
654    /// # }
655    /// ```
656    pub fn flat_map_ordered<U, I, F, C, Idemp, const WAS_MUT: bool>(
657        self,
658        f: impl IntoQuotedMut<
659            'a,
660            F,
661            OperatorContext<L, B>,
662            StreamMapFuncAlgebra<T, B, C, Idemp, L::SimHookScope>,
663        >,
664    ) -> Stream<U, L, B, O, R>
665    where
666        I: IntoIterator<Item = U>,
667        F: FnMut(T) -> I + 'a,
668        C: ValidMutCommutativityFor<F, T, I, O, WAS_MUT>,
669        Idemp: ValidMutIdempotenceFor<F, T, I, R, WAS_MUT>,
670    {
671        let f = crate::handoff_ref::with_ref_capture(|| {
672            let (expr, proof) =
673                f.splice_fnmut1_ctx_props(&OperatorContext::<L, B>::new(&self.location));
674            proof.register_proof(&expr);
675            expr.into()
676        });
677        Stream::new(
678            self.location.clone(),
679            HydroNode::FlatMap {
680                f,
681                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
682                metadata: self
683                    .location
684                    .new_node_metadata(Stream::<U, L, B, O, R>::collection_kind()),
685            },
686        )
687    }
688
689    /// Like [`Stream::flat_map_ordered`], but allows the implementation of [`Iterator`]
690    /// for the output type `U` to produce items in any order.
691    ///
692    /// # Example
693    /// ```rust
694    /// # #[cfg(feature = "deploy")] {
695    /// # use hydro_lang::{prelude::*, live_collections::stream::{NoOrder, ExactlyOnce}};
696    /// # use futures::StreamExt;
697    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test::<_, _, _, NoOrder, ExactlyOnce>(|process| {
698    /// process
699    ///     .source_iter(q!(vec![
700    ///         std::collections::HashSet::<i32>::from_iter(vec![1, 2]),
701    ///         std::collections::HashSet::from_iter(vec![3, 4]),
702    ///     ]))
703    ///     .flat_map_unordered(q!(|x| x))
704    /// # }, |mut stream| async move {
705    /// // 1, 2, 3, 4, but in no particular order
706    /// # let mut results = Vec::new();
707    /// # for w in (1..5) {
708    /// #     results.push(stream.next().await.unwrap());
709    /// # }
710    /// # results.sort();
711    /// # assert_eq!(results, vec![1, 2, 3, 4]);
712    /// # }));
713    /// # }
714    /// ```
715    pub fn flat_map_unordered<U, I, F, C, Idemp, const WAS_MUT: bool>(
716        self,
717        f: impl IntoQuotedMut<
718            'a,
719            F,
720            OperatorContext<L, B>,
721            StreamMapFuncAlgebra<T, B, C, Idemp, L::SimHookScope>,
722        >,
723    ) -> Stream<U, L, B, NoOrder, R>
724    where
725        I: IntoIterator<Item = U>,
726        F: FnMut(T) -> I + 'a,
727        C: ValidMutCommutativityFor<F, T, I, O, WAS_MUT>,
728        Idemp: ValidMutIdempotenceFor<F, T, I, R, WAS_MUT>,
729    {
730        let f = crate::handoff_ref::with_ref_capture(|| {
731            let (expr, proof) =
732                f.splice_fnmut1_ctx_props(&OperatorContext::<L, B>::new(&self.location));
733            proof.register_proof(&expr);
734            expr.into()
735        });
736        Stream::new(
737            self.location.clone(),
738            HydroNode::FlatMap {
739                f,
740                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
741                metadata: self
742                    .location
743                    .new_node_metadata(Stream::<U, L, B, NoOrder, R>::collection_kind()),
744            },
745        )
746    }
747
748    /// For each item `i` in the input stream, treat `i` as an [`Iterator`] and produce its items one by one.
749    /// The implementation for [`Iterator`] for the element type `T` must produce items in a **deterministic** order.
750    ///
751    /// For example, `T` could be a `Vec`, but not a `HashSet`. If the order of the items in `T` is
752    /// not deterministic, use [`Stream::flatten_unordered`] instead.
753    ///
754    /// ```rust
755    /// # #[cfg(feature = "deploy")] {
756    /// # use hydro_lang::prelude::*;
757    /// # use futures::StreamExt;
758    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
759    /// process
760    ///     .source_iter(q!(vec![vec![1, 2], vec![3, 4]]))
761    ///     .flatten_ordered()
762    /// # }, |mut stream| async move {
763    /// // 1, 2, 3, 4
764    /// # for w in (1..5) {
765    /// #     assert_eq!(stream.next().await.unwrap(), w);
766    /// # }
767    /// # }));
768    /// # }
769    /// ```
770    pub fn flatten_ordered<U>(self) -> Stream<U, L, B, O, R>
771    where
772        T: IntoIterator<Item = U>,
773    {
774        self.flat_map_ordered(q!(|d| d))
775    }
776
777    /// Like [`Stream::flatten_ordered`], but allows the implementation of [`Iterator`]
778    /// for the element type `T` to produce items in any order.
779    ///
780    /// # Example
781    /// ```rust
782    /// # #[cfg(feature = "deploy")] {
783    /// # use hydro_lang::{prelude::*, live_collections::stream::{NoOrder, ExactlyOnce}};
784    /// # use futures::StreamExt;
785    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test::<_, _, _, NoOrder, ExactlyOnce>(|process| {
786    /// process
787    ///     .source_iter(q!(vec![
788    ///         std::collections::HashSet::<i32>::from_iter(vec![1, 2]),
789    ///         std::collections::HashSet::from_iter(vec![3, 4]),
790    ///     ]))
791    ///     .flatten_unordered()
792    /// # }, |mut stream| async move {
793    /// // 1, 2, 3, 4, but in no particular order
794    /// # let mut results = Vec::new();
795    /// # for w in (1..5) {
796    /// #     results.push(stream.next().await.unwrap());
797    /// # }
798    /// # results.sort();
799    /// # assert_eq!(results, vec![1, 2, 3, 4]);
800    /// # }));
801    /// # }
802    /// ```
803    pub fn flatten_unordered<U>(self) -> Stream<U, L, B, NoOrder, R>
804    where
805        T: IntoIterator<Item = U>,
806    {
807        self.flat_map_unordered(q!(|d| d))
808    }
809
810    /// For each item in the input stream, apply `f` to produce a [`futures::stream::Stream`],
811    /// then emit the elements of that stream one by one. When the inner stream yields
812    /// `Pending`, this operator yields as well.
813    pub fn flat_map_stream_blocking<U, S, F, C, Idemp, const WAS_MUT: bool>(
814        self,
815        f: impl IntoQuotedMut<
816            'a,
817            F,
818            OperatorContext<L, B>,
819            StreamMapFuncAlgebra<T, B, C, Idemp, L::SimHookScope>,
820        >,
821    ) -> Stream<U, L, B, O, R>
822    where
823        S: futures::Stream<Item = U>,
824        F: FnMut(T) -> S + 'a,
825        C: ValidMutCommutativityFor<F, T, S, O, WAS_MUT>,
826        Idemp: ValidMutIdempotenceFor<F, T, S, R, WAS_MUT>,
827    {
828        let f = crate::handoff_ref::with_ref_capture(|| {
829            let (expr, proof) =
830                f.splice_fnmut1_ctx_props(&OperatorContext::<L, B>::new(&self.location));
831            proof.register_proof(&expr);
832            expr.into()
833        });
834        Stream::new(
835            self.location.clone(),
836            HydroNode::FlatMapStreamBlocking {
837                f,
838                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
839                metadata: self
840                    .location
841                    .new_node_metadata(Stream::<U, L, B, O, R>::collection_kind()),
842            },
843        )
844    }
845
846    /// For each item in the input stream, treat it as a [`futures::stream::Stream`] and
847    /// emit its elements one by one. When the inner stream yields `Pending`, this operator
848    /// yields as well.
849    pub fn flatten_stream_blocking<U>(self) -> Stream<U, L, B, O, R>
850    where
851        T: futures::Stream<Item = U>,
852    {
853        self.flat_map_stream_blocking(q!(|d| d))
854    }
855
856    /// Creates a stream containing only the elements of the input stream that satisfy a predicate
857    /// `f`, preserving the order of the elements.
858    ///
859    /// The closure `f` receives a reference `&T` rather than an owned value `T` because filtering does
860    /// not modify or take ownership of the values. If you need to modify the values while filtering
861    /// use [`Stream::filter_map`] instead.
862    ///
863    /// If the input stream is unordered **and** `f` mutably captures state, `f` must be
864    /// proven **commutative**: processing any two elements in either order must leave
865    /// the mutably-captured state in the same final value *and* retain the same multiset
866    /// of elements. In particular, the decision for each element must not depend on the
867    /// processing order: a stateful predicate like a rate limiter is not commutative —
868    /// its budget converges either way, but *which* element passes depends on the order.
869    ///
870    /// Similarly, if the input stream has retries (`AtLeastOnce`) **and** `f` mutably
871    /// captures state, `f` must be proven **idempotent**: processing an element twice in
872    /// a row must leave the mutably-captured state in the same final value as processing
873    /// it once *and* make the same decision both times. In particular, the decision must
874    /// not depend on whether the element was seen before.
875    ///
876    /// # Example
877    /// ```rust
878    /// # #[cfg(feature = "deploy")] {
879    /// # use hydro_lang::prelude::*;
880    /// # use futures::StreamExt;
881    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
882    /// process
883    ///     .source_iter(q!(vec![1, 2, 3, 4]))
884    ///     .filter(q!(|&x| x > 2))
885    /// # }, |mut stream| async move {
886    /// // 3, 4
887    /// # for w in (3..5) {
888    /// #     assert_eq!(stream.next().await.unwrap(), w);
889    /// # }
890    /// # }));
891    /// # }
892    /// ```
893    pub fn filter<F, C, Idemp, const WAS_MUT: bool>(
894        self,
895        f: impl IntoQuotedMut<
896            'a,
897            F,
898            OperatorContext<L, B>,
899            StreamMapFuncAlgebra<T, B, C, Idemp, L::SimHookScope>,
900        >,
901    ) -> Self
902    where
903        F: FnMut(&T) -> bool + 'a,
904        C: ValidMutBorrowCommutativityFor<F, T, bool, O, WAS_MUT>,
905        Idemp: ValidMutBorrowIdempotenceFor<F, T, bool, R, WAS_MUT>,
906    {
907        let f = crate::handoff_ref::with_ref_capture(|| {
908            let (expr, proof) =
909                f.splice_fnmut1_borrow_ctx_props(&OperatorContext::<L, B>::new(&self.location));
910            proof.register_proof(&expr);
911            expr.into()
912        });
913        Stream::new(
914            self.location.clone(),
915            HydroNode::Filter {
916                f,
917                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
918                metadata: self.location.new_node_metadata(Self::collection_kind()),
919            },
920        )
921    }
922
923    /// Splits the stream into two streams based on a predicate, without cloning elements.
924    ///
925    /// Elements for which `f` returns `true` are sent to the first output stream,
926    /// and elements for which `f` returns `false` are sent to the second output stream.
927    ///
928    /// Unlike using `filter` twice, this only evaluates the predicate once per element
929    /// and does not require `T: Clone`.
930    ///
931    /// The closure `f` receives a reference `&T` rather than an owned value `T` because
932    /// the predicate is only used for routing; the element itself is moved to the
933    /// appropriate output stream.
934    ///
935    /// If the input stream has retries (`AtLeastOnce`) **and** `f` mutably captures
936    /// state, `f` must be proven **idempotent**: processing an element twice in a row
937    /// must leave the mutably-captured state in the same final value as processing it
938    /// once *and* make the same decision both times (otherwise a retried element would
939    /// appear in the *other* output).
940    ///
941    /// # Example
942    /// ```rust
943    /// # #[cfg(feature = "deploy")] {
944    /// # use hydro_lang::prelude::*;
945    /// # use hydro_lang::live_collections::stream::{NoOrder, ExactlyOnce};
946    /// # use futures::StreamExt;
947    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test::<_, _, _, NoOrder, ExactlyOnce>(|process| {
948    /// let numbers: Stream<_, _, Unbounded> = process.source_iter(q!(vec![1, 2, 3, 4, 5, 6])).into();
949    /// let (evens, odds) = numbers.partition(q!(|&x| x % 2 == 0));
950    /// // evens: 2, 4, 6 tagged with true; odds: 1, 3, 5 tagged with false
951    /// evens.map(q!(|x| (x, true)))
952    ///     .merge_unordered(odds.map(q!(|x| (x, false))))
953    /// # }, |mut stream| async move {
954    /// # let mut results = Vec::new();
955    /// # for _ in 0..6 {
956    /// #     results.push(stream.next().await.unwrap());
957    /// # }
958    /// # results.sort();
959    /// # assert_eq!(results, vec![(1, false), (2, true), (3, false), (4, true), (5, false), (6, true)]);
960    /// # }));
961    /// # }
962    /// ```
963    pub fn partition<F, C, Idemp, const WAS_MUT: bool>(
964        self,
965        f: impl IntoQuotedMut<
966            'a,
967            F,
968            OperatorContext<L, B>,
969            StreamMapFuncAlgebra<T, B, C, Idemp, L::SimHookScope>,
970        >,
971    ) -> (Stream<T, L, B, O, R>, Stream<T, L, B, O, R>)
972    where
973        F: FnMut(&T) -> bool + 'a,
974        C: ValidMutBorrowCommutativityFor<F, T, bool, O, WAS_MUT>,
975        Idemp: ValidMutBorrowIdempotenceFor<F, T, bool, R, WAS_MUT>,
976    {
977        let f = crate::handoff_ref::with_ref_capture(|| {
978            let (expr, proof) =
979                f.splice_fnmut1_borrow_ctx_props(&OperatorContext::<L, B>::new(&self.location));
980            proof.register_proof(&expr);
981            expr.into()
982        });
983        let shared = Rc::new(RefCell::new(HydroNode::PartitionShared {
984            input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
985            f,
986            metadata: self.location.new_node_metadata(Self::collection_kind()),
987        }));
988
989        let true_stream = Stream::new(
990            self.location.clone(),
991            HydroNode::PartitionSide {
992                inner: SharedNode(Rc::clone(&shared)),
993                is_true: true,
994                metadata: self.location.new_node_metadata(Self::collection_kind()),
995            },
996        );
997
998        let false_stream = Stream::new(
999            self.location.clone(),
1000            HydroNode::PartitionSide {
1001                inner: SharedNode(shared),
1002                is_true: false,
1003                metadata: self.location.new_node_metadata(Self::collection_kind()),
1004            },
1005        );
1006
1007        (true_stream, false_stream)
1008    }
1009
1010    /// An operator that both filters and maps. It yields only the items for which the supplied closure `f` returns `Some(value)`.
1011    ///
1012    /// # Example
1013    /// ```rust
1014    /// # #[cfg(feature = "deploy")] {
1015    /// # use hydro_lang::prelude::*;
1016    /// # use futures::StreamExt;
1017    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1018    /// process
1019    ///     .source_iter(q!(vec!["1", "hello", "world", "2"]))
1020    ///     .filter_map(q!(|s| s.parse::<usize>().ok()))
1021    /// # }, |mut stream| async move {
1022    /// // 1, 2
1023    /// # for w in (1..3) {
1024    /// #     assert_eq!(stream.next().await.unwrap(), w);
1025    /// # }
1026    /// # }));
1027    /// # }
1028    /// ```
1029    pub fn filter_map<U, F, C, Idemp, const WAS_MUT: bool>(
1030        self,
1031        f: impl IntoQuotedMut<
1032            'a,
1033            F,
1034            OperatorContext<L, B>,
1035            StreamMapFuncAlgebra<T, B, C, Idemp, L::SimHookScope>,
1036        >,
1037    ) -> Stream<U, L, B, O, R>
1038    where
1039        F: FnMut(T) -> Option<U> + 'a,
1040        C: ValidMutCommutativityFor<F, T, Option<U>, O, WAS_MUT>,
1041        Idemp: ValidMutIdempotenceFor<F, T, Option<U>, R, WAS_MUT>,
1042    {
1043        let f = crate::handoff_ref::with_ref_capture(|| {
1044            let (expr, proof) =
1045                f.splice_fnmut1_ctx_props(&OperatorContext::<L, B>::new(&self.location));
1046            proof.register_proof(&expr);
1047            expr.into()
1048        });
1049        Stream::new(
1050            self.location.clone(),
1051            HydroNode::FilterMap {
1052                f,
1053                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1054                metadata: self
1055                    .location
1056                    .new_node_metadata(Stream::<U, L, B, O, R>::collection_kind()),
1057            },
1058        )
1059    }
1060
1061    /// Generates a stream that maps each input element `i` to a tuple `(i, x)`,
1062    /// where `x` is the final value of `other`, a bounded [`Singleton`] or [`Optional`].
1063    /// If `other` is an empty [`Optional`], no values will be produced.
1064    ///
1065    /// # Example
1066    /// ```rust
1067    /// # #[cfg(feature = "deploy")] {
1068    /// # use hydro_lang::prelude::*;
1069    /// # use futures::StreamExt;
1070    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1071    /// let tick = process.tick();
1072    /// let batch = process
1073    ///   .source_iter(q!(vec![1, 2, 3, 4]))
1074    ///   .batch(&tick, nondet!(/** test */));
1075    /// let count = batch.clone().count(); // `count()` returns a singleton
1076    /// batch.cross_singleton(count).all_ticks()
1077    /// # }, |mut stream| async move {
1078    /// // (1, 4), (2, 4), (3, 4), (4, 4)
1079    /// # for w in vec![(1, 4), (2, 4), (3, 4), (4, 4)] {
1080    /// #     assert_eq!(stream.next().await.unwrap(), w);
1081    /// # }
1082    /// # }));
1083    /// # }
1084    /// ```
1085    pub fn cross_singleton<O2>(
1086        self,
1087        other: impl Into<Optional<O2, L, Bounded>>,
1088    ) -> Stream<(T, O2), L, B, O, R>
1089    where
1090        O2: Clone,
1091    {
1092        let other: Optional<O2, L, Bounded> = other.into();
1093        check_matching_location(&self.location, &other.location);
1094
1095        Stream::new(
1096            self.location.clone(),
1097            HydroNode::CrossSingleton {
1098                left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1099                right: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
1100                metadata: self
1101                    .location
1102                    .new_node_metadata(Stream::<(T, O2), L, B, O, R>::collection_kind()),
1103            },
1104        )
1105    }
1106
1107    /// Passes this stream through if the boolean signal is `true`, otherwise the output is empty.
1108    ///
1109    /// # Example
1110    /// ```rust
1111    /// # #[cfg(feature = "deploy")] {
1112    /// # use hydro_lang::prelude::*;
1113    /// # use futures::StreamExt;
1114    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1115    /// let tick = process.tick();
1116    /// // ticks are lazy by default, forces the second tick to run
1117    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1118    ///
1119    /// let signal = tick.optional_first_tick(q!(())).is_some(); // true on tick 1, false on tick 2
1120    /// let batch_first_tick = process
1121    ///   .source_iter(q!(vec![1, 2, 3, 4]))
1122    ///   .batch(&tick, nondet!(/** test */));
1123    /// let batch_second_tick = process
1124    ///   .source_iter(q!(vec![5, 6, 7, 8]))
1125    ///   .batch(&tick, nondet!(/** test */))
1126    ///   .defer_tick();
1127    /// batch_first_tick.chain(batch_second_tick)
1128    ///   .filter_if(signal)
1129    ///   .all_ticks()
1130    /// # }, |mut stream| async move {
1131    /// // [1, 2, 3, 4]
1132    /// # for w in vec![1, 2, 3, 4] {
1133    /// #     assert_eq!(stream.next().await.unwrap(), w);
1134    /// # }
1135    /// # }));
1136    /// # }
1137    /// ```
1138    pub fn filter_if(self, signal: Singleton<bool, L, Bounded>) -> Stream<T, L, B, O, R> {
1139        self.cross_singleton(signal.filter(q!(|b| *b)))
1140            .map(q!(|(d, _)| d))
1141    }
1142
1143    /// Passes this stream through if the argument (a [`Bounded`] [`Optional`]) is non-null, otherwise the output is empty.
1144    ///
1145    /// Useful for gating the release of elements based on a condition, such as only processing requests if you are the
1146    /// leader of a cluster.
1147    ///
1148    /// # Example
1149    /// ```rust
1150    /// # #[cfg(feature = "deploy")] {
1151    /// # use hydro_lang::prelude::*;
1152    /// # use futures::StreamExt;
1153    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1154    /// let tick = process.tick();
1155    /// // ticks are lazy by default, forces the second tick to run
1156    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1157    ///
1158    /// let batch_first_tick = process
1159    ///   .source_iter(q!(vec![1, 2, 3, 4]))
1160    ///   .batch(&tick, nondet!(/** test */));
1161    /// let batch_second_tick = process
1162    ///   .source_iter(q!(vec![5, 6, 7, 8]))
1163    ///   .batch(&tick, nondet!(/** test */))
1164    ///   .defer_tick(); // appears on the second tick
1165    /// let some_on_first_tick = tick.optional_first_tick(q!(()));
1166    /// batch_first_tick.chain(batch_second_tick)
1167    ///   .filter_if_some(some_on_first_tick)
1168    ///   .all_ticks()
1169    /// # }, |mut stream| async move {
1170    /// // [1, 2, 3, 4]
1171    /// # for w in vec![1, 2, 3, 4] {
1172    /// #     assert_eq!(stream.next().await.unwrap(), w);
1173    /// # }
1174    /// # }));
1175    /// # }
1176    /// ```
1177    #[deprecated(note = "use `filter_if` with `Optional::is_some()` instead")]
1178    pub fn filter_if_some<U>(self, signal: Optional<U, L, Bounded>) -> Stream<T, L, B, O, R> {
1179        self.filter_if(signal.is_some())
1180    }
1181
1182    /// Passes this stream through if the argument (a [`Bounded`] [`Optional`]) is null, otherwise the output is empty.
1183    ///
1184    /// Useful for gating the release of elements based on a condition, such as triggering a protocol if you are missing
1185    /// some local state.
1186    ///
1187    /// # Example
1188    /// ```rust
1189    /// # #[cfg(feature = "deploy")] {
1190    /// # use hydro_lang::prelude::*;
1191    /// # use futures::StreamExt;
1192    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1193    /// let tick = process.tick();
1194    /// // ticks are lazy by default, forces the second tick to run
1195    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1196    ///
1197    /// let batch_first_tick = process
1198    ///   .source_iter(q!(vec![1, 2, 3, 4]))
1199    ///   .batch(&tick, nondet!(/** test */));
1200    /// let batch_second_tick = process
1201    ///   .source_iter(q!(vec![5, 6, 7, 8]))
1202    ///   .batch(&tick, nondet!(/** test */))
1203    ///   .defer_tick(); // appears on the second tick
1204    /// let some_on_first_tick = tick.optional_first_tick(q!(()));
1205    /// batch_first_tick.chain(batch_second_tick)
1206    ///   .filter_if_none(some_on_first_tick)
1207    ///   .all_ticks()
1208    /// # }, |mut stream| async move {
1209    /// // [5, 6, 7, 8]
1210    /// # for w in vec![5, 6, 7, 8] {
1211    /// #     assert_eq!(stream.next().await.unwrap(), w);
1212    /// # }
1213    /// # }));
1214    /// # }
1215    /// ```
1216    #[deprecated(note = "use `filter_if` with `!Optional::is_some()` instead")]
1217    pub fn filter_if_none<U>(self, other: Optional<U, L, Bounded>) -> Stream<T, L, B, O, R> {
1218        self.filter_if(other.is_none())
1219    }
1220
1221    /// Forms the cross-product (Cartesian product, cross-join) of the items in the 2 input streams,
1222    /// returning all tupled pairs.
1223    ///
1224    /// When the right side is [`Bounded`], it is accumulated first and the left side streams
1225    /// through, preserving the left side's ordering. When both sides are [`Unbounded`], a
1226    /// symmetric hash join is used and ordering is [`NoOrder`].
1227    ///
1228    /// # Example
1229    /// ```rust
1230    /// # #[cfg(feature = "deploy")] {
1231    /// # use hydro_lang::prelude::*;
1232    /// # use std::collections::HashSet;
1233    /// # use futures::StreamExt;
1234    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1235    /// let tick = process.tick();
1236    /// let stream1 = process.source_iter(q!(vec![1, 2]));
1237    /// let stream2 = process.source_iter(q!(vec!['a', 'b']));
1238    /// stream1.cross_product(stream2)
1239    /// # }, |mut stream| async move {
1240    /// // (1, 'a'), (1, 'b'), (2, 'a'), (2, 'b') in any order
1241    /// # let expected = HashSet::from([(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]);
1242    /// # stream.map(|i| assert!(expected.contains(&i)));
1243    /// # }));
1244    /// # }
1245    pub fn cross_product<T2, B2: Boundedness, O2: Ordering, R2: Retries>(
1246        self,
1247        other: Stream<T2, L, B2, O2, R2>,
1248    ) -> Stream<(T, T2), L, B, B2::PreserveOrderIfBounded<O>, <R as MinRetries<R2>>::Min>
1249    where
1250        T: Clone,
1251        T2: Clone,
1252        R: MinRetries<R2>,
1253    {
1254        self.map(q!(|v| ((), v)))
1255            .join(other.map(q!(|v| ((), v))))
1256            .map(q!(|((), (v1, v2))| (v1, v2)))
1257    }
1258
1259    /// Takes one stream as input and filters out any duplicate occurrences. The output
1260    /// contains all unique values from the input.
1261    ///
1262    /// # Example
1263    /// ```rust
1264    /// # #[cfg(feature = "deploy")] {
1265    /// # use hydro_lang::prelude::*;
1266    /// # use futures::StreamExt;
1267    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1268    /// let tick = process.tick();
1269    /// process.source_iter(q!(vec![1, 2, 3, 2, 1, 4])).unique()
1270    /// # }, |mut stream| async move {
1271    /// # for w in vec![1, 2, 3, 4] {
1272    /// #     assert_eq!(stream.next().await.unwrap(), w);
1273    /// # }
1274    /// # }));
1275    /// # }
1276    /// ```
1277    pub fn unique(self) -> Stream<T, L, B, O, ExactlyOnce>
1278    where
1279        T: Eq + Hash,
1280    {
1281        Stream::new(
1282            self.location.clone(),
1283            HydroNode::Unique {
1284                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1285                metadata: self
1286                    .location
1287                    .new_node_metadata(Stream::<T, L, B, O, ExactlyOnce>::collection_kind()),
1288            },
1289        )
1290    }
1291
1292    /// Outputs everything in this stream that is *not* contained in the `other` stream.
1293    ///
1294    /// The `other` stream must be [`Bounded`], since this function will wait until
1295    /// all its elements are available before producing any output.
1296    /// # Example
1297    /// ```rust
1298    /// # #[cfg(feature = "deploy")] {
1299    /// # use hydro_lang::prelude::*;
1300    /// # use futures::StreamExt;
1301    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1302    /// let tick = process.tick();
1303    /// let stream = process
1304    ///   .source_iter(q!(vec![ 1, 2, 3, 4 ]))
1305    ///   .batch(&tick, nondet!(/** test */));
1306    /// let batch = process
1307    ///   .source_iter(q!(vec![1, 2]))
1308    ///   .batch(&tick, nondet!(/** test */));
1309    /// stream.filter_not_in(batch).all_ticks()
1310    /// # }, |mut stream| async move {
1311    /// # for w in vec![3, 4] {
1312    /// #     assert_eq!(stream.next().await.unwrap(), w);
1313    /// # }
1314    /// # }));
1315    /// # }
1316    /// ```
1317    pub fn filter_not_in<O2: Ordering, B2>(self, other: Stream<T, L, B2, O2, R>) -> Self
1318    where
1319        T: Eq + Hash,
1320        B2: IsBounded,
1321    {
1322        check_matching_location(&self.location, &other.location);
1323
1324        Stream::new(
1325            self.location.clone(),
1326            HydroNode::Difference {
1327                pos: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1328                neg: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
1329                metadata: self
1330                    .location
1331                    .new_node_metadata(Stream::<T, L, Bounded, O, R>::collection_kind()),
1332            },
1333        )
1334    }
1335
1336    /// An operator which allows you to "inspect" each element of a stream without
1337    /// modifying it. The closure `f` is called on a reference to each item. This is
1338    /// mainly useful for debugging, and should not be used to generate side-effects.
1339    ///
1340    /// If the input stream is unordered **and** `f` mutably captures state, `f` must be
1341    /// proven **commutative**: executing it on any two elements in either order must
1342    /// leave the mutably-captured state in the same final value. (The elements
1343    /// themselves pass through unchanged.) Similarly, if the input stream has retries
1344    /// (`AtLeastOnce`), `f` must be proven **idempotent**: executing it on an element
1345    /// twice in a row must leave the mutably-captured state in the same final value as
1346    /// executing it once.
1347    ///
1348    /// # Example
1349    /// ```rust
1350    /// # #[cfg(feature = "deploy")] {
1351    /// # use hydro_lang::prelude::*;
1352    /// # use futures::StreamExt;
1353    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1354    /// let nums = process.source_iter(q!(vec![1, 2]));
1355    /// // prints "1 * 10 = 10" and "2 * 10 = 20"
1356    /// nums.inspect(q!(|x| println!("{} * 10 = {}", x, x * 10)))
1357    /// # }, |mut stream| async move {
1358    /// # for w in vec![1, 2] {
1359    /// #     assert_eq!(stream.next().await.unwrap(), w);
1360    /// # }
1361    /// # }));
1362    /// # }
1363    /// ```
1364    pub fn inspect<F, C, Idemp, const WAS_MUT: bool>(
1365        self,
1366        f: impl IntoQuotedMut<
1367            'a,
1368            F,
1369            OperatorContext<L::DropConsistency, B>,
1370            StreamMapFuncAlgebra<T, B, C, Idemp, L::SimHookScope>,
1371        >,
1372    ) -> Self
1373    where
1374        F: FnMut(&T) + 'a,
1375        C: ValidMutBorrowCommutativityFor<F, T, (), O, WAS_MUT>,
1376        Idemp: ValidMutBorrowIdempotenceFor<F, T, (), R, WAS_MUT>,
1377    {
1378        let f = crate::handoff_ref::with_ref_capture(|| {
1379            let (expr, proof) =
1380                f.splice_fnmut1_borrow_ctx_props(&OperatorContext::<L::DropConsistency, B>::new(
1381                    &self.location.drop_consistency(),
1382                ));
1383            proof.register_proof(&expr);
1384            expr.into()
1385        });
1386
1387        Stream::new(
1388            self.location.clone(),
1389            HydroNode::Inspect {
1390                f,
1391                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1392                metadata: self.location.new_node_metadata(Self::collection_kind()),
1393            },
1394        )
1395    }
1396
1397    /// Executes the provided closure for every element in this stream.
1398    ///
1399    /// If the stream is unordered or has retries, the closure must demonstrate commutativity
1400    /// and/or idempotence via annotations:
1401    /// ```rust,ignore
1402    /// stream.for_each(q!(
1403    ///     |x| *flag_mut |= x,
1404    ///     commutative = manual_proof!(/** boolean OR is commutative */),
1405    ///     idempotent = manual_proof!(/** boolean OR is idempotent */)
1406    /// ));
1407    /// ```
1408    ///
1409    /// **Commutative** here means that executing the closure on any two elements in
1410    /// either order must leave its side effects (e.g. mutably-captured state) in the
1411    /// same final value. **Idempotent** here means that executing the closure on an
1412    /// element twice in a row must leave its side effects in the same final value as
1413    /// executing it once.
1414    ///
1415    /// On a `TotalOrder + ExactlyOnce` stream, no annotations are needed.
1416    ///
1417    /// The closure may capture singletons via `by_ref()` or `by_mut()`, as long as the
1418    /// referenced collection lives at the same location and has the same boundedness as this
1419    /// stream.
1420    pub fn for_each<F: FnMut(T) + 'a, C, I>(
1421        self,
1422        f: impl IntoQuotedMut<
1423            'a,
1424            F,
1425            OperatorContext<L, B>,
1426            AggFuncAlgebra<T, B, C, I, NotProved, L::SimHookScope>,
1427        >,
1428    ) where
1429        C: ValidCommutativityFor<O>,
1430        I: ValidIdempotenceFor<R>,
1431    {
1432        let f = crate::handoff_ref::with_ref_capture(|| {
1433            let (f, proof) =
1434                f.splice_fnmut1_ctx_props(&OperatorContext::<L, B>::new(&self.location));
1435            proof.register_proof(&f);
1436            f.into()
1437        });
1438        self.location
1439            .flow_state()
1440            .borrow_mut()
1441            .push_root(HydroRoot::ForEach {
1442                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1443                f,
1444                op_metadata: HydroIrOpMetadata::new(),
1445            });
1446    }
1447
1448    /// Sends all elements of this stream to a provided [`futures::Sink`], such as an external
1449    /// TCP socket to some other server. You should _not_ use this API for interacting with
1450    /// external clients, instead see [`Location::bidi_external_many_bytes`] and
1451    /// [`Location::bidi_external_many_bincode`]. This should be used for custom, low-level
1452    /// interaction with asynchronous sinks.
1453    pub fn dest_sink<S>(self, sink: impl QuotedWithContext<'a, S, L>)
1454    where
1455        O: IsOrdered,
1456        R: IsExactlyOnce,
1457        S: 'a + futures::Sink<T> + Unpin,
1458    {
1459        self.location
1460            .flow_state()
1461            .borrow_mut()
1462            .push_root(HydroRoot::DestSink {
1463                sink: sink.splice_typed_ctx(&self.location).into(),
1464                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1465                op_metadata: HydroIrOpMetadata::new(),
1466            });
1467    }
1468
1469    /// Maps each element `x` of the stream to `(i, x)`, where `i` is the index of the element.
1470    ///
1471    /// # Example
1472    /// ```rust
1473    /// # #[cfg(feature = "deploy")] {
1474    /// # use hydro_lang::{prelude::*, live_collections::stream::{TotalOrder, ExactlyOnce}};
1475    /// # use futures::StreamExt;
1476    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test::<_, _, _, TotalOrder, ExactlyOnce>(|process| {
1477    /// let tick = process.tick();
1478    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1479    /// numbers.enumerate()
1480    /// # }, |mut stream| async move {
1481    /// // (0, 1), (1, 2), (2, 3), (3, 4)
1482    /// # for w in vec![(0, 1), (1, 2), (2, 3), (3, 4)] {
1483    /// #     assert_eq!(stream.next().await.unwrap(), w);
1484    /// # }
1485    /// # }));
1486    /// # }
1487    /// ```
1488    pub fn enumerate(self) -> Stream<(usize, T), L, B, O, R>
1489    where
1490        O: IsOrdered,
1491        R: IsExactlyOnce,
1492    {
1493        Stream::new(
1494            self.location.clone(),
1495            HydroNode::Enumerate {
1496                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1497                metadata: self.location.new_node_metadata(Stream::<
1498                    (usize, T),
1499                    L,
1500                    B,
1501                    TotalOrder,
1502                    ExactlyOnce,
1503                >::collection_kind()),
1504            },
1505        )
1506    }
1507
1508    /// Combines elements of the stream into a [`Singleton`], by starting with an intitial value,
1509    /// generated by the `init` closure, and then applying the `comb` closure to each element in the stream.
1510    /// Unlike iterators, `comb` takes the accumulator by `&mut` reference, so that it can be modified in place.
1511    ///
1512    /// Depending on the input stream guarantees, the closure may need to be commutative
1513    /// (for unordered streams) or idempotent (for streams with non-deterministic duplicates).
1514    ///
1515    /// # Example
1516    /// ```rust
1517    /// # #[cfg(feature = "deploy")] {
1518    /// # use hydro_lang::prelude::*;
1519    /// # use futures::StreamExt;
1520    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1521    /// let words = process.source_iter(q!(vec!["HELLO", "WORLD"]));
1522    /// words
1523    ///     .fold(q!(|| String::new()), q!(|acc, x| acc.push_str(x)))
1524    ///     .into_stream()
1525    /// # }, |mut stream| async move {
1526    /// // "HELLOWORLD"
1527    /// # assert_eq!(stream.next().await.unwrap(), "HELLOWORLD");
1528    /// # }));
1529    /// # }
1530    /// ```
1531    pub fn fold<A, I, F, C, Idemp, M, B2: SingletonBound>(
1532        self,
1533        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>>,
1534        comb: impl IntoQuotedMut<
1535            'a,
1536            F,
1537            OperatorContext<L, B>,
1538            AggFuncAlgebra<T, B, C, Idemp, M, L::SimHookScope>,
1539        >,
1540    ) -> Singleton<A, L, B2>
1541    where
1542        I: Fn() -> A + 'a,
1543        F: 'a + Fn(&mut A, T),
1544        C: ValidCommutativityFor<O>,
1545        Idemp: ValidIdempotenceFor<R>,
1546        B: ApplyMonotoneStream<M, B2>,
1547    {
1548        let init = init
1549            .splice_fn0_ctx(&OperatorContext::<L, B>::new(&self.location))
1550            .into();
1551        let (comb, proof) =
1552            comb.splice_fn2_borrow_mut_ctx_props(&OperatorContext::<L, B>::new(&self.location));
1553        let ordering_hook = proof.register_proof(&comb);
1554
1555        // Only assume_retries (for idempotence), not assume_ordering.
1556        // The fold hook in the simulator handles ordering non-determinism directly, so an
1557        // ordering hook on the commutativity proof binds to the fold operator itself.
1558        let nondet = nondet!(/** the combinator function is commutative and idempotent */);
1559        let retried: Stream<T, L::DropConsistency, B, O, ExactlyOnce> = self.assume_retries(nondet);
1560
1561        let mut metadata = retried
1562            .location
1563            .new_node_metadata(Singleton::<A, L::DropConsistency, B2>::collection_kind());
1564        metadata.op.sim_hook_id = ordering_hook.map(|hook| hook.id);
1565
1566        let core = HydroNode::Fold {
1567            init,
1568            acc: comb.into(),
1569            input: Box::new(retried.ir_node.replace(HydroNode::Placeholder)),
1570            metadata,
1571            // we do not guarantee consistency at this point because if the algebraic properties
1572            // do not hold in practice, replica consistency may fail to be maintained, so we
1573            // would like the simulator to assert consistency; in the future, this will be dynamic
1574            // based on the proof mechanism
1575        };
1576
1577        Singleton::new(retried.location.clone(), core)
1578            .assert_has_consistency_of(manual_proof!(/** algebraic properties */))
1579    }
1580
1581    /// Combines elements of the stream into an [`Optional`], by starting with the first element in the stream,
1582    /// and then applying the `comb` closure to each element in the stream. The [`Optional`] will be empty
1583    /// until the first element in the input arrives. Unlike iterators, `comb` takes the accumulator by `&mut`
1584    /// reference, so that it can be modified in place.
1585    ///
1586    /// Depending on the input stream guarantees, the closure may need to be commutative
1587    /// (for unordered streams) or idempotent (for streams with non-deterministic duplicates).
1588    ///
1589    /// # Example
1590    /// ```rust
1591    /// # #[cfg(feature = "deploy")] {
1592    /// # use hydro_lang::prelude::*;
1593    /// # use futures::StreamExt;
1594    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1595    /// let bools = process.source_iter(q!(vec![false, true, false]));
1596    /// bools.reduce(q!(|acc, x| *acc |= x)).into_stream()
1597    /// # }, |mut stream| async move {
1598    /// // true
1599    /// # assert_eq!(stream.next().await.unwrap(), true);
1600    /// # }));
1601    /// # }
1602    /// ```
1603    pub fn reduce<F, C, Idemp>(
1604        self,
1605        comb: impl IntoQuotedMut<
1606            'a,
1607            F,
1608            OperatorContext<L, B>,
1609            AggFuncAlgebra<T, B, C, Idemp, NotProved, L::SimHookScope>,
1610        >,
1611    ) -> Optional<T, L, B::AggregatedOptional>
1612    where
1613        F: Fn(&mut T, T) + 'a,
1614        C: ValidCommutativityFor<O>,
1615        Idemp: ValidIdempotenceFor<R>,
1616    {
1617        let (f, proof) =
1618            comb.splice_fn2_borrow_mut_ctx_props(&OperatorContext::<L, B>::new(&self.location));
1619        let ordering_hook = proof.register_proof(&f);
1620
1621        let nondet_retries = nondet!(/** the combinator function is commutative and idempotent */);
1622        let ordered_etc: Stream<T, L::DropConsistency, B> =
1623            self.assume_retries(nondet_retries).assume_ordering(nondet!(
1624                /// the combinator function is commutative; the simulator still explores
1625                /// (or scripts, via the proof's hook) the ordering
1626                hook = ordering_hook
1627            ));
1628
1629        let core = HydroNode::Reduce {
1630            f: f.into(),
1631            input: Box::new(ordered_etc.ir_node.replace(HydroNode::Placeholder)),
1632            metadata: ordered_etc.location.new_node_metadata(Optional::<
1633                T,
1634                L::DropConsistency,
1635                B::AggregatedOptional,
1636            >::collection_kind()),
1637        };
1638
1639        Optional::new(ordered_etc.location.clone(), core)
1640            .assert_has_consistency_of(manual_proof!(/** algebraic properties */))
1641    }
1642
1643    /// Computes the maximum element in the stream as an [`Optional`], which
1644    /// will be empty until the first element in the input arrives.
1645    ///
1646    /// # Example
1647    /// ```rust
1648    /// # #[cfg(feature = "deploy")] {
1649    /// # use hydro_lang::prelude::*;
1650    /// # use futures::StreamExt;
1651    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1652    /// let tick = process.tick();
1653    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1654    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1655    /// batch.max().all_ticks()
1656    /// # }, |mut stream| async move {
1657    /// // 4
1658    /// # assert_eq!(stream.next().await.unwrap(), 4);
1659    /// # }));
1660    /// # }
1661    /// ```
1662    pub fn max(self) -> Optional<T, L, B::AggregatedOptional>
1663    where
1664        T: Ord,
1665    {
1666        self.assume_retries_trusted::<ExactlyOnce>(nondet!(/** max is idempotent */))
1667            .assume_ordering_trusted_bounded::<TotalOrder>(
1668                nondet!(/** max is commutative, but order affects intermediates */),
1669            )
1670            .reduce(q!(|curr, new| {
1671                if new > *curr {
1672                    *curr = new;
1673                }
1674            }))
1675    }
1676
1677    /// Computes the minimum element in the stream as an [`Optional`], which
1678    /// will be empty until the first element in the input arrives.
1679    ///
1680    /// # Example
1681    /// ```rust
1682    /// # #[cfg(feature = "deploy")] {
1683    /// # use hydro_lang::prelude::*;
1684    /// # use futures::StreamExt;
1685    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1686    /// let tick = process.tick();
1687    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1688    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1689    /// batch.min().all_ticks()
1690    /// # }, |mut stream| async move {
1691    /// // 1
1692    /// # assert_eq!(stream.next().await.unwrap(), 1);
1693    /// # }));
1694    /// # }
1695    /// ```
1696    pub fn min(self) -> Optional<T, L, B::AggregatedOptional>
1697    where
1698        T: Ord,
1699    {
1700        self.assume_retries_trusted::<ExactlyOnce>(nondet!(/** min is idempotent */))
1701            .assume_ordering_trusted_bounded::<TotalOrder>(
1702                nondet!(/** max is commutative, but order affects intermediates */),
1703            )
1704            .reduce(q!(|curr, new| {
1705                if new < *curr {
1706                    *curr = new;
1707                }
1708            }))
1709    }
1710
1711    /// Computes the first element in the stream as an [`Optional`], which
1712    /// will be empty until the first element in the input arrives.
1713    ///
1714    /// This requires the stream to have a [`TotalOrder`] guarantee, otherwise
1715    /// re-ordering of elements may cause the first element to change.
1716    ///
1717    /// # Example
1718    /// ```rust
1719    /// # #[cfg(feature = "deploy")] {
1720    /// # use hydro_lang::prelude::*;
1721    /// # use futures::StreamExt;
1722    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1723    /// let tick = process.tick();
1724    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1725    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1726    /// batch.first().all_ticks()
1727    /// # }, |mut stream| async move {
1728    /// // 1
1729    /// # assert_eq!(stream.next().await.unwrap(), 1);
1730    /// # }));
1731    /// # }
1732    /// ```
1733    pub fn first(self) -> Optional<T, L, B::AggregatedOptional>
1734    where
1735        O: IsOrdered,
1736    {
1737        self.make_totally_ordered()
1738            .assume_retries_trusted::<ExactlyOnce>(nondet!(/** first is idempotent */))
1739            .generator(q!(|| ()), q!(|_, item| Generate::Return(item)))
1740            .reduce(q!(|_, _| {}))
1741    }
1742
1743    /// Computes the last element in the stream as an [`Optional`], which
1744    /// will be empty until an element in the input arrives.
1745    ///
1746    /// This requires the stream to have a [`TotalOrder`] guarantee, otherwise
1747    /// re-ordering of elements may cause the last element to change.
1748    ///
1749    /// # Example
1750    /// ```rust
1751    /// # #[cfg(feature = "deploy")] {
1752    /// # use hydro_lang::prelude::*;
1753    /// # use futures::StreamExt;
1754    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1755    /// let tick = process.tick();
1756    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1757    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1758    /// batch.last().all_ticks()
1759    /// # }, |mut stream| async move {
1760    /// // 4
1761    /// # assert_eq!(stream.next().await.unwrap(), 4);
1762    /// # }));
1763    /// # }
1764    /// ```
1765    pub fn last(self) -> Optional<T, L, B::AggregatedOptional>
1766    where
1767        O: IsOrdered,
1768    {
1769        self.make_totally_ordered()
1770            .assume_retries_trusted::<ExactlyOnce>(nondet!(/** last is idempotent */))
1771            .reduce(q!(|curr, new| *curr = new))
1772    }
1773
1774    /// Returns a stream containing at most the first `n` elements of the input stream,
1775    /// preserving the original order. Similar to `LIMIT` in SQL.
1776    ///
1777    /// This requires the stream to have a [`TotalOrder`] guarantee and [`ExactlyOnce`]
1778    /// retries, since the result depends on the order and cardinality of elements.
1779    ///
1780    /// # Example
1781    /// ```rust
1782    /// # #[cfg(feature = "deploy")] {
1783    /// # use hydro_lang::prelude::*;
1784    /// # use futures::StreamExt;
1785    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1786    /// let numbers = process.source_iter(q!(vec![10, 20, 30, 40, 50]));
1787    /// numbers.limit(q!(3))
1788    /// # }, |mut stream| async move {
1789    /// // 10, 20, 30
1790    /// # for w in vec![10, 20, 30] {
1791    /// #     assert_eq!(stream.next().await.unwrap(), w);
1792    /// # }
1793    /// # }));
1794    /// # }
1795    /// ```
1796    pub fn limit(
1797        self,
1798        n: impl QuotedWithContext<'a, usize, OperatorContext<L, B>> + Copy + 'a,
1799    ) -> Stream<T, L, B, TotalOrder, ExactlyOnce>
1800    where
1801        O: IsOrdered,
1802        R: IsExactlyOnce,
1803    {
1804        self.generator(
1805            q!(|| 0usize),
1806            q!(move |count, item| {
1807                if *count == n {
1808                    Generate::Break
1809                } else {
1810                    *count += 1;
1811                    if *count == n {
1812                        Generate::Return(item)
1813                    } else {
1814                        Generate::Yield(item)
1815                    }
1816                }
1817            }),
1818        )
1819    }
1820
1821    /// Collects all the elements of this stream into a single [`Vec`] element.
1822    ///
1823    /// If the input stream is [`Unbounded`], the output [`Singleton`] will be [`Unbounded`] as
1824    /// well, which means that the value of the [`Vec`] will asynchronously grow as new elements
1825    /// are added. On such a value, you can use [`Singleton::snapshot`] to grab an instance of
1826    /// the vector at an arbitrary point in time.
1827    ///
1828    /// # Example
1829    /// ```rust
1830    /// # #[cfg(feature = "deploy")] {
1831    /// # use hydro_lang::prelude::*;
1832    /// # use futures::StreamExt;
1833    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1834    /// let tick = process.tick();
1835    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1836    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1837    /// batch.collect_vec().all_ticks() // emit each tick's Vec into an unbounded stream
1838    /// # }, |mut stream| async move {
1839    /// // [ vec![1, 2, 3, 4] ]
1840    /// # for w in vec![vec![1, 2, 3, 4]] {
1841    /// #     assert_eq!(stream.next().await.unwrap(), w);
1842    /// # }
1843    /// # }));
1844    /// # }
1845    /// ```
1846    pub fn collect_vec(self) -> Singleton<Vec<T>, L, B>
1847    where
1848        O: IsOrdered,
1849        R: IsExactlyOnce,
1850    {
1851        self.make_totally_ordered().make_exactly_once().fold(
1852            q!(|| vec![]),
1853            q!(|acc, v| {
1854                acc.push(v);
1855            }),
1856        )
1857    }
1858
1859    /// Applies a function to each element of the stream, maintaining an internal state (accumulator)
1860    /// and emitting each intermediate result.
1861    ///
1862    /// Unlike `fold` which only returns the final accumulated value, `scan` produces a new stream
1863    /// containing all intermediate accumulated values. The scan operation can also terminate early
1864    /// by returning `None`.
1865    ///
1866    /// The function takes a mutable reference to the accumulator and the current element, and returns
1867    /// an `Option<U>`. If the function returns `Some(value)`, `value` is emitted to the output stream.
1868    /// If the function returns `None`, the stream is terminated and no more elements are processed.
1869    ///
1870    /// The `init` and `f` closures may capture bounded singletons, optionals, or streams by
1871    /// reference via [`by_ref()`](crate::live_collections::Singleton::by_ref), as long as the
1872    /// referenced collection lives at the same location and has the same boundedness as this
1873    /// stream.
1874    ///
1875    /// # Examples
1876    ///
1877    /// Basic usage - running sum:
1878    /// ```rust
1879    /// # #[cfg(feature = "deploy")] {
1880    /// # use hydro_lang::prelude::*;
1881    /// # use futures::StreamExt;
1882    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1883    /// process.source_iter(q!(vec![1, 2, 3, 4])).scan(
1884    ///     q!(|| 0),
1885    ///     q!(|acc, x| {
1886    ///         *acc += x;
1887    ///         Some(*acc)
1888    ///     }),
1889    /// )
1890    /// # }, |mut stream| async move {
1891    /// // Output: 1, 3, 6, 10
1892    /// # for w in vec![1, 3, 6, 10] {
1893    /// #     assert_eq!(stream.next().await.unwrap(), w);
1894    /// # }
1895    /// # }));
1896    /// # }
1897    /// ```
1898    ///
1899    /// Early termination example:
1900    /// ```rust
1901    /// # #[cfg(feature = "deploy")] {
1902    /// # use hydro_lang::prelude::*;
1903    /// # use futures::StreamExt;
1904    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1905    /// process.source_iter(q!(vec![1, 2, 3, 4])).scan(
1906    ///     q!(|| 1),
1907    ///     q!(|state, x| {
1908    ///         *state = *state * x;
1909    ///         if *state > 6 {
1910    ///             None // Terminate the stream
1911    ///         } else {
1912    ///             Some(-*state)
1913    ///         }
1914    ///     }),
1915    /// )
1916    /// # }, |mut stream| async move {
1917    /// // Output: -1, -2, -6
1918    /// # for w in vec![-1, -2, -6] {
1919    /// #     assert_eq!(stream.next().await.unwrap(), w);
1920    /// # }
1921    /// # }));
1922    /// # }
1923    /// ```
1924    pub fn scan<A, U, I, F>(
1925        self,
1926        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>>,
1927        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>>,
1928    ) -> Stream<U, L, B, TotalOrder, ExactlyOnce>
1929    where
1930        O: IsOrdered,
1931        R: IsExactlyOnce,
1932        I: Fn() -> A + 'a,
1933        F: Fn(&mut A, T) -> Option<U> + 'a,
1934    {
1935        let init = crate::handoff_ref::with_ref_capture(|| {
1936            init.splice_fn0_ctx(&OperatorContext::<L, B>::new(&self.location))
1937                .into()
1938        });
1939        let f = crate::handoff_ref::with_ref_capture(|| {
1940            f.splice_fn2_borrow_mut_ctx(&OperatorContext::<L, B>::new(&self.location))
1941                .into()
1942        });
1943
1944        Stream::new(
1945            self.location.clone(),
1946            HydroNode::Scan {
1947                init,
1948                acc: f,
1949                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1950                metadata: self.location.new_node_metadata(
1951                    Stream::<U, L, B, TotalOrder, ExactlyOnce>::collection_kind(),
1952                ),
1953            },
1954        )
1955    }
1956
1957    /// Async version of [`Stream::scan`]. Applies an async function to each element of the
1958    /// stream, maintaining an internal state (accumulator) and emitting the values returned
1959    /// by the function.
1960    ///
1961    /// The closure runs synchronously (so it can mutate the accumulator), then returns a
1962    /// future. The future is polled to completion. If it resolves to `Some`, the value is
1963    /// emitted. If it resolves to `None`, the item is filtered out.
1964    ///
1965    /// The `init` and `f` closures may capture bounded singletons, optionals, or streams by
1966    /// reference via [`by_ref()`](crate::live_collections::Singleton::by_ref), as long as the
1967    /// referenced collection lives at the same location and has the same boundedness as this
1968    /// stream.
1969    ///
1970    /// # Examples
1971    ///
1972    /// ```rust
1973    /// # #[cfg(feature = "deploy")] {
1974    /// # use hydro_lang::prelude::*;
1975    /// # use futures::StreamExt;
1976    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1977    /// process
1978    ///     .source_iter(q!(vec![1, 2, 3, 4]))
1979    ///     .scan_async_blocking(
1980    ///         q!(|| 0),
1981    ///         q!(|acc, x| {
1982    ///             *acc += x;
1983    ///             let val = *acc;
1984    ///             async move { Some(val) }
1985    ///         }),
1986    ///     )
1987    /// # }, |mut stream| async move {
1988    /// // Output: 1, 3, 6, 10
1989    /// # for w in vec![1, 3, 6, 10] {
1990    /// #     assert_eq!(stream.next().await.unwrap(), w);
1991    /// # }
1992    /// # }));
1993    /// # }
1994    /// ```
1995    pub fn scan_async_blocking<A, U, I, F, Fut>(
1996        self,
1997        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>>,
1998        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>>,
1999    ) -> Stream<U, L, B, TotalOrder, ExactlyOnce>
2000    where
2001        O: IsOrdered,
2002        R: IsExactlyOnce,
2003        I: Fn() -> A + 'a,
2004        F: Fn(&mut A, T) -> Fut + 'a,
2005        Fut: Future<Output = Option<U>> + 'a,
2006    {
2007        let init = crate::handoff_ref::with_ref_capture(|| {
2008            init.splice_fn0_ctx(&OperatorContext::<L, B>::new(&self.location))
2009                .into()
2010        });
2011        let f = crate::handoff_ref::with_ref_capture(|| {
2012            f.splice_fn2_borrow_mut_ctx(&OperatorContext::<L, B>::new(&self.location))
2013                .into()
2014        });
2015
2016        Stream::new(
2017            self.location.clone(),
2018            HydroNode::ScanAsyncBlocking {
2019                init,
2020                acc: f,
2021                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2022                metadata: self.location.new_node_metadata(
2023                    Stream::<U, L, B, TotalOrder, ExactlyOnce>::collection_kind(),
2024                ),
2025            },
2026        )
2027    }
2028
2029    /// Iteratively processes the elements of the stream using a state machine that can yield
2030    /// elements as it processes its inputs. This is designed to mirror the unstable generator
2031    /// syntax in Rust, without requiring special syntax.
2032    ///
2033    /// Like [`Stream::scan`], this function takes in an initializer that emits the initial
2034    /// state. The second argument defines the processing logic, taking in a mutable reference
2035    /// to the state and the value to be processed. It emits a [`Generate`] value, whose
2036    /// variants define what is emitted and whether further inputs should be processed.
2037    ///
2038    /// The `init` and `f` closures may capture bounded singletons, optionals, or streams by
2039    /// reference via [`by_ref()`](crate::live_collections::Singleton::by_ref), as long as the
2040    /// referenced collection lives at the same location and has the same boundedness as this
2041    /// stream.
2042    ///
2043    /// # Example
2044    /// ```rust
2045    /// # #[cfg(feature = "deploy")] {
2046    /// # use hydro_lang::prelude::*;
2047    /// # use futures::StreamExt;
2048    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2049    /// process.source_iter(q!(vec![1, 3, 100, 10])).generator(
2050    ///     q!(|| 0),
2051    ///     q!(|acc, x| {
2052    ///         *acc += x;
2053    ///         if *acc > 100 {
2054    ///             hydro_lang::live_collections::keyed_stream::Generate::Return("done!".to_owned())
2055    ///         } else if *acc % 2 == 0 {
2056    ///             hydro_lang::live_collections::keyed_stream::Generate::Yield("even".to_owned())
2057    ///         } else {
2058    ///             hydro_lang::live_collections::keyed_stream::Generate::Continue
2059    ///         }
2060    ///     }),
2061    /// )
2062    /// # }, |mut stream| async move {
2063    /// // Output: "even", "done!"
2064    /// # let mut results = Vec::new();
2065    /// # for _ in 0..2 {
2066    /// #     results.push(stream.next().await.unwrap());
2067    /// # }
2068    /// # results.sort();
2069    /// # assert_eq!(results, vec!["done!".to_owned(), "even".to_owned()]);
2070    /// # }));
2071    /// # }
2072    /// ```
2073    pub fn generator<A, U, I, F>(
2074        self,
2075        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>> + Copy,
2076        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
2077    ) -> Stream<U, L, B, TotalOrder, ExactlyOnce>
2078    where
2079        O: IsOrdered,
2080        R: IsExactlyOnce,
2081        I: Fn() -> A + 'a,
2082        F: Fn(&mut A, T) -> Generate<U> + 'a,
2083    {
2084        let init: ManualExpr<I, _> =
2085            ManualExpr::new(move |ctx: &OperatorContext<L, B>| init.splice_fn0_ctx(ctx));
2086        let f: ManualExpr<F, _> =
2087            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn2_borrow_mut_ctx(ctx));
2088
2089        let this = self.make_totally_ordered().make_exactly_once();
2090
2091        // State is Option<Option<A>>:
2092        //   None = not yet initialized
2093        //   Some(Some(a)) = active with state a
2094        //   Some(None) = terminated
2095        let scan_init = crate::handoff_ref::with_ref_capture(|| {
2096            q!(|| None)
2097                .splice_fn0_ctx::<Option<Option<A>>>(&this.location)
2098                .into()
2099        });
2100        let scan_f = crate::handoff_ref::with_ref_capture(|| {
2101            q!(move |state: &mut Option<Option<_>>, v| {
2102                if state.is_none() {
2103                    *state = Some(Some(init()));
2104                }
2105                match state {
2106                    Some(Some(state_value)) => match f(state_value, v) {
2107                        Generate::Yield(out) => Some(Some(out)),
2108                        Generate::Return(out) => {
2109                            *state = Some(None);
2110                            Some(Some(out))
2111                        }
2112                        // Unlike KeyedStream, we can terminate the scan directly on
2113                        // Break/Return because there is only one state (no other keys
2114                        // that still need processing).
2115                        Generate::Break => None,
2116                        Generate::Continue => Some(None),
2117                    },
2118                    // State is Some(None) after Return; terminate the scan.
2119                    _ => None,
2120                }
2121            })
2122            .splice_fn2_borrow_mut_ctx::<Option<Option<A>>, T, _>(&OperatorContext::<L, B>::new(
2123                &this.location,
2124            ))
2125            .into()
2126        });
2127
2128        let scan_node = HydroNode::Scan {
2129            init: scan_init,
2130            acc: scan_f,
2131            input: Box::new(this.ir_node.replace(HydroNode::Placeholder)),
2132            metadata: this.location.new_node_metadata(Stream::<
2133                Option<U>,
2134                L,
2135                B,
2136                TotalOrder,
2137                ExactlyOnce,
2138            >::collection_kind()),
2139        };
2140
2141        let flatten_f = q!(|d| d)
2142            .splice_fn1_ctx::<Option<U>, _>(&this.location)
2143            .into();
2144        let flatten_node = HydroNode::FlatMap {
2145            f: flatten_f,
2146            input: Box::new(scan_node),
2147            metadata: this
2148                .location
2149                .new_node_metadata(Stream::<U, L, B, TotalOrder, ExactlyOnce>::collection_kind()),
2150        };
2151
2152        Stream::new(this.location.clone(), flatten_node)
2153    }
2154
2155    /// Given a time interval, returns a stream corresponding to samples taken from the
2156    /// stream roughly at that interval. The output will have elements in the same order
2157    /// as the input, but with arbitrary elements skipped between samples. There is also
2158    /// no guarantee on the exact timing of the samples.
2159    ///
2160    /// # Non-Determinism
2161    /// The output stream is non-deterministic in which elements are sampled, since this
2162    /// is controlled by a clock.
2163    ///
2164    /// In simulation tests, the internal batching of elements and of clock samples can be
2165    /// scripted through the guard's composite hook payload, e.g.
2166    /// `nondet!(/** reason */ hook = (elements_hook.into(), None))`.
2167    #[cfg(feature = "tokio")]
2168    #[expect(
2169        clippy::type_complexity,
2170        reason = "composite hook payload names each internal operator's handle type"
2171    )]
2172    pub fn sample_every(
2173        self,
2174        interval: impl QuotedWithContext<'a, std::time::Duration, L> + Copy + 'a,
2175        mut nondet: NonDet<(
2176            Option<crate::sim_hooks::BatchHook<T, O, R, L::SimHookScope>>,
2177            Option<crate::sim_hooks::BatchHook<(), TotalOrder, AtLeastOnce, L::SimHookScope>>,
2178        )>,
2179    ) -> Stream<T, L::DropConsistency, Unbounded, O, AtLeastOnce>
2180    where
2181        L: TopLevel<'a>,
2182    {
2183        let samples = self.location.source_interval(interval);
2184        let (elements_hook, samples_hook) = nondet.take_hook();
2185
2186        let tick = self.location.tick();
2187        self.batch(
2188            &tick,
2189            nondet!(
2190                /// which elements are batched between samples is captured by the caller's guard
2191                hook = elements_hook
2192            ),
2193        )
2194        .filter_if(
2195            samples
2196                .batch(
2197                    &tick,
2198                    nondet!(
2199                        /// sample timing is captured by the caller's guard
2200                        hook = samples_hook
2201                    ),
2202                )
2203                .first()
2204                .is_some(),
2205        )
2206        .all_ticks()
2207        .weaken_retries()
2208    }
2209
2210    /// Given a timeout duration, returns an [`Optional`]  which will have a value if the
2211    /// stream has not emitted a value since that duration.
2212    ///
2213    /// The clock is read once per tick (`use::time`); the time of the latest arrival is a
2214    /// `use::state` register stamped with that reading. No wall-clock read hides inside a
2215    /// closure: the verdict at a tick is a function of the tick's inputs and its time.
2216    ///
2217    /// # Non-Determinism
2218    /// Timeout relies on non-deterministic sampling of the stream, so depending on when
2219    /// samples take place, timeouts may be non-deterministically generated or missed,
2220    /// and the notification of the timeout may be delayed as well. There is also no
2221    /// guarantee on how long the [`Optional`] will have a value after the timeout is
2222    /// detected based on when the next sample is taken.
2223    #[cfg(feature = "tokio")]
2224    pub fn timeout(
2225        self,
2226        duration: impl QuotedWithContext<
2227            'a,
2228            std::time::Duration,
2229            OperatorContext<Tick<L::DropConsistency>, Bounded>,
2230        > + Copy
2231        + 'a,
2232        nondet: NonDet,
2233    ) -> Optional<(), L::DropConsistency, Unbounded>
2234    where
2235        L: TopLevel<'a>,
2236    {
2237        sliced! {
2238            let new_arrivals = use::batch(self, nondet!(
2239                /// sampling timing is captured by the caller's guard
2240                nondet
2241            ));
2242            let now = use::time(nondet!(
2243                /// the verdict depends on when the tick runs; captured by the caller's guard
2244                nondet
2245            ));
2246            let mut latest_received = use::state_null::<Optional<tokio::time::Instant, Tick<_>, Bounded>>();
2247
2248            // the latest arrival is stamped with this tick's clock reading
2249            latest_received = now
2250                .clone()
2251                .filter_if(new_arrivals.is_empty().map(q!(|empty| !empty)))
2252                .or(latest_received);
2253
2254            latest_received
2255                .clone()
2256                .into_singleton()
2257                .zip(now)
2258                .filter_map(q!(move |(latest_received, now)| {
2259                    if let Some(latest_received) = latest_received {
2260                        if now.duration_since(latest_received) > duration {
2261                            Some(())
2262                        } else {
2263                            None
2264                        }
2265                    } else {
2266                        Some(())
2267                    }
2268                }))
2269        }
2270    }
2271
2272    /// Shifts this stream into an atomic context, which guarantees that any downstream logic
2273    /// will all be executed synchronously before any outputs are yielded (in [`Stream::end_atomic`]).
2274    ///
2275    /// This is useful to enforce local consistency constraints, such as ensuring that a write is
2276    /// processed before an acknowledgement is emitted.
2277    pub fn atomic(self) -> Stream<T, Atomic<L>, B, O, R>
2278    where
2279        L: TopLevel<'a>,
2280    {
2281        let out_location = Atomic {
2282            tick: self.location.tick(),
2283        };
2284        Stream::new(
2285            out_location.clone(),
2286            HydroNode::BeginAtomic {
2287                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2288                metadata: out_location
2289                    .new_node_metadata(Stream::<T, Atomic<L>, B, O, R>::collection_kind()),
2290            },
2291        )
2292    }
2293
2294    /// Given a tick, returns a stream corresponding to a batch of elements segmented by
2295    /// that tick. These batches are guaranteed to be contiguous across ticks and preserve
2296    /// the order of the input. The output stream will execute in the [`Tick`] that was
2297    /// used to create the atomic section.
2298    ///
2299    /// # Non-Determinism
2300    /// The batch boundaries are non-deterministic and may change across executions.
2301    ///
2302    /// In simulation tests, the batching decisions can be scripted by attaching a
2303    /// [`BatchHook`](crate::sim_hooks::BatchHook) to the guard via
2304    /// `nondet!(/** reason */ hook = my_hook)`.
2305    pub fn batch<L2: Location<'a, DropConsistency = L::DropConsistency>>(
2306        self,
2307        tick: &Tick<L2>,
2308        mut nondet: NonDet<Option<crate::sim_hooks::BatchHook<T, O, R, L::SimHookScope>>>,
2309    ) -> Stream<T, Tick<L::DropConsistency>, Bounded, O, R> {
2310        assert_eq!(
2311            Location::id(tick.parent_location()),
2312            Location::id(&self.location)
2313        );
2314
2315        let mut metadata =
2316            tick.new_node_metadata(Stream::<T, Tick<L>, Bounded, O, R>::collection_kind());
2317        metadata.op.sim_hook_id = nondet.take_hook().map(|h| h.id);
2318        Stream::new(
2319            tick.drop_consistency(),
2320            HydroNode::Batch {
2321                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2322                metadata,
2323            },
2324        )
2325    }
2326
2327    /// An operator which allows you to "name" a `HydroNode`.
2328    /// This is only used for testing, to correlate certain `HydroNode`s with IDs.
2329    pub fn ir_node_named(self, name: &str) -> Stream<T, L, B, O, R> {
2330        {
2331            let mut node = self.ir_node.borrow_mut();
2332            let metadata = node.metadata_mut();
2333            metadata.tag = Some(name.to_owned());
2334        }
2335        self
2336    }
2337
2338    /// Turns this [`Stream`] into a [`Optional`], under the invariant assumption that there is at
2339    /// most one element. If this invariant is broken, the program may exhibit undefined behavior,
2340    /// so uses must be carefully vetted.
2341    pub(crate) fn cast_at_most_one_element(self) -> Optional<T, L, B>
2342    where
2343        B: IsBounded,
2344    {
2345        Optional::new(
2346            self.location.clone(),
2347            HydroNode::Cast {
2348                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2349                metadata: self
2350                    .location
2351                    .new_node_metadata(Optional::<T, L, B>::collection_kind()),
2352            },
2353        )
2354    }
2355
2356    pub(crate) fn use_ordering_type<O2: Ordering>(self) -> Stream<T, L, B, O2, R> {
2357        if O::ORDERING_KIND == O2::ORDERING_KIND {
2358            Stream::new(
2359                self.location.clone(),
2360                self.ir_node.replace(HydroNode::Placeholder),
2361            )
2362        } else {
2363            panic!(
2364                "Runtime ordering {:?} did not match requested cast {:?}.",
2365                O::ORDERING_KIND,
2366                O2::ORDERING_KIND
2367            )
2368        }
2369    }
2370
2371    /// Explicitly "casts" the stream to a type with a different ordering
2372    /// guarantee. Useful in unsafe code where the ordering cannot be proven
2373    /// by the type-system.
2374    ///
2375    /// # Non-Determinism
2376    /// This function is used as an escape hatch, and any mistakes in the
2377    /// provided ordering guarantee will propagate into the guarantees
2378    /// for the rest of the program.
2379    pub fn assume_ordering<O2: Ordering>(
2380        self,
2381        mut nondet: NonDet<Option<crate::sim_hooks::OrderingHook<T, B, R, L::SimHookScope>>>,
2382    ) -> Stream<T, L::DropConsistency, B, O2, R> {
2383        if O::ORDERING_KIND == O2::ORDERING_KIND {
2384            self.use_ordering_type().weaken_consistency()
2385        } else if O2::ORDERING_KIND == StreamOrder::NoOrder {
2386            // We can always weaken the ordering guarantee
2387            let target_location = self.location().drop_consistency();
2388            Stream::new(
2389                target_location.clone(),
2390                HydroNode::Cast {
2391                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2392                    metadata: target_location
2393                        .new_node_metadata(Stream::<T, L, B, O2, R>::collection_kind()),
2394                },
2395            )
2396        } else {
2397            let target_location = self.location().drop_consistency();
2398            let mut metadata =
2399                target_location.new_node_metadata(Stream::<T, L, B, O2, R>::collection_kind());
2400            metadata.op.sim_hook_id = nondet.take_hook().map(|hook| hook.id);
2401            Stream::new(
2402                target_location,
2403                HydroNode::ObserveNonDet {
2404                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2405                    trusted: false,
2406                    metadata,
2407                },
2408            )
2409        }
2410    }
2411
2412    // like `assume_ordering_trusted`, but only if the input stream is bounded and therefore
2413    // intermediate states will not be revealed
2414    fn assume_ordering_trusted_bounded<O2: Ordering>(
2415        self,
2416        nondet: NonDet,
2417    ) -> Stream<T, L, B, O2, R> {
2418        if B::BOUNDED {
2419            self.assume_ordering_trusted(nondet)
2420        } else {
2421            let self_location = self.location.clone();
2422            let inner: Stream<T, L::DropConsistency, B, O2, R> = self.assume_ordering(nondet!(
2423                /// the unbounded stream exposes ordering non-determinism in intermediate states
2424                nondet
2425            ));
2426            Stream::new(self_location, inner.ir_node.replace(HydroNode::Placeholder))
2427        }
2428    }
2429
2430    // only for internal APIs that have been carefully vetted to ensure that the non-determinism
2431    // is not observable
2432    pub(crate) fn assume_ordering_trusted<O2: Ordering>(
2433        self,
2434        _nondet: NonDet,
2435    ) -> Stream<T, L, B, O2, R> {
2436        if O::ORDERING_KIND == O2::ORDERING_KIND {
2437            self.use_ordering_type()
2438        } else if O2::ORDERING_KIND == StreamOrder::NoOrder {
2439            // We can always weaken the ordering guarantee
2440            Stream::new(
2441                self.location.clone(),
2442                HydroNode::Cast {
2443                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2444                    metadata: self
2445                        .location
2446                        .new_node_metadata(Stream::<T, L, B, O2, R>::collection_kind()),
2447                },
2448            )
2449        } else {
2450            Stream::new(
2451                self.location.clone(),
2452                HydroNode::ObserveNonDet {
2453                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2454                    trusted: true,
2455                    metadata: self
2456                        .location
2457                        .new_node_metadata(Stream::<T, L, B, O2, R>::collection_kind()),
2458                },
2459            )
2460        }
2461    }
2462
2463    #[deprecated = "use `weaken_ordering::<NoOrder>()` instead"]
2464    /// Weakens the ordering guarantee provided by the stream to [`NoOrder`],
2465    /// which is always safe because that is the weakest possible guarantee.
2466    pub fn weakest_ordering(self) -> Stream<T, L, B, NoOrder, R> {
2467        self.weaken_ordering::<NoOrder>()
2468    }
2469
2470    /// Weakens the ordering guarantee provided by the stream to `O2`, with the type-system
2471    /// enforcing that `O2` is weaker than the input ordering guarantee.
2472    pub fn weaken_ordering<O2: WeakerOrderingThan<O>>(self) -> Stream<T, L, B, O2, R> {
2473        let nondet = nondet!(/** this is a weaker ordering guarantee, so it is safe to assume */);
2474        self.assume_ordering_trusted::<O2>(nondet)
2475    }
2476
2477    /// Strengthens the ordering guarantee to `TotalOrder`, given that `O: IsOrdered`, which
2478    /// implies that `O == TotalOrder`.
2479    pub fn make_totally_ordered(self) -> Stream<T, L, B, TotalOrder, R>
2480    where
2481        O: IsOrdered,
2482    {
2483        self.assume_ordering_trusted(nondet!(/** no-op */))
2484    }
2485
2486    /// Explicitly "casts" the stream to a type with a different retries
2487    /// guarantee. Useful in unsafe code where the lack of retries cannot
2488    /// be proven by the type-system.
2489    ///
2490    /// # Non-Determinism
2491    /// This function is used as an escape hatch, and any mistakes in the
2492    /// provided retries guarantee will propagate into the guarantees
2493    /// for the rest of the program.
2494    ///
2495    /// In simulation tests, the retry decisions (how many times each element is
2496    /// observed) can be scripted by attaching a
2497    /// [`RetriesHook`](crate::sim_hooks::RetriesHook) to the guard via
2498    /// `nondet!(/** reason */ hook = my_hook)`. Because every element admits arbitrarily
2499    /// many retries, this non-determinism cannot be explored autonomously: simulating a
2500    /// program containing a strengthening `assume_retries` **requires** a bound hook.
2501    pub fn assume_retries<R2: Retries>(
2502        self,
2503        mut nondet: NonDet<Option<crate::sim_hooks::RetriesHook<T, O, B, L::SimHookScope>>>,
2504    ) -> Stream<T, L::DropConsistency, B, O, R2> {
2505        if R::RETRIES_KIND == R2::RETRIES_KIND {
2506            Stream::new(
2507                self.location.drop_consistency(),
2508                self.ir_node.replace(HydroNode::Placeholder),
2509            )
2510        } else if R2::RETRIES_KIND == StreamRetry::AtLeastOnce {
2511            // We can always weaken the retries guarantee
2512            let target_location = self.location.drop_consistency();
2513            Stream::new(
2514                target_location.clone(),
2515                HydroNode::Cast {
2516                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2517                    metadata: target_location
2518                        .new_node_metadata(Stream::<T, L, B, O, R2>::collection_kind()),
2519                },
2520            )
2521        } else {
2522            let target_location = self.location.drop_consistency();
2523            let mut metadata =
2524                target_location.new_node_metadata(Stream::<T, L, B, O, R2>::collection_kind());
2525            metadata.op.sim_hook_id = nondet.take_hook().map(|hook| hook.id);
2526            Stream::new(
2527                target_location.clone(),
2528                HydroNode::ObserveNonDet {
2529                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2530                    trusted: false,
2531                    metadata,
2532                },
2533            )
2534        }
2535    }
2536
2537    // only for internal APIs that have been carefully vetted to ensure that the non-determinism
2538    // is not observable
2539    fn assume_retries_trusted<R2: Retries>(self, _nondet: NonDet) -> Stream<T, L, B, O, R2> {
2540        if R::RETRIES_KIND == R2::RETRIES_KIND {
2541            Stream::new(
2542                self.location.clone(),
2543                self.ir_node.replace(HydroNode::Placeholder),
2544            )
2545        } else if R2::RETRIES_KIND == StreamRetry::AtLeastOnce {
2546            // We can always weaken the retries guarantee
2547            Stream::new(
2548                self.location.clone(),
2549                HydroNode::Cast {
2550                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2551                    metadata: self
2552                        .location
2553                        .new_node_metadata(Stream::<T, L, B, O, R2>::collection_kind()),
2554                },
2555            )
2556        } else {
2557            Stream::new(
2558                self.location.clone(),
2559                HydroNode::ObserveNonDet {
2560                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2561                    trusted: true,
2562                    metadata: self
2563                        .location
2564                        .new_node_metadata(Stream::<T, L, B, O, R2>::collection_kind()),
2565                },
2566            )
2567        }
2568    }
2569
2570    #[deprecated = "use `weaken_retries::<AtLeastOnce>()` instead"]
2571    /// Weakens the retries guarantee provided by the stream to [`AtLeastOnce`],
2572    /// which is always safe because that is the weakest possible guarantee.
2573    pub fn weakest_retries(self) -> Stream<T, L, B, O, AtLeastOnce> {
2574        self.weaken_retries::<AtLeastOnce>()
2575    }
2576
2577    /// Weakens the retries guarantee provided by the stream to `R2`, with the type-system
2578    /// enforcing that `R2` is weaker than the input retries guarantee.
2579    pub fn weaken_retries<R2: WeakerRetryThan<R>>(self) -> Stream<T, L, B, O, R2> {
2580        let nondet = nondet!(/** this is a weaker retry guarantee, so it is safe to assume */);
2581        self.assume_retries_trusted::<R2>(nondet)
2582    }
2583
2584    /// Strengthens the retry guarantee to `ExactlyOnce`, given that `R: IsExactlyOnce`, which
2585    /// implies that `R == ExactlyOnce`.
2586    pub fn make_exactly_once(self) -> Stream<T, L, B, O, ExactlyOnce>
2587    where
2588        R: IsExactlyOnce,
2589    {
2590        self.assume_retries_trusted(nondet!(/** no-op */))
2591    }
2592
2593    /// Strengthens the boundedness guarantee to `Bounded`, given that `B: IsBounded`, which
2594    /// implies that `B == Bounded`.
2595    pub fn make_bounded(self) -> Stream<T, L, Bounded, O, R>
2596    where
2597        B: IsBounded,
2598    {
2599        self.weaken_boundedness()
2600    }
2601
2602    /// Weakens the boundedness guarantee to an arbitrary boundedness `B2`, given that `B: IsBounded`,
2603    /// which implies that `B == Bounded`.
2604    pub fn weaken_boundedness<B2: Boundedness>(self) -> Stream<T, L, B2, O, R> {
2605        if B::BOUNDED == B2::BOUNDED {
2606            Stream::new(
2607                self.location.clone(),
2608                self.ir_node.replace(HydroNode::Placeholder),
2609            )
2610        } else {
2611            // We can always weaken the boundedness
2612            Stream::new(
2613                self.location.clone(),
2614                HydroNode::Cast {
2615                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2616                    metadata: self
2617                        .location
2618                        .new_node_metadata(Stream::<T, L, B2, O, R>::collection_kind()),
2619                },
2620            )
2621        }
2622    }
2623}
2624
2625impl<'a, T, L, B: Boundedness, O: Ordering, R: Retries> Stream<&T, L, B, O, R>
2626where
2627    L: Location<'a>,
2628{
2629    /// Clone each element of the stream; akin to `map(q!(|d| d.clone()))`.
2630    ///
2631    /// # Example
2632    /// ```rust
2633    /// # #[cfg(feature = "deploy")] {
2634    /// # use hydro_lang::prelude::*;
2635    /// # use futures::StreamExt;
2636    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2637    /// process.source_iter(q!(&[1, 2, 3])).cloned()
2638    /// # }, |mut stream| async move {
2639    /// // 1, 2, 3
2640    /// # for w in vec![1, 2, 3] {
2641    /// #     assert_eq!(stream.next().await.unwrap(), w);
2642    /// # }
2643    /// # }));
2644    /// # }
2645    /// ```
2646    pub fn cloned(self) -> Stream<T, L, B, O, R>
2647    where
2648        T: Clone,
2649    {
2650        self.map(q!(|d| d.clone()))
2651    }
2652}
2653
2654impl<'a, T, L, B: Boundedness, O: Ordering> Stream<T, L, B, O, ExactlyOnce>
2655where
2656    L: Location<'a>,
2657{
2658    /// Computes the number of elements in the stream as a [`Singleton`].
2659    ///
2660    /// # Example
2661    /// ```rust
2662    /// # #[cfg(feature = "deploy")] {
2663    /// # use hydro_lang::prelude::*;
2664    /// # use futures::StreamExt;
2665    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2666    /// let tick = process.tick();
2667    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
2668    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2669    /// batch.count().all_ticks()
2670    /// # }, |mut stream| async move {
2671    /// // 4
2672    /// # assert_eq!(stream.next().await.unwrap(), 4);
2673    /// # }));
2674    /// # }
2675    /// ```
2676    pub fn count(self) -> Singleton<usize, L, B::StreamToMonotone> {
2677        self.assume_ordering_trusted::<TotalOrder>(nondet!(
2678            /// Order does not affect eventual count, and also does not affect intermediate states.
2679        ))
2680        .fold(
2681            q!(|| 0usize),
2682            q!(
2683                |count, _| *count += 1,
2684                monotone = manual_proof!(/** += 1 is monotone */)
2685            ),
2686        )
2687    }
2688}
2689
2690impl<'a, T, L: Location<'a>, O: Ordering, R: Retries> Stream<T, L, Unbounded, O, R> {
2691    /// Produces a new stream that merges the elements of the two input streams.
2692    /// The result has [`NoOrder`] because the order of merging is not guaranteed.
2693    ///
2694    /// Currently, both input streams must be [`Unbounded`]. When the streams are
2695    /// [`Bounded`], you can use [`Stream::chain`] instead.
2696    ///
2697    /// # Example
2698    /// ```rust
2699    /// # #[cfg(feature = "deploy")] {
2700    /// # use hydro_lang::prelude::*;
2701    /// # use futures::StreamExt;
2702    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2703    /// let numbers: Stream<i32, _, Unbounded> = // 1, 2, 3, 4
2704    /// # process.source_iter(q!(vec![1, 2, 3, 4])).into();
2705    /// numbers.clone().map(q!(|x| x + 1)).merge_unordered(numbers)
2706    /// # }, |mut stream| async move {
2707    /// // 2, 3, 4, 5, and 1, 2, 3, 4 merged in unknown order
2708    /// # for w in vec![2, 3, 4, 5, 1, 2, 3, 4] {
2709    /// #     assert_eq!(stream.next().await.unwrap(), w);
2710    /// # }
2711    /// # }));
2712    /// # }
2713    /// ```
2714    pub fn merge_unordered<O2: Ordering, R2: Retries>(
2715        self,
2716        other: Stream<T, L, Unbounded, O2, R2>,
2717    ) -> Stream<T, L, Unbounded, NoOrder, <R as MinRetries<R2>>::Min>
2718    where
2719        R: MinRetries<R2>,
2720    {
2721        Stream::new(
2722            self.location.clone(),
2723            HydroNode::Chain {
2724                first: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2725                second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2726                metadata: self.location.new_node_metadata(Stream::<
2727                    T,
2728                    L,
2729                    Unbounded,
2730                    NoOrder,
2731                    <R as MinRetries<R2>>::Min,
2732                >::collection_kind()),
2733            },
2734        )
2735    }
2736
2737    /// Deprecated: use [`Stream::merge_unordered`] instead.
2738    #[deprecated(note = "use `merge_unordered` instead")]
2739    pub fn interleave<O2: Ordering, R2: Retries>(
2740        self,
2741        other: Stream<T, L, Unbounded, O2, R2>,
2742    ) -> Stream<T, L, Unbounded, NoOrder, <R as MinRetries<R2>>::Min>
2743    where
2744        R: MinRetries<R2>,
2745    {
2746        self.merge_unordered(other)
2747    }
2748}
2749
2750impl<'a, T, L: Location<'a>, B: Boundedness, R: Retries> Stream<T, L, B, TotalOrder, R> {
2751    /// Produces a new stream that combines the elements of the two input streams,
2752    /// preserving the relative order of elements within each input.
2753    ///
2754    /// # Non-Determinism
2755    /// The order in which elements *across* the two streams will be interleaved is
2756    /// non-deterministic, so the order of elements will vary across runs. If the output
2757    /// order is irrelevant, use [`Stream::merge_unordered`] instead, which is deterministic
2758    /// but emits an unordered stream. For deterministic first-then-second ordering on
2759    /// bounded streams, use [`Stream::chain`].
2760    ///
2761    /// In simulation tests, the interleaving decisions can be scripted by attaching a
2762    /// [`MergeOrderedHook`](crate::sim_hooks::MergeOrderedHook) to the guard via
2763    /// `nondet!(/** reason */ hook = my_hook)`.
2764    ///
2765    /// # Example
2766    /// ```rust
2767    /// # #[cfg(feature = "deploy")] {
2768    /// # use hydro_lang::prelude::*;
2769    /// # use futures::StreamExt;
2770    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2771    /// let numbers: Stream<i32, _, Unbounded> = // 1, 3
2772    /// # process.source_iter(q!(vec![1, 3])).into();
2773    /// numbers.clone().merge_ordered(numbers.map(q!(|x| x + 1)), nondet!(/** example */))
2774    /// # }, |mut stream| async move {
2775    /// // 1, 3 and 2, 4 in some order, preserving the original local order
2776    /// # for w in vec![1, 3, 2, 4] {
2777    /// #     assert_eq!(stream.next().await.unwrap(), w);
2778    /// # }
2779    /// # }));
2780    /// # }
2781    /// ```
2782    pub fn merge_ordered<R2: Retries>(
2783        self,
2784        other: Stream<T, L, B, TotalOrder, R2>,
2785        mut nondet: NonDet<Option<crate::sim_hooks::MergeOrderedHook<T, B, L::SimHookScope>>>,
2786    ) -> Stream<T, L::DropConsistency, B, TotalOrder, <R as MinRetries<R2>>::Min>
2787    where
2788        R: MinRetries<R2>,
2789    {
2790        let target_location = self.location().drop_consistency();
2791        let mut metadata = target_location.new_node_metadata(Stream::<
2792            T,
2793            L::DropConsistency,
2794            B,
2795            TotalOrder,
2796            <R as MinRetries<R2>>::Min,
2797        >::collection_kind());
2798        metadata.op.sim_hook_id = nondet.take_hook().map(|hook| hook.id);
2799        Stream::new(
2800            target_location,
2801            HydroNode::MergeOrdered {
2802                first: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2803                second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2804                metadata,
2805            },
2806        )
2807    }
2808}
2809
2810impl<'a, T, L, B: Boundedness, O: Ordering, R: Retries> Stream<T, L, B, O, R>
2811where
2812    L: Location<'a>,
2813{
2814    /// Produces a new stream that emits the input elements in sorted order.
2815    ///
2816    /// The input stream can have any ordering guarantee, but the output stream
2817    /// will have a [`TotalOrder`] guarantee. This operator will block until all
2818    /// elements in the input stream are available, so it requires the input stream
2819    /// to be [`Bounded`].
2820    ///
2821    /// # Example
2822    /// ```rust
2823    /// # #[cfg(feature = "deploy")] {
2824    /// # use hydro_lang::prelude::*;
2825    /// # use futures::StreamExt;
2826    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2827    /// let tick = process.tick();
2828    /// let numbers = process.source_iter(q!(vec![4, 2, 3, 1]));
2829    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2830    /// batch.sort().all_ticks()
2831    /// # }, |mut stream| async move {
2832    /// // 1, 2, 3, 4
2833    /// # for w in (1..5) {
2834    /// #     assert_eq!(stream.next().await.unwrap(), w);
2835    /// # }
2836    /// # }));
2837    /// # }
2838    /// ```
2839    pub fn sort(self) -> Stream<T, L, Bounded, TotalOrder, R>
2840    where
2841        B: IsBounded,
2842        T: Ord,
2843    {
2844        let this = self.make_bounded();
2845        Stream::new(
2846            this.location.clone(),
2847            HydroNode::Sort {
2848                input: Box::new(this.ir_node.replace(HydroNode::Placeholder)),
2849                metadata: this
2850                    .location
2851                    .new_node_metadata(Stream::<T, L, Bounded, TotalOrder, R>::collection_kind()),
2852            },
2853        )
2854    }
2855
2856    /// Produces a new stream that first emits the elements of the `self` stream,
2857    /// and then emits the elements of the `other` stream. The output stream has
2858    /// a [`TotalOrder`] guarantee if and only if both input streams have a
2859    /// [`TotalOrder`] guarantee.
2860    ///
2861    /// Currently, both input streams must be [`Bounded`]. This operator will block
2862    /// on the first stream until all its elements are available. In a future version,
2863    /// we will relax the requirement on the `other` stream.
2864    ///
2865    /// # Example
2866    /// ```rust
2867    /// # #[cfg(feature = "deploy")] {
2868    /// # use hydro_lang::prelude::*;
2869    /// # use futures::StreamExt;
2870    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2871    /// let tick = process.tick();
2872    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
2873    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2874    /// batch.clone().map(q!(|x| x + 1)).chain(batch).all_ticks()
2875    /// # }, |mut stream| async move {
2876    /// // 2, 3, 4, 5, 1, 2, 3, 4
2877    /// # for w in vec![2, 3, 4, 5, 1, 2, 3, 4] {
2878    /// #     assert_eq!(stream.next().await.unwrap(), w);
2879    /// # }
2880    /// # }));
2881    /// # }
2882    /// ```
2883    pub fn chain<O2: Ordering, R2: Retries, B2: Boundedness>(
2884        self,
2885        other: Stream<T, L, B2, O2, R2>,
2886    ) -> Stream<T, L, B2, <O as MinOrder<O2>>::Min, <R as MinRetries<R2>>::Min>
2887    where
2888        B: IsBounded,
2889        O: MinOrder<O2>,
2890        R: MinRetries<R2>,
2891    {
2892        check_matching_location(&self.location, &other.location);
2893
2894        Stream::new(
2895            self.location.clone(),
2896            HydroNode::Chain {
2897                first: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2898                second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2899                metadata: self.location.new_node_metadata(Stream::<
2900                    T,
2901                    L,
2902                    B2,
2903                    <O as MinOrder<O2>>::Min,
2904                    <R as MinRetries<R2>>::Min,
2905                >::collection_kind()),
2906            },
2907        )
2908    }
2909
2910    /// Forms the cross-product (Cartesian product, cross-join) of the items in the 2 input streams.
2911    /// Unlike [`Stream::cross_product`], the output order is totally ordered when the inputs are
2912    /// because this is compiled into a nested loop.
2913    pub fn cross_product_nested_loop<T2, O2: Ordering + MinOrder<O>, R2: Retries>(
2914        self,
2915        other: Stream<T2, L, Bounded, O2, R2>,
2916    ) -> Stream<(T, T2), L, Bounded, <O2 as MinOrder<O>>::Min, <R as MinRetries<R2>>::Min>
2917    where
2918        B: IsBounded,
2919        T: Clone,
2920        T2: Clone,
2921        R: MinRetries<R2>,
2922    {
2923        let this = self.make_bounded();
2924        check_matching_location(&this.location, &other.location);
2925
2926        Stream::new(
2927            this.location.clone(),
2928            HydroNode::CrossProduct {
2929                left: Box::new(this.ir_node.replace(HydroNode::Placeholder)),
2930                right: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2931                metadata: this.location.new_node_metadata(Stream::<
2932                    (T, T2),
2933                    L,
2934                    Bounded,
2935                    <O2 as MinOrder<O>>::Min,
2936                    <R as MinRetries<R2>>::Min,
2937                >::collection_kind()),
2938            },
2939        )
2940    }
2941
2942    /// Creates a [`KeyedStream`] with the same set of keys as `keys`, but with the elements in
2943    /// `self` used as the values for *each* key.
2944    ///
2945    /// This is helpful when "broadcasting" a set of values so that all the keys have the same
2946    /// values. For example, it can be used to send the same set of elements to several cluster
2947    /// members, if the membership information is available as a [`KeyedSingleton`].
2948    ///
2949    /// # Example
2950    /// ```rust
2951    /// # #[cfg(feature = "deploy")] {
2952    /// # use hydro_lang::prelude::*;
2953    /// # use futures::StreamExt;
2954    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2955    /// # let tick = process.tick();
2956    /// let keyed_singleton = // { 1: (), 2: () }
2957    /// # process
2958    /// #     .source_iter(q!(vec![(1, ()), (2, ())]))
2959    /// #     .into_keyed()
2960    /// #     .batch(&tick, nondet!(/** test */))
2961    /// #     .first();
2962    /// let stream = // [ "a", "b" ]
2963    /// # process
2964    /// #     .source_iter(q!(vec!["a".to_owned(), "b".to_owned()]))
2965    /// #     .batch(&tick, nondet!(/** test */));
2966    /// stream.repeat_with_keys(keyed_singleton)
2967    /// # .entries().all_ticks()
2968    /// # }, |mut stream| async move {
2969    /// // { 1: ["a", "b" ], 2: ["a", "b"] }
2970    /// # let mut results = Vec::new();
2971    /// # for _ in 0..4 {
2972    /// #     results.push(stream.next().await.unwrap());
2973    /// # }
2974    /// # results.sort();
2975    /// # assert_eq!(results, vec![(1, "a".to_owned()), (1, "b".to_owned()), (2, "a".to_owned()), (2, "b".to_owned())]);
2976    /// # }));
2977    /// # }
2978    /// ```
2979    pub fn repeat_with_keys<K, V2>(
2980        self,
2981        keys: KeyedSingleton<K, V2, L, Bounded>,
2982    ) -> KeyedStream<K, T, L, Bounded, O, R>
2983    where
2984        B: IsBounded,
2985        K: Clone,
2986        T: Clone,
2987    {
2988        keys.keys()
2989            .assume_ordering_trusted::<TotalOrder>(
2990                nondet!(/** keyed stream does not depend on ordering of keys */),
2991            )
2992            .cross_product_nested_loop(self.make_bounded())
2993            .into_keyed()
2994    }
2995
2996    /// Consumes a stream of `Future<T>`, resolving each future while blocking subgraph
2997    /// execution until all results are available. The output order is based on when futures
2998    /// complete, and may be different than the input order.
2999    ///
3000    /// Unlike [`Stream::resolve_futures`], which allows the subgraph to continue executing
3001    /// while futures are pending, this variant blocks until the futures resolve.
3002    ///
3003    /// # Example
3004    /// ```rust
3005    /// # #[cfg(feature = "deploy")] {
3006    /// # use std::collections::HashSet;
3007    /// # use futures::StreamExt;
3008    /// # use hydro_lang::prelude::*;
3009    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3010    /// process
3011    ///     .source_iter(q!([2, 3, 1, 9, 6, 5, 4, 7, 8]))
3012    ///     .map(q!(|x| async move {
3013    ///         tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
3014    ///         x
3015    ///     }))
3016    ///     .resolve_futures_blocking()
3017    /// #   },
3018    /// #   |mut stream| async move {
3019    /// // 1, 2, 3, 4, 5, 6, 7, 8, 9 (in any order)
3020    /// #       let mut output = HashSet::new();
3021    /// #       for _ in 1..10 {
3022    /// #           output.insert(stream.next().await.unwrap());
3023    /// #       }
3024    /// #       assert_eq!(
3025    /// #           output,
3026    /// #           HashSet::<i32>::from_iter(1..10)
3027    /// #       );
3028    /// #   },
3029    /// # ));
3030    /// # }
3031    /// ```
3032    pub fn resolve_futures_blocking(self) -> Stream<T::Output, L, B, NoOrder, R>
3033    where
3034        T: Future,
3035    {
3036        Stream::new(
3037            self.location.clone(),
3038            HydroNode::ResolveFuturesBlocking {
3039                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3040                metadata: self
3041                    .location
3042                    .new_node_metadata(Stream::<T::Output, L, B, NoOrder, R>::collection_kind()),
3043            },
3044        )
3045    }
3046
3047    /// Returns a [`Singleton`] containing `true` if the stream has no elements, or `false` otherwise.
3048    ///
3049    /// # Example
3050    /// ```rust
3051    /// # #[cfg(feature = "deploy")] {
3052    /// # use hydro_lang::prelude::*;
3053    /// # use futures::StreamExt;
3054    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3055    /// let tick = process.tick();
3056    /// let empty: Stream<i32, _, Bounded> = process
3057    ///   .source_iter(q!(Vec::<i32>::new()))
3058    ///   .batch(&tick, nondet!(/** test */));
3059    /// empty.is_empty().all_ticks()
3060    /// # }, |mut stream| async move {
3061    /// // true
3062    /// # assert_eq!(stream.next().await.unwrap(), true);
3063    /// # }));
3064    /// # }
3065    /// ```
3066    #[expect(clippy::wrong_self_convention, reason = "stream function naming")]
3067    pub fn is_empty(self) -> Singleton<bool, L, Bounded>
3068    where
3069        B: IsBounded,
3070    {
3071        self.make_bounded()
3072            .assume_ordering_trusted::<TotalOrder>(
3073                nondet!(/** is_empty intermediates unaffected by order */),
3074            )
3075            .first()
3076            .is_none()
3077    }
3078}
3079
3080impl<'a, K, V1, L, B: Boundedness, O: Ordering, R: Retries> Stream<(K, V1), L, B, O, R>
3081where
3082    L: Location<'a>,
3083{
3084    /// Given two streams of pairs `(K, V1)` and `(K, V2)`, produces a new stream of nested pairs `(K, (V1, V2))`
3085    /// by equi-joining the two streams on the key attribute `K`.
3086    ///
3087    /// When the right-hand side is [`Bounded`], the join accumulates the right side first
3088    /// and streams the left side through, preserving the left side's ordering. When both
3089    /// sides are [`Unbounded`], a symmetric hash join is used and ordering is [`NoOrder`].
3090    ///
3091    /// # Example
3092    /// ```rust
3093    /// # #[cfg(feature = "deploy")] {
3094    /// # use hydro_lang::prelude::*;
3095    /// # use std::collections::HashSet;
3096    /// # use futures::StreamExt;
3097    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3098    /// let tick = process.tick();
3099    /// let stream1 = process.source_iter(q!(vec![(1, 'a'), (2, 'b')]));
3100    /// let stream2 = process.source_iter(q!(vec![(1, 'x'), (2, 'y')]));
3101    /// stream1.join(stream2)
3102    /// # }, |mut stream| async move {
3103    /// // (1, ('a', 'x')), (2, ('b', 'y'))
3104    /// # let expected = HashSet::from([(1, ('a', 'x')), (2, ('b', 'y'))]);
3105    /// # stream.map(|i| assert!(expected.contains(&i)));
3106    /// # }));
3107    /// # }
3108    pub fn join<V2, B2: Boundedness, O2: Ordering, R2: Retries>(
3109        self,
3110        n: Stream<(K, V2), L, B2, O2, R2>,
3111    ) -> Stream<(K, (V1, V2)), L, B, B2::PreserveOrderIfBounded<O>, <R as MinRetries<R2>>::Min>
3112    where
3113        K: Eq + Hash + Clone,
3114        R: MinRetries<R2>,
3115        V1: Clone,
3116        V2: Clone,
3117    {
3118        check_matching_location(&self.location, &n.location);
3119
3120        let ir_node = if B2::BOUNDED {
3121            HydroNode::JoinHalf {
3122                left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3123                right: Box::new(n.ir_node.replace(HydroNode::Placeholder)),
3124                metadata: self.location.new_node_metadata(Stream::<
3125                    (K, (V1, V2)),
3126                    L,
3127                    B,
3128                    B2::PreserveOrderIfBounded<O>,
3129                    <R as MinRetries<R2>>::Min,
3130                >::collection_kind()),
3131            }
3132        } else {
3133            HydroNode::Join {
3134                left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3135                right: Box::new(n.ir_node.replace(HydroNode::Placeholder)),
3136                metadata: self.location.new_node_metadata(Stream::<
3137                    (K, (V1, V2)),
3138                    L,
3139                    B,
3140                    B2::PreserveOrderIfBounded<O>,
3141                    <R as MinRetries<R2>>::Min,
3142                >::collection_kind()),
3143            }
3144        };
3145
3146        Stream::new(self.location.clone(), ir_node)
3147    }
3148
3149    /// Given a stream of pairs `(K, V1)` and a bounded stream of keys `K`,
3150    /// computes the anti-join of the items in the input -- i.e. returns
3151    /// unique items in the first input that do not have a matching key
3152    /// in the second input.
3153    ///
3154    /// # Example
3155    /// ```rust
3156    /// # #[cfg(feature = "deploy")] {
3157    /// # use hydro_lang::prelude::*;
3158    /// # use futures::StreamExt;
3159    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3160    /// let tick = process.tick();
3161    /// let stream = process
3162    ///   .source_iter(q!(vec![ (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd') ]))
3163    ///   .batch(&tick, nondet!(/** test */));
3164    /// let batch = process
3165    ///   .source_iter(q!(vec![1, 2]))
3166    ///   .batch(&tick, nondet!(/** test */));
3167    /// stream.anti_join(batch).all_ticks()
3168    /// # }, |mut stream| async move {
3169    /// # for w in vec![(3, 'c'), (4, 'd')] {
3170    /// #     assert_eq!(stream.next().await.unwrap(), w);
3171    /// # }
3172    /// # }));
3173    /// # }
3174    pub fn anti_join<O2: Ordering, R2: Retries>(
3175        self,
3176        n: Stream<K, L, Bounded, O2, R2>,
3177    ) -> Stream<(K, V1), L, B, O, R>
3178    where
3179        K: Eq + Hash,
3180    {
3181        check_matching_location(&self.location, &n.location);
3182
3183        Stream::new(
3184            self.location.clone(),
3185            HydroNode::AntiJoin {
3186                pos: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3187                neg: Box::new(n.ir_node.replace(HydroNode::Placeholder)),
3188                metadata: self
3189                    .location
3190                    .new_node_metadata(Stream::<(K, V1), L, B, O, R>::collection_kind()),
3191            },
3192        )
3193    }
3194}
3195
3196impl<'a, K, V, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
3197    Stream<(K, V), L, B, O, R>
3198{
3199    /// Transforms this stream into a [`KeyedStream`], where the first element of each tuple
3200    /// is used as the key and the second element is added to the entries associated with that key.
3201    ///
3202    /// Because [`KeyedStream`] lazily groups values into buckets, this operator has zero computational
3203    /// cost and _does not_ require that the key type is hashable. Keyed streams are useful for
3204    /// performing grouped aggregations, but also for more precise ordering guarantees such as
3205    /// total ordering _within_ each group but no ordering _across_ groups.
3206    ///
3207    /// # Example
3208    /// ```rust
3209    /// # #[cfg(feature = "deploy")] {
3210    /// # use hydro_lang::prelude::*;
3211    /// # use futures::StreamExt;
3212    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3213    /// process
3214    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
3215    ///     .into_keyed()
3216    /// #   .entries()
3217    /// # }, |mut stream| async move {
3218    /// // { 1: [2, 3], 2: [4] }
3219    /// # for w in vec![(1, 2), (1, 3), (2, 4)] {
3220    /// #     assert_eq!(stream.next().await.unwrap(), w);
3221    /// # }
3222    /// # }));
3223    /// # }
3224    /// ```
3225    pub fn into_keyed(self) -> KeyedStream<K, V, L, B, O, R> {
3226        KeyedStream::new(
3227            self.location.clone(),
3228            HydroNode::Cast {
3229                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3230                metadata: self
3231                    .location
3232                    .new_node_metadata(KeyedStream::<K, V, L, B, O, R>::collection_kind()),
3233            },
3234        )
3235    }
3236}
3237
3238impl<'a, K, V, L, O: Ordering, R: Retries> Stream<(K, V), Tick<L>, Bounded, O, R>
3239where
3240    K: Eq + Hash,
3241    L: Location<'a>,
3242{
3243    /// Given a stream of pairs `(K, V)`, produces a new stream of unique keys `K`.
3244    /// # Example
3245    /// ```rust
3246    /// # #[cfg(feature = "deploy")] {
3247    /// # use hydro_lang::prelude::*;
3248    /// # use futures::StreamExt;
3249    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3250    /// let tick = process.tick();
3251    /// let numbers = process.source_iter(q!(vec![(1, 2), (2, 3), (1, 3), (2, 4)]));
3252    /// let batch = numbers.batch(&tick, nondet!(/** test */));
3253    /// batch.keys().all_ticks()
3254    /// # }, |mut stream| async move {
3255    /// // 1, 2
3256    /// # assert_eq!(stream.next().await.unwrap(), 1);
3257    /// # assert_eq!(stream.next().await.unwrap(), 2);
3258    /// # }));
3259    /// # }
3260    /// ```
3261    pub fn keys(self) -> Stream<K, Tick<L>, Bounded, NoOrder, ExactlyOnce> {
3262        self.into_keyed()
3263            .fold(
3264                q!(|| ()),
3265                q!(
3266                    |_, _| {},
3267                    commutative = manual_proof!(/** values are ignored */),
3268                    idempotent = manual_proof!(/** values are ignored */)
3269                ),
3270            )
3271            .keys()
3272    }
3273}
3274
3275impl<'a, T, L, B: Boundedness, O: Ordering, R: Retries> Stream<T, Atomic<L>, B, O, R>
3276where
3277    L: Location<'a>,
3278{
3279    /// Returns a stream corresponding to the latest batch of elements being atomically
3280    /// processed. These batches are guaranteed to be contiguous across ticks and preserve
3281    /// the order of the input.
3282    ///
3283    /// # Non-Determinism
3284    /// The batch boundaries are non-deterministic and may change across executions.
3285    pub fn batch_atomic<L2: Location<'a, DropConsistency = L::DropConsistency>>(
3286        self,
3287        tick: &Tick<L2>,
3288        mut nondet: NonDet<Option<crate::sim_hooks::BatchHook<T, O, R, L::SimHookScope>>>,
3289    ) -> Stream<T, Tick<L::DropConsistency>, Bounded, O, R> {
3290        assert_eq!(
3291            Location::id(tick.parent_location()),
3292            Location::id(self.location.tick.parent_location())
3293        );
3294
3295        let mut metadata =
3296            tick.new_node_metadata(Stream::<T, Tick<L>, Bounded, O, R>::collection_kind());
3297
3298        metadata.op.sim_hook_id = nondet.take_hook().map(|h| h.id);
3299        Stream::new(
3300            tick.drop_consistency(),
3301            HydroNode::Batch {
3302                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3303                metadata,
3304            },
3305        )
3306    }
3307
3308    /// Yields the elements of this stream back into a top-level, asynchronous execution context.
3309    /// See [`Stream::atomic`] for more details.
3310    pub fn end_atomic(self) -> Stream<T, L, B, O, R> {
3311        Stream::new(
3312            self.location.tick.l.clone(),
3313            HydroNode::EndAtomic {
3314                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3315                metadata: self
3316                    .location
3317                    .tick
3318                    .l
3319                    .new_node_metadata(Stream::<T, L, B, O, R>::collection_kind()),
3320            },
3321        )
3322    }
3323}
3324
3325impl<'a, F, T, L, B: Boundedness, O: Ordering, R: Retries> Stream<F, L, B, O, R>
3326where
3327    L: TopLevel<'a>,
3328    F: Future<Output = T>,
3329{
3330    /// Consumes a stream of `Future<T>`, produces a new stream of the resulting `T` outputs.
3331    /// Future outputs are produced as available, regardless of input arrival order.
3332    ///
3333    /// # Example
3334    /// ```rust
3335    /// # #[cfg(feature = "deploy")] {
3336    /// # use std::collections::HashSet;
3337    /// # use futures::StreamExt;
3338    /// # use hydro_lang::prelude::*;
3339    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3340    /// process.source_iter(q!([2, 3, 1, 9, 6, 5, 4, 7, 8]))
3341    ///     .map(q!(|x| async move {
3342    ///         tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
3343    ///         x
3344    ///     }))
3345    ///     .resolve_futures()
3346    /// #   },
3347    /// #   |mut stream| async move {
3348    /// // 1, 2, 3, 4, 5, 6, 7, 8, 9 (in any order)
3349    /// #       let mut output = HashSet::new();
3350    /// #       for _ in 1..10 {
3351    /// #           output.insert(stream.next().await.unwrap());
3352    /// #       }
3353    /// #       assert_eq!(
3354    /// #           output,
3355    /// #           HashSet::<i32>::from_iter(1..10)
3356    /// #       );
3357    /// #   },
3358    /// # ));
3359    /// # }
3360    pub fn resolve_futures(self) -> Stream<T, L, Unbounded, NoOrder, R> {
3361        Stream::new(
3362            self.location.clone(),
3363            HydroNode::ResolveFutures {
3364                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3365                metadata: self
3366                    .location
3367                    .new_node_metadata(Stream::<T, L, Unbounded, NoOrder, R>::collection_kind()),
3368            },
3369        )
3370    }
3371
3372    /// Consumes a stream of `Future<T>`, produces a new stream of the resulting `T` outputs.
3373    /// Future outputs are produced in the same order as the input stream.
3374    ///
3375    /// # Example
3376    /// ```rust
3377    /// # #[cfg(feature = "deploy")] {
3378    /// # use std::collections::HashSet;
3379    /// # use futures::StreamExt;
3380    /// # use hydro_lang::prelude::*;
3381    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3382    /// process.source_iter(q!([2, 3, 1, 9, 6, 5, 4, 7, 8]))
3383    ///     .map(q!(|x| async move {
3384    ///         tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
3385    ///         x
3386    ///     }))
3387    ///     .resolve_futures_ordered()
3388    /// #   },
3389    /// #   |mut stream| async move {
3390    /// // 2, 3, 1, 9, 6, 5, 4, 7, 8
3391    /// #       let mut output = Vec::new();
3392    /// #       for _ in 1..10 {
3393    /// #           output.push(stream.next().await.unwrap());
3394    /// #       }
3395    /// #       assert_eq!(
3396    /// #           output,
3397    /// #           vec![2, 3, 1, 9, 6, 5, 4, 7, 8]
3398    /// #       );
3399    /// #   },
3400    /// # ));
3401    /// # }
3402    pub fn resolve_futures_ordered(self) -> Stream<T, L, Unbounded, O, R> {
3403        Stream::new(
3404            self.location.clone(),
3405            HydroNode::ResolveFuturesOrdered {
3406                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3407                metadata: self
3408                    .location
3409                    .new_node_metadata(Stream::<T, L, Unbounded, O, R>::collection_kind()),
3410            },
3411        )
3412    }
3413}
3414
3415impl<'a, T, L, O: Ordering, R: Retries> Stream<T, Tick<L>, Bounded, O, R>
3416where
3417    L: Location<'a>,
3418{
3419    /// Asynchronously yields this batch of elements outside the tick as an unbounded stream,
3420    /// which will stream all the elements across _all_ tick iterations by concatenating the batches.
3421    pub fn all_ticks(self) -> Stream<T, L, Unbounded, O, R> {
3422        Stream::new(
3423            self.location.parent_location().clone(),
3424            HydroNode::YieldConcat {
3425                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3426                metadata: self.location.parent_location().new_node_metadata(Stream::<
3427                    T,
3428                    L,
3429                    Unbounded,
3430                    O,
3431                    R,
3432                >::collection_kind(
3433                )),
3434            },
3435        )
3436    }
3437
3438    /// Synchronously yields this batch of elements outside the tick as an unbounded stream,
3439    /// which will stream all the elements across _all_ tick iterations by concatenating the batches.
3440    ///
3441    /// Unlike [`Stream::all_ticks`], this preserves synchronous execution, as the output stream
3442    /// is emitted in an [`Atomic`] context that will process elements synchronously with the input
3443    /// stream's [`Tick`] context.
3444    pub fn all_ticks_atomic(self) -> Stream<T, Atomic<L>, Unbounded, O, R> {
3445        let out_location = Atomic {
3446            tick: self.location.clone(),
3447        };
3448
3449        Stream::new(
3450            out_location.clone(),
3451            HydroNode::YieldConcat {
3452                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3453                metadata: out_location
3454                    .new_node_metadata(Stream::<T, Atomic<L>, Unbounded, O, R>::collection_kind()),
3455            },
3456        )
3457    }
3458
3459    /// Transforms the stream using the given closure in "stateful" mode, where stateful operators
3460    /// such as `fold` retrain their memory across ticks rather than resetting across batches of
3461    /// input.
3462    ///
3463    /// This API is particularly useful for stateful computation on batches of data, such as
3464    /// maintaining an accumulated state that is up to date with the current batch.
3465    ///
3466    /// # Example
3467    /// ```rust
3468    /// # #[cfg(feature = "deploy")] {
3469    /// # use hydro_lang::prelude::*;
3470    /// # use futures::StreamExt;
3471    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3472    /// let tick = process.tick();
3473    /// # // ticks are lazy by default, forces the second tick to run
3474    /// # tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
3475    /// # let batch_first_tick = process
3476    /// #   .source_iter(q!(vec![1, 2, 3, 4]))
3477    /// #  .batch(&tick, nondet!(/** test */));
3478    /// # let batch_second_tick = process
3479    /// #   .source_iter(q!(vec![5, 6, 7]))
3480    /// #   .batch(&tick, nondet!(/** test */))
3481    /// #   .defer_tick(); // appears on the second tick
3482    /// let input = // [1, 2, 3, 4 (first batch), 5, 6, 7 (second batch)]
3483    /// # batch_first_tick.chain(batch_second_tick);
3484    ///
3485    /// input.across_ticks(|s| s.count()).all_ticks()
3486    /// # }, |mut stream| async move {
3487    /// // [4, 7]
3488    /// assert_eq!(stream.next().await.unwrap(), 4);
3489    /// assert_eq!(stream.next().await.unwrap(), 7);
3490    /// # }));
3491    /// # }
3492    /// ```
3493    pub fn across_ticks<Out: BatchAtomic<'a>>(
3494        self,
3495        thunk: impl FnOnce(Stream<T, Atomic<L>, Unbounded, O, R>) -> Out,
3496    ) -> Out::Batched {
3497        thunk(self.all_ticks_atomic()).batched_atomic()
3498    }
3499
3500    /// Shifts the elements in `self` to the **next tick**, so that the returned stream at tick `T`
3501    /// always has the elements of `self` at tick `T - 1`.
3502    ///
3503    /// At tick `0`, the output stream is empty, since there is no previous tick.
3504    ///
3505    /// This operator enables stateful iterative processing with ticks, by sending data from one
3506    /// tick to the next. For example, you can use it to compare inputs across consecutive batches.
3507    ///
3508    /// # Example
3509    /// ```rust
3510    /// # #[cfg(feature = "deploy")] {
3511    /// # use hydro_lang::prelude::*;
3512    /// # use futures::StreamExt;
3513    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3514    /// let tick = process.tick();
3515    /// // ticks are lazy by default, forces the second tick to run
3516    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
3517    ///
3518    /// let batch_first_tick = process
3519    ///   .source_iter(q!(vec![1, 2, 3, 4]))
3520    ///   .batch(&tick, nondet!(/** test */));
3521    /// let batch_second_tick = process
3522    ///   .source_iter(q!(vec![0, 3, 4, 5, 6]))
3523    ///   .batch(&tick, nondet!(/** test */))
3524    ///   .defer_tick(); // appears on the second tick
3525    /// let changes_across_ticks = batch_first_tick.chain(batch_second_tick);
3526    ///
3527    /// changes_across_ticks.clone().filter_not_in(
3528    ///     changes_across_ticks.defer_tick() // the elements from the previous tick
3529    /// ).all_ticks()
3530    /// # }, |mut stream| async move {
3531    /// // [1, 2, 3, 4 /* first tick */, 0, 5, 6 /* second tick */]
3532    /// # for w in vec![1, 2, 3, 4, 0, 5, 6] {
3533    /// #     assert_eq!(stream.next().await.unwrap(), w);
3534    /// # }
3535    /// # }));
3536    /// # }
3537    /// ```
3538    pub fn defer_tick(self) -> Stream<T, Tick<L>, Bounded, O, R> {
3539        Stream::new(
3540            self.location.clone(),
3541            HydroNode::DeferTick {
3542                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3543                metadata: self
3544                    .location
3545                    .new_node_metadata(Stream::<T, Tick<L>, Bounded, O, R>::collection_kind()),
3546            },
3547        )
3548    }
3549}
3550
3551#[cfg(test)]
3552mod tests {
3553    #[cfg(feature = "deploy")]
3554    use futures::{SinkExt, StreamExt};
3555    #[cfg(feature = "deploy")]
3556    use hydro_deploy::Deployment;
3557    #[cfg(feature = "deploy")]
3558    use serde::{Deserialize, Serialize};
3559    #[cfg(any(feature = "deploy", feature = "sim"))]
3560    use stageleft::q;
3561
3562    #[cfg(any(feature = "deploy", feature = "sim"))]
3563    use crate::compile::builder::FlowBuilder;
3564    #[cfg(feature = "deploy")]
3565    use crate::live_collections::sliced::sliced;
3566    #[cfg(feature = "deploy")]
3567    use crate::live_collections::stream::ExactlyOnce;
3568    #[cfg(feature = "sim")]
3569    use crate::live_collections::stream::NoOrder;
3570    #[cfg(any(feature = "deploy", feature = "sim"))]
3571    use crate::live_collections::stream::TotalOrder;
3572    #[cfg(any(feature = "deploy", feature = "sim"))]
3573    use crate::location::Location;
3574    #[cfg(feature = "sim")]
3575    use crate::networking::TCP;
3576    #[cfg(any(feature = "deploy", feature = "sim"))]
3577    use crate::nondet::nondet;
3578
3579    mod backtrace_chained_ops;
3580
3581    #[cfg(feature = "deploy")]
3582    struct P1 {}
3583    #[cfg(feature = "deploy")]
3584    struct P2 {}
3585
3586    #[cfg(feature = "deploy")]
3587    #[derive(Serialize, Deserialize, Debug)]
3588    struct SendOverNetwork {
3589        n: u32,
3590    }
3591
3592    #[cfg(feature = "deploy")]
3593    #[tokio::test]
3594    async fn first_ten_distributed() {
3595        use crate::networking::TCP;
3596
3597        let mut deployment = Deployment::new();
3598
3599        let mut flow = FlowBuilder::new();
3600        let first_node = flow.process::<P1>();
3601        let second_node = flow.process::<P2>();
3602        let external = flow.external::<P2>();
3603
3604        let numbers = first_node.source_iter(q!(0..10));
3605        let out_port = numbers
3606            .map(q!(|n| SendOverNetwork { n }))
3607            .send(&second_node, TCP.fail_stop().bincode())
3608            .send_bincode_external(&external);
3609
3610        let nodes = flow
3611            .with_process(&first_node, deployment.Localhost())
3612            .with_process(&second_node, deployment.Localhost())
3613            .with_external(&external, deployment.Localhost())
3614            .deploy(&mut deployment);
3615
3616        deployment.deploy().await.unwrap();
3617
3618        let mut external_out = nodes.connect(out_port).await;
3619
3620        deployment.start().await.unwrap();
3621
3622        for i in 0..10 {
3623            assert_eq!(external_out.next().await.unwrap().n, i);
3624        }
3625    }
3626
3627    #[cfg(feature = "deploy")]
3628    #[tokio::test]
3629    async fn first_cardinality() {
3630        let mut deployment = Deployment::new();
3631
3632        let mut flow = FlowBuilder::new();
3633        let node = flow.process::<()>();
3634        let external = flow.external::<()>();
3635
3636        let node_tick = node.tick();
3637        let count = node_tick
3638            .singleton(q!([1, 2, 3]))
3639            .into_stream()
3640            .flatten_ordered()
3641            .first()
3642            .into_stream()
3643            .count()
3644            .all_ticks()
3645            .send_bincode_external(&external);
3646
3647        let nodes = flow
3648            .with_process(&node, deployment.Localhost())
3649            .with_external(&external, deployment.Localhost())
3650            .deploy(&mut deployment);
3651
3652        deployment.deploy().await.unwrap();
3653
3654        let mut external_out = nodes.connect(count).await;
3655
3656        deployment.start().await.unwrap();
3657
3658        assert_eq!(external_out.next().await.unwrap(), 1);
3659    }
3660
3661    #[cfg(feature = "deploy")]
3662    #[tokio::test]
3663    async fn unbounded_reduce_remembers_state() {
3664        let mut deployment = Deployment::new();
3665
3666        let mut flow = FlowBuilder::new();
3667        let node = flow.process::<()>();
3668        let external = flow.external::<()>();
3669
3670        let (input_port, input) = node.source_external_bincode(&external);
3671        let out = input
3672            .reduce(q!(|acc, v| *acc += v))
3673            .sample_eager(nondet!(/** test */))
3674            .send_bincode_external(&external);
3675
3676        let nodes = flow
3677            .with_process(&node, deployment.Localhost())
3678            .with_external(&external, deployment.Localhost())
3679            .deploy(&mut deployment);
3680
3681        deployment.deploy().await.unwrap();
3682
3683        let mut external_in = nodes.connect(input_port).await;
3684        let mut external_out = nodes.connect(out).await;
3685
3686        deployment.start().await.unwrap();
3687
3688        external_in.send(1).await.unwrap();
3689        assert_eq!(external_out.next().await.unwrap(), 1);
3690
3691        external_in.send(2).await.unwrap();
3692        assert_eq!(external_out.next().await.unwrap(), 3);
3693    }
3694
3695    #[cfg(feature = "deploy")]
3696    #[tokio::test]
3697    async fn top_level_bounded_cross_singleton() {
3698        let mut deployment = Deployment::new();
3699
3700        let mut flow = FlowBuilder::new();
3701        let node = flow.process::<()>();
3702        let external = flow.external::<()>();
3703
3704        let (input_port, input) =
3705            node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
3706
3707        let out = input
3708            .cross_singleton(
3709                node.source_iter(q!(vec![1, 2, 3]))
3710                    .fold(q!(|| 0), q!(|acc, v| *acc += v)),
3711            )
3712            .send_bincode_external(&external);
3713
3714        let nodes = flow
3715            .with_process(&node, deployment.Localhost())
3716            .with_external(&external, deployment.Localhost())
3717            .deploy(&mut deployment);
3718
3719        deployment.deploy().await.unwrap();
3720
3721        let mut external_in = nodes.connect(input_port).await;
3722        let mut external_out = nodes.connect(out).await;
3723
3724        deployment.start().await.unwrap();
3725
3726        external_in.send(1).await.unwrap();
3727        assert_eq!(external_out.next().await.unwrap(), (1, 6));
3728
3729        external_in.send(2).await.unwrap();
3730        assert_eq!(external_out.next().await.unwrap(), (2, 6));
3731    }
3732
3733    #[cfg(feature = "deploy")]
3734    #[tokio::test]
3735    async fn top_level_bounded_reduce_cardinality() {
3736        let mut deployment = Deployment::new();
3737
3738        let mut flow = FlowBuilder::new();
3739        let node = flow.process::<()>();
3740        let external = flow.external::<()>();
3741
3742        let (input_port, input) =
3743            node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
3744
3745        let out = sliced! {
3746            let input = use::batch(input, nondet!(/** test */));
3747            let v = use::snapshot(node.source_iter(q!(vec![1, 2, 3])).reduce(q!(|acc, v| *acc += v)), nondet!(/** test */));
3748            input.cross_singleton(v.into_stream().count())
3749        }
3750        .send_bincode_external(&external);
3751
3752        let nodes = flow
3753            .with_process(&node, deployment.Localhost())
3754            .with_external(&external, deployment.Localhost())
3755            .deploy(&mut deployment);
3756
3757        deployment.deploy().await.unwrap();
3758
3759        let mut external_in = nodes.connect(input_port).await;
3760        let mut external_out = nodes.connect(out).await;
3761
3762        deployment.start().await.unwrap();
3763
3764        external_in.send(1).await.unwrap();
3765        assert_eq!(external_out.next().await.unwrap(), (1, 1));
3766
3767        external_in.send(2).await.unwrap();
3768        assert_eq!(external_out.next().await.unwrap(), (2, 1));
3769    }
3770
3771    #[cfg(feature = "deploy")]
3772    #[tokio::test]
3773    async fn top_level_bounded_into_singleton_cardinality() {
3774        let mut deployment = Deployment::new();
3775
3776        let mut flow = FlowBuilder::new();
3777        let node = flow.process::<()>();
3778        let external = flow.external::<()>();
3779
3780        let (input_port, input) =
3781            node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
3782
3783        let out = sliced! {
3784            let input = use::batch(input, nondet!(/** test */));
3785            let v = use::snapshot(node.source_iter(q!(vec![1, 2, 3])).reduce(q!(|acc, v| *acc += v)).into_singleton(), nondet!(/** test */));
3786            input.cross_singleton(v.into_stream().count())
3787        }
3788        .send_bincode_external(&external);
3789
3790        let nodes = flow
3791            .with_process(&node, deployment.Localhost())
3792            .with_external(&external, deployment.Localhost())
3793            .deploy(&mut deployment);
3794
3795        deployment.deploy().await.unwrap();
3796
3797        let mut external_in = nodes.connect(input_port).await;
3798        let mut external_out = nodes.connect(out).await;
3799
3800        deployment.start().await.unwrap();
3801
3802        external_in.send(1).await.unwrap();
3803        assert_eq!(external_out.next().await.unwrap(), (1, 1));
3804
3805        external_in.send(2).await.unwrap();
3806        assert_eq!(external_out.next().await.unwrap(), (2, 1));
3807    }
3808
3809    #[cfg(feature = "deploy")]
3810    #[tokio::test]
3811    async fn atomic_fold_replays_each_tick() {
3812        let mut deployment = Deployment::new();
3813
3814        let mut flow = FlowBuilder::new();
3815        let node = flow.process::<()>();
3816        let external = flow.external::<()>();
3817
3818        let (input_port, input) =
3819            node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
3820        let tick = node.tick();
3821
3822        let out = input
3823            .batch(&tick, nondet!(/** test */))
3824            .cross_singleton(
3825                node.source_iter(q!(vec![1, 2, 3]))
3826                    .atomic()
3827                    .fold(q!(|| 0), q!(|acc, v| *acc += v))
3828                    .snapshot_atomic(&tick, nondet!(/** test */)),
3829            )
3830            .all_ticks()
3831            .send_bincode_external(&external);
3832
3833        let nodes = flow
3834            .with_process(&node, deployment.Localhost())
3835            .with_external(&external, deployment.Localhost())
3836            .deploy(&mut deployment);
3837
3838        deployment.deploy().await.unwrap();
3839
3840        let mut external_in = nodes.connect(input_port).await;
3841        let mut external_out = nodes.connect(out).await;
3842
3843        deployment.start().await.unwrap();
3844
3845        external_in.send(1).await.unwrap();
3846        assert_eq!(external_out.next().await.unwrap(), (1, 6));
3847
3848        external_in.send(2).await.unwrap();
3849        assert_eq!(external_out.next().await.unwrap(), (2, 6));
3850    }
3851
3852    #[cfg(feature = "deploy")]
3853    #[tokio::test]
3854    async fn unbounded_scan_remembers_state() {
3855        let mut deployment = Deployment::new();
3856
3857        let mut flow = FlowBuilder::new();
3858        let node = flow.process::<()>();
3859        let external = flow.external::<()>();
3860
3861        let (input_port, input) = node.source_external_bincode(&external);
3862        let out = input
3863            .scan(
3864                q!(|| 0),
3865                q!(|acc, v| {
3866                    *acc += v;
3867                    Some(*acc)
3868                }),
3869            )
3870            .send_bincode_external(&external);
3871
3872        let nodes = flow
3873            .with_process(&node, deployment.Localhost())
3874            .with_external(&external, deployment.Localhost())
3875            .deploy(&mut deployment);
3876
3877        deployment.deploy().await.unwrap();
3878
3879        let mut external_in = nodes.connect(input_port).await;
3880        let mut external_out = nodes.connect(out).await;
3881
3882        deployment.start().await.unwrap();
3883
3884        external_in.send(1).await.unwrap();
3885        assert_eq!(external_out.next().await.unwrap(), 1);
3886
3887        external_in.send(2).await.unwrap();
3888        assert_eq!(external_out.next().await.unwrap(), 3);
3889    }
3890
3891    #[cfg(feature = "deploy")]
3892    #[tokio::test]
3893    async fn unbounded_enumerate_remembers_state() {
3894        let mut deployment = Deployment::new();
3895
3896        let mut flow = FlowBuilder::new();
3897        let node = flow.process::<()>();
3898        let external = flow.external::<()>();
3899
3900        let (input_port, input) = node.source_external_bincode(&external);
3901        let out = input.enumerate().send_bincode_external(&external);
3902
3903        let nodes = flow
3904            .with_process(&node, deployment.Localhost())
3905            .with_external(&external, deployment.Localhost())
3906            .deploy(&mut deployment);
3907
3908        deployment.deploy().await.unwrap();
3909
3910        let mut external_in = nodes.connect(input_port).await;
3911        let mut external_out = nodes.connect(out).await;
3912
3913        deployment.start().await.unwrap();
3914
3915        external_in.send(1).await.unwrap();
3916        assert_eq!(external_out.next().await.unwrap(), (0, 1));
3917
3918        external_in.send(2).await.unwrap();
3919        assert_eq!(external_out.next().await.unwrap(), (1, 2));
3920    }
3921
3922    #[cfg(feature = "deploy")]
3923    #[tokio::test]
3924    async fn unbounded_unique_remembers_state() {
3925        let mut deployment = Deployment::new();
3926
3927        let mut flow = FlowBuilder::new();
3928        let node = flow.process::<()>();
3929        let external = flow.external::<()>();
3930
3931        let (input_port, input) =
3932            node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
3933        let out = input.unique().send_bincode_external(&external);
3934
3935        let nodes = flow
3936            .with_process(&node, deployment.Localhost())
3937            .with_external(&external, deployment.Localhost())
3938            .deploy(&mut deployment);
3939
3940        deployment.deploy().await.unwrap();
3941
3942        let mut external_in = nodes.connect(input_port).await;
3943        let mut external_out = nodes.connect(out).await;
3944
3945        deployment.start().await.unwrap();
3946
3947        external_in.send(1).await.unwrap();
3948        assert_eq!(external_out.next().await.unwrap(), 1);
3949
3950        external_in.send(2).await.unwrap();
3951        assert_eq!(external_out.next().await.unwrap(), 2);
3952
3953        external_in.send(1).await.unwrap();
3954        external_in.send(3).await.unwrap();
3955        assert_eq!(external_out.next().await.unwrap(), 3);
3956    }
3957
3958    #[cfg(feature = "sim")]
3959    #[test]
3960    #[should_panic]
3961    fn sim_batch_nondet_size() {
3962        let mut flow = FlowBuilder::new();
3963        let node = flow.process::<()>();
3964
3965        let (in_send, input) = node.sim_input::<_, TotalOrder, _>();
3966
3967        let tick = node.tick();
3968        let out_recv = input
3969            .batch(&tick, nondet!(/** test */))
3970            .count()
3971            .all_ticks()
3972            .sim_output();
3973
3974        flow.sim().exhaustive(async || {
3975            in_send.send(());
3976            in_send.send(());
3977            in_send.send(());
3978
3979            assert_eq!(out_recv.next().await, 3); // fails with nondet batching
3980        });
3981    }
3982
3983    #[cfg(feature = "sim")]
3984    #[test]
3985    fn sim_batch_preserves_order() {
3986        let mut flow = FlowBuilder::new();
3987        let node = flow.process::<()>();
3988
3989        let (in_send, input) = node.sim_input();
3990
3991        let tick = node.tick();
3992        let out_recv = input
3993            .batch(&tick, nondet!(/** test */))
3994            .all_ticks()
3995            .sim_output();
3996
3997        flow.sim().exhaustive(async || {
3998            in_send.send(1);
3999            in_send.send(2);
4000            in_send.send(3);
4001
4002            out_recv.assert_yields_only([1, 2, 3]).await;
4003        });
4004    }
4005
4006    #[cfg(feature = "sim")]
4007    #[test]
4008    #[should_panic]
4009    fn sim_batch_unordered_shuffles() {
4010        let mut flow = FlowBuilder::new();
4011        let node = flow.process::<()>();
4012
4013        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4014
4015        let tick = node.tick();
4016        let batch = input.batch(&tick, nondet!(/** test */));
4017        let out_recv = batch
4018            .clone()
4019            .min()
4020            .zip(batch.max())
4021            .all_ticks()
4022            .sim_output();
4023
4024        flow.sim().exhaustive(async || {
4025            in_send.send_many_unordered([1, 2, 3]);
4026
4027            assert!(
4028                out_recv.collect::<Vec<_>>().await != vec![(1, 3), (2, 2)],
4029                "saw both (1, 3) and (2, 2), so batching must have shuffled the order"
4030            )
4031        });
4032    }
4033
4034    #[cfg(feature = "sim")]
4035    #[test]
4036    fn sim_batch_unordered_shuffles_count() {
4037        let mut flow = FlowBuilder::new();
4038        let node = flow.process::<()>();
4039
4040        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4041
4042        let tick = node.tick();
4043        let batch = input.batch(&tick, nondet!(/** test */));
4044        let out_recv = batch.all_ticks().sim_output();
4045
4046        let instance_count = flow.sim().exhaustive(async || {
4047            in_send.send_many_unordered([1, 2, 3, 4]);
4048            out_recv.assert_yields_only_unordered([1, 2, 3, 4]).await;
4049        });
4050
4051        assert_eq!(
4052            instance_count,
4053            75 // ∑ (k=1 to 4) S(4,k) × k! = 75
4054        )
4055    }
4056
4057    #[cfg(feature = "sim")]
4058    #[test]
4059    #[should_panic]
4060    fn sim_observe_order_batched() {
4061        let mut flow = FlowBuilder::new();
4062        let node = flow.process::<()>();
4063
4064        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4065
4066        let tick = node.tick();
4067        let batch = input.batch(&tick, nondet!(/** test */));
4068        let out_recv = batch
4069            .assume_ordering::<TotalOrder>(nondet!(/** test */))
4070            .all_ticks()
4071            .sim_output();
4072
4073        flow.sim().exhaustive(async || {
4074            in_send.send_many_unordered([1, 2, 3, 4]);
4075            out_recv.assert_yields_only([1, 2, 3, 4]).await; // fails with assume_ordering
4076        });
4077    }
4078
4079    #[cfg(feature = "sim")]
4080    #[test]
4081    fn sim_observe_order_batched_count() {
4082        let mut flow = FlowBuilder::new();
4083        let node = flow.process::<()>();
4084
4085        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4086
4087        let tick = node.tick();
4088        let batch = input.batch(&tick, nondet!(/** test */));
4089        let out_recv = batch
4090            .assume_ordering::<TotalOrder>(nondet!(/** test */))
4091            .all_ticks()
4092            .sim_output();
4093
4094        let instance_count = flow.sim().exhaustive(async || {
4095            in_send.send_many_unordered([1, 2, 3, 4]);
4096            let _ = out_recv.collect::<Vec<_>>().await;
4097        });
4098
4099        assert_eq!(
4100            instance_count,
4101            192 // 4! * 2^{4 - 1}
4102        )
4103    }
4104
4105    #[cfg(feature = "sim")]
4106    #[test]
4107    fn sim_unordered_count_instance_count() {
4108        let mut flow = FlowBuilder::new();
4109        let node = flow.process::<()>();
4110
4111        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4112
4113        let tick = node.tick();
4114        let out_recv = input
4115            .count()
4116            .snapshot(&tick, nondet!(/** test */))
4117            .all_ticks()
4118            .sim_output();
4119
4120        let instance_count = flow.sim().exhaustive(async || {
4121            in_send.send_many_unordered([1, 2, 3, 4]);
4122            assert_eq!(out_recv.collect::<Vec<_>>().await.last(), Some(&4));
4123        });
4124
4125        assert_eq!(
4126            instance_count,
4127            16 // 2^4, { 0, 1, 2, 3 } can be a snapshot and 4 is always included
4128        )
4129    }
4130
4131    #[cfg(feature = "sim")]
4132    #[test]
4133    fn sim_top_level_assume_ordering() {
4134        let mut flow = FlowBuilder::new();
4135        let node = flow.process::<()>();
4136
4137        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4138
4139        let out_recv = input
4140            .assume_ordering::<TotalOrder>(nondet!(/** test */))
4141            .sim_output();
4142
4143        let instance_count = flow.sim().exhaustive(async || {
4144            in_send.send_many_unordered([1, 2, 3]);
4145            let mut out = out_recv.collect::<Vec<_>>().await;
4146            out.sort();
4147            assert_eq!(out, vec![1, 2, 3]);
4148        });
4149
4150        assert_eq!(instance_count, 6)
4151    }
4152
4153    #[cfg(feature = "sim")]
4154    #[test]
4155    fn sim_top_level_assume_ordering_cycle_back() {
4156        let mut flow = FlowBuilder::new();
4157        let node = flow.process::<()>();
4158        let node2 = flow.process::<()>();
4159
4160        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4161
4162        let (complete_cycle_back, cycle_back) =
4163            node.forward_ref::<super::Stream<_, _, _, NoOrder>>();
4164        let ordered = input
4165            .merge_unordered(cycle_back)
4166            .assume_ordering::<TotalOrder>(nondet!(/** test */));
4167        complete_cycle_back.complete(
4168            ordered
4169                .clone()
4170                .map(q!(|v| v + 1))
4171                .filter(q!(|v| v % 2 == 1))
4172                .send(&node2, TCP.fail_stop().bincode())
4173                .send(&node, TCP.fail_stop().bincode()),
4174        );
4175
4176        let out_recv = ordered.sim_output();
4177
4178        let mut saw = false;
4179        let instance_count = flow.sim().exhaustive(async || {
4180            in_send.send_many_unordered([0, 2]);
4181            let out = out_recv.collect::<Vec<_>>().await;
4182
4183            if out.starts_with(&[0, 1, 2]) {
4184                saw = true;
4185            }
4186        });
4187
4188        assert!(saw, "did not see an instance with 0, 1, 2 in order");
4189        assert_eq!(instance_count, 6);
4190    }
4191
4192    #[cfg(feature = "sim")]
4193    #[test]
4194    fn sim_top_level_assume_ordering_cycle_back_tick() {
4195        let mut flow = FlowBuilder::new();
4196        let node = flow.process::<()>();
4197        let node2 = flow.process::<()>();
4198
4199        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4200
4201        let (complete_cycle_back, cycle_back) =
4202            node.forward_ref::<super::Stream<_, _, _, NoOrder>>();
4203        let ordered = input
4204            .merge_unordered(cycle_back)
4205            .assume_ordering::<TotalOrder>(nondet!(/** test */));
4206        complete_cycle_back.complete(
4207            ordered
4208                .clone()
4209                .batch(&node.tick(), nondet!(/** test */))
4210                .all_ticks()
4211                .map(q!(|v| v + 1))
4212                .filter(q!(|v| v % 2 == 1))
4213                .send(&node2, TCP.fail_stop().bincode())
4214                .send(&node, TCP.fail_stop().bincode()),
4215        );
4216
4217        let out_recv = ordered.sim_output();
4218
4219        let mut saw = false;
4220        let instance_count = flow.sim().exhaustive(async || {
4221            in_send.send_many_unordered([0, 2]);
4222            let out = out_recv.collect::<Vec<_>>().await;
4223
4224            if out.starts_with(&[0, 1, 2]) {
4225                saw = true;
4226            }
4227        });
4228
4229        assert!(saw, "did not see an instance with 0, 1, 2 in order");
4230        assert_eq!(instance_count, 58);
4231    }
4232
4233    #[cfg(feature = "sim")]
4234    #[test]
4235    fn sim_top_level_assume_ordering_multiple() {
4236        let mut flow = FlowBuilder::new();
4237        let node = flow.process::<()>();
4238        let node2 = flow.process::<()>();
4239
4240        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4241        let (_, input2) = node.sim_input::<_, NoOrder, _>();
4242
4243        let (complete_cycle_back, cycle_back) =
4244            node.forward_ref::<super::Stream<_, _, _, NoOrder>>();
4245        let input1_ordered = input
4246            .clone()
4247            .merge_unordered(cycle_back)
4248            .assume_ordering::<TotalOrder>(nondet!(/** test */));
4249        let foo = input1_ordered
4250            .clone()
4251            .map(q!(|v| v + 3))
4252            .weaken_ordering::<NoOrder>()
4253            .merge_unordered(input2)
4254            .assume_ordering::<TotalOrder>(nondet!(/** test */));
4255
4256        complete_cycle_back.complete(
4257            foo.filter(q!(|v| *v == 3))
4258                .send(&node2, TCP.fail_stop().bincode())
4259                .send(&node, TCP.fail_stop().bincode()),
4260        );
4261
4262        let out_recv = input1_ordered.sim_output();
4263
4264        let mut saw = false;
4265        let instance_count = flow.sim().exhaustive(async || {
4266            in_send.send_many_unordered([0, 1]);
4267            let out = out_recv.collect::<Vec<_>>().await;
4268
4269            if out.starts_with(&[0, 3, 1]) {
4270                saw = true;
4271            }
4272        });
4273
4274        assert!(saw, "did not see an instance with 0, 3, 1 in order");
4275        assert_eq!(instance_count, 15);
4276    }
4277
4278    #[cfg(feature = "sim")]
4279    #[test]
4280    fn sim_atomic_assume_ordering_cycle_back() {
4281        let mut flow = FlowBuilder::new();
4282        let node = flow.process::<()>();
4283        let node2 = flow.process::<()>();
4284
4285        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4286
4287        let (complete_cycle_back, cycle_back) =
4288            node.forward_ref::<super::Stream<_, _, _, NoOrder>>();
4289        let ordered = input
4290            .merge_unordered(cycle_back)
4291            .atomic()
4292            .assume_ordering::<TotalOrder>(nondet!(/** test */))
4293            .end_atomic();
4294        complete_cycle_back.complete(
4295            ordered
4296                .clone()
4297                .map(q!(|v| v + 1))
4298                .filter(q!(|v| v % 2 == 1))
4299                .send(&node2, TCP.fail_stop().bincode())
4300                .send(&node, TCP.fail_stop().bincode()),
4301        );
4302
4303        let out_recv = ordered.sim_output();
4304
4305        let instance_count = flow.sim().exhaustive(async || {
4306            in_send.send_many_unordered([0, 2]);
4307            let out = out_recv.collect::<Vec<_>>().await;
4308            assert_eq!(out.len(), 4);
4309        });
4310        assert_eq!(instance_count, 22);
4311    }
4312
4313    #[cfg(feature = "deploy")]
4314    #[tokio::test]
4315    async fn partition_evens_odds() {
4316        let mut deployment = Deployment::new();
4317
4318        let mut flow = FlowBuilder::new();
4319        let node = flow.process::<()>();
4320        let external = flow.external::<()>();
4321
4322        let numbers = node.source_iter(q!(vec![1i32, 2, 3, 4, 5, 6]));
4323        let (evens, odds) = numbers.partition(q!(|x: &i32| x % 2 == 0));
4324        let evens_port = evens.send_bincode_external(&external);
4325        let odds_port = odds.send_bincode_external(&external);
4326
4327        let nodes = flow
4328            .with_process(&node, deployment.Localhost())
4329            .with_external(&external, deployment.Localhost())
4330            .deploy(&mut deployment);
4331
4332        deployment.deploy().await.unwrap();
4333
4334        let mut evens_out = nodes.connect(evens_port).await;
4335        let mut odds_out = nodes.connect(odds_port).await;
4336
4337        deployment.start().await.unwrap();
4338
4339        let mut even_results = Vec::new();
4340        for _ in 0..3 {
4341            even_results.push(evens_out.next().await.unwrap());
4342        }
4343        even_results.sort();
4344        assert_eq!(even_results, vec![2, 4, 6]);
4345
4346        let mut odd_results = Vec::new();
4347        for _ in 0..3 {
4348            odd_results.push(odds_out.next().await.unwrap());
4349        }
4350        odd_results.sort();
4351        assert_eq!(odd_results, vec![1, 3, 5]);
4352    }
4353
4354    #[cfg(feature = "deploy")]
4355    #[tokio::test]
4356    async fn unconsumed_inspect_still_runs() {
4357        use crate::deploy::DeployCrateWrapper;
4358
4359        let mut deployment = Deployment::new();
4360
4361        let mut flow = FlowBuilder::new();
4362        let node = flow.process::<()>();
4363
4364        // The return value of .inspect() is intentionally dropped.
4365        // Before the Null-root fix, this would silently do nothing.
4366        node.source_iter(q!(0..5))
4367            .inspect(q!(|x| println!("inspect: {}", x)));
4368
4369        let nodes = flow
4370            .with_process(&node, deployment.Localhost())
4371            .deploy(&mut deployment);
4372
4373        deployment.deploy().await.unwrap();
4374
4375        let mut stdout = nodes.get_process(&node).stdout();
4376
4377        deployment.start().await.unwrap();
4378
4379        let mut lines = Vec::new();
4380        for _ in 0..5 {
4381            lines.push(stdout.recv().await.unwrap());
4382        }
4383        lines.sort();
4384        assert_eq!(
4385            lines,
4386            vec![
4387                "inspect: 0",
4388                "inspect: 1",
4389                "inspect: 2",
4390                "inspect: 3",
4391                "inspect: 4",
4392            ]
4393        );
4394    }
4395
4396    #[cfg(feature = "deploy")]
4397    #[tokio::test]
4398    async fn unconsumed_inspect_alive_at_deploy_still_runs() {
4399        use crate::deploy::DeployCrateWrapper;
4400
4401        let mut deployment = Deployment::new();
4402
4403        let mut flow = FlowBuilder::new();
4404        let node = flow.process::<()>();
4405
4406        // The return value of .inspect() is bound to a variable that is still alive
4407        // when the flow is finalized by `deploy` below, so its `Drop` runs too late
4408        // to register a root the usual way. The FlowBuilder must yank the IR from
4409        // still-live collections when finalizing.
4410        let _inspected = node
4411            .source_iter(q!(0..5))
4412            .inspect(q!(|x| println!("inspect: {}", x)));
4413
4414        let nodes = flow
4415            .with_process(&node, deployment.Localhost())
4416            .deploy(&mut deployment);
4417
4418        deployment.deploy().await.unwrap();
4419
4420        let mut stdout = nodes.get_process(&node).stdout();
4421
4422        deployment.start().await.unwrap();
4423
4424        let mut lines = Vec::new();
4425        for _ in 0..5 {
4426            lines.push(stdout.recv().await.unwrap());
4427        }
4428        lines.sort();
4429        assert_eq!(
4430            lines,
4431            vec![
4432                "inspect: 0",
4433                "inspect: 1",
4434                "inspect: 2",
4435                "inspect: 3",
4436                "inspect: 4",
4437            ]
4438        );
4439    }
4440
4441    #[cfg(feature = "sim")]
4442    #[test]
4443    fn sim_limit() {
4444        let mut flow = FlowBuilder::new();
4445        let node = flow.process::<()>();
4446
4447        let (in_send, input) = node.sim_input();
4448
4449        let out_recv = input.limit(q!(3)).sim_output();
4450
4451        flow.sim().exhaustive(async || {
4452            in_send.send(1);
4453            in_send.send(2);
4454            in_send.send(3);
4455            in_send.send(4);
4456            in_send.send(5);
4457
4458            out_recv.assert_yields_only([1, 2, 3]).await;
4459        });
4460    }
4461
4462    #[cfg(feature = "sim")]
4463    #[test]
4464    fn sim_limit_zero() {
4465        let mut flow = FlowBuilder::new();
4466        let node = flow.process::<()>();
4467
4468        let (in_send, input) = node.sim_input();
4469
4470        let out_recv = input.limit(q!(0)).sim_output();
4471
4472        flow.sim().exhaustive(async || {
4473            in_send.send(1);
4474            in_send.send(2);
4475
4476            out_recv.assert_yields_only::<i32, _>([]).await;
4477        });
4478    }
4479
4480    #[cfg(feature = "sim")]
4481    #[test]
4482    fn sim_merge_ordered() {
4483        let mut flow = FlowBuilder::new();
4484        let node = flow.process::<()>();
4485
4486        let (in_send, input) = node.sim_input();
4487        let (in_send2, input2) = node.sim_input();
4488
4489        let out_recv = input
4490            .merge_ordered(input2, nondet!(/** test */))
4491            .sim_output();
4492
4493        let mut saw_out_of_order = false;
4494        let instances = flow.sim().exhaustive(async || {
4495            in_send.send(1);
4496            in_send.send(2);
4497            in_send2.send(3);
4498            in_send2.send(4);
4499
4500            let out = out_recv.collect::<Vec<_>>().await;
4501
4502            if out == [1, 3, 2, 4] {
4503                saw_out_of_order = true;
4504            }
4505
4506            // Assert ordering preservation: elements from each input must
4507            // appear in their original relative order.
4508            let mut first_elements = out.iter().filter(|v| **v <= 2).copied().collect::<Vec<_>>();
4509            let mut second_elements = out.iter().filter(|v| **v > 2).copied().collect::<Vec<_>>();
4510            assert_eq!(
4511                first_elements,
4512                vec![1, 2],
4513                "first input order violated: {:?}",
4514                out
4515            );
4516            assert_eq!(
4517                second_elements,
4518                vec![3, 4],
4519                "second input order violated: {:?}",
4520                out
4521            );
4522
4523            first_elements.append(&mut second_elements);
4524            first_elements.sort();
4525            assert_eq!(first_elements, vec![1, 2, 3, 4]);
4526        });
4527
4528        assert!(saw_out_of_order);
4529        assert_eq!(instances, 6);
4530    }
4531
4532    /// Tests that `merge_ordered` passes through elements when only one input
4533    /// has data.
4534    #[cfg(feature = "sim")]
4535    #[test]
4536    fn sim_merge_ordered_one_empty() {
4537        let mut flow = FlowBuilder::new();
4538        let node = flow.process::<()>();
4539
4540        let (in_send, input) = node.sim_input();
4541        let (_in_send2, input2) = node.sim_input();
4542
4543        let out_recv = input
4544            .merge_ordered(input2, nondet!(/** test */))
4545            .sim_output();
4546
4547        let instances = flow.sim().exhaustive(async || {
4548            in_send.send(1);
4549            in_send.send(2);
4550
4551            let out = out_recv.collect::<Vec<_>>().await;
4552            assert_eq!(out, vec![1, 2]);
4553        });
4554
4555        // Only one possible interleaving when one input is empty
4556        assert_eq!(instances, 1);
4557    }
4558
4559    /// Tests that `merge_ordered` correctly handles feedback cycles.
4560    /// An element output from `merge_ordered` is filtered and cycled back to
4561    /// one of its inputs. The one-at-a-time release must allow the cycled-back
4562    /// element to arrive and potentially be emitted before elements still
4563    /// waiting on the other input.
4564    #[cfg(feature = "sim")]
4565    #[test]
4566    fn sim_merge_ordered_cycle_back() {
4567        let mut flow = FlowBuilder::new();
4568        let node = flow.process::<()>();
4569
4570        let (in_send, input) = node.sim_input();
4571
4572        // Create a forward ref for the cycle back
4573        let (complete_cycle_back, cycle_back) =
4574            node.forward_ref::<super::Stream<_, _, _, TotalOrder>>();
4575
4576        // merge_ordered: input (external) with cycle_back
4577        let merged = input.merge_ordered(cycle_back, nondet!(/** test */));
4578
4579        // Cycle back: elements equal to 1 get mapped to 10 and fed back
4580        complete_cycle_back.complete(merged.clone().filter(q!(|v| *v == 1)).map(q!(|v| v * 10)));
4581
4582        let out_recv = merged.sim_output();
4583
4584        // Send 1 and 2. Element 1 should cycle back as 10.
4585        // Valid orderings must have 1 before 10 (since 10 depends on 1).
4586        let mut saw_cycle_before_second = false;
4587        flow.sim().exhaustive(async || {
4588            in_send.send(1);
4589            in_send.send(2);
4590
4591            let out = out_recv.collect::<Vec<_>>().await;
4592
4593            // 10 must always come after 1 (causal dependency)
4594            let pos_1 = out.iter().position(|v| *v == 1).unwrap();
4595            let pos_10 = out.iter().position(|v| *v == 10).unwrap();
4596            assert!(pos_1 < pos_10, "causal order violated: {:?}", out);
4597
4598            // Check if we see [1, 10, 2] — the cycled element beats the second input
4599            if out == [1, 10, 2] {
4600                saw_cycle_before_second = true;
4601            }
4602
4603            let mut sorted = out;
4604            sorted.sort();
4605            assert_eq!(sorted, vec![1, 2, 10]);
4606        });
4607
4608        assert!(
4609            saw_cycle_before_second,
4610            "never saw the cycled element arrive before the second input element"
4611        );
4612    }
4613
4614    /// Tests that `merge_ordered` correctly interleaves when one input has a
4615    /// delayed element. With a: [1, _delay_, 2] and b: [3, 4], the delayed
4616    /// element 2 should be able to appear after b's elements.
4617    #[cfg(feature = "sim")]
4618    #[test]
4619    fn sim_merge_ordered_delayed() {
4620        let mut flow = FlowBuilder::new();
4621        let node = flow.process::<()>();
4622
4623        let (in_send, input) = node.sim_input();
4624        let (in_send2, input2) = node.sim_input();
4625
4626        let out_recv = input
4627            .merge_ordered(input2, nondet!(/** test */))
4628            .sim_output();
4629
4630        let mut saw_delayed_interleaving = false;
4631        flow.sim().exhaustive(async || {
4632            // Send 1 from a, and 3, 4 from b
4633            in_send.send(1);
4634            in_send2.send(3);
4635            in_send2.send(4);
4636
4637            // Collect what's available so far
4638            let first_batch = out_recv.collect::<Vec<_>>().await;
4639
4640            // Now send the delayed element 2 from a
4641            in_send.send(2);
4642            let second_batch = out_recv.collect::<Vec<_>>().await;
4643
4644            let mut all: Vec<_> = first_batch
4645                .iter()
4646                .chain(second_batch.iter())
4647                .copied()
4648                .collect();
4649
4650            // Check if we saw [1, 3, 4, 2] — the delayed interleaving
4651            if all == [1, 3, 4, 2] {
4652                saw_delayed_interleaving = true;
4653            }
4654
4655            all.sort();
4656            assert_eq!(all, vec![1, 2, 3, 4]);
4657        });
4658
4659        assert!(saw_delayed_interleaving);
4660    }
4661
4662    /// Deploy test: `merge_ordered` with a delayed element on one input.
4663    /// Sends a=1, b=3, b=4, then after receiving those, sends a=2.
4664    /// Expects to see [1, 3, 4] first, then [2] — demonstrating that
4665    /// both inputs are pulled and the delayed element arrives later.
4666    #[cfg(feature = "deploy")]
4667    #[tokio::test]
4668    async fn deploy_merge_ordered_delayed() {
4669        let mut deployment = Deployment::new();
4670
4671        let mut flow = FlowBuilder::new();
4672        let node = flow.process::<()>();
4673        let external = flow.external::<()>();
4674
4675        let (input_a_port, input_a) = node.source_external_bincode(&external);
4676        let (input_b_port, input_b) = node.source_external_bincode(&external);
4677
4678        let out = input_a
4679            .assume_ordering(nondet!(/** test */))
4680            .merge_ordered(
4681                input_b.assume_ordering(nondet!(/** test */)),
4682                nondet!(/** test */),
4683            )
4684            .send_bincode_external(&external);
4685
4686        let nodes = flow
4687            .with_process(&node, deployment.Localhost())
4688            .with_external(&external, deployment.Localhost())
4689            .deploy(&mut deployment);
4690
4691        deployment.deploy().await.unwrap();
4692
4693        let mut ext_a = nodes.connect(input_a_port).await;
4694        let mut ext_b = nodes.connect(input_b_port).await;
4695        let mut ext_out = nodes.connect(out).await;
4696
4697        deployment.start().await.unwrap();
4698
4699        // Send a=1, b=3, b=4
4700        ext_a.send(1).await.unwrap();
4701        ext_b.send(3).await.unwrap();
4702        ext_b.send(4).await.unwrap();
4703
4704        // Collect the first 3 elements
4705        let mut received = Vec::new();
4706        for _ in 0..3 {
4707            received.push(ext_out.next().await.unwrap());
4708        }
4709
4710        // Now send the delayed a=2
4711        ext_a.send(2).await.unwrap();
4712        received.push(ext_out.next().await.unwrap());
4713
4714        // All elements should be present
4715        received.sort();
4716        assert_eq!(received, vec![1, 2, 3, 4]);
4717    }
4718
4719    #[cfg(feature = "deploy")]
4720    #[tokio::test]
4721    async fn monotone_fold_threshold() {
4722        use crate::properties::manual_proof;
4723
4724        let mut deployment = Deployment::new();
4725
4726        let mut flow = FlowBuilder::new();
4727        let node = flow.process::<()>();
4728        let external = flow.external::<()>();
4729
4730        let in_unbounded: super::Stream<_, _> =
4731            node.source_iter(q!(vec![1i32, 2, 3, 4, 5, 6])).into();
4732        let sum = in_unbounded.fold(
4733            q!(|| 0),
4734            q!(
4735                |sum, v| {
4736                    *sum += v;
4737                },
4738                monotone = manual_proof!(/** test */)
4739            ),
4740        );
4741
4742        let threshold_out = sum
4743            .threshold_greater_or_equal(node.singleton(q!(7)))
4744            .send_bincode_external(&external);
4745
4746        let nodes = flow
4747            .with_process(&node, deployment.Localhost())
4748            .with_external(&external, deployment.Localhost())
4749            .deploy(&mut deployment);
4750
4751        deployment.deploy().await.unwrap();
4752
4753        let mut threshold_out = nodes.connect(threshold_out).await;
4754
4755        deployment.start().await.unwrap();
4756
4757        assert_eq!(threshold_out.next().await.unwrap(), 7);
4758    }
4759
4760    #[cfg(feature = "deploy")]
4761    #[tokio::test]
4762    async fn monotone_count_threshold() {
4763        let mut deployment = Deployment::new();
4764
4765        let mut flow = FlowBuilder::new();
4766        let node = flow.process::<()>();
4767        let external = flow.external::<()>();
4768
4769        let in_unbounded: super::Stream<_, _> =
4770            node.source_iter(q!(vec![1i32, 2, 3, 4, 5, 6])).into();
4771        let sum = in_unbounded.count();
4772
4773        let threshold_out = sum
4774            .threshold_greater_or_equal(node.singleton(q!(3)))
4775            .send_bincode_external(&external);
4776
4777        let nodes = flow
4778            .with_process(&node, deployment.Localhost())
4779            .with_external(&external, deployment.Localhost())
4780            .deploy(&mut deployment);
4781
4782        deployment.deploy().await.unwrap();
4783
4784        let mut threshold_out = nodes.connect(threshold_out).await;
4785
4786        deployment.start().await.unwrap();
4787
4788        assert_eq!(threshold_out.next().await.unwrap(), 3);
4789    }
4790
4791    #[cfg(feature = "deploy")]
4792    #[tokio::test]
4793    async fn monotone_map_order_preserving_threshold() {
4794        use crate::properties::manual_proof;
4795
4796        let mut deployment = Deployment::new();
4797
4798        let mut flow = FlowBuilder::new();
4799        let node = flow.process::<()>();
4800        let external = flow.external::<()>();
4801
4802        let in_unbounded: super::Stream<_, _> =
4803            node.source_iter(q!(vec![1i32, 2, 3, 4, 5, 6])).into();
4804        let sum = in_unbounded.fold(
4805            q!(|| 0),
4806            q!(
4807                |sum, v| {
4808                    *sum += v;
4809                },
4810                monotone = manual_proof!(/** test */)
4811            ),
4812        );
4813
4814        // map with order_preserving should preserve monotonicity
4815        let doubled = sum.map(q!(
4816            |v| v * 2,
4817            order_preserving = manual_proof!(/** doubling preserves order */)
4818        ));
4819
4820        let threshold_out = doubled
4821            .threshold_greater_or_equal(node.singleton(q!(14)))
4822            .send_bincode_external(&external);
4823
4824        let nodes = flow
4825            .with_process(&node, deployment.Localhost())
4826            .with_external(&external, deployment.Localhost())
4827            .deploy(&mut deployment);
4828
4829        deployment.deploy().await.unwrap();
4830
4831        let mut threshold_out = nodes.connect(threshold_out).await;
4832
4833        deployment.start().await.unwrap();
4834
4835        assert_eq!(threshold_out.next().await.unwrap(), 14);
4836    }
4837
4838    // === Compile-time type tests for join/cross_product ordering ===
4839
4840    #[cfg(any(feature = "deploy", feature = "sim"))]
4841    mod join_ordering_type_tests {
4842        use crate::live_collections::boundedness::{Bounded, Unbounded};
4843        use crate::live_collections::stream::{ExactlyOnce, NoOrder, Stream, TotalOrder};
4844        use crate::location::{Location, Process};
4845
4846        #[expect(dead_code, reason = "compile-time type test")]
4847        fn join_unbounded_with_bounded_preserves_order<'a>(
4848            left: Stream<(i32, char), Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4849            right: Stream<(i32, char), Process<'a>, Bounded, TotalOrder, ExactlyOnce>,
4850        ) -> Stream<(i32, (char, char)), Process<'a>, Unbounded, TotalOrder, ExactlyOnce> {
4851            left.join(right)
4852        }
4853
4854        #[expect(dead_code, reason = "compile-time type test")]
4855        fn join_unbounded_with_unbounded_is_no_order<'a>(
4856            left: Stream<(i32, char), Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4857            right: Stream<(i32, char), Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4858        ) -> Stream<(i32, (char, char)), Process<'a>, Unbounded, NoOrder, ExactlyOnce> {
4859            left.join(right)
4860        }
4861
4862        #[expect(dead_code, reason = "compile-time type test")]
4863        fn join_bounded_with_bounded_preserves_order<'a, L: Location<'a>>(
4864            left: Stream<(i32, char), L, Bounded, TotalOrder, ExactlyOnce>,
4865            right: Stream<(i32, char), L, Bounded, TotalOrder, ExactlyOnce>,
4866        ) -> Stream<(i32, (char, char)), L, Bounded, TotalOrder, ExactlyOnce> {
4867            left.join(right)
4868        }
4869
4870        #[expect(dead_code, reason = "compile-time type test")]
4871        fn join_unbounded_noorder_with_bounded<'a>(
4872            left: Stream<(i32, char), Process<'a>, Unbounded, NoOrder, ExactlyOnce>,
4873            right: Stream<(i32, char), Process<'a>, Bounded, NoOrder, ExactlyOnce>,
4874        ) -> Stream<(i32, (char, char)), Process<'a>, Unbounded, NoOrder, ExactlyOnce> {
4875            left.join(right)
4876        }
4877
4878        // === Compile-time type tests for cross_product ordering ===
4879
4880        #[expect(dead_code, reason = "compile-time type test")]
4881        fn cross_product_unbounded_with_bounded_preserves_order<'a>(
4882            left: Stream<i32, Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4883            right: Stream<char, Process<'a>, Bounded, TotalOrder, ExactlyOnce>,
4884        ) -> Stream<(i32, char), Process<'a>, Unbounded, TotalOrder, ExactlyOnce> {
4885            left.cross_product(right)
4886        }
4887
4888        #[expect(dead_code, reason = "compile-time type test")]
4889        fn cross_product_bounded_with_bounded_preserves_order<'a>(
4890            left: Stream<i32, Process<'a>, Bounded, TotalOrder, ExactlyOnce>,
4891            right: Stream<char, Process<'a>, Bounded, TotalOrder, ExactlyOnce>,
4892        ) -> Stream<(i32, char), Process<'a>, Bounded, TotalOrder, ExactlyOnce> {
4893            left.cross_product(right)
4894        }
4895
4896        #[expect(dead_code, reason = "compile-time type test")]
4897        fn cross_product_unbounded_with_unbounded_is_no_order<'a>(
4898            left: Stream<i32, Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4899            right: Stream<char, Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4900        ) -> Stream<(i32, char), Process<'a>, Unbounded, NoOrder, ExactlyOnce> {
4901            left.cross_product(right)
4902        }
4903    } // mod join_ordering_type_tests
4904
4905    // === Runtime correctness tests for bounded join/cross_product ===
4906
4907    #[cfg(feature = "sim")]
4908    #[test]
4909    fn cross_product_mixed_boundedness_correctness() {
4910        use stageleft::q;
4911
4912        use crate::compile::builder::FlowBuilder;
4913        use crate::nondet::nondet;
4914
4915        let mut flow = FlowBuilder::new();
4916        let process = flow.process::<()>();
4917        let tick = process.tick();
4918
4919        let left = process.source_iter(q!(vec![1, 2]));
4920        let right = process
4921            .source_iter(q!(vec!['a', 'b']))
4922            .batch(&tick, nondet!(/** test */))
4923            .all_ticks();
4924
4925        let out = left.cross_product(right).sim_output();
4926
4927        flow.sim().exhaustive(async || {
4928            out.assert_yields_only_unordered(vec![(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')])
4929                .await;
4930        });
4931    }
4932
4933    #[cfg(feature = "sim")]
4934    #[test]
4935    fn join_mixed_boundedness_correctness() {
4936        use stageleft::q;
4937
4938        use crate::compile::builder::FlowBuilder;
4939        use crate::nondet::nondet;
4940
4941        let mut flow = FlowBuilder::new();
4942        let process = flow.process::<()>();
4943        let tick = process.tick();
4944
4945        let left = process.source_iter(q!(vec![(1, 'a'), (2, 'b')]));
4946        let right = process
4947            .source_iter(q!(vec![(1, 'x'), (2, 'y')]))
4948            .batch(&tick, nondet!(/** test */))
4949            .all_ticks();
4950
4951        let out = left.join(right).sim_output();
4952
4953        flow.sim().exhaustive(async || {
4954            out.assert_yields_only_unordered(vec![(1, ('a', 'x')), (2, ('b', 'y'))])
4955                .await;
4956        });
4957    }
4958
4959    #[cfg(feature = "sim")]
4960    #[test]
4961    fn sim_merge_unordered_independent_atomics() {
4962        let mut flow = FlowBuilder::new();
4963        let node = flow.process::<()>();
4964
4965        let (in1_send, input1) = node.sim_input::<_, TotalOrder, _>();
4966        let (in2_send, input2) = node.sim_input::<_, TotalOrder, _>();
4967
4968        let out = input1
4969            .atomic()
4970            .merge_unordered(input2.atomic())
4971            .end_atomic()
4972            .sim_output();
4973
4974        flow.sim().exhaustive(async || {
4975            in1_send.send(1);
4976            in2_send.send(2);
4977
4978            out.assert_yields_only_unordered(vec![1, 2]).await;
4979        });
4980    }
4981
4982    #[cfg(feature = "deploy")]
4983    #[tokio::test]
4984    async fn test_stream_ref() {
4985        let mut deployment = Deployment::new();
4986
4987        let mut flow = FlowBuilder::new();
4988        let external = flow.external::<()>();
4989        let p1 = flow.process::<()>();
4990
4991        // Create a bounded stream (source_iter is bounded within a tick)
4992        let my_stream = p1.source_iter(q!(1..=5i32));
4993
4994        let stream_ref = my_stream.by_ref();
4995
4996        // Use the stream ref to get the vec's length
4997        let out_port = p1
4998            .source_iter(q!([()]))
4999            .map(q!(|_| stream_ref.len() as i32))
5000            .send_bincode_external(&external);
5001
5002        // Also consume the stream via pipe
5003        my_stream.for_each(q!(|_| {}));
5004
5005        let nodes = flow
5006            .with_default_optimize()
5007            .with_process(&p1, deployment.Localhost())
5008            .with_external(&external, deployment.Localhost())
5009            .deploy(&mut deployment);
5010
5011        deployment.deploy().await.unwrap();
5012
5013        let mut out_recv = nodes.connect(out_port).await;
5014
5015        deployment.start().await.unwrap();
5016
5017        let result = out_recv.next().await.unwrap();
5018        // stream has 5 elements
5019        assert_eq!(result, 5);
5020    }
5021
5022    #[cfg(feature = "deploy")]
5023    #[tokio::test]
5024    async fn test_stream_ref_contents() {
5025        let mut deployment = Deployment::new();
5026
5027        let mut flow = FlowBuilder::new();
5028        let external = flow.external::<()>();
5029        let p1 = flow.process::<()>();
5030
5031        // Create a bounded stream
5032        let my_stream = p1.source_iter(q!(1..=3i32));
5033
5034        let stream_ref = my_stream.by_ref();
5035
5036        // Sum the referenced vec's contents
5037        let out_port = p1
5038            .source_iter(q!([()]))
5039            .map(q!(|_| stream_ref.iter().sum::<i32>()))
5040            .send_bincode_external(&external);
5041
5042        my_stream.for_each(q!(|_| {}));
5043
5044        let nodes = flow
5045            .with_default_optimize()
5046            .with_process(&p1, deployment.Localhost())
5047            .with_external(&external, deployment.Localhost())
5048            .deploy(&mut deployment);
5049
5050        deployment.deploy().await.unwrap();
5051
5052        let mut out_recv = nodes.connect(out_port).await;
5053
5054        deployment.start().await.unwrap();
5055
5056        let result = out_recv.next().await.unwrap();
5057        // sum of 1+2+3 = 6
5058        assert_eq!(result, 6);
5059    }
5060
5061    #[cfg(feature = "deploy")]
5062    #[tokio::test]
5063    async fn test_stream_ref_no_consumer() {
5064        let mut deployment = Deployment::new();
5065
5066        let mut flow = FlowBuilder::new();
5067        let external = flow.external::<()>();
5068        let p1 = flow.process::<()>();
5069
5070        // Create a bounded stream — no pipe consumer, only ref
5071        let my_stream = p1.source_iter(q!(1..=4i32));
5072
5073        let stream_ref = my_stream.by_ref();
5074
5075        let out_port = p1
5076            .source_iter(q!([()]))
5077            .map(q!(|_| stream_ref.len() as i32))
5078            .send_bincode_external(&external);
5079
5080        let nodes = flow
5081            .with_default_optimize()
5082            .with_process(&p1, deployment.Localhost())
5083            .with_external(&external, deployment.Localhost())
5084            .deploy(&mut deployment);
5085
5086        deployment.deploy().await.unwrap();
5087
5088        let mut out_recv = nodes.connect(out_port).await;
5089
5090        deployment.start().await.unwrap();
5091
5092        let result = out_recv.next().await.unwrap();
5093        assert_eq!(result, 4);
5094    }
5095
5096    #[cfg(feature = "deploy")]
5097    #[tokio::test]
5098    async fn test_stream_mut() {
5099        let mut deployment = Deployment::new();
5100
5101        let mut flow = FlowBuilder::new();
5102        let external = flow.external::<()>();
5103        let p1 = flow.process::<()>();
5104
5105        // Create a bounded stream
5106        let my_stream = p1.source_iter(q!(1..=5i32));
5107
5108        let stream_mut = my_stream.by_mut();
5109
5110        // Mutably reference the buffer to retain only items > 3
5111        let out_port = p1
5112            .source_iter(q!([()]))
5113            .map(q!(|_| {
5114                stream_mut.retain(|x| *x > 3);
5115                stream_mut.len() as i32
5116            }))
5117            .send_bincode_external(&external);
5118
5119        my_stream.for_each(q!(|_| {}));
5120
5121        let nodes = flow
5122            .with_default_optimize()
5123            .with_process(&p1, deployment.Localhost())
5124            .with_external(&external, deployment.Localhost())
5125            .deploy(&mut deployment);
5126
5127        deployment.deploy().await.unwrap();
5128
5129        let mut out_recv = nodes.connect(out_port).await;
5130
5131        deployment.start().await.unwrap();
5132
5133        let result = out_recv.next().await.unwrap();
5134        // After retain(> 3): [4, 5] => len = 2
5135        assert_eq!(result, 2);
5136    }
5137
5138    /// A map with a mut singleton ref on an unordered input should produce > 1
5139    /// simulation instance because the ordering of elements through the mut closure
5140    /// is non-deterministic.
5141    #[cfg(feature = "sim")]
5142    #[test]
5143    fn sim_map_with_mut_on_unordered_explores_multiple_states() {
5144        use crate::live_collections::sliced::sliced;
5145        use crate::live_collections::stream::ExactlyOnce;
5146        use crate::properties::manual_proof;
5147
5148        let mut flow = FlowBuilder::new();
5149        let node = flow.process::<()>();
5150
5151        let (trigger_send, trigger) = node.sim_input::<i32, TotalOrder, ExactlyOnce>();
5152
5153        let out_recv = sliced! {
5154            let batch = use::batch(trigger, nondet!(/** test */));
5155            let counter = batch.location().source_iter(q!(vec![0i32]))
5156                .fold(q!(|| 0i32), q!(|acc, v| *acc += v));
5157            let counter_mut = counter.by_mut();
5158            let items = batch.location().source_iter(q!(vec![1i32, 2])).weaken_ordering::<NoOrder>();
5159            items.map(q!(
5160                |x| {
5161                    *counter_mut += x;
5162                    *counter_mut
5163                },
5164                commutative = manual_proof!(/** test */)
5165            ))
5166        }
5167        .sim_output();
5168
5169        let count = flow.sim().exhaustive(async || {
5170            trigger_send.send(1);
5171            let _all: Vec<i32> = out_recv.collect_sorted().await;
5172        });
5173
5174        assert_eq!(
5175            count, 2,
5176            "Expected 2 simulation instances due to mut on unordered input, got {}",
5177            count
5178        );
5179    }
5180
5181    /// A `scan` closure that captures a bounded singleton by reference should compile,
5182    /// run correctly, and (because the input is totally ordered) explore a single
5183    /// simulation instance.
5184    #[cfg(feature = "sim")]
5185    #[test]
5186    fn sim_scan_with_ref_capture() {
5187        use crate::live_collections::sliced::sliced;
5188        use crate::live_collections::stream::ExactlyOnce;
5189
5190        let mut flow = FlowBuilder::new();
5191        let node = flow.process::<()>();
5192
5193        let (trigger_send, trigger) = node.sim_input::<i32, TotalOrder, ExactlyOnce>();
5194
5195        let out_recv = sliced! {
5196            let batch = use::batch(trigger, nondet!(/** test */));
5197            let offset = batch
5198                .location()
5199                .source_iter(q!(vec![10i32]))
5200                .fold(q!(|| 0i32), q!(|acc, v| *acc += v));
5201            let offset_ref = offset.by_ref();
5202            batch
5203                .location()
5204                .source_iter(q!(vec![1i32, 2, 3]))
5205                .scan(
5206                    q!(|| 0i32),
5207                    q!(move |acc: &mut i32, x| {
5208                        *acc += x + *offset_ref;
5209                        Some(*acc)
5210                    }),
5211                )
5212        }
5213        .sim_output();
5214
5215        let count = flow.sim().exhaustive(async || {
5216            trigger_send.send(1);
5217            let all: Vec<i32> = out_recv.collect().await;
5218            // offset = 10, running accumulator starts at 0:
5219            //   x=1: acc += 1 + 10 = 11 -> 11
5220            //   x=2: acc += 2 + 10 = 12 -> 23
5221            //   x=3: acc += 3 + 10 = 13 -> 36
5222            assert_eq!(all, vec![11, 23, 36]);
5223        });
5224
5225        assert_eq!(
5226            count, 1,
5227            "Expected a single simulation instance for a totally-ordered scan, got {}",
5228            count
5229        );
5230    }
5231
5232    /// A map with a mut singleton ref on a top-level unordered input should produce > 1
5233    /// simulation instance. Currently panics because `observe_nondet` doesn't support
5234    /// top-level bounded inputs yet.
5235    #[cfg(feature = "sim")]
5236    #[test]
5237    #[ignore = "observe_nondet not yet supported for top-level bounded inputs (https://github.com/hydro-project/hydro/issues/2950)"]
5238    fn sim_map_with_mut_on_unordered_top_level() {
5239        use crate::properties::manual_proof;
5240
5241        let mut flow = FlowBuilder::new();
5242        let node = flow.process::<()>();
5243
5244        let counter = node
5245            .source_iter(q!(vec![0i32]))
5246            .fold(q!(|| 0i32), q!(|acc, v| *acc += v));
5247        let counter_mut = counter.by_mut();
5248
5249        let out_recv = node
5250            .source_iter(q!(vec![1i32, 2]))
5251            .weaken_ordering::<NoOrder>()
5252            .map(q!(
5253                |x| {
5254                    *counter_mut += x;
5255                    *counter_mut
5256                },
5257                commutative = manual_proof!(/** test */)
5258            ))
5259            .assume_ordering::<TotalOrder>(nondet!(/** test */))
5260            .sim_output();
5261
5262        counter.into_stream().for_each(q!(|_| {}));
5263
5264        let count = flow.sim().exhaustive(async || {
5265            let _all: Vec<i32> = out_recv.collect().await;
5266        });
5267
5268        assert_eq!(
5269            count, 2,
5270            "Expected 2 simulation instances due to mut on unordered input, got {}",
5271            count
5272        );
5273    }
5274}