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