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<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<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<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<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<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<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 = SharedNode(Rc::new(RefCell::new(
918            self.ir_node.replace(HydroNode::Placeholder),
919        )));
920
921        let true_stream = Stream::new(
922            self.location.clone(),
923            HydroNode::Partition {
924                inner: SharedNode(shared.0.clone()),
925                f: f.clone(),
926                is_true: true,
927                metadata: self.location.new_node_metadata(Self::collection_kind()),
928            },
929        );
930
931        let false_stream = Stream::new(
932            self.location.clone(),
933            HydroNode::Partition {
934                inner: SharedNode(shared.0),
935                f,
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<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<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<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<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        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.
1462        let nondet = nondet!(/** the combinator function is commutative and idempotent */);
1463        let retried: Stream<T, L::DropConsistency, B, O, ExactlyOnce> = self.assume_retries(nondet);
1464
1465        let core = HydroNode::Fold {
1466            init,
1467            acc: comb.into(),
1468            input: Box::new(retried.ir_node.replace(HydroNode::Placeholder)),
1469            metadata: retried
1470                .location
1471                .new_node_metadata(Singleton::<A, L::DropConsistency, B2>::collection_kind()),
1472            // we do not guarantee consistency at this point because if the algebraic properties
1473            // do not hold in practice, replica consistency may fail to be maintained, so we
1474            // would like the simulator to assert consistency; in the future, this will be dynamic
1475            // based on the proof mechanism
1476        };
1477
1478        Singleton::new(retried.location.clone(), core)
1479            .assert_has_consistency_of(manual_proof!(/** algebraic properties */))
1480    }
1481
1482    /// Combines elements of the stream into an [`Optional`], by starting with the first element in the stream,
1483    /// and then applying the `comb` closure to each element in the stream. The [`Optional`] will be empty
1484    /// until the first element in the input arrives. Unlike iterators, `comb` takes the accumulator by `&mut`
1485    /// reference, so that it can be modified in place.
1486    ///
1487    /// Depending on the input stream guarantees, the closure may need to be commutative
1488    /// (for unordered streams) or idempotent (for streams with non-deterministic duplicates).
1489    ///
1490    /// # Example
1491    /// ```rust
1492    /// # #[cfg(feature = "deploy")] {
1493    /// # use hydro_lang::prelude::*;
1494    /// # use futures::StreamExt;
1495    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1496    /// let bools = process.source_iter(q!(vec![false, true, false]));
1497    /// bools.reduce(q!(|acc, x| *acc |= x)).into_stream()
1498    /// # }, |mut stream| async move {
1499    /// // true
1500    /// # assert_eq!(stream.next().await.unwrap(), true);
1501    /// # }));
1502    /// # }
1503    /// ```
1504    pub fn reduce<F, C, Idemp>(
1505        self,
1506        comb: impl IntoQuotedMut<'a, F, OperatorContext<L, B>, AggFuncAlgebra<C, Idemp>>,
1507    ) -> Optional<T, L, B>
1508    where
1509        F: Fn(&mut T, T) + 'a,
1510        C: ValidCommutativityFor<O>,
1511        Idemp: ValidIdempotenceFor<R>,
1512    {
1513        let (f, proof) =
1514            comb.splice_fn2_borrow_mut_ctx_props(&OperatorContext::<L, B>::new(&self.location));
1515        proof.register_proof(&f);
1516
1517        let nondet = nondet!(/** the combinator function is commutative and idempotent */);
1518        let ordered_etc: Stream<T, L::DropConsistency, B> =
1519            self.assume_retries(nondet).assume_ordering(nondet);
1520
1521        let core = HydroNode::Reduce {
1522            f: f.into(),
1523            input: Box::new(ordered_etc.ir_node.replace(HydroNode::Placeholder)),
1524            metadata: ordered_etc
1525                .location
1526                .new_node_metadata(Optional::<T, L::DropConsistency, B>::collection_kind()),
1527        };
1528
1529        Optional::new(ordered_etc.location.clone(), core)
1530            .assert_has_consistency_of(manual_proof!(/** algebraic properties */))
1531    }
1532
1533    /// Computes the maximum element in the stream as an [`Optional`], which
1534    /// will be empty until the first element in the input arrives.
1535    ///
1536    /// # Example
1537    /// ```rust
1538    /// # #[cfg(feature = "deploy")] {
1539    /// # use hydro_lang::prelude::*;
1540    /// # use futures::StreamExt;
1541    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1542    /// let tick = process.tick();
1543    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1544    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1545    /// batch.max().all_ticks()
1546    /// # }, |mut stream| async move {
1547    /// // 4
1548    /// # assert_eq!(stream.next().await.unwrap(), 4);
1549    /// # }));
1550    /// # }
1551    /// ```
1552    pub fn max(self) -> Optional<T, L, B>
1553    where
1554        T: Ord,
1555    {
1556        self.assume_retries_trusted::<ExactlyOnce>(nondet!(/** max is idempotent */))
1557            .assume_ordering_trusted_bounded::<TotalOrder>(
1558                nondet!(/** max is commutative, but order affects intermediates */),
1559            )
1560            .reduce(q!(|curr, new| {
1561                if new > *curr {
1562                    *curr = new;
1563                }
1564            }))
1565    }
1566
1567    /// Computes the minimum element in the stream as an [`Optional`], which
1568    /// will be empty until the first element in the input arrives.
1569    ///
1570    /// # Example
1571    /// ```rust
1572    /// # #[cfg(feature = "deploy")] {
1573    /// # use hydro_lang::prelude::*;
1574    /// # use futures::StreamExt;
1575    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1576    /// let tick = process.tick();
1577    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1578    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1579    /// batch.min().all_ticks()
1580    /// # }, |mut stream| async move {
1581    /// // 1
1582    /// # assert_eq!(stream.next().await.unwrap(), 1);
1583    /// # }));
1584    /// # }
1585    /// ```
1586    pub fn min(self) -> Optional<T, L, B>
1587    where
1588        T: Ord,
1589    {
1590        self.assume_retries_trusted::<ExactlyOnce>(nondet!(/** min is idempotent */))
1591            .assume_ordering_trusted_bounded::<TotalOrder>(
1592                nondet!(/** max is commutative, but order affects intermediates */),
1593            )
1594            .reduce(q!(|curr, new| {
1595                if new < *curr {
1596                    *curr = new;
1597                }
1598            }))
1599    }
1600
1601    /// Computes the first element in the stream as an [`Optional`], which
1602    /// will be empty until the first element in the input arrives.
1603    ///
1604    /// This requires the stream to have a [`TotalOrder`] guarantee, otherwise
1605    /// re-ordering of elements may cause the first element to change.
1606    ///
1607    /// # Example
1608    /// ```rust
1609    /// # #[cfg(feature = "deploy")] {
1610    /// # use hydro_lang::prelude::*;
1611    /// # use futures::StreamExt;
1612    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1613    /// let tick = process.tick();
1614    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1615    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1616    /// batch.first().all_ticks()
1617    /// # }, |mut stream| async move {
1618    /// // 1
1619    /// # assert_eq!(stream.next().await.unwrap(), 1);
1620    /// # }));
1621    /// # }
1622    /// ```
1623    pub fn first(self) -> Optional<T, L, B>
1624    where
1625        O: IsOrdered,
1626    {
1627        self.make_totally_ordered()
1628            .assume_retries_trusted::<ExactlyOnce>(nondet!(/** first is idempotent */))
1629            .generator(q!(|| ()), q!(|_, item| Generate::Return(item)))
1630            .reduce(q!(|_, _| {}))
1631    }
1632
1633    /// Computes the last element in the stream as an [`Optional`], which
1634    /// will be empty until an element in the input arrives.
1635    ///
1636    /// This requires the stream to have a [`TotalOrder`] guarantee, otherwise
1637    /// re-ordering of elements may cause the last element to change.
1638    ///
1639    /// # Example
1640    /// ```rust
1641    /// # #[cfg(feature = "deploy")] {
1642    /// # use hydro_lang::prelude::*;
1643    /// # use futures::StreamExt;
1644    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1645    /// let tick = process.tick();
1646    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1647    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1648    /// batch.last().all_ticks()
1649    /// # }, |mut stream| async move {
1650    /// // 4
1651    /// # assert_eq!(stream.next().await.unwrap(), 4);
1652    /// # }));
1653    /// # }
1654    /// ```
1655    pub fn last(self) -> Optional<T, L, B>
1656    where
1657        O: IsOrdered,
1658    {
1659        self.make_totally_ordered()
1660            .assume_retries_trusted::<ExactlyOnce>(nondet!(/** last is idempotent */))
1661            .reduce(q!(|curr, new| *curr = new))
1662    }
1663
1664    /// Returns a stream containing at most the first `n` elements of the input stream,
1665    /// preserving the original order. Similar to `LIMIT` in SQL.
1666    ///
1667    /// This requires the stream to have a [`TotalOrder`] guarantee and [`ExactlyOnce`]
1668    /// retries, since the result depends on the order and cardinality of elements.
1669    ///
1670    /// # Example
1671    /// ```rust
1672    /// # #[cfg(feature = "deploy")] {
1673    /// # use hydro_lang::prelude::*;
1674    /// # use futures::StreamExt;
1675    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1676    /// let numbers = process.source_iter(q!(vec![10, 20, 30, 40, 50]));
1677    /// numbers.limit(q!(3))
1678    /// # }, |mut stream| async move {
1679    /// // 10, 20, 30
1680    /// # for w in vec![10, 20, 30] {
1681    /// #     assert_eq!(stream.next().await.unwrap(), w);
1682    /// # }
1683    /// # }));
1684    /// # }
1685    /// ```
1686    pub fn limit(
1687        self,
1688        n: impl QuotedWithContext<'a, usize, OperatorContext<L, B>> + Copy + 'a,
1689    ) -> Stream<T, L, B, TotalOrder, ExactlyOnce>
1690    where
1691        O: IsOrdered,
1692        R: IsExactlyOnce,
1693    {
1694        self.generator(
1695            q!(|| 0usize),
1696            q!(move |count, item| {
1697                if *count == n {
1698                    Generate::Break
1699                } else {
1700                    *count += 1;
1701                    if *count == n {
1702                        Generate::Return(item)
1703                    } else {
1704                        Generate::Yield(item)
1705                    }
1706                }
1707            }),
1708        )
1709    }
1710
1711    /// Collects all the elements of this stream into a single [`Vec`] element.
1712    ///
1713    /// If the input stream is [`Unbounded`], the output [`Singleton`] will be [`Unbounded`] as
1714    /// well, which means that the value of the [`Vec`] will asynchronously grow as new elements
1715    /// are added. On such a value, you can use [`Singleton::snapshot`] to grab an instance of
1716    /// the vector at an arbitrary point in time.
1717    ///
1718    /// # Example
1719    /// ```rust
1720    /// # #[cfg(feature = "deploy")] {
1721    /// # use hydro_lang::prelude::*;
1722    /// # use futures::StreamExt;
1723    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1724    /// let tick = process.tick();
1725    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
1726    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1727    /// batch.collect_vec().all_ticks() // emit each tick's Vec into an unbounded stream
1728    /// # }, |mut stream| async move {
1729    /// // [ vec![1, 2, 3, 4] ]
1730    /// # for w in vec![vec![1, 2, 3, 4]] {
1731    /// #     assert_eq!(stream.next().await.unwrap(), w);
1732    /// # }
1733    /// # }));
1734    /// # }
1735    /// ```
1736    pub fn collect_vec(self) -> Singleton<Vec<T>, L, B>
1737    where
1738        O: IsOrdered,
1739        R: IsExactlyOnce,
1740    {
1741        self.make_totally_ordered().make_exactly_once().fold(
1742            q!(|| vec![]),
1743            q!(|acc, v| {
1744                acc.push(v);
1745            }),
1746        )
1747    }
1748
1749    /// Applies a function to each element of the stream, maintaining an internal state (accumulator)
1750    /// and emitting each intermediate result.
1751    ///
1752    /// Unlike `fold` which only returns the final accumulated value, `scan` produces a new stream
1753    /// containing all intermediate accumulated values. The scan operation can also terminate early
1754    /// by returning `None`.
1755    ///
1756    /// The function takes a mutable reference to the accumulator and the current element, and returns
1757    /// an `Option<U>`. If the function returns `Some(value)`, `value` is emitted to the output stream.
1758    /// If the function returns `None`, the stream is terminated and no more elements are processed.
1759    ///
1760    /// The `init` and `f` closures may capture bounded singletons, optionals, or streams by
1761    /// reference via [`by_ref()`](crate::live_collections::Singleton::by_ref), as long as the
1762    /// referenced collection lives at the same location and has the same boundedness as this
1763    /// stream.
1764    ///
1765    /// # Examples
1766    ///
1767    /// Basic usage - running sum:
1768    /// ```rust
1769    /// # #[cfg(feature = "deploy")] {
1770    /// # use hydro_lang::prelude::*;
1771    /// # use futures::StreamExt;
1772    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1773    /// process.source_iter(q!(vec![1, 2, 3, 4])).scan(
1774    ///     q!(|| 0),
1775    ///     q!(|acc, x| {
1776    ///         *acc += x;
1777    ///         Some(*acc)
1778    ///     }),
1779    /// )
1780    /// # }, |mut stream| async move {
1781    /// // Output: 1, 3, 6, 10
1782    /// # for w in vec![1, 3, 6, 10] {
1783    /// #     assert_eq!(stream.next().await.unwrap(), w);
1784    /// # }
1785    /// # }));
1786    /// # }
1787    /// ```
1788    ///
1789    /// Early termination example:
1790    /// ```rust
1791    /// # #[cfg(feature = "deploy")] {
1792    /// # use hydro_lang::prelude::*;
1793    /// # use futures::StreamExt;
1794    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1795    /// process.source_iter(q!(vec![1, 2, 3, 4])).scan(
1796    ///     q!(|| 1),
1797    ///     q!(|state, x| {
1798    ///         *state = *state * x;
1799    ///         if *state > 6 {
1800    ///             None // Terminate the stream
1801    ///         } else {
1802    ///             Some(-*state)
1803    ///         }
1804    ///     }),
1805    /// )
1806    /// # }, |mut stream| async move {
1807    /// // Output: -1, -2, -6
1808    /// # for w in vec![-1, -2, -6] {
1809    /// #     assert_eq!(stream.next().await.unwrap(), w);
1810    /// # }
1811    /// # }));
1812    /// # }
1813    /// ```
1814    pub fn scan<A, U, I, F>(
1815        self,
1816        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>>,
1817        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>>,
1818    ) -> Stream<U, L, B, TotalOrder, ExactlyOnce>
1819    where
1820        O: IsOrdered,
1821        R: IsExactlyOnce,
1822        I: Fn() -> A + 'a,
1823        F: Fn(&mut A, T) -> Option<U> + 'a,
1824    {
1825        let init = crate::handoff_ref::with_ref_capture(|| {
1826            init.splice_fn0_ctx(&OperatorContext::<L, B>::new(&self.location))
1827                .into()
1828        });
1829        let f = crate::handoff_ref::with_ref_capture(|| {
1830            f.splice_fn2_borrow_mut_ctx(&OperatorContext::<L, B>::new(&self.location))
1831                .into()
1832        });
1833
1834        Stream::new(
1835            self.location.clone(),
1836            HydroNode::Scan {
1837                init,
1838                acc: f,
1839                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1840                metadata: self.location.new_node_metadata(
1841                    Stream::<U, L, B, TotalOrder, ExactlyOnce>::collection_kind(),
1842                ),
1843            },
1844        )
1845    }
1846
1847    /// Async version of [`Stream::scan`]. Applies an async function to each element of the
1848    /// stream, maintaining an internal state (accumulator) and emitting the values returned
1849    /// by the function.
1850    ///
1851    /// The closure runs synchronously (so it can mutate the accumulator), then returns a
1852    /// future. The future is polled to completion. If it resolves to `Some`, the value is
1853    /// emitted. If it resolves to `None`, the item is filtered out.
1854    ///
1855    /// The `init` and `f` closures may capture bounded singletons, optionals, or streams by
1856    /// reference via [`by_ref()`](crate::live_collections::Singleton::by_ref), as long as the
1857    /// referenced collection lives at the same location and has the same boundedness as this
1858    /// stream.
1859    ///
1860    /// # Examples
1861    ///
1862    /// ```rust
1863    /// # #[cfg(feature = "deploy")] {
1864    /// # use hydro_lang::prelude::*;
1865    /// # use futures::StreamExt;
1866    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1867    /// process
1868    ///     .source_iter(q!(vec![1, 2, 3, 4]))
1869    ///     .scan_async_blocking(
1870    ///         q!(|| 0),
1871    ///         q!(|acc, x| {
1872    ///             *acc += x;
1873    ///             let val = *acc;
1874    ///             async move { Some(val) }
1875    ///         }),
1876    ///     )
1877    /// # }, |mut stream| async move {
1878    /// // Output: 1, 3, 6, 10
1879    /// # for w in vec![1, 3, 6, 10] {
1880    /// #     assert_eq!(stream.next().await.unwrap(), w);
1881    /// # }
1882    /// # }));
1883    /// # }
1884    /// ```
1885    pub fn scan_async_blocking<A, U, I, F, Fut>(
1886        self,
1887        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>>,
1888        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>>,
1889    ) -> Stream<U, L, B, TotalOrder, ExactlyOnce>
1890    where
1891        O: IsOrdered,
1892        R: IsExactlyOnce,
1893        I: Fn() -> A + 'a,
1894        F: Fn(&mut A, T) -> Fut + 'a,
1895        Fut: Future<Output = Option<U>> + 'a,
1896    {
1897        let init = crate::handoff_ref::with_ref_capture(|| {
1898            init.splice_fn0_ctx(&OperatorContext::<L, B>::new(&self.location))
1899                .into()
1900        });
1901        let f = crate::handoff_ref::with_ref_capture(|| {
1902            f.splice_fn2_borrow_mut_ctx(&OperatorContext::<L, B>::new(&self.location))
1903                .into()
1904        });
1905
1906        Stream::new(
1907            self.location.clone(),
1908            HydroNode::ScanAsyncBlocking {
1909                init,
1910                acc: f,
1911                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1912                metadata: self.location.new_node_metadata(
1913                    Stream::<U, L, B, TotalOrder, ExactlyOnce>::collection_kind(),
1914                ),
1915            },
1916        )
1917    }
1918
1919    /// Iteratively processes the elements of the stream using a state machine that can yield
1920    /// elements as it processes its inputs. This is designed to mirror the unstable generator
1921    /// syntax in Rust, without requiring special syntax.
1922    ///
1923    /// Like [`Stream::scan`], this function takes in an initializer that emits the initial
1924    /// state. The second argument defines the processing logic, taking in a mutable reference
1925    /// to the state and the value to be processed. It emits a [`Generate`] value, whose
1926    /// variants define what is emitted and whether further inputs should be processed.
1927    ///
1928    /// The `init` and `f` closures may capture bounded singletons, optionals, or streams by
1929    /// reference via [`by_ref()`](crate::live_collections::Singleton::by_ref), as long as the
1930    /// referenced collection lives at the same location and has the same boundedness as this
1931    /// stream.
1932    ///
1933    /// # Example
1934    /// ```rust
1935    /// # #[cfg(feature = "deploy")] {
1936    /// # use hydro_lang::prelude::*;
1937    /// # use futures::StreamExt;
1938    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1939    /// process.source_iter(q!(vec![1, 3, 100, 10])).generator(
1940    ///     q!(|| 0),
1941    ///     q!(|acc, x| {
1942    ///         *acc += x;
1943    ///         if *acc > 100 {
1944    ///             hydro_lang::live_collections::keyed_stream::Generate::Return("done!".to_owned())
1945    ///         } else if *acc % 2 == 0 {
1946    ///             hydro_lang::live_collections::keyed_stream::Generate::Yield("even".to_owned())
1947    ///         } else {
1948    ///             hydro_lang::live_collections::keyed_stream::Generate::Continue
1949    ///         }
1950    ///     }),
1951    /// )
1952    /// # }, |mut stream| async move {
1953    /// // Output: "even", "done!"
1954    /// # let mut results = Vec::new();
1955    /// # for _ in 0..2 {
1956    /// #     results.push(stream.next().await.unwrap());
1957    /// # }
1958    /// # results.sort();
1959    /// # assert_eq!(results, vec!["done!".to_owned(), "even".to_owned()]);
1960    /// # }));
1961    /// # }
1962    /// ```
1963    pub fn generator<A, U, I, F>(
1964        self,
1965        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>> + Copy,
1966        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
1967    ) -> Stream<U, L, B, TotalOrder, ExactlyOnce>
1968    where
1969        O: IsOrdered,
1970        R: IsExactlyOnce,
1971        I: Fn() -> A + 'a,
1972        F: Fn(&mut A, T) -> Generate<U> + 'a,
1973    {
1974        let init: ManualExpr<I, _> =
1975            ManualExpr::new(move |ctx: &OperatorContext<L, B>| init.splice_fn0_ctx(ctx));
1976        let f: ManualExpr<F, _> =
1977            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn2_borrow_mut_ctx(ctx));
1978
1979        let this = self.make_totally_ordered().make_exactly_once();
1980
1981        // State is Option<Option<A>>:
1982        //   None = not yet initialized
1983        //   Some(Some(a)) = active with state a
1984        //   Some(None) = terminated
1985        let scan_init = crate::handoff_ref::with_ref_capture(|| {
1986            q!(|| None)
1987                .splice_fn0_ctx::<Option<Option<A>>>(&this.location)
1988                .into()
1989        });
1990        let scan_f = crate::handoff_ref::with_ref_capture(|| {
1991            q!(move |state: &mut Option<Option<_>>, v| {
1992                if state.is_none() {
1993                    *state = Some(Some(init()));
1994                }
1995                match state {
1996                    Some(Some(state_value)) => match f(state_value, v) {
1997                        Generate::Yield(out) => Some(Some(out)),
1998                        Generate::Return(out) => {
1999                            *state = Some(None);
2000                            Some(Some(out))
2001                        }
2002                        // Unlike KeyedStream, we can terminate the scan directly on
2003                        // Break/Return because there is only one state (no other keys
2004                        // that still need processing).
2005                        Generate::Break => None,
2006                        Generate::Continue => Some(None),
2007                    },
2008                    // State is Some(None) after Return; terminate the scan.
2009                    _ => None,
2010                }
2011            })
2012            .splice_fn2_borrow_mut_ctx::<Option<Option<A>>, T, _>(&OperatorContext::<L, B>::new(
2013                &this.location,
2014            ))
2015            .into()
2016        });
2017
2018        let scan_node = HydroNode::Scan {
2019            init: scan_init,
2020            acc: scan_f,
2021            input: Box::new(this.ir_node.replace(HydroNode::Placeholder)),
2022            metadata: this.location.new_node_metadata(Stream::<
2023                Option<U>,
2024                L,
2025                B,
2026                TotalOrder,
2027                ExactlyOnce,
2028            >::collection_kind()),
2029        };
2030
2031        let flatten_f = q!(|d| d)
2032            .splice_fn1_ctx::<Option<U>, _>(&this.location)
2033            .into();
2034        let flatten_node = HydroNode::FlatMap {
2035            f: flatten_f,
2036            input: Box::new(scan_node),
2037            metadata: this
2038                .location
2039                .new_node_metadata(Stream::<U, L, B, TotalOrder, ExactlyOnce>::collection_kind()),
2040        };
2041
2042        Stream::new(this.location.clone(), flatten_node)
2043    }
2044
2045    /// Given a time interval, returns a stream corresponding to samples taken from the
2046    /// stream roughly at that interval. The output will have elements in the same order
2047    /// as the input, but with arbitrary elements skipped between samples. There is also
2048    /// no guarantee on the exact timing of the samples.
2049    ///
2050    /// # Non-Determinism
2051    /// The output stream is non-deterministic in which elements are sampled, since this
2052    /// is controlled by a clock.
2053    #[cfg(feature = "tokio")]
2054    pub fn sample_every(
2055        self,
2056        interval: impl QuotedWithContext<'a, std::time::Duration, L> + Copy + 'a,
2057        nondet: NonDet,
2058    ) -> Stream<T, L::DropConsistency, Unbounded, O, AtLeastOnce>
2059    where
2060        L: TopLevel<'a>,
2061    {
2062        let samples = self.location.source_interval(interval);
2063
2064        let tick = self.location.tick();
2065        self.batch(&tick, nondet)
2066            .filter_if(samples.batch(&tick, nondet).first().is_some())
2067            .all_ticks()
2068            .weaken_retries()
2069    }
2070
2071    /// Given a timeout duration, returns an [`Optional`]  which will have a value if the
2072    /// stream has not emitted a value since that duration.
2073    ///
2074    /// # Non-Determinism
2075    /// Timeout relies on non-deterministic sampling of the stream, so depending on when
2076    /// samples take place, timeouts may be non-deterministically generated or missed,
2077    /// and the notification of the timeout may be delayed as well. There is also no
2078    /// guarantee on how long the [`Optional`] will have a value after the timeout is
2079    /// detected based on when the next sample is taken.
2080    #[cfg(feature = "tokio")]
2081    pub fn timeout(
2082        self,
2083        duration: impl QuotedWithContext<
2084            'a,
2085            std::time::Duration,
2086            OperatorContext<Tick<L::DropConsistency>, Bounded>,
2087        > + Copy
2088        + 'a,
2089        nondet: NonDet,
2090    ) -> Optional<(), L::DropConsistency, Unbounded>
2091    where
2092        L: TopLevel<'a>,
2093    {
2094        let tick = self.location.tick();
2095
2096        let latest_received = self.assume_retries::<ExactlyOnce>(nondet).fold(
2097            q!(|| None),
2098            q!(
2099                |latest, _| {
2100                    *latest = Some(Instant::now());
2101                },
2102                commutative = manual_proof!(/** TODO */)
2103            ),
2104        );
2105
2106        latest_received
2107            .snapshot(&tick, nondet)
2108            .filter_map(q!(move |latest_received| {
2109                if let Some(latest_received) = latest_received {
2110                    if Instant::now().duration_since(latest_received) > duration {
2111                        Some(())
2112                    } else {
2113                        None
2114                    }
2115                } else {
2116                    Some(())
2117                }
2118            }))
2119            .latest()
2120    }
2121
2122    /// Shifts this stream into an atomic context, which guarantees that any downstream logic
2123    /// will all be executed synchronously before any outputs are yielded (in [`Stream::end_atomic`]).
2124    ///
2125    /// This is useful to enforce local consistency constraints, such as ensuring that a write is
2126    /// processed before an acknowledgement is emitted.
2127    pub fn atomic(self) -> Stream<T, Atomic<L>, B, O, R> {
2128        let id = self.location.flow_state().borrow_mut().next_clock_id();
2129        let out_location = Atomic {
2130            tick: Tick {
2131                id,
2132                l: self.location.clone(),
2133            },
2134        };
2135        Stream::new(
2136            out_location.clone(),
2137            HydroNode::BeginAtomic {
2138                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2139                metadata: out_location
2140                    .new_node_metadata(Stream::<T, Atomic<L>, B, O, R>::collection_kind()),
2141            },
2142        )
2143    }
2144
2145    /// Given a tick, returns a stream corresponding to a batch of elements segmented by
2146    /// that tick. These batches are guaranteed to be contiguous across ticks and preserve
2147    /// the order of the input. The output stream will execute in the [`Tick`] that was
2148    /// used to create the atomic section.
2149    ///
2150    /// # Non-Determinism
2151    /// The batch boundaries are non-deterministic and may change across executions.
2152    pub fn batch<L2: Location<'a, DropConsistency = L::DropConsistency>>(
2153        self,
2154        tick: &Tick<L2>,
2155        _nondet: NonDet,
2156    ) -> Stream<T, Tick<L::DropConsistency>, Bounded, O, R> {
2157        assert_eq!(Location::id(tick.outer()), Location::id(&self.location));
2158        Stream::new(
2159            tick.drop_consistency(),
2160            HydroNode::Batch {
2161                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2162                metadata: tick
2163                    .new_node_metadata(Stream::<T, Tick<L>, Bounded, O, R>::collection_kind()),
2164            },
2165        )
2166    }
2167
2168    /// An operator which allows you to "name" a `HydroNode`.
2169    /// This is only used for testing, to correlate certain `HydroNode`s with IDs.
2170    pub fn ir_node_named(self, name: &str) -> Stream<T, L, B, O, R> {
2171        {
2172            let mut node = self.ir_node.borrow_mut();
2173            let metadata = node.metadata_mut();
2174            metadata.tag = Some(name.to_owned());
2175        }
2176        self
2177    }
2178
2179    /// Turns this [`Stream`] into a [`Optional`], under the invariant assumption that there is at
2180    /// most one element. If this invariant is broken, the program may exhibit undefined behavior,
2181    /// so uses must be carefully vetted.
2182    pub(crate) fn cast_at_most_one_element(self) -> Optional<T, L, B>
2183    where
2184        B: IsBounded,
2185    {
2186        Optional::new(
2187            self.location.clone(),
2188            HydroNode::Cast {
2189                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2190                metadata: self
2191                    .location
2192                    .new_node_metadata(Optional::<T, L, B>::collection_kind()),
2193            },
2194        )
2195    }
2196
2197    pub(crate) fn use_ordering_type<O2: Ordering>(self) -> Stream<T, L, B, O2, R> {
2198        if O::ORDERING_KIND == O2::ORDERING_KIND {
2199            Stream::new(
2200                self.location.clone(),
2201                self.ir_node.replace(HydroNode::Placeholder),
2202            )
2203        } else {
2204            panic!(
2205                "Runtime ordering {:?} did not match requested cast {:?}.",
2206                O::ORDERING_KIND,
2207                O2::ORDERING_KIND
2208            )
2209        }
2210    }
2211
2212    /// Explicitly "casts" the stream to a type with a different ordering
2213    /// guarantee. Useful in unsafe code where the ordering cannot be proven
2214    /// by the type-system.
2215    ///
2216    /// # Non-Determinism
2217    /// This function is used as an escape hatch, and any mistakes in the
2218    /// provided ordering guarantee will propagate into the guarantees
2219    /// for the rest of the program.
2220    pub fn assume_ordering<O2: Ordering>(
2221        self,
2222        _nondet: NonDet,
2223    ) -> Stream<T, L::DropConsistency, B, O2, R> {
2224        if O::ORDERING_KIND == O2::ORDERING_KIND {
2225            self.use_ordering_type().weaken_consistency()
2226        } else if O2::ORDERING_KIND == StreamOrder::NoOrder {
2227            // We can always weaken the ordering guarantee
2228            let target_location = self.location().drop_consistency();
2229            Stream::new(
2230                target_location.clone(),
2231                HydroNode::Cast {
2232                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2233                    metadata: target_location
2234                        .new_node_metadata(Stream::<T, L, B, O2, R>::collection_kind()),
2235                },
2236            )
2237        } else {
2238            let target_location = self.location().drop_consistency();
2239            Stream::new(
2240                target_location.clone(),
2241                HydroNode::ObserveNonDet {
2242                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2243                    trusted: false,
2244                    metadata: target_location
2245                        .new_node_metadata(Stream::<T, L, B, O2, R>::collection_kind()),
2246                },
2247            )
2248        }
2249    }
2250
2251    // like `assume_ordering_trusted`, but only if the input stream is bounded and therefore
2252    // intermediate states will not be revealed
2253    fn assume_ordering_trusted_bounded<O2: Ordering>(
2254        self,
2255        nondet: NonDet,
2256    ) -> Stream<T, L, B, O2, R> {
2257        if B::BOUNDED {
2258            self.assume_ordering_trusted(nondet)
2259        } else {
2260            let self_location = self.location.clone();
2261            let inner: Stream<T, L::DropConsistency, B, O2, R> = self.assume_ordering(nondet);
2262            Stream::new(self_location, inner.ir_node.replace(HydroNode::Placeholder))
2263        }
2264    }
2265
2266    // only for internal APIs that have been carefully vetted to ensure that the non-determinism
2267    // is not observable
2268    pub(crate) fn assume_ordering_trusted<O2: Ordering>(
2269        self,
2270        _nondet: NonDet,
2271    ) -> Stream<T, L, B, O2, R> {
2272        if O::ORDERING_KIND == O2::ORDERING_KIND {
2273            self.use_ordering_type()
2274        } else if O2::ORDERING_KIND == StreamOrder::NoOrder {
2275            // We can always weaken the ordering guarantee
2276            Stream::new(
2277                self.location.clone(),
2278                HydroNode::Cast {
2279                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2280                    metadata: self
2281                        .location
2282                        .new_node_metadata(Stream::<T, L, B, O2, R>::collection_kind()),
2283                },
2284            )
2285        } else {
2286            Stream::new(
2287                self.location.clone(),
2288                HydroNode::ObserveNonDet {
2289                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2290                    trusted: true,
2291                    metadata: self
2292                        .location
2293                        .new_node_metadata(Stream::<T, L, B, O2, R>::collection_kind()),
2294                },
2295            )
2296        }
2297    }
2298
2299    #[deprecated = "use `weaken_ordering::<NoOrder>()` instead"]
2300    /// Weakens the ordering guarantee provided by the stream to [`NoOrder`],
2301    /// which is always safe because that is the weakest possible guarantee.
2302    pub fn weakest_ordering(self) -> Stream<T, L, B, NoOrder, R> {
2303        self.weaken_ordering::<NoOrder>()
2304    }
2305
2306    /// Weakens the ordering guarantee provided by the stream to `O2`, with the type-system
2307    /// enforcing that `O2` is weaker than the input ordering guarantee.
2308    pub fn weaken_ordering<O2: WeakerOrderingThan<O>>(self) -> Stream<T, L, B, O2, R> {
2309        let nondet = nondet!(/** this is a weaker ordering guarantee, so it is safe to assume */);
2310        self.assume_ordering_trusted::<O2>(nondet)
2311    }
2312
2313    /// Strengthens the ordering guarantee to `TotalOrder`, given that `O: IsOrdered`, which
2314    /// implies that `O == TotalOrder`.
2315    pub fn make_totally_ordered(self) -> Stream<T, L, B, TotalOrder, R>
2316    where
2317        O: IsOrdered,
2318    {
2319        self.assume_ordering_trusted(nondet!(/** no-op */))
2320    }
2321
2322    /// Explicitly "casts" the stream to a type with a different retries
2323    /// guarantee. Useful in unsafe code where the lack of retries cannot
2324    /// be proven by the type-system.
2325    ///
2326    /// # Non-Determinism
2327    /// This function is used as an escape hatch, and any mistakes in the
2328    /// provided retries guarantee will propagate into the guarantees
2329    /// for the rest of the program.
2330    pub fn assume_retries<R2: Retries>(
2331        self,
2332        _nondet: NonDet,
2333    ) -> Stream<T, L::DropConsistency, B, O, R2> {
2334        if R::RETRIES_KIND == R2::RETRIES_KIND {
2335            Stream::new(
2336                self.location.drop_consistency(),
2337                self.ir_node.replace(HydroNode::Placeholder),
2338            )
2339        } else if R2::RETRIES_KIND == StreamRetry::AtLeastOnce {
2340            // We can always weaken the retries guarantee
2341            let target_location = self.location.drop_consistency();
2342            Stream::new(
2343                target_location.clone(),
2344                HydroNode::Cast {
2345                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2346                    metadata: target_location
2347                        .new_node_metadata(Stream::<T, L, B, O, R2>::collection_kind()),
2348                },
2349            )
2350        } else {
2351            let target_location = self.location.drop_consistency();
2352            Stream::new(
2353                target_location.clone(),
2354                HydroNode::ObserveNonDet {
2355                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2356                    trusted: false,
2357                    metadata: target_location
2358                        .new_node_metadata(Stream::<T, L, B, O, R2>::collection_kind()),
2359                },
2360            )
2361        }
2362    }
2363
2364    // only for internal APIs that have been carefully vetted to ensure that the non-determinism
2365    // is not observable
2366    fn assume_retries_trusted<R2: Retries>(self, _nondet: NonDet) -> Stream<T, L, B, O, R2> {
2367        if R::RETRIES_KIND == R2::RETRIES_KIND {
2368            Stream::new(
2369                self.location.clone(),
2370                self.ir_node.replace(HydroNode::Placeholder),
2371            )
2372        } else if R2::RETRIES_KIND == StreamRetry::AtLeastOnce {
2373            // We can always weaken the retries guarantee
2374            Stream::new(
2375                self.location.clone(),
2376                HydroNode::Cast {
2377                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2378                    metadata: self
2379                        .location
2380                        .new_node_metadata(Stream::<T, L, B, O, R2>::collection_kind()),
2381                },
2382            )
2383        } else {
2384            Stream::new(
2385                self.location.clone(),
2386                HydroNode::ObserveNonDet {
2387                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2388                    trusted: true,
2389                    metadata: self
2390                        .location
2391                        .new_node_metadata(Stream::<T, L, B, O, R2>::collection_kind()),
2392                },
2393            )
2394        }
2395    }
2396
2397    #[deprecated = "use `weaken_retries::<AtLeastOnce>()` instead"]
2398    /// Weakens the retries guarantee provided by the stream to [`AtLeastOnce`],
2399    /// which is always safe because that is the weakest possible guarantee.
2400    pub fn weakest_retries(self) -> Stream<T, L, B, O, AtLeastOnce> {
2401        self.weaken_retries::<AtLeastOnce>()
2402    }
2403
2404    /// Weakens the retries guarantee provided by the stream to `R2`, with the type-system
2405    /// enforcing that `R2` is weaker than the input retries guarantee.
2406    pub fn weaken_retries<R2: WeakerRetryThan<R>>(self) -> Stream<T, L, B, O, R2> {
2407        let nondet = nondet!(/** this is a weaker retry guarantee, so it is safe to assume */);
2408        self.assume_retries_trusted::<R2>(nondet)
2409    }
2410
2411    /// Strengthens the retry guarantee to `ExactlyOnce`, given that `R: IsExactlyOnce`, which
2412    /// implies that `R == ExactlyOnce`.
2413    pub fn make_exactly_once(self) -> Stream<T, L, B, O, ExactlyOnce>
2414    where
2415        R: IsExactlyOnce,
2416    {
2417        self.assume_retries_trusted(nondet!(/** no-op */))
2418    }
2419
2420    /// Strengthens the boundedness guarantee to `Bounded`, given that `B: IsBounded`, which
2421    /// implies that `B == Bounded`.
2422    pub fn make_bounded(self) -> Stream<T, L, Bounded, O, R>
2423    where
2424        B: IsBounded,
2425    {
2426        self.weaken_boundedness()
2427    }
2428
2429    /// Weakens the boundedness guarantee to an arbitrary boundedness `B2`, given that `B: IsBounded`,
2430    /// which implies that `B == Bounded`.
2431    pub fn weaken_boundedness<B2: Boundedness>(self) -> Stream<T, L, B2, O, R> {
2432        if B::BOUNDED == B2::BOUNDED {
2433            Stream::new(
2434                self.location.clone(),
2435                self.ir_node.replace(HydroNode::Placeholder),
2436            )
2437        } else {
2438            // We can always weaken the boundedness
2439            Stream::new(
2440                self.location.clone(),
2441                HydroNode::Cast {
2442                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2443                    metadata: self
2444                        .location
2445                        .new_node_metadata(Stream::<T, L, B2, O, R>::collection_kind()),
2446                },
2447            )
2448        }
2449    }
2450}
2451
2452impl<'a, T, L, B: Boundedness, O: Ordering, R: Retries> Stream<&T, L, B, O, R>
2453where
2454    L: Location<'a>,
2455{
2456    /// Clone each element of the stream; akin to `map(q!(|d| d.clone()))`.
2457    ///
2458    /// # Example
2459    /// ```rust
2460    /// # #[cfg(feature = "deploy")] {
2461    /// # use hydro_lang::prelude::*;
2462    /// # use futures::StreamExt;
2463    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2464    /// process.source_iter(q!(&[1, 2, 3])).cloned()
2465    /// # }, |mut stream| async move {
2466    /// // 1, 2, 3
2467    /// # for w in vec![1, 2, 3] {
2468    /// #     assert_eq!(stream.next().await.unwrap(), w);
2469    /// # }
2470    /// # }));
2471    /// # }
2472    /// ```
2473    pub fn cloned(self) -> Stream<T, L, B, O, R>
2474    where
2475        T: Clone,
2476    {
2477        self.map(q!(|d| d.clone()))
2478    }
2479}
2480
2481impl<'a, T, L, B: Boundedness, O: Ordering> Stream<T, L, B, O, ExactlyOnce>
2482where
2483    L: Location<'a>,
2484{
2485    /// Computes the number of elements in the stream as a [`Singleton`].
2486    ///
2487    /// # Example
2488    /// ```rust
2489    /// # #[cfg(feature = "deploy")] {
2490    /// # use hydro_lang::prelude::*;
2491    /// # use futures::StreamExt;
2492    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2493    /// let tick = process.tick();
2494    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
2495    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2496    /// batch.count().all_ticks()
2497    /// # }, |mut stream| async move {
2498    /// // 4
2499    /// # assert_eq!(stream.next().await.unwrap(), 4);
2500    /// # }));
2501    /// # }
2502    /// ```
2503    pub fn count(self) -> Singleton<usize, L, B::StreamToMonotone> {
2504        self.assume_ordering_trusted::<TotalOrder>(nondet!(
2505            /// Order does not affect eventual count, and also does not affect intermediate states.
2506        ))
2507        .fold(
2508            q!(|| 0usize),
2509            q!(
2510                |count, _| *count += 1,
2511                monotone = manual_proof!(/** += 1 is monotone */)
2512            ),
2513        )
2514    }
2515}
2516
2517impl<'a, T, L: Location<'a>, O: Ordering, R: Retries> Stream<T, L, Unbounded, O, R> {
2518    /// Produces a new stream that merges the elements of the two input streams.
2519    /// The result has [`NoOrder`] because the order of merging is not guaranteed.
2520    ///
2521    /// Currently, both input streams must be [`Unbounded`]. When the streams are
2522    /// [`Bounded`], you can use [`Stream::chain`] instead.
2523    ///
2524    /// # Example
2525    /// ```rust
2526    /// # #[cfg(feature = "deploy")] {
2527    /// # use hydro_lang::prelude::*;
2528    /// # use futures::StreamExt;
2529    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2530    /// let numbers: Stream<i32, _, Unbounded> = // 1, 2, 3, 4
2531    /// # process.source_iter(q!(vec![1, 2, 3, 4])).into();
2532    /// numbers.clone().map(q!(|x| x + 1)).merge_unordered(numbers)
2533    /// # }, |mut stream| async move {
2534    /// // 2, 3, 4, 5, and 1, 2, 3, 4 merged in unknown order
2535    /// # for w in vec![2, 3, 4, 5, 1, 2, 3, 4] {
2536    /// #     assert_eq!(stream.next().await.unwrap(), w);
2537    /// # }
2538    /// # }));
2539    /// # }
2540    /// ```
2541    pub fn merge_unordered<O2: Ordering, R2: Retries>(
2542        self,
2543        other: Stream<T, L, Unbounded, O2, R2>,
2544    ) -> Stream<T, L, Unbounded, NoOrder, <R as MinRetries<R2>>::Min>
2545    where
2546        R: MinRetries<R2>,
2547    {
2548        Stream::new(
2549            self.location.clone(),
2550            HydroNode::Chain {
2551                first: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2552                second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2553                metadata: self.location.new_node_metadata(Stream::<
2554                    T,
2555                    L,
2556                    Unbounded,
2557                    NoOrder,
2558                    <R as MinRetries<R2>>::Min,
2559                >::collection_kind()),
2560            },
2561        )
2562    }
2563
2564    /// Deprecated: use [`Stream::merge_unordered`] instead.
2565    #[deprecated(note = "use `merge_unordered` instead")]
2566    pub fn interleave<O2: Ordering, R2: Retries>(
2567        self,
2568        other: Stream<T, L, Unbounded, O2, R2>,
2569    ) -> Stream<T, L, Unbounded, NoOrder, <R as MinRetries<R2>>::Min>
2570    where
2571        R: MinRetries<R2>,
2572    {
2573        self.merge_unordered(other)
2574    }
2575}
2576
2577impl<'a, T, L: Location<'a>, B: Boundedness, R: Retries> Stream<T, L, B, TotalOrder, R> {
2578    /// Produces a new stream that combines the elements of the two input streams,
2579    /// preserving the relative order of elements within each input.
2580    ///
2581    /// # Non-Determinism
2582    /// The order in which elements *across* the two streams will be interleaved is
2583    /// non-deterministic, so the order of elements will vary across runs. If the output
2584    /// order is irrelevant, use [`Stream::merge_unordered`] instead, which is deterministic
2585    /// but emits an unordered stream. For deterministic first-then-second ordering on
2586    /// bounded streams, use [`Stream::chain`].
2587    ///
2588    /// # Example
2589    /// ```rust
2590    /// # #[cfg(feature = "deploy")] {
2591    /// # use hydro_lang::prelude::*;
2592    /// # use futures::StreamExt;
2593    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2594    /// let numbers: Stream<i32, _, Unbounded> = // 1, 3
2595    /// # process.source_iter(q!(vec![1, 3])).into();
2596    /// numbers.clone().merge_ordered(numbers.map(q!(|x| x + 1)), nondet!(/** example */))
2597    /// # }, |mut stream| async move {
2598    /// // 1, 3 and 2, 4 in some order, preserving the original local order
2599    /// # for w in vec![1, 3, 2, 4] {
2600    /// #     assert_eq!(stream.next().await.unwrap(), w);
2601    /// # }
2602    /// # }));
2603    /// # }
2604    /// ```
2605    pub fn merge_ordered<R2: Retries>(
2606        self,
2607        other: Stream<T, L, B, TotalOrder, R2>,
2608        _nondet: NonDet,
2609    ) -> Stream<T, L::DropConsistency, B, TotalOrder, <R as MinRetries<R2>>::Min>
2610    where
2611        R: MinRetries<R2>,
2612    {
2613        let target_location = self.location().drop_consistency();
2614        Stream::new(
2615            target_location.clone(),
2616            HydroNode::MergeOrdered {
2617                first: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2618                second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2619                metadata: target_location.new_node_metadata(Stream::<
2620                    T,
2621                    L::DropConsistency,
2622                    B,
2623                    TotalOrder,
2624                    <R as MinRetries<R2>>::Min,
2625                >::collection_kind()),
2626            },
2627        )
2628    }
2629}
2630
2631impl<'a, T, L, B: Boundedness, O: Ordering, R: Retries> Stream<T, L, B, O, R>
2632where
2633    L: Location<'a>,
2634{
2635    /// Produces a new stream that emits the input elements in sorted order.
2636    ///
2637    /// The input stream can have any ordering guarantee, but the output stream
2638    /// will have a [`TotalOrder`] guarantee. This operator will block until all
2639    /// elements in the input stream are available, so it requires the input stream
2640    /// to be [`Bounded`].
2641    ///
2642    /// # Example
2643    /// ```rust
2644    /// # #[cfg(feature = "deploy")] {
2645    /// # use hydro_lang::prelude::*;
2646    /// # use futures::StreamExt;
2647    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2648    /// let tick = process.tick();
2649    /// let numbers = process.source_iter(q!(vec![4, 2, 3, 1]));
2650    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2651    /// batch.sort().all_ticks()
2652    /// # }, |mut stream| async move {
2653    /// // 1, 2, 3, 4
2654    /// # for w in (1..5) {
2655    /// #     assert_eq!(stream.next().await.unwrap(), w);
2656    /// # }
2657    /// # }));
2658    /// # }
2659    /// ```
2660    pub fn sort(self) -> Stream<T, L, Bounded, TotalOrder, R>
2661    where
2662        B: IsBounded,
2663        T: Ord,
2664    {
2665        let this = self.make_bounded();
2666        Stream::new(
2667            this.location.clone(),
2668            HydroNode::Sort {
2669                input: Box::new(this.ir_node.replace(HydroNode::Placeholder)),
2670                metadata: this
2671                    .location
2672                    .new_node_metadata(Stream::<T, L, Bounded, TotalOrder, R>::collection_kind()),
2673            },
2674        )
2675    }
2676
2677    /// Produces a new stream that first emits the elements of the `self` stream,
2678    /// and then emits the elements of the `other` stream. The output stream has
2679    /// a [`TotalOrder`] guarantee if and only if both input streams have a
2680    /// [`TotalOrder`] guarantee.
2681    ///
2682    /// Currently, both input streams must be [`Bounded`]. This operator will block
2683    /// on the first stream until all its elements are available. In a future version,
2684    /// we will relax the requirement on the `other` stream.
2685    ///
2686    /// # Example
2687    /// ```rust
2688    /// # #[cfg(feature = "deploy")] {
2689    /// # use hydro_lang::prelude::*;
2690    /// # use futures::StreamExt;
2691    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2692    /// let tick = process.tick();
2693    /// let numbers = process.source_iter(q!(vec![1, 2, 3, 4]));
2694    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2695    /// batch.clone().map(q!(|x| x + 1)).chain(batch).all_ticks()
2696    /// # }, |mut stream| async move {
2697    /// // 2, 3, 4, 5, 1, 2, 3, 4
2698    /// # for w in vec![2, 3, 4, 5, 1, 2, 3, 4] {
2699    /// #     assert_eq!(stream.next().await.unwrap(), w);
2700    /// # }
2701    /// # }));
2702    /// # }
2703    /// ```
2704    pub fn chain<O2: Ordering, R2: Retries, B2: Boundedness>(
2705        self,
2706        other: Stream<T, L, B2, O2, R2>,
2707    ) -> Stream<T, L, B2, <O as MinOrder<O2>>::Min, <R as MinRetries<R2>>::Min>
2708    where
2709        B: IsBounded,
2710        O: MinOrder<O2>,
2711        R: MinRetries<R2>,
2712    {
2713        check_matching_location(&self.location, &other.location);
2714
2715        Stream::new(
2716            self.location.clone(),
2717            HydroNode::Chain {
2718                first: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2719                second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2720                metadata: self.location.new_node_metadata(Stream::<
2721                    T,
2722                    L,
2723                    B2,
2724                    <O as MinOrder<O2>>::Min,
2725                    <R as MinRetries<R2>>::Min,
2726                >::collection_kind()),
2727            },
2728        )
2729    }
2730
2731    /// Forms the cross-product (Cartesian product, cross-join) of the items in the 2 input streams.
2732    /// Unlike [`Stream::cross_product`], the output order is totally ordered when the inputs are
2733    /// because this is compiled into a nested loop.
2734    pub fn cross_product_nested_loop<T2, O2: Ordering + MinOrder<O>, R2: Retries>(
2735        self,
2736        other: Stream<T2, L, Bounded, O2, R2>,
2737    ) -> Stream<(T, T2), L, Bounded, <O2 as MinOrder<O>>::Min, <R as MinRetries<R2>>::Min>
2738    where
2739        B: IsBounded,
2740        T: Clone,
2741        T2: Clone,
2742        R: MinRetries<R2>,
2743    {
2744        let this = self.make_bounded();
2745        check_matching_location(&this.location, &other.location);
2746
2747        Stream::new(
2748            this.location.clone(),
2749            HydroNode::CrossProduct {
2750                left: Box::new(this.ir_node.replace(HydroNode::Placeholder)),
2751                right: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2752                metadata: this.location.new_node_metadata(Stream::<
2753                    (T, T2),
2754                    L,
2755                    Bounded,
2756                    <O2 as MinOrder<O>>::Min,
2757                    <R as MinRetries<R2>>::Min,
2758                >::collection_kind()),
2759            },
2760        )
2761    }
2762
2763    /// Creates a [`KeyedStream`] with the same set of keys as `keys`, but with the elements in
2764    /// `self` used as the values for *each* key.
2765    ///
2766    /// This is helpful when "broadcasting" a set of values so that all the keys have the same
2767    /// values. For example, it can be used to send the same set of elements to several cluster
2768    /// members, if the membership information is available as a [`KeyedSingleton`].
2769    ///
2770    /// # Example
2771    /// ```rust
2772    /// # #[cfg(feature = "deploy")] {
2773    /// # use hydro_lang::prelude::*;
2774    /// # use futures::StreamExt;
2775    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2776    /// # let tick = process.tick();
2777    /// let keyed_singleton = // { 1: (), 2: () }
2778    /// # process
2779    /// #     .source_iter(q!(vec![(1, ()), (2, ())]))
2780    /// #     .into_keyed()
2781    /// #     .batch(&tick, nondet!(/** test */))
2782    /// #     .first();
2783    /// let stream = // [ "a", "b" ]
2784    /// # process
2785    /// #     .source_iter(q!(vec!["a".to_owned(), "b".to_owned()]))
2786    /// #     .batch(&tick, nondet!(/** test */));
2787    /// stream.repeat_with_keys(keyed_singleton)
2788    /// # .entries().all_ticks()
2789    /// # }, |mut stream| async move {
2790    /// // { 1: ["a", "b" ], 2: ["a", "b"] }
2791    /// # let mut results = Vec::new();
2792    /// # for _ in 0..4 {
2793    /// #     results.push(stream.next().await.unwrap());
2794    /// # }
2795    /// # results.sort();
2796    /// # assert_eq!(results, vec![(1, "a".to_owned()), (1, "b".to_owned()), (2, "a".to_owned()), (2, "b".to_owned())]);
2797    /// # }));
2798    /// # }
2799    /// ```
2800    pub fn repeat_with_keys<K, V2>(
2801        self,
2802        keys: KeyedSingleton<K, V2, L, Bounded>,
2803    ) -> KeyedStream<K, T, L, Bounded, O, R>
2804    where
2805        B: IsBounded,
2806        K: Clone,
2807        T: Clone,
2808    {
2809        keys.keys()
2810            .assume_ordering_trusted::<TotalOrder>(
2811                nondet!(/** keyed stream does not depend on ordering of keys */),
2812            )
2813            .cross_product_nested_loop(self.make_bounded())
2814            .into_keyed()
2815    }
2816
2817    /// Consumes a stream of `Future<T>`, resolving each future while blocking subgraph
2818    /// execution until all results are available. The output order is based on when futures
2819    /// complete, and may be different than the input order.
2820    ///
2821    /// Unlike [`Stream::resolve_futures`], which allows the subgraph to continue executing
2822    /// while futures are pending, this variant blocks until the futures resolve.
2823    ///
2824    /// # Example
2825    /// ```rust
2826    /// # #[cfg(feature = "deploy")] {
2827    /// # use std::collections::HashSet;
2828    /// # use futures::StreamExt;
2829    /// # use hydro_lang::prelude::*;
2830    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2831    /// process
2832    ///     .source_iter(q!([2, 3, 1, 9, 6, 5, 4, 7, 8]))
2833    ///     .map(q!(|x| async move {
2834    ///         tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
2835    ///         x
2836    ///     }))
2837    ///     .resolve_futures_blocking()
2838    /// #   },
2839    /// #   |mut stream| async move {
2840    /// // 1, 2, 3, 4, 5, 6, 7, 8, 9 (in any order)
2841    /// #       let mut output = HashSet::new();
2842    /// #       for _ in 1..10 {
2843    /// #           output.insert(stream.next().await.unwrap());
2844    /// #       }
2845    /// #       assert_eq!(
2846    /// #           output,
2847    /// #           HashSet::<i32>::from_iter(1..10)
2848    /// #       );
2849    /// #   },
2850    /// # ));
2851    /// # }
2852    /// ```
2853    pub fn resolve_futures_blocking(self) -> Stream<T::Output, L, B, NoOrder, R>
2854    where
2855        T: Future,
2856    {
2857        Stream::new(
2858            self.location.clone(),
2859            HydroNode::ResolveFuturesBlocking {
2860                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2861                metadata: self
2862                    .location
2863                    .new_node_metadata(Stream::<T::Output, L, B, NoOrder, R>::collection_kind()),
2864            },
2865        )
2866    }
2867
2868    /// Returns a [`Singleton`] containing `true` if the stream has no elements, or `false` otherwise.
2869    ///
2870    /// # Example
2871    /// ```rust
2872    /// # #[cfg(feature = "deploy")] {
2873    /// # use hydro_lang::prelude::*;
2874    /// # use futures::StreamExt;
2875    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2876    /// let tick = process.tick();
2877    /// let empty: Stream<i32, _, Bounded> = process
2878    ///   .source_iter(q!(Vec::<i32>::new()))
2879    ///   .batch(&tick, nondet!(/** test */));
2880    /// empty.is_empty().all_ticks()
2881    /// # }, |mut stream| async move {
2882    /// // true
2883    /// # assert_eq!(stream.next().await.unwrap(), true);
2884    /// # }));
2885    /// # }
2886    /// ```
2887    #[expect(clippy::wrong_self_convention, reason = "stream function naming")]
2888    pub fn is_empty(self) -> Singleton<bool, L, Bounded>
2889    where
2890        B: IsBounded,
2891    {
2892        self.make_bounded()
2893            .assume_ordering_trusted::<TotalOrder>(
2894                nondet!(/** is_empty intermediates unaffected by order */),
2895            )
2896            .first()
2897            .is_none()
2898    }
2899}
2900
2901impl<'a, K, V1, L, B: Boundedness, O: Ordering, R: Retries> Stream<(K, V1), L, B, O, R>
2902where
2903    L: Location<'a>,
2904{
2905    /// Given two streams of pairs `(K, V1)` and `(K, V2)`, produces a new stream of nested pairs `(K, (V1, V2))`
2906    /// by equi-joining the two streams on the key attribute `K`.
2907    ///
2908    /// When the right-hand side is [`Bounded`], the join accumulates the right side first
2909    /// and streams the left side through, preserving the left side's ordering. When both
2910    /// sides are [`Unbounded`], a symmetric hash join is used and ordering is [`NoOrder`].
2911    ///
2912    /// # Example
2913    /// ```rust
2914    /// # #[cfg(feature = "deploy")] {
2915    /// # use hydro_lang::prelude::*;
2916    /// # use std::collections::HashSet;
2917    /// # use futures::StreamExt;
2918    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2919    /// let tick = process.tick();
2920    /// let stream1 = process.source_iter(q!(vec![(1, 'a'), (2, 'b')]));
2921    /// let stream2 = process.source_iter(q!(vec![(1, 'x'), (2, 'y')]));
2922    /// stream1.join(stream2)
2923    /// # }, |mut stream| async move {
2924    /// // (1, ('a', 'x')), (2, ('b', 'y'))
2925    /// # let expected = HashSet::from([(1, ('a', 'x')), (2, ('b', 'y'))]);
2926    /// # stream.map(|i| assert!(expected.contains(&i)));
2927    /// # }));
2928    /// # }
2929    pub fn join<V2, B2: Boundedness, O2: Ordering, R2: Retries>(
2930        self,
2931        n: Stream<(K, V2), L, B2, O2, R2>,
2932    ) -> Stream<(K, (V1, V2)), L, B, B2::PreserveOrderIfBounded<O>, <R as MinRetries<R2>>::Min>
2933    where
2934        K: Eq + Hash + Clone,
2935        R: MinRetries<R2>,
2936        V1: Clone,
2937        V2: Clone,
2938    {
2939        check_matching_location(&self.location, &n.location);
2940
2941        let ir_node = if B2::BOUNDED {
2942            HydroNode::JoinHalf {
2943                left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2944                right: Box::new(n.ir_node.replace(HydroNode::Placeholder)),
2945                metadata: self.location.new_node_metadata(Stream::<
2946                    (K, (V1, V2)),
2947                    L,
2948                    B,
2949                    B2::PreserveOrderIfBounded<O>,
2950                    <R as MinRetries<R2>>::Min,
2951                >::collection_kind()),
2952            }
2953        } else {
2954            HydroNode::Join {
2955                left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2956                right: Box::new(n.ir_node.replace(HydroNode::Placeholder)),
2957                metadata: self.location.new_node_metadata(Stream::<
2958                    (K, (V1, V2)),
2959                    L,
2960                    B,
2961                    B2::PreserveOrderIfBounded<O>,
2962                    <R as MinRetries<R2>>::Min,
2963                >::collection_kind()),
2964            }
2965        };
2966
2967        Stream::new(self.location.clone(), ir_node)
2968    }
2969
2970    /// Given a stream of pairs `(K, V1)` and a bounded stream of keys `K`,
2971    /// computes the anti-join of the items in the input -- i.e. returns
2972    /// unique items in the first input that do not have a matching key
2973    /// in the second input.
2974    ///
2975    /// # Example
2976    /// ```rust
2977    /// # #[cfg(feature = "deploy")] {
2978    /// # use hydro_lang::prelude::*;
2979    /// # use futures::StreamExt;
2980    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2981    /// let tick = process.tick();
2982    /// let stream = process
2983    ///   .source_iter(q!(vec![ (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd') ]))
2984    ///   .batch(&tick, nondet!(/** test */));
2985    /// let batch = process
2986    ///   .source_iter(q!(vec![1, 2]))
2987    ///   .batch(&tick, nondet!(/** test */));
2988    /// stream.anti_join(batch).all_ticks()
2989    /// # }, |mut stream| async move {
2990    /// # for w in vec![(3, 'c'), (4, 'd')] {
2991    /// #     assert_eq!(stream.next().await.unwrap(), w);
2992    /// # }
2993    /// # }));
2994    /// # }
2995    pub fn anti_join<O2: Ordering, R2: Retries>(
2996        self,
2997        n: Stream<K, L, Bounded, O2, R2>,
2998    ) -> Stream<(K, V1), L, B, O, R>
2999    where
3000        K: Eq + Hash,
3001    {
3002        check_matching_location(&self.location, &n.location);
3003
3004        Stream::new(
3005            self.location.clone(),
3006            HydroNode::AntiJoin {
3007                pos: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3008                neg: Box::new(n.ir_node.replace(HydroNode::Placeholder)),
3009                metadata: self
3010                    .location
3011                    .new_node_metadata(Stream::<(K, V1), L, B, O, R>::collection_kind()),
3012            },
3013        )
3014    }
3015}
3016
3017impl<'a, K, V, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
3018    Stream<(K, V), L, B, O, R>
3019{
3020    /// Transforms this stream into a [`KeyedStream`], where the first element of each tuple
3021    /// is used as the key and the second element is added to the entries associated with that key.
3022    ///
3023    /// Because [`KeyedStream`] lazily groups values into buckets, this operator has zero computational
3024    /// cost and _does not_ require that the key type is hashable. Keyed streams are useful for
3025    /// performing grouped aggregations, but also for more precise ordering guarantees such as
3026    /// total ordering _within_ each group but no ordering _across_ groups.
3027    ///
3028    /// # Example
3029    /// ```rust
3030    /// # #[cfg(feature = "deploy")] {
3031    /// # use hydro_lang::prelude::*;
3032    /// # use futures::StreamExt;
3033    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3034    /// process
3035    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
3036    ///     .into_keyed()
3037    /// #   .entries()
3038    /// # }, |mut stream| async move {
3039    /// // { 1: [2, 3], 2: [4] }
3040    /// # for w in vec![(1, 2), (1, 3), (2, 4)] {
3041    /// #     assert_eq!(stream.next().await.unwrap(), w);
3042    /// # }
3043    /// # }));
3044    /// # }
3045    /// ```
3046    pub fn into_keyed(self) -> KeyedStream<K, V, L, B, O, R> {
3047        KeyedStream::new(
3048            self.location.clone(),
3049            HydroNode::Cast {
3050                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3051                metadata: self
3052                    .location
3053                    .new_node_metadata(KeyedStream::<K, V, L, B, O, R>::collection_kind()),
3054            },
3055        )
3056    }
3057}
3058
3059impl<'a, K, V, L, O: Ordering, R: Retries> Stream<(K, V), Tick<L>, Bounded, O, R>
3060where
3061    K: Eq + Hash,
3062    L: Location<'a>,
3063{
3064    /// Given a stream of pairs `(K, V)`, produces a new stream of unique keys `K`.
3065    /// # Example
3066    /// ```rust
3067    /// # #[cfg(feature = "deploy")] {
3068    /// # use hydro_lang::prelude::*;
3069    /// # use futures::StreamExt;
3070    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3071    /// let tick = process.tick();
3072    /// let numbers = process.source_iter(q!(vec![(1, 2), (2, 3), (1, 3), (2, 4)]));
3073    /// let batch = numbers.batch(&tick, nondet!(/** test */));
3074    /// batch.keys().all_ticks()
3075    /// # }, |mut stream| async move {
3076    /// // 1, 2
3077    /// # assert_eq!(stream.next().await.unwrap(), 1);
3078    /// # assert_eq!(stream.next().await.unwrap(), 2);
3079    /// # }));
3080    /// # }
3081    /// ```
3082    pub fn keys(self) -> Stream<K, Tick<L>, Bounded, NoOrder, ExactlyOnce> {
3083        self.into_keyed()
3084            .fold(
3085                q!(|| ()),
3086                q!(
3087                    |_, _| {},
3088                    commutative = manual_proof!(/** values are ignored */),
3089                    idempotent = manual_proof!(/** values are ignored */)
3090                ),
3091            )
3092            .keys()
3093    }
3094}
3095
3096impl<'a, T, L, B: Boundedness, O: Ordering, R: Retries> Stream<T, Atomic<L>, B, O, R>
3097where
3098    L: Location<'a>,
3099{
3100    /// Returns a stream corresponding to the latest batch of elements being atomically
3101    /// processed. These batches are guaranteed to be contiguous across ticks and preserve
3102    /// the order of the input.
3103    ///
3104    /// # Non-Determinism
3105    /// The batch boundaries are non-deterministic and may change across executions.
3106    pub fn batch_atomic<L2: Location<'a, DropConsistency = L::DropConsistency>>(
3107        self,
3108        tick: &Tick<L2>,
3109        _nondet: NonDet,
3110    ) -> Stream<T, Tick<L::DropConsistency>, Bounded, O, R> {
3111        Stream::new(
3112            tick.drop_consistency(),
3113            HydroNode::Batch {
3114                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3115                metadata: tick
3116                    .new_node_metadata(Stream::<T, Tick<L>, Bounded, O, R>::collection_kind()),
3117            },
3118        )
3119    }
3120
3121    /// Yields the elements of this stream back into a top-level, asynchronous execution context.
3122    /// See [`Stream::atomic`] for more details.
3123    pub fn end_atomic(self) -> Stream<T, L, B, O, R> {
3124        Stream::new(
3125            self.location.tick.l.clone(),
3126            HydroNode::EndAtomic {
3127                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3128                metadata: self
3129                    .location
3130                    .tick
3131                    .l
3132                    .new_node_metadata(Stream::<T, L, B, O, R>::collection_kind()),
3133            },
3134        )
3135    }
3136}
3137
3138impl<'a, F, T, L, B: Boundedness, O: Ordering, R: Retries> Stream<F, L, B, O, R>
3139where
3140    L: TopLevel<'a>,
3141    F: Future<Output = T>,
3142{
3143    /// Consumes a stream of `Future<T>`, produces a new stream of the resulting `T` outputs.
3144    /// Future outputs are produced as available, regardless of input arrival order.
3145    ///
3146    /// # Example
3147    /// ```rust
3148    /// # #[cfg(feature = "deploy")] {
3149    /// # use std::collections::HashSet;
3150    /// # use futures::StreamExt;
3151    /// # use hydro_lang::prelude::*;
3152    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3153    /// process.source_iter(q!([2, 3, 1, 9, 6, 5, 4, 7, 8]))
3154    ///     .map(q!(|x| async move {
3155    ///         tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
3156    ///         x
3157    ///     }))
3158    ///     .resolve_futures()
3159    /// #   },
3160    /// #   |mut stream| async move {
3161    /// // 1, 2, 3, 4, 5, 6, 7, 8, 9 (in any order)
3162    /// #       let mut output = HashSet::new();
3163    /// #       for _ in 1..10 {
3164    /// #           output.insert(stream.next().await.unwrap());
3165    /// #       }
3166    /// #       assert_eq!(
3167    /// #           output,
3168    /// #           HashSet::<i32>::from_iter(1..10)
3169    /// #       );
3170    /// #   },
3171    /// # ));
3172    /// # }
3173    pub fn resolve_futures(self) -> Stream<T, L, Unbounded, NoOrder, R> {
3174        Stream::new(
3175            self.location.clone(),
3176            HydroNode::ResolveFutures {
3177                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3178                metadata: self
3179                    .location
3180                    .new_node_metadata(Stream::<T, L, Unbounded, NoOrder, R>::collection_kind()),
3181            },
3182        )
3183    }
3184
3185    /// Consumes a stream of `Future<T>`, produces a new stream of the resulting `T` outputs.
3186    /// Future outputs are produced in the same order as the input stream.
3187    ///
3188    /// # Example
3189    /// ```rust
3190    /// # #[cfg(feature = "deploy")] {
3191    /// # use std::collections::HashSet;
3192    /// # use futures::StreamExt;
3193    /// # use hydro_lang::prelude::*;
3194    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3195    /// process.source_iter(q!([2, 3, 1, 9, 6, 5, 4, 7, 8]))
3196    ///     .map(q!(|x| async move {
3197    ///         tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
3198    ///         x
3199    ///     }))
3200    ///     .resolve_futures_ordered()
3201    /// #   },
3202    /// #   |mut stream| async move {
3203    /// // 2, 3, 1, 9, 6, 5, 4, 7, 8
3204    /// #       let mut output = Vec::new();
3205    /// #       for _ in 1..10 {
3206    /// #           output.push(stream.next().await.unwrap());
3207    /// #       }
3208    /// #       assert_eq!(
3209    /// #           output,
3210    /// #           vec![2, 3, 1, 9, 6, 5, 4, 7, 8]
3211    /// #       );
3212    /// #   },
3213    /// # ));
3214    /// # }
3215    pub fn resolve_futures_ordered(self) -> Stream<T, L, Unbounded, O, R> {
3216        Stream::new(
3217            self.location.clone(),
3218            HydroNode::ResolveFuturesOrdered {
3219                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3220                metadata: self
3221                    .location
3222                    .new_node_metadata(Stream::<T, L, Unbounded, O, R>::collection_kind()),
3223            },
3224        )
3225    }
3226}
3227
3228impl<'a, T, L, O: Ordering, R: Retries> Stream<T, Tick<L>, Bounded, O, R>
3229where
3230    L: Location<'a>,
3231{
3232    /// Asynchronously yields this batch of elements outside the tick as an unbounded stream,
3233    /// which will stream all the elements across _all_ tick iterations by concatenating the batches.
3234    pub fn all_ticks(self) -> Stream<T, L, Unbounded, O, R> {
3235        Stream::new(
3236            self.location.outer().clone(),
3237            HydroNode::YieldConcat {
3238                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3239                metadata: self
3240                    .location
3241                    .outer()
3242                    .new_node_metadata(Stream::<T, L, Unbounded, O, R>::collection_kind()),
3243            },
3244        )
3245    }
3246
3247    /// Synchronously yields this batch of elements outside the tick as an unbounded stream,
3248    /// which will stream all the elements across _all_ tick iterations by concatenating the batches.
3249    ///
3250    /// Unlike [`Stream::all_ticks`], this preserves synchronous execution, as the output stream
3251    /// is emitted in an [`Atomic`] context that will process elements synchronously with the input
3252    /// stream's [`Tick`] context.
3253    pub fn all_ticks_atomic(self) -> Stream<T, Atomic<L>, Unbounded, O, R> {
3254        let out_location = Atomic {
3255            tick: self.location.clone(),
3256        };
3257
3258        Stream::new(
3259            out_location.clone(),
3260            HydroNode::YieldConcat {
3261                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3262                metadata: out_location
3263                    .new_node_metadata(Stream::<T, Atomic<L>, Unbounded, O, R>::collection_kind()),
3264            },
3265        )
3266    }
3267
3268    /// Transforms the stream using the given closure in "stateful" mode, where stateful operators
3269    /// such as `fold` retrain their memory across ticks rather than resetting across batches of
3270    /// input.
3271    ///
3272    /// This API is particularly useful for stateful computation on batches of data, such as
3273    /// maintaining an accumulated state that is up to date with the current batch.
3274    ///
3275    /// # Example
3276    /// ```rust
3277    /// # #[cfg(feature = "deploy")] {
3278    /// # use hydro_lang::prelude::*;
3279    /// # use futures::StreamExt;
3280    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3281    /// let tick = process.tick();
3282    /// # // ticks are lazy by default, forces the second tick to run
3283    /// # tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
3284    /// # let batch_first_tick = process
3285    /// #   .source_iter(q!(vec![1, 2, 3, 4]))
3286    /// #  .batch(&tick, nondet!(/** test */));
3287    /// # let batch_second_tick = process
3288    /// #   .source_iter(q!(vec![5, 6, 7]))
3289    /// #   .batch(&tick, nondet!(/** test */))
3290    /// #   .defer_tick(); // appears on the second tick
3291    /// let input = // [1, 2, 3, 4 (first batch), 5, 6, 7 (second batch)]
3292    /// # batch_first_tick.chain(batch_second_tick).all_ticks();
3293    ///
3294    /// input.batch(&tick, nondet!(/** test */))
3295    ///     .across_ticks(|s| s.count()).all_ticks()
3296    /// # }, |mut stream| async move {
3297    /// // [4, 7]
3298    /// assert_eq!(stream.next().await.unwrap(), 4);
3299    /// assert_eq!(stream.next().await.unwrap(), 7);
3300    /// # }));
3301    /// # }
3302    /// ```
3303    pub fn across_ticks<Out: BatchAtomic<'a>>(
3304        self,
3305        thunk: impl FnOnce(Stream<T, Atomic<L>, Unbounded, O, R>) -> Out,
3306    ) -> Out::Batched {
3307        thunk(self.all_ticks_atomic()).batched_atomic()
3308    }
3309
3310    /// Shifts the elements in `self` to the **next tick**, so that the returned stream at tick `T`
3311    /// always has the elements of `self` at tick `T - 1`.
3312    ///
3313    /// At tick `0`, the output stream is empty, since there is no previous tick.
3314    ///
3315    /// This operator enables stateful iterative processing with ticks, by sending data from one
3316    /// tick to the next. For example, you can use it to compare inputs across consecutive batches.
3317    ///
3318    /// # Example
3319    /// ```rust
3320    /// # #[cfg(feature = "deploy")] {
3321    /// # use hydro_lang::prelude::*;
3322    /// # use futures::StreamExt;
3323    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3324    /// let tick = process.tick();
3325    /// // ticks are lazy by default, forces the second tick to run
3326    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
3327    ///
3328    /// let batch_first_tick = process
3329    ///   .source_iter(q!(vec![1, 2, 3, 4]))
3330    ///   .batch(&tick, nondet!(/** test */));
3331    /// let batch_second_tick = process
3332    ///   .source_iter(q!(vec![0, 3, 4, 5, 6]))
3333    ///   .batch(&tick, nondet!(/** test */))
3334    ///   .defer_tick(); // appears on the second tick
3335    /// let changes_across_ticks = batch_first_tick.chain(batch_second_tick);
3336    ///
3337    /// changes_across_ticks.clone().filter_not_in(
3338    ///     changes_across_ticks.defer_tick() // the elements from the previous tick
3339    /// ).all_ticks()
3340    /// # }, |mut stream| async move {
3341    /// // [1, 2, 3, 4 /* first tick */, 0, 5, 6 /* second tick */]
3342    /// # for w in vec![1, 2, 3, 4, 0, 5, 6] {
3343    /// #     assert_eq!(stream.next().await.unwrap(), w);
3344    /// # }
3345    /// # }));
3346    /// # }
3347    /// ```
3348    pub fn defer_tick(self) -> Stream<T, Tick<L>, Bounded, O, R> {
3349        Stream::new(
3350            self.location.clone(),
3351            HydroNode::DeferTick {
3352                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3353                metadata: self
3354                    .location
3355                    .new_node_metadata(Stream::<T, Tick<L>, Bounded, O, R>::collection_kind()),
3356            },
3357        )
3358    }
3359}
3360
3361#[cfg(test)]
3362mod tests {
3363    #[cfg(feature = "deploy")]
3364    use futures::{SinkExt, StreamExt};
3365    #[cfg(feature = "deploy")]
3366    use hydro_deploy::Deployment;
3367    #[cfg(feature = "deploy")]
3368    use serde::{Deserialize, Serialize};
3369    #[cfg(any(feature = "deploy", feature = "sim"))]
3370    use stageleft::q;
3371
3372    #[cfg(any(feature = "deploy", feature = "sim"))]
3373    use crate::compile::builder::FlowBuilder;
3374    #[cfg(feature = "deploy")]
3375    use crate::live_collections::sliced::sliced;
3376    #[cfg(feature = "deploy")]
3377    use crate::live_collections::stream::ExactlyOnce;
3378    #[cfg(feature = "sim")]
3379    use crate::live_collections::stream::NoOrder;
3380    #[cfg(any(feature = "deploy", feature = "sim"))]
3381    use crate::live_collections::stream::TotalOrder;
3382    #[cfg(any(feature = "deploy", feature = "sim"))]
3383    use crate::location::Location;
3384    #[cfg(feature = "sim")]
3385    use crate::networking::TCP;
3386    #[cfg(any(feature = "deploy", feature = "sim"))]
3387    use crate::nondet::nondet;
3388
3389    mod backtrace_chained_ops;
3390
3391    #[cfg(feature = "deploy")]
3392    struct P1 {}
3393    #[cfg(feature = "deploy")]
3394    struct P2 {}
3395
3396    #[cfg(feature = "deploy")]
3397    #[derive(Serialize, Deserialize, Debug)]
3398    struct SendOverNetwork {
3399        n: u32,
3400    }
3401
3402    #[cfg(feature = "deploy")]
3403    #[tokio::test]
3404    async fn first_ten_distributed() {
3405        use crate::networking::TCP;
3406
3407        let mut deployment = Deployment::new();
3408
3409        let mut flow = FlowBuilder::new();
3410        let first_node = flow.process::<P1>();
3411        let second_node = flow.process::<P2>();
3412        let external = flow.external::<P2>();
3413
3414        let numbers = first_node.source_iter(q!(0..10));
3415        let out_port = numbers
3416            .map(q!(|n| SendOverNetwork { n }))
3417            .send(&second_node, TCP.fail_stop().bincode())
3418            .send_bincode_external(&external);
3419
3420        let nodes = flow
3421            .with_process(&first_node, deployment.Localhost())
3422            .with_process(&second_node, deployment.Localhost())
3423            .with_external(&external, deployment.Localhost())
3424            .deploy(&mut deployment);
3425
3426        deployment.deploy().await.unwrap();
3427
3428        let mut external_out = nodes.connect(out_port).await;
3429
3430        deployment.start().await.unwrap();
3431
3432        for i in 0..10 {
3433            assert_eq!(external_out.next().await.unwrap().n, i);
3434        }
3435    }
3436
3437    #[cfg(feature = "deploy")]
3438    #[tokio::test]
3439    async fn first_cardinality() {
3440        let mut deployment = Deployment::new();
3441
3442        let mut flow = FlowBuilder::new();
3443        let node = flow.process::<()>();
3444        let external = flow.external::<()>();
3445
3446        let node_tick = node.tick();
3447        let count = node_tick
3448            .singleton(q!([1, 2, 3]))
3449            .into_stream()
3450            .flatten_ordered()
3451            .first()
3452            .into_stream()
3453            .count()
3454            .all_ticks()
3455            .send_bincode_external(&external);
3456
3457        let nodes = flow
3458            .with_process(&node, deployment.Localhost())
3459            .with_external(&external, deployment.Localhost())
3460            .deploy(&mut deployment);
3461
3462        deployment.deploy().await.unwrap();
3463
3464        let mut external_out = nodes.connect(count).await;
3465
3466        deployment.start().await.unwrap();
3467
3468        assert_eq!(external_out.next().await.unwrap(), 1);
3469    }
3470
3471    #[cfg(feature = "deploy")]
3472    #[tokio::test]
3473    async fn unbounded_reduce_remembers_state() {
3474        let mut deployment = Deployment::new();
3475
3476        let mut flow = FlowBuilder::new();
3477        let node = flow.process::<()>();
3478        let external = flow.external::<()>();
3479
3480        let (input_port, input) = node.source_external_bincode(&external);
3481        let out = input
3482            .reduce(q!(|acc, v| *acc += v))
3483            .sample_eager(nondet!(/** test */))
3484            .send_bincode_external(&external);
3485
3486        let nodes = flow
3487            .with_process(&node, deployment.Localhost())
3488            .with_external(&external, deployment.Localhost())
3489            .deploy(&mut deployment);
3490
3491        deployment.deploy().await.unwrap();
3492
3493        let mut external_in = nodes.connect(input_port).await;
3494        let mut external_out = nodes.connect(out).await;
3495
3496        deployment.start().await.unwrap();
3497
3498        external_in.send(1).await.unwrap();
3499        assert_eq!(external_out.next().await.unwrap(), 1);
3500
3501        external_in.send(2).await.unwrap();
3502        assert_eq!(external_out.next().await.unwrap(), 3);
3503    }
3504
3505    #[cfg(feature = "deploy")]
3506    #[tokio::test]
3507    async fn top_level_bounded_cross_singleton() {
3508        let mut deployment = Deployment::new();
3509
3510        let mut flow = FlowBuilder::new();
3511        let node = flow.process::<()>();
3512        let external = flow.external::<()>();
3513
3514        let (input_port, input) =
3515            node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
3516
3517        let out = input
3518            .cross_singleton(
3519                node.source_iter(q!(vec![1, 2, 3]))
3520                    .fold(q!(|| 0), q!(|acc, v| *acc += v)),
3521            )
3522            .send_bincode_external(&external);
3523
3524        let nodes = flow
3525            .with_process(&node, deployment.Localhost())
3526            .with_external(&external, deployment.Localhost())
3527            .deploy(&mut deployment);
3528
3529        deployment.deploy().await.unwrap();
3530
3531        let mut external_in = nodes.connect(input_port).await;
3532        let mut external_out = nodes.connect(out).await;
3533
3534        deployment.start().await.unwrap();
3535
3536        external_in.send(1).await.unwrap();
3537        assert_eq!(external_out.next().await.unwrap(), (1, 6));
3538
3539        external_in.send(2).await.unwrap();
3540        assert_eq!(external_out.next().await.unwrap(), (2, 6));
3541    }
3542
3543    #[cfg(feature = "deploy")]
3544    #[tokio::test]
3545    async fn top_level_bounded_reduce_cardinality() {
3546        let mut deployment = Deployment::new();
3547
3548        let mut flow = FlowBuilder::new();
3549        let node = flow.process::<()>();
3550        let external = flow.external::<()>();
3551
3552        let (input_port, input) =
3553            node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
3554
3555        let out = sliced! {
3556            let input = use::batch(input, nondet!(/** test */));
3557            let v = use::snapshot(node.source_iter(q!(vec![1, 2, 3])).reduce(q!(|acc, v| *acc += v)), nondet!(/** test */));
3558            input.cross_singleton(v.into_stream().count())
3559        }
3560        .send_bincode_external(&external);
3561
3562        let nodes = flow
3563            .with_process(&node, deployment.Localhost())
3564            .with_external(&external, deployment.Localhost())
3565            .deploy(&mut deployment);
3566
3567        deployment.deploy().await.unwrap();
3568
3569        let mut external_in = nodes.connect(input_port).await;
3570        let mut external_out = nodes.connect(out).await;
3571
3572        deployment.start().await.unwrap();
3573
3574        external_in.send(1).await.unwrap();
3575        assert_eq!(external_out.next().await.unwrap(), (1, 1));
3576
3577        external_in.send(2).await.unwrap();
3578        assert_eq!(external_out.next().await.unwrap(), (2, 1));
3579    }
3580
3581    #[cfg(feature = "deploy")]
3582    #[tokio::test]
3583    async fn top_level_bounded_into_singleton_cardinality() {
3584        let mut deployment = Deployment::new();
3585
3586        let mut flow = FlowBuilder::new();
3587        let node = flow.process::<()>();
3588        let external = flow.external::<()>();
3589
3590        let (input_port, input) =
3591            node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
3592
3593        let out = sliced! {
3594            let input = use::batch(input, nondet!(/** test */));
3595            let v = use::snapshot(node.source_iter(q!(vec![1, 2, 3])).reduce(q!(|acc, v| *acc += v)).into_singleton(), nondet!(/** test */));
3596            input.cross_singleton(v.into_stream().count())
3597        }
3598        .send_bincode_external(&external);
3599
3600        let nodes = flow
3601            .with_process(&node, deployment.Localhost())
3602            .with_external(&external, deployment.Localhost())
3603            .deploy(&mut deployment);
3604
3605        deployment.deploy().await.unwrap();
3606
3607        let mut external_in = nodes.connect(input_port).await;
3608        let mut external_out = nodes.connect(out).await;
3609
3610        deployment.start().await.unwrap();
3611
3612        external_in.send(1).await.unwrap();
3613        assert_eq!(external_out.next().await.unwrap(), (1, 1));
3614
3615        external_in.send(2).await.unwrap();
3616        assert_eq!(external_out.next().await.unwrap(), (2, 1));
3617    }
3618
3619    #[cfg(feature = "deploy")]
3620    #[tokio::test]
3621    async fn atomic_fold_replays_each_tick() {
3622        let mut deployment = Deployment::new();
3623
3624        let mut flow = FlowBuilder::new();
3625        let node = flow.process::<()>();
3626        let external = flow.external::<()>();
3627
3628        let (input_port, input) =
3629            node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
3630        let tick = node.tick();
3631
3632        let out = input
3633            .batch(&tick, nondet!(/** test */))
3634            .cross_singleton(
3635                node.source_iter(q!(vec![1, 2, 3]))
3636                    .atomic()
3637                    .fold(q!(|| 0), q!(|acc, v| *acc += v))
3638                    .snapshot_atomic(&tick, nondet!(/** test */)),
3639            )
3640            .all_ticks()
3641            .send_bincode_external(&external);
3642
3643        let nodes = flow
3644            .with_process(&node, deployment.Localhost())
3645            .with_external(&external, deployment.Localhost())
3646            .deploy(&mut deployment);
3647
3648        deployment.deploy().await.unwrap();
3649
3650        let mut external_in = nodes.connect(input_port).await;
3651        let mut external_out = nodes.connect(out).await;
3652
3653        deployment.start().await.unwrap();
3654
3655        external_in.send(1).await.unwrap();
3656        assert_eq!(external_out.next().await.unwrap(), (1, 6));
3657
3658        external_in.send(2).await.unwrap();
3659        assert_eq!(external_out.next().await.unwrap(), (2, 6));
3660    }
3661
3662    #[cfg(feature = "deploy")]
3663    #[tokio::test]
3664    async fn unbounded_scan_remembers_state() {
3665        let mut deployment = Deployment::new();
3666
3667        let mut flow = FlowBuilder::new();
3668        let node = flow.process::<()>();
3669        let external = flow.external::<()>();
3670
3671        let (input_port, input) = node.source_external_bincode(&external);
3672        let out = input
3673            .scan(
3674                q!(|| 0),
3675                q!(|acc, v| {
3676                    *acc += v;
3677                    Some(*acc)
3678                }),
3679            )
3680            .send_bincode_external(&external);
3681
3682        let nodes = flow
3683            .with_process(&node, deployment.Localhost())
3684            .with_external(&external, deployment.Localhost())
3685            .deploy(&mut deployment);
3686
3687        deployment.deploy().await.unwrap();
3688
3689        let mut external_in = nodes.connect(input_port).await;
3690        let mut external_out = nodes.connect(out).await;
3691
3692        deployment.start().await.unwrap();
3693
3694        external_in.send(1).await.unwrap();
3695        assert_eq!(external_out.next().await.unwrap(), 1);
3696
3697        external_in.send(2).await.unwrap();
3698        assert_eq!(external_out.next().await.unwrap(), 3);
3699    }
3700
3701    #[cfg(feature = "deploy")]
3702    #[tokio::test]
3703    async fn unbounded_enumerate_remembers_state() {
3704        let mut deployment = Deployment::new();
3705
3706        let mut flow = FlowBuilder::new();
3707        let node = flow.process::<()>();
3708        let external = flow.external::<()>();
3709
3710        let (input_port, input) = node.source_external_bincode(&external);
3711        let out = input.enumerate().send_bincode_external(&external);
3712
3713        let nodes = flow
3714            .with_process(&node, deployment.Localhost())
3715            .with_external(&external, deployment.Localhost())
3716            .deploy(&mut deployment);
3717
3718        deployment.deploy().await.unwrap();
3719
3720        let mut external_in = nodes.connect(input_port).await;
3721        let mut external_out = nodes.connect(out).await;
3722
3723        deployment.start().await.unwrap();
3724
3725        external_in.send(1).await.unwrap();
3726        assert_eq!(external_out.next().await.unwrap(), (0, 1));
3727
3728        external_in.send(2).await.unwrap();
3729        assert_eq!(external_out.next().await.unwrap(), (1, 2));
3730    }
3731
3732    #[cfg(feature = "deploy")]
3733    #[tokio::test]
3734    async fn unbounded_unique_remembers_state() {
3735        let mut deployment = Deployment::new();
3736
3737        let mut flow = FlowBuilder::new();
3738        let node = flow.process::<()>();
3739        let external = flow.external::<()>();
3740
3741        let (input_port, input) =
3742            node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
3743        let out = input.unique().send_bincode_external(&external);
3744
3745        let nodes = flow
3746            .with_process(&node, deployment.Localhost())
3747            .with_external(&external, deployment.Localhost())
3748            .deploy(&mut deployment);
3749
3750        deployment.deploy().await.unwrap();
3751
3752        let mut external_in = nodes.connect(input_port).await;
3753        let mut external_out = nodes.connect(out).await;
3754
3755        deployment.start().await.unwrap();
3756
3757        external_in.send(1).await.unwrap();
3758        assert_eq!(external_out.next().await.unwrap(), 1);
3759
3760        external_in.send(2).await.unwrap();
3761        assert_eq!(external_out.next().await.unwrap(), 2);
3762
3763        external_in.send(1).await.unwrap();
3764        external_in.send(3).await.unwrap();
3765        assert_eq!(external_out.next().await.unwrap(), 3);
3766    }
3767
3768    #[cfg(feature = "sim")]
3769    #[test]
3770    #[should_panic]
3771    fn sim_batch_nondet_size() {
3772        let mut flow = FlowBuilder::new();
3773        let node = flow.process::<()>();
3774
3775        let (in_send, input) = node.sim_input::<_, TotalOrder, _>();
3776
3777        let tick = node.tick();
3778        let out_recv = input
3779            .batch(&tick, nondet!(/** test */))
3780            .count()
3781            .all_ticks()
3782            .sim_output();
3783
3784        flow.sim().exhaustive(async || {
3785            in_send.send(());
3786            in_send.send(());
3787            in_send.send(());
3788
3789            assert_eq!(out_recv.next().await, 3); // fails with nondet batching
3790        });
3791    }
3792
3793    #[cfg(feature = "sim")]
3794    #[test]
3795    fn sim_batch_preserves_order() {
3796        let mut flow = FlowBuilder::new();
3797        let node = flow.process::<()>();
3798
3799        let (in_send, input) = node.sim_input();
3800
3801        let tick = node.tick();
3802        let out_recv = input
3803            .batch(&tick, nondet!(/** test */))
3804            .all_ticks()
3805            .sim_output();
3806
3807        flow.sim().exhaustive(async || {
3808            in_send.send(1);
3809            in_send.send(2);
3810            in_send.send(3);
3811
3812            out_recv.assert_yields_only([1, 2, 3]).await;
3813        });
3814    }
3815
3816    #[cfg(feature = "sim")]
3817    #[test]
3818    #[should_panic]
3819    fn sim_batch_unordered_shuffles() {
3820        let mut flow = FlowBuilder::new();
3821        let node = flow.process::<()>();
3822
3823        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3824
3825        let tick = node.tick();
3826        let batch = input.batch(&tick, nondet!(/** test */));
3827        let out_recv = batch
3828            .clone()
3829            .min()
3830            .zip(batch.max())
3831            .all_ticks()
3832            .sim_output();
3833
3834        flow.sim().exhaustive(async || {
3835            in_send.send_many_unordered([1, 2, 3]);
3836
3837            if out_recv.collect::<Vec<_>>().await == vec![(1, 3), (2, 2)] {
3838                panic!("saw both (1, 3) and (2, 2), so batching must have shuffled the order");
3839            }
3840        });
3841    }
3842
3843    #[cfg(feature = "sim")]
3844    #[test]
3845    fn sim_batch_unordered_shuffles_count() {
3846        let mut flow = FlowBuilder::new();
3847        let node = flow.process::<()>();
3848
3849        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3850
3851        let tick = node.tick();
3852        let batch = input.batch(&tick, nondet!(/** test */));
3853        let out_recv = batch.all_ticks().sim_output();
3854
3855        let instance_count = flow.sim().exhaustive(async || {
3856            in_send.send_many_unordered([1, 2, 3, 4]);
3857            out_recv.assert_yields_only_unordered([1, 2, 3, 4]).await;
3858        });
3859
3860        assert_eq!(
3861            instance_count,
3862            75 // ∑ (k=1 to 4) S(4,k) × k! = 75
3863        )
3864    }
3865
3866    #[cfg(feature = "sim")]
3867    #[test]
3868    #[should_panic]
3869    fn sim_observe_order_batched() {
3870        let mut flow = FlowBuilder::new();
3871        let node = flow.process::<()>();
3872
3873        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3874
3875        let tick = node.tick();
3876        let batch = input.batch(&tick, nondet!(/** test */));
3877        let out_recv = batch
3878            .assume_ordering::<TotalOrder>(nondet!(/** test */))
3879            .all_ticks()
3880            .sim_output();
3881
3882        flow.sim().exhaustive(async || {
3883            in_send.send_many_unordered([1, 2, 3, 4]);
3884            out_recv.assert_yields_only([1, 2, 3, 4]).await; // fails with assume_ordering
3885        });
3886    }
3887
3888    #[cfg(feature = "sim")]
3889    #[test]
3890    fn sim_observe_order_batched_count() {
3891        let mut flow = FlowBuilder::new();
3892        let node = flow.process::<()>();
3893
3894        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3895
3896        let tick = node.tick();
3897        let batch = input.batch(&tick, nondet!(/** test */));
3898        let out_recv = batch
3899            .assume_ordering::<TotalOrder>(nondet!(/** test */))
3900            .all_ticks()
3901            .sim_output();
3902
3903        let instance_count = flow.sim().exhaustive(async || {
3904            in_send.send_many_unordered([1, 2, 3, 4]);
3905            let _ = out_recv.collect::<Vec<_>>().await;
3906        });
3907
3908        assert_eq!(
3909            instance_count,
3910            192 // 4! * 2^{4 - 1}
3911        )
3912    }
3913
3914    #[cfg(feature = "sim")]
3915    #[test]
3916    fn sim_unordered_count_instance_count() {
3917        let mut flow = FlowBuilder::new();
3918        let node = flow.process::<()>();
3919
3920        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3921
3922        let tick = node.tick();
3923        let out_recv = input
3924            .count()
3925            .snapshot(&tick, nondet!(/** test */))
3926            .all_ticks()
3927            .sim_output();
3928
3929        let instance_count = flow.sim().exhaustive(async || {
3930            in_send.send_many_unordered([1, 2, 3, 4]);
3931            assert!(out_recv.collect::<Vec<_>>().await.last().unwrap() == &4);
3932        });
3933
3934        assert_eq!(
3935            instance_count,
3936            16 // 2^4, { 0, 1, 2, 3 } can be a snapshot and 4 is always included
3937        )
3938    }
3939
3940    #[cfg(feature = "sim")]
3941    #[test]
3942    fn sim_top_level_assume_ordering() {
3943        let mut flow = FlowBuilder::new();
3944        let node = flow.process::<()>();
3945
3946        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3947
3948        let out_recv = input
3949            .assume_ordering::<TotalOrder>(nondet!(/** test */))
3950            .sim_output();
3951
3952        let instance_count = flow.sim().exhaustive(async || {
3953            in_send.send_many_unordered([1, 2, 3]);
3954            let mut out = out_recv.collect::<Vec<_>>().await;
3955            out.sort();
3956            assert_eq!(out, vec![1, 2, 3]);
3957        });
3958
3959        assert_eq!(instance_count, 6)
3960    }
3961
3962    #[cfg(feature = "sim")]
3963    #[test]
3964    fn sim_top_level_assume_ordering_cycle_back() {
3965        let mut flow = FlowBuilder::new();
3966        let node = flow.process::<()>();
3967        let node2 = flow.process::<()>();
3968
3969        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3970
3971        let (complete_cycle_back, cycle_back) =
3972            node.forward_ref::<super::Stream<_, _, _, NoOrder>>();
3973        let ordered = input
3974            .merge_unordered(cycle_back)
3975            .assume_ordering::<TotalOrder>(nondet!(/** test */));
3976        complete_cycle_back.complete(
3977            ordered
3978                .clone()
3979                .map(q!(|v| v + 1))
3980                .filter(q!(|v| v % 2 == 1))
3981                .send(&node2, TCP.fail_stop().bincode())
3982                .send(&node, TCP.fail_stop().bincode()),
3983        );
3984
3985        let out_recv = ordered.sim_output();
3986
3987        let mut saw = false;
3988        let instance_count = flow.sim().exhaustive(async || {
3989            in_send.send_many_unordered([0, 2]);
3990            let out = out_recv.collect::<Vec<_>>().await;
3991
3992            if out.starts_with(&[0, 1, 2]) {
3993                saw = true;
3994            }
3995        });
3996
3997        assert!(saw, "did not see an instance with 0, 1, 2 in order");
3998        assert_eq!(instance_count, 6);
3999    }
4000
4001    #[cfg(feature = "sim")]
4002    #[test]
4003    fn sim_top_level_assume_ordering_cycle_back_tick() {
4004        let mut flow = FlowBuilder::new();
4005        let node = flow.process::<()>();
4006        let node2 = flow.process::<()>();
4007
4008        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4009
4010        let (complete_cycle_back, cycle_back) =
4011            node.forward_ref::<super::Stream<_, _, _, NoOrder>>();
4012        let ordered = input
4013            .merge_unordered(cycle_back)
4014            .assume_ordering::<TotalOrder>(nondet!(/** test */));
4015        complete_cycle_back.complete(
4016            ordered
4017                .clone()
4018                .batch(&node.tick(), nondet!(/** test */))
4019                .all_ticks()
4020                .map(q!(|v| v + 1))
4021                .filter(q!(|v| v % 2 == 1))
4022                .send(&node2, TCP.fail_stop().bincode())
4023                .send(&node, TCP.fail_stop().bincode()),
4024        );
4025
4026        let out_recv = ordered.sim_output();
4027
4028        let mut saw = false;
4029        let instance_count = flow.sim().exhaustive(async || {
4030            in_send.send_many_unordered([0, 2]);
4031            let out = out_recv.collect::<Vec<_>>().await;
4032
4033            if out.starts_with(&[0, 1, 2]) {
4034                saw = true;
4035            }
4036        });
4037
4038        assert!(saw, "did not see an instance with 0, 1, 2 in order");
4039        assert_eq!(instance_count, 58);
4040    }
4041
4042    #[cfg(feature = "sim")]
4043    #[test]
4044    fn sim_top_level_assume_ordering_multiple() {
4045        let mut flow = FlowBuilder::new();
4046        let node = flow.process::<()>();
4047        let node2 = flow.process::<()>();
4048
4049        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4050        let (_, input2) = node.sim_input::<_, NoOrder, _>();
4051
4052        let (complete_cycle_back, cycle_back) =
4053            node.forward_ref::<super::Stream<_, _, _, NoOrder>>();
4054        let input1_ordered = input
4055            .clone()
4056            .merge_unordered(cycle_back)
4057            .assume_ordering::<TotalOrder>(nondet!(/** test */));
4058        let foo = input1_ordered
4059            .clone()
4060            .map(q!(|v| v + 3))
4061            .weaken_ordering::<NoOrder>()
4062            .merge_unordered(input2)
4063            .assume_ordering::<TotalOrder>(nondet!(/** test */));
4064
4065        complete_cycle_back.complete(
4066            foo.filter(q!(|v| *v == 3))
4067                .send(&node2, TCP.fail_stop().bincode())
4068                .send(&node, TCP.fail_stop().bincode()),
4069        );
4070
4071        let out_recv = input1_ordered.sim_output();
4072
4073        let mut saw = false;
4074        let instance_count = flow.sim().exhaustive(async || {
4075            in_send.send_many_unordered([0, 1]);
4076            let out = out_recv.collect::<Vec<_>>().await;
4077
4078            if out.starts_with(&[0, 3, 1]) {
4079                saw = true;
4080            }
4081        });
4082
4083        assert!(saw, "did not see an instance with 0, 3, 1 in order");
4084        assert_eq!(instance_count, 24);
4085    }
4086
4087    #[cfg(feature = "sim")]
4088    #[test]
4089    fn sim_atomic_assume_ordering_cycle_back() {
4090        let mut flow = FlowBuilder::new();
4091        let node = flow.process::<()>();
4092        let node2 = flow.process::<()>();
4093
4094        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
4095
4096        let (complete_cycle_back, cycle_back) =
4097            node.forward_ref::<super::Stream<_, _, _, NoOrder>>();
4098        let ordered = input
4099            .merge_unordered(cycle_back)
4100            .atomic()
4101            .assume_ordering::<TotalOrder>(nondet!(/** test */))
4102            .end_atomic();
4103        complete_cycle_back.complete(
4104            ordered
4105                .clone()
4106                .map(q!(|v| v + 1))
4107                .filter(q!(|v| v % 2 == 1))
4108                .send(&node2, TCP.fail_stop().bincode())
4109                .send(&node, TCP.fail_stop().bincode()),
4110        );
4111
4112        let out_recv = ordered.sim_output();
4113
4114        let instance_count = flow.sim().exhaustive(async || {
4115            in_send.send_many_unordered([0, 2]);
4116            let out = out_recv.collect::<Vec<_>>().await;
4117            assert_eq!(out.len(), 4);
4118        });
4119        assert_eq!(instance_count, 22);
4120    }
4121
4122    #[cfg(feature = "deploy")]
4123    #[tokio::test]
4124    async fn partition_evens_odds() {
4125        let mut deployment = Deployment::new();
4126
4127        let mut flow = FlowBuilder::new();
4128        let node = flow.process::<()>();
4129        let external = flow.external::<()>();
4130
4131        let numbers = node.source_iter(q!(vec![1i32, 2, 3, 4, 5, 6]));
4132        let (evens, odds) = numbers.partition(q!(|x: &i32| x % 2 == 0));
4133        let evens_port = evens.send_bincode_external(&external);
4134        let odds_port = odds.send_bincode_external(&external);
4135
4136        let nodes = flow
4137            .with_process(&node, deployment.Localhost())
4138            .with_external(&external, deployment.Localhost())
4139            .deploy(&mut deployment);
4140
4141        deployment.deploy().await.unwrap();
4142
4143        let mut evens_out = nodes.connect(evens_port).await;
4144        let mut odds_out = nodes.connect(odds_port).await;
4145
4146        deployment.start().await.unwrap();
4147
4148        let mut even_results = Vec::new();
4149        for _ in 0..3 {
4150            even_results.push(evens_out.next().await.unwrap());
4151        }
4152        even_results.sort();
4153        assert_eq!(even_results, vec![2, 4, 6]);
4154
4155        let mut odd_results = Vec::new();
4156        for _ in 0..3 {
4157            odd_results.push(odds_out.next().await.unwrap());
4158        }
4159        odd_results.sort();
4160        assert_eq!(odd_results, vec![1, 3, 5]);
4161    }
4162
4163    #[cfg(feature = "deploy")]
4164    #[tokio::test]
4165    async fn unconsumed_inspect_still_runs() {
4166        use crate::deploy::DeployCrateWrapper;
4167
4168        let mut deployment = Deployment::new();
4169
4170        let mut flow = FlowBuilder::new();
4171        let node = flow.process::<()>();
4172
4173        // The return value of .inspect() is intentionally dropped.
4174        // Before the Null-root fix, this would silently do nothing.
4175        node.source_iter(q!(0..5))
4176            .inspect(q!(|x| println!("inspect: {}", x)));
4177
4178        let nodes = flow
4179            .with_process(&node, deployment.Localhost())
4180            .deploy(&mut deployment);
4181
4182        deployment.deploy().await.unwrap();
4183
4184        let mut stdout = nodes.get_process(&node).stdout();
4185
4186        deployment.start().await.unwrap();
4187
4188        let mut lines = Vec::new();
4189        for _ in 0..5 {
4190            lines.push(stdout.recv().await.unwrap());
4191        }
4192        lines.sort();
4193        assert_eq!(
4194            lines,
4195            vec![
4196                "inspect: 0",
4197                "inspect: 1",
4198                "inspect: 2",
4199                "inspect: 3",
4200                "inspect: 4",
4201            ]
4202        );
4203    }
4204
4205    #[cfg(feature = "deploy")]
4206    #[tokio::test]
4207    async fn unconsumed_inspect_alive_at_deploy_still_runs() {
4208        use crate::deploy::DeployCrateWrapper;
4209
4210        let mut deployment = Deployment::new();
4211
4212        let mut flow = FlowBuilder::new();
4213        let node = flow.process::<()>();
4214
4215        // The return value of .inspect() is bound to a variable that is still alive
4216        // when the flow is finalized by `deploy` below, so its `Drop` runs too late
4217        // to register a root the usual way. The FlowBuilder must yank the IR from
4218        // still-live collections when finalizing.
4219        let _inspected = node
4220            .source_iter(q!(0..5))
4221            .inspect(q!(|x| println!("inspect: {}", x)));
4222
4223        let nodes = flow
4224            .with_process(&node, deployment.Localhost())
4225            .deploy(&mut deployment);
4226
4227        deployment.deploy().await.unwrap();
4228
4229        let mut stdout = nodes.get_process(&node).stdout();
4230
4231        deployment.start().await.unwrap();
4232
4233        let mut lines = Vec::new();
4234        for _ in 0..5 {
4235            lines.push(stdout.recv().await.unwrap());
4236        }
4237        lines.sort();
4238        assert_eq!(
4239            lines,
4240            vec![
4241                "inspect: 0",
4242                "inspect: 1",
4243                "inspect: 2",
4244                "inspect: 3",
4245                "inspect: 4",
4246            ]
4247        );
4248    }
4249
4250    #[cfg(feature = "sim")]
4251    #[test]
4252    fn sim_limit() {
4253        let mut flow = FlowBuilder::new();
4254        let node = flow.process::<()>();
4255
4256        let (in_send, input) = node.sim_input();
4257
4258        let out_recv = input.limit(q!(3)).sim_output();
4259
4260        flow.sim().exhaustive(async || {
4261            in_send.send(1);
4262            in_send.send(2);
4263            in_send.send(3);
4264            in_send.send(4);
4265            in_send.send(5);
4266
4267            out_recv.assert_yields_only([1, 2, 3]).await;
4268        });
4269    }
4270
4271    #[cfg(feature = "sim")]
4272    #[test]
4273    fn sim_limit_zero() {
4274        let mut flow = FlowBuilder::new();
4275        let node = flow.process::<()>();
4276
4277        let (in_send, input) = node.sim_input();
4278
4279        let out_recv = input.limit(q!(0)).sim_output();
4280
4281        flow.sim().exhaustive(async || {
4282            in_send.send(1);
4283            in_send.send(2);
4284
4285            out_recv.assert_yields_only::<i32, _>([]).await;
4286        });
4287    }
4288
4289    #[cfg(feature = "sim")]
4290    #[test]
4291    fn sim_merge_ordered() {
4292        let mut flow = FlowBuilder::new();
4293        let node = flow.process::<()>();
4294
4295        let (in_send, input) = node.sim_input();
4296        let (in_send2, input2) = node.sim_input();
4297
4298        let out_recv = input
4299            .merge_ordered(input2, nondet!(/** test */))
4300            .sim_output();
4301
4302        let mut saw_out_of_order = false;
4303        let instances = flow.sim().exhaustive(async || {
4304            in_send.send(1);
4305            in_send.send(2);
4306            in_send2.send(3);
4307            in_send2.send(4);
4308
4309            let out = out_recv.collect::<Vec<_>>().await;
4310
4311            if out == [1, 3, 2, 4] {
4312                saw_out_of_order = true;
4313            }
4314
4315            // Assert ordering preservation: elements from each input must
4316            // appear in their original relative order.
4317            let mut first_elements = out.iter().filter(|v| **v <= 2).copied().collect::<Vec<_>>();
4318            let mut second_elements = out.iter().filter(|v| **v > 2).copied().collect::<Vec<_>>();
4319            assert_eq!(
4320                first_elements,
4321                vec![1, 2],
4322                "first input order violated: {:?}",
4323                out
4324            );
4325            assert_eq!(
4326                second_elements,
4327                vec![3, 4],
4328                "second input order violated: {:?}",
4329                out
4330            );
4331
4332            first_elements.append(&mut second_elements);
4333            first_elements.sort();
4334            assert_eq!(first_elements, vec![1, 2, 3, 4]);
4335        });
4336
4337        assert!(saw_out_of_order);
4338        assert_eq!(instances, 6);
4339    }
4340
4341    /// Tests that merge_ordered passes through elements when only one input
4342    /// has data.
4343    #[cfg(feature = "sim")]
4344    #[test]
4345    fn sim_merge_ordered_one_empty() {
4346        let mut flow = FlowBuilder::new();
4347        let node = flow.process::<()>();
4348
4349        let (in_send, input) = node.sim_input();
4350        let (_in_send2, input2) = node.sim_input();
4351
4352        let out_recv = input
4353            .merge_ordered(input2, nondet!(/** test */))
4354            .sim_output();
4355
4356        let instances = flow.sim().exhaustive(async || {
4357            in_send.send(1);
4358            in_send.send(2);
4359
4360            let out = out_recv.collect::<Vec<_>>().await;
4361            assert_eq!(out, vec![1, 2]);
4362        });
4363
4364        // Only one possible interleaving when one input is empty
4365        assert_eq!(instances, 1);
4366    }
4367
4368    /// Tests that merge_ordered correctly handles feedback cycles.
4369    /// An element output from merge_ordered is filtered and cycled back to
4370    /// one of its inputs. The one-at-a-time release must allow the cycled-back
4371    /// element to arrive and potentially be emitted before elements still
4372    /// waiting on the other input.
4373    #[cfg(feature = "sim")]
4374    #[test]
4375    fn sim_merge_ordered_cycle_back() {
4376        let mut flow = FlowBuilder::new();
4377        let node = flow.process::<()>();
4378
4379        let (in_send, input) = node.sim_input();
4380
4381        // Create a forward ref for the cycle back
4382        let (complete_cycle_back, cycle_back) =
4383            node.forward_ref::<super::Stream<_, _, _, TotalOrder>>();
4384
4385        // merge_ordered: input (external) with cycle_back
4386        let merged = input.merge_ordered(cycle_back, nondet!(/** test */));
4387
4388        // Cycle back: elements equal to 1 get mapped to 10 and fed back
4389        complete_cycle_back.complete(merged.clone().filter(q!(|v| *v == 1)).map(q!(|v| v * 10)));
4390
4391        let out_recv = merged.sim_output();
4392
4393        // Send 1 and 2. Element 1 should cycle back as 10.
4394        // Valid orderings must have 1 before 10 (since 10 depends on 1).
4395        let mut saw_cycle_before_second = false;
4396        flow.sim().exhaustive(async || {
4397            in_send.send(1);
4398            in_send.send(2);
4399
4400            let out = out_recv.collect::<Vec<_>>().await;
4401
4402            // 10 must always come after 1 (causal dependency)
4403            let pos_1 = out.iter().position(|v| *v == 1).unwrap();
4404            let pos_10 = out.iter().position(|v| *v == 10).unwrap();
4405            assert!(pos_1 < pos_10, "causal order violated: {:?}", out);
4406
4407            // Check if we see [1, 10, 2] — the cycled element beats the second input
4408            if out == [1, 10, 2] {
4409                saw_cycle_before_second = true;
4410            }
4411
4412            let mut sorted = out;
4413            sorted.sort();
4414            assert_eq!(sorted, vec![1, 2, 10]);
4415        });
4416
4417        assert!(
4418            saw_cycle_before_second,
4419            "never saw the cycled element arrive before the second input element"
4420        );
4421    }
4422
4423    /// Tests that merge_ordered correctly interleaves when one input has a
4424    /// delayed element. With a: [1, _delay_, 2] and b: [3, 4], the delayed
4425    /// element 2 should be able to appear after b's elements.
4426    #[cfg(feature = "sim")]
4427    #[test]
4428    fn sim_merge_ordered_delayed() {
4429        let mut flow = FlowBuilder::new();
4430        let node = flow.process::<()>();
4431
4432        let (in_send, input) = node.sim_input();
4433        let (in_send2, input2) = node.sim_input();
4434
4435        let out_recv = input
4436            .merge_ordered(input2, nondet!(/** test */))
4437            .sim_output();
4438
4439        let mut saw_delayed_interleaving = false;
4440        flow.sim().exhaustive(async || {
4441            // Send 1 from a, and 3, 4 from b
4442            in_send.send(1);
4443            in_send2.send(3);
4444            in_send2.send(4);
4445
4446            // Collect what's available so far
4447            let first_batch = out_recv.collect::<Vec<_>>().await;
4448
4449            // Now send the delayed element 2 from a
4450            in_send.send(2);
4451            let second_batch = out_recv.collect::<Vec<_>>().await;
4452
4453            let mut all: Vec<_> = first_batch
4454                .iter()
4455                .chain(second_batch.iter())
4456                .copied()
4457                .collect();
4458
4459            // Check if we saw [1, 3, 4, 2] — the delayed interleaving
4460            if all == [1, 3, 4, 2] {
4461                saw_delayed_interleaving = true;
4462            }
4463
4464            all.sort();
4465            assert_eq!(all, vec![1, 2, 3, 4]);
4466        });
4467
4468        assert!(saw_delayed_interleaving);
4469    }
4470
4471    /// Deploy test: merge_ordered with a delayed element on one input.
4472    /// Sends a=1, b=3, b=4, then after receiving those, sends a=2.
4473    /// Expects to see [1, 3, 4] first, then [2] — demonstrating that
4474    /// both inputs are pulled and the delayed element arrives later.
4475    #[cfg(feature = "deploy")]
4476    #[tokio::test]
4477    async fn deploy_merge_ordered_delayed() {
4478        let mut deployment = Deployment::new();
4479
4480        let mut flow = FlowBuilder::new();
4481        let node = flow.process::<()>();
4482        let external = flow.external::<()>();
4483
4484        let (input_a_port, input_a) = node.source_external_bincode(&external);
4485        let (input_b_port, input_b) = node.source_external_bincode(&external);
4486
4487        let out = input_a
4488            .assume_ordering(nondet!(/** test */))
4489            .merge_ordered(
4490                input_b.assume_ordering(nondet!(/** test */)),
4491                nondet!(/** test */),
4492            )
4493            .send_bincode_external(&external);
4494
4495        let nodes = flow
4496            .with_process(&node, deployment.Localhost())
4497            .with_external(&external, deployment.Localhost())
4498            .deploy(&mut deployment);
4499
4500        deployment.deploy().await.unwrap();
4501
4502        let mut ext_a = nodes.connect(input_a_port).await;
4503        let mut ext_b = nodes.connect(input_b_port).await;
4504        let mut ext_out = nodes.connect(out).await;
4505
4506        deployment.start().await.unwrap();
4507
4508        // Send a=1, b=3, b=4
4509        ext_a.send(1).await.unwrap();
4510        ext_b.send(3).await.unwrap();
4511        ext_b.send(4).await.unwrap();
4512
4513        // Collect the first 3 elements
4514        let mut received = Vec::new();
4515        for _ in 0..3 {
4516            received.push(ext_out.next().await.unwrap());
4517        }
4518
4519        // Now send the delayed a=2
4520        ext_a.send(2).await.unwrap();
4521        received.push(ext_out.next().await.unwrap());
4522
4523        // All elements should be present
4524        received.sort();
4525        assert_eq!(received, vec![1, 2, 3, 4]);
4526    }
4527
4528    #[cfg(feature = "deploy")]
4529    #[tokio::test]
4530    async fn monotone_fold_threshold() {
4531        use crate::properties::manual_proof;
4532
4533        let mut deployment = Deployment::new();
4534
4535        let mut flow = FlowBuilder::new();
4536        let node = flow.process::<()>();
4537        let external = flow.external::<()>();
4538
4539        let in_unbounded: super::Stream<_, _> =
4540            node.source_iter(q!(vec![1i32, 2, 3, 4, 5, 6])).into();
4541        let sum = in_unbounded.fold(
4542            q!(|| 0),
4543            q!(
4544                |sum, v| {
4545                    *sum += v;
4546                },
4547                monotone = manual_proof!(/** test */)
4548            ),
4549        );
4550
4551        let threshold_out = sum
4552            .threshold_greater_or_equal(node.singleton(q!(7)))
4553            .send_bincode_external(&external);
4554
4555        let nodes = flow
4556            .with_process(&node, deployment.Localhost())
4557            .with_external(&external, deployment.Localhost())
4558            .deploy(&mut deployment);
4559
4560        deployment.deploy().await.unwrap();
4561
4562        let mut threshold_out = nodes.connect(threshold_out).await;
4563
4564        deployment.start().await.unwrap();
4565
4566        assert_eq!(threshold_out.next().await.unwrap(), 7);
4567    }
4568
4569    #[cfg(feature = "deploy")]
4570    #[tokio::test]
4571    async fn monotone_count_threshold() {
4572        let mut deployment = Deployment::new();
4573
4574        let mut flow = FlowBuilder::new();
4575        let node = flow.process::<()>();
4576        let external = flow.external::<()>();
4577
4578        let in_unbounded: super::Stream<_, _> =
4579            node.source_iter(q!(vec![1i32, 2, 3, 4, 5, 6])).into();
4580        let sum = in_unbounded.count();
4581
4582        let threshold_out = sum
4583            .threshold_greater_or_equal(node.singleton(q!(3)))
4584            .send_bincode_external(&external);
4585
4586        let nodes = flow
4587            .with_process(&node, deployment.Localhost())
4588            .with_external(&external, deployment.Localhost())
4589            .deploy(&mut deployment);
4590
4591        deployment.deploy().await.unwrap();
4592
4593        let mut threshold_out = nodes.connect(threshold_out).await;
4594
4595        deployment.start().await.unwrap();
4596
4597        assert_eq!(threshold_out.next().await.unwrap(), 3);
4598    }
4599
4600    #[cfg(feature = "deploy")]
4601    #[tokio::test]
4602    async fn monotone_map_order_preserving_threshold() {
4603        use crate::properties::manual_proof;
4604
4605        let mut deployment = Deployment::new();
4606
4607        let mut flow = FlowBuilder::new();
4608        let node = flow.process::<()>();
4609        let external = flow.external::<()>();
4610
4611        let in_unbounded: super::Stream<_, _> =
4612            node.source_iter(q!(vec![1i32, 2, 3, 4, 5, 6])).into();
4613        let sum = in_unbounded.fold(
4614            q!(|| 0),
4615            q!(
4616                |sum, v| {
4617                    *sum += v;
4618                },
4619                monotone = manual_proof!(/** test */)
4620            ),
4621        );
4622
4623        // map with order_preserving should preserve monotonicity
4624        let doubled = sum.map(q!(
4625            |v| v * 2,
4626            order_preserving = manual_proof!(/** doubling preserves order */)
4627        ));
4628
4629        let threshold_out = doubled
4630            .threshold_greater_or_equal(node.singleton(q!(14)))
4631            .send_bincode_external(&external);
4632
4633        let nodes = flow
4634            .with_process(&node, deployment.Localhost())
4635            .with_external(&external, deployment.Localhost())
4636            .deploy(&mut deployment);
4637
4638        deployment.deploy().await.unwrap();
4639
4640        let mut threshold_out = nodes.connect(threshold_out).await;
4641
4642        deployment.start().await.unwrap();
4643
4644        assert_eq!(threshold_out.next().await.unwrap(), 14);
4645    }
4646
4647    // === Compile-time type tests for join/cross_product ordering ===
4648
4649    #[cfg(any(feature = "deploy", feature = "sim"))]
4650    mod join_ordering_type_tests {
4651        use crate::live_collections::boundedness::{Bounded, Unbounded};
4652        use crate::live_collections::stream::{ExactlyOnce, NoOrder, Stream, TotalOrder};
4653        use crate::location::{Location, Process};
4654
4655        #[expect(dead_code, reason = "compile-time type test")]
4656        fn join_unbounded_with_bounded_preserves_order<'a>(
4657            left: Stream<(i32, char), Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4658            right: Stream<(i32, char), Process<'a>, Bounded, TotalOrder, ExactlyOnce>,
4659        ) -> Stream<(i32, (char, char)), Process<'a>, Unbounded, TotalOrder, ExactlyOnce> {
4660            left.join(right)
4661        }
4662
4663        #[expect(dead_code, reason = "compile-time type test")]
4664        fn join_unbounded_with_unbounded_is_no_order<'a>(
4665            left: Stream<(i32, char), Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4666            right: Stream<(i32, char), Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4667        ) -> Stream<(i32, (char, char)), Process<'a>, Unbounded, NoOrder, ExactlyOnce> {
4668            left.join(right)
4669        }
4670
4671        #[expect(dead_code, reason = "compile-time type test")]
4672        fn join_bounded_with_bounded_preserves_order<'a, L: Location<'a>>(
4673            left: Stream<(i32, char), L, Bounded, TotalOrder, ExactlyOnce>,
4674            right: Stream<(i32, char), L, Bounded, TotalOrder, ExactlyOnce>,
4675        ) -> Stream<(i32, (char, char)), L, Bounded, TotalOrder, ExactlyOnce> {
4676            left.join(right)
4677        }
4678
4679        #[expect(dead_code, reason = "compile-time type test")]
4680        fn join_unbounded_noorder_with_bounded<'a>(
4681            left: Stream<(i32, char), Process<'a>, Unbounded, NoOrder, ExactlyOnce>,
4682            right: Stream<(i32, char), Process<'a>, Bounded, NoOrder, ExactlyOnce>,
4683        ) -> Stream<(i32, (char, char)), Process<'a>, Unbounded, NoOrder, ExactlyOnce> {
4684            left.join(right)
4685        }
4686
4687        // === Compile-time type tests for cross_product ordering ===
4688
4689        #[expect(dead_code, reason = "compile-time type test")]
4690        fn cross_product_unbounded_with_bounded_preserves_order<'a>(
4691            left: Stream<i32, Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4692            right: Stream<char, Process<'a>, Bounded, TotalOrder, ExactlyOnce>,
4693        ) -> Stream<(i32, char), Process<'a>, Unbounded, TotalOrder, ExactlyOnce> {
4694            left.cross_product(right)
4695        }
4696
4697        #[expect(dead_code, reason = "compile-time type test")]
4698        fn cross_product_bounded_with_bounded_preserves_order<'a>(
4699            left: Stream<i32, Process<'a>, Bounded, TotalOrder, ExactlyOnce>,
4700            right: Stream<char, Process<'a>, Bounded, TotalOrder, ExactlyOnce>,
4701        ) -> Stream<(i32, char), Process<'a>, Bounded, TotalOrder, ExactlyOnce> {
4702            left.cross_product(right)
4703        }
4704
4705        #[expect(dead_code, reason = "compile-time type test")]
4706        fn cross_product_unbounded_with_unbounded_is_no_order<'a>(
4707            left: Stream<i32, Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4708            right: Stream<char, Process<'a>, Unbounded, TotalOrder, ExactlyOnce>,
4709        ) -> Stream<(i32, char), Process<'a>, Unbounded, NoOrder, ExactlyOnce> {
4710            left.cross_product(right)
4711        }
4712    } // mod join_ordering_type_tests
4713
4714    // === Runtime correctness tests for bounded join/cross_product ===
4715
4716    #[cfg(feature = "sim")]
4717    #[test]
4718    fn cross_product_mixed_boundedness_correctness() {
4719        use stageleft::q;
4720
4721        use crate::compile::builder::FlowBuilder;
4722        use crate::nondet::nondet;
4723
4724        let mut flow = FlowBuilder::new();
4725        let process = flow.process::<()>();
4726        let tick = process.tick();
4727
4728        let left = process.source_iter(q!(vec![1, 2]));
4729        let right = process
4730            .source_iter(q!(vec!['a', 'b']))
4731            .batch(&tick, nondet!(/** test */))
4732            .all_ticks();
4733
4734        let out = left.cross_product(right).sim_output();
4735
4736        flow.sim().exhaustive(async || {
4737            out.assert_yields_only_unordered(vec![(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')])
4738                .await;
4739        });
4740    }
4741
4742    #[cfg(feature = "sim")]
4743    #[test]
4744    fn join_mixed_boundedness_correctness() {
4745        use stageleft::q;
4746
4747        use crate::compile::builder::FlowBuilder;
4748        use crate::nondet::nondet;
4749
4750        let mut flow = FlowBuilder::new();
4751        let process = flow.process::<()>();
4752        let tick = process.tick();
4753
4754        let left = process.source_iter(q!(vec![(1, 'a'), (2, 'b')]));
4755        let right = process
4756            .source_iter(q!(vec![(1, 'x'), (2, 'y')]))
4757            .batch(&tick, nondet!(/** test */))
4758            .all_ticks();
4759
4760        let out = left.join(right).sim_output();
4761
4762        flow.sim().exhaustive(async || {
4763            out.assert_yields_only_unordered(vec![(1, ('a', 'x')), (2, ('b', 'y'))])
4764                .await;
4765        });
4766    }
4767
4768    #[cfg(feature = "sim")]
4769    #[test]
4770    fn sim_merge_unordered_independent_atomics() {
4771        let mut flow = FlowBuilder::new();
4772        let node = flow.process::<()>();
4773
4774        let (in1_send, input1) = node.sim_input::<_, TotalOrder, _>();
4775        let (in2_send, input2) = node.sim_input::<_, TotalOrder, _>();
4776
4777        let out = input1
4778            .atomic()
4779            .merge_unordered(input2.atomic())
4780            .end_atomic()
4781            .sim_output();
4782
4783        flow.sim().exhaustive(async || {
4784            in1_send.send(1);
4785            in2_send.send(2);
4786
4787            out.assert_yields_only_unordered(vec![1, 2]).await;
4788        });
4789    }
4790
4791    #[cfg(feature = "deploy")]
4792    #[tokio::test]
4793    async fn test_stream_ref() {
4794        let mut deployment = Deployment::new();
4795
4796        let mut flow = FlowBuilder::new();
4797        let external = flow.external::<()>();
4798        let p1 = flow.process::<()>();
4799
4800        // Create a bounded stream (source_iter is bounded within a tick)
4801        let my_stream = p1.source_iter(q!(1..=5i32));
4802
4803        let stream_ref = my_stream.by_ref();
4804
4805        // Use the stream ref to get the vec's length
4806        let out_port = p1
4807            .source_iter(q!([()]))
4808            .map(q!(|_| stream_ref.len() as i32))
4809            .send_bincode_external(&external);
4810
4811        // Also consume the stream via pipe
4812        my_stream.for_each(q!(|_| {}));
4813
4814        let nodes = flow
4815            .with_default_optimize()
4816            .with_process(&p1, deployment.Localhost())
4817            .with_external(&external, deployment.Localhost())
4818            .deploy(&mut deployment);
4819
4820        deployment.deploy().await.unwrap();
4821
4822        let mut out_recv = nodes.connect(out_port).await;
4823
4824        deployment.start().await.unwrap();
4825
4826        let result = out_recv.next().await.unwrap();
4827        // stream has 5 elements
4828        assert_eq!(result, 5);
4829    }
4830
4831    #[cfg(feature = "deploy")]
4832    #[tokio::test]
4833    async fn test_stream_ref_contents() {
4834        let mut deployment = Deployment::new();
4835
4836        let mut flow = FlowBuilder::new();
4837        let external = flow.external::<()>();
4838        let p1 = flow.process::<()>();
4839
4840        // Create a bounded stream
4841        let my_stream = p1.source_iter(q!(1..=3i32));
4842
4843        let stream_ref = my_stream.by_ref();
4844
4845        // Sum the referenced vec's contents
4846        let out_port = p1
4847            .source_iter(q!([()]))
4848            .map(q!(|_| stream_ref.iter().sum::<i32>()))
4849            .send_bincode_external(&external);
4850
4851        my_stream.for_each(q!(|_| {}));
4852
4853        let nodes = flow
4854            .with_default_optimize()
4855            .with_process(&p1, deployment.Localhost())
4856            .with_external(&external, deployment.Localhost())
4857            .deploy(&mut deployment);
4858
4859        deployment.deploy().await.unwrap();
4860
4861        let mut out_recv = nodes.connect(out_port).await;
4862
4863        deployment.start().await.unwrap();
4864
4865        let result = out_recv.next().await.unwrap();
4866        // sum of 1+2+3 = 6
4867        assert_eq!(result, 6);
4868    }
4869
4870    #[cfg(feature = "deploy")]
4871    #[tokio::test]
4872    async fn test_stream_ref_no_consumer() {
4873        let mut deployment = Deployment::new();
4874
4875        let mut flow = FlowBuilder::new();
4876        let external = flow.external::<()>();
4877        let p1 = flow.process::<()>();
4878
4879        // Create a bounded stream — no pipe consumer, only ref
4880        let my_stream = p1.source_iter(q!(1..=4i32));
4881
4882        let stream_ref = my_stream.by_ref();
4883
4884        let out_port = p1
4885            .source_iter(q!([()]))
4886            .map(q!(|_| stream_ref.len() as i32))
4887            .send_bincode_external(&external);
4888
4889        let nodes = flow
4890            .with_default_optimize()
4891            .with_process(&p1, deployment.Localhost())
4892            .with_external(&external, deployment.Localhost())
4893            .deploy(&mut deployment);
4894
4895        deployment.deploy().await.unwrap();
4896
4897        let mut out_recv = nodes.connect(out_port).await;
4898
4899        deployment.start().await.unwrap();
4900
4901        let result = out_recv.next().await.unwrap();
4902        assert_eq!(result, 4);
4903    }
4904
4905    #[cfg(feature = "deploy")]
4906    #[tokio::test]
4907    async fn test_stream_mut() {
4908        let mut deployment = Deployment::new();
4909
4910        let mut flow = FlowBuilder::new();
4911        let external = flow.external::<()>();
4912        let p1 = flow.process::<()>();
4913
4914        // Create a bounded stream
4915        let my_stream = p1.source_iter(q!(1..=5i32));
4916
4917        let stream_mut = my_stream.by_mut();
4918
4919        // Mutably reference the buffer to retain only items > 3
4920        let out_port = p1
4921            .source_iter(q!([()]))
4922            .map(q!(|_| {
4923                stream_mut.retain(|x| *x > 3);
4924                stream_mut.len() as i32
4925            }))
4926            .send_bincode_external(&external);
4927
4928        my_stream.for_each(q!(|_| {}));
4929
4930        let nodes = flow
4931            .with_default_optimize()
4932            .with_process(&p1, deployment.Localhost())
4933            .with_external(&external, deployment.Localhost())
4934            .deploy(&mut deployment);
4935
4936        deployment.deploy().await.unwrap();
4937
4938        let mut out_recv = nodes.connect(out_port).await;
4939
4940        deployment.start().await.unwrap();
4941
4942        let result = out_recv.next().await.unwrap();
4943        // After retain(> 3): [4, 5] => len = 2
4944        assert_eq!(result, 2);
4945    }
4946
4947    /// A map with a mut singleton ref on an unordered input should produce > 1
4948    /// simulation instance because the ordering of elements through the mut closure
4949    /// is non-deterministic.
4950    #[cfg(feature = "sim")]
4951    #[test]
4952    fn sim_map_with_mut_on_unordered_explores_multiple_states() {
4953        use crate::live_collections::sliced::sliced;
4954        use crate::live_collections::stream::ExactlyOnce;
4955        use crate::properties::manual_proof;
4956
4957        let mut flow = FlowBuilder::new();
4958        let node = flow.process::<()>();
4959
4960        let (trigger_send, trigger) = node.sim_input::<i32, TotalOrder, ExactlyOnce>();
4961
4962        let out_recv = sliced! {
4963            let batch = use::batch(trigger, nondet!(/** test */));
4964            let counter = batch.location().source_iter(q!(vec![0i32]))
4965                .fold(q!(|| 0i32), q!(|acc, v| *acc += v));
4966            let counter_mut = counter.by_mut();
4967            let items = batch.location().source_iter(q!(vec![1i32, 2])).weaken_ordering::<NoOrder>();
4968            items.map(q!(
4969                |x| {
4970                    *counter_mut += x;
4971                    *counter_mut
4972                },
4973                commutative = manual_proof!(/** test */)
4974            ))
4975        }
4976        .sim_output();
4977
4978        let count = flow.sim().exhaustive(async || {
4979            trigger_send.send(1);
4980            let _all: Vec<i32> = out_recv.collect_sorted().await;
4981        });
4982
4983        assert_eq!(
4984            count, 2,
4985            "Expected 2 simulation instances due to mut on unordered input, got {}",
4986            count
4987        );
4988    }
4989
4990    /// A `scan` closure that captures a bounded singleton by reference should compile,
4991    /// run correctly, and (because the input is totally ordered) explore a single
4992    /// simulation instance.
4993    #[cfg(feature = "sim")]
4994    #[test]
4995    fn sim_scan_with_ref_capture() {
4996        use crate::live_collections::sliced::sliced;
4997        use crate::live_collections::stream::ExactlyOnce;
4998
4999        let mut flow = FlowBuilder::new();
5000        let node = flow.process::<()>();
5001
5002        let (trigger_send, trigger) = node.sim_input::<i32, TotalOrder, ExactlyOnce>();
5003
5004        let out_recv = sliced! {
5005            let batch = use::batch(trigger, nondet!(/** test */));
5006            let offset = batch
5007                .location()
5008                .source_iter(q!(vec![10i32]))
5009                .fold(q!(|| 0i32), q!(|acc, v| *acc += v));
5010            let offset_ref = offset.by_ref();
5011            batch
5012                .location()
5013                .source_iter(q!(vec![1i32, 2, 3]))
5014                .scan(
5015                    q!(|| 0i32),
5016                    q!(move |acc: &mut i32, x| {
5017                        *acc += x + *offset_ref;
5018                        Some(*acc)
5019                    }),
5020                )
5021        }
5022        .sim_output();
5023
5024        let count = flow.sim().exhaustive(async || {
5025            trigger_send.send(1);
5026            let all: Vec<i32> = out_recv.collect().await;
5027            // offset = 10, running accumulator starts at 0:
5028            //   x=1: acc += 1 + 10 = 11 -> 11
5029            //   x=2: acc += 2 + 10 = 12 -> 23
5030            //   x=3: acc += 3 + 10 = 13 -> 36
5031            assert_eq!(all, vec![11, 23, 36]);
5032        });
5033
5034        assert_eq!(
5035            count, 1,
5036            "Expected a single simulation instance for a totally-ordered scan, got {}",
5037            count
5038        );
5039    }
5040
5041    /// A map with a mut singleton ref on a top-level unordered input should produce > 1
5042    /// simulation instance. Currently panics because observe_nondet doesn't support
5043    /// top-level bounded inputs yet.
5044    #[cfg(feature = "sim")]
5045    #[test]
5046    #[ignore = "observe_nondet not yet supported for top-level bounded inputs (https://github.com/hydro-project/hydro/issues/2950)"]
5047    fn sim_map_with_mut_on_unordered_top_level() {
5048        use crate::properties::manual_proof;
5049
5050        let mut flow = FlowBuilder::new();
5051        let node = flow.process::<()>();
5052
5053        let counter = node
5054            .source_iter(q!(vec![0i32]))
5055            .fold(q!(|| 0i32), q!(|acc, v| *acc += v));
5056        let counter_mut = counter.by_mut();
5057
5058        let out_recv = node
5059            .source_iter(q!(vec![1i32, 2]))
5060            .weaken_ordering::<NoOrder>()
5061            .map(q!(
5062                |x| {
5063                    *counter_mut += x;
5064                    *counter_mut
5065                },
5066                commutative = manual_proof!(/** test */)
5067            ))
5068            .assume_ordering::<TotalOrder>(nondet!(/** test */))
5069            .sim_output();
5070
5071        counter.into_stream().for_each(q!(|_| {}));
5072
5073        let count = flow.sim().exhaustive(async || {
5074            let _all: Vec<i32> = out_recv.collect().await;
5075        });
5076
5077        assert_eq!(
5078            count, 2,
5079            "Expected 2 simulation instances due to mut on unordered input, got {}",
5080            count
5081        );
5082    }
5083}