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