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