Skip to main content

hydro_lang/live_collections/stream/
mod.rs

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