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