Skip to main content

hydro_lang/
handoff_ref.rs

1//! Reference handles for capturing singletons, optionals, and streams in `q!()` closures.
2//!
3//! Each handle type wraps a `&RefCell<HydroNode>` and, when captured inside a `q!()` closure,
4//! registers itself with the current capture scope. At codegen time, the IR node is lowered
5//! to the corresponding DFIR pseudo-operator (`singleton()`, `optional()`, or `handoff()`),
6//! and the reference resolves to the appropriate borrow type.
7//!
8//! Each handle tracks the [`Location`] **and** the boundedness of the collection it refers to
9//! (see [`OperatorContext`]). A handle can only be captured inside closures passed to operators
10//! on collections with a matching location and boundedness. This is required for soundness:
11//! a bounded collection is only materialized on the first tick, while closures on unbounded
12//! collections continue to run on later ticks, where the referenced value no longer exists and
13//! accessing it would crash at runtime.
14
15use std::cell::RefCell;
16use std::marker::PhantomData;
17use std::rc::Rc;
18
19use proc_macro2::Span;
20use quote::quote;
21use stageleft::runtime_support::{FreeVariableWithContextWithProps, QuoteTokens};
22
23use crate::compile::ir::{AccessCounter, HydroNode, SharedNode};
24use crate::live_collections::OperatorContext;
25use crate::location::Location;
26
27/// Determines which DFIR pseudo-operator a reference node lowers to.
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
29pub enum HandoffRefKind {
30    /// `-> singleton()` — exactly one item, `#var` gives `&T`.
31    Singleton,
32    /// `-> optional()` — zero or one item, `#var` gives `&Option<T>`.
33    Optional,
34    /// `-> handoff()` — zero or more items, `#var` gives `&Vec<T>`.
35    Vec,
36}
37
38// Thread-local storage for handoff references captured during `q!()` expansion.
39// Stores the `HydroNode::Reference` and `is_mut: bool` for each reference captured in the current closure.
40// The index determines the ident name via `handoff_ref_ident`.
41thread_local! {
42    static CAPTURED_REFS: RefCell<Option<Vec<(HydroNode, bool)>>> = const { RefCell::new(None) };
43}
44
45/// Returns the canonical ident for a captured ref at the given index within a closure.
46pub(crate) fn handoff_ref_ident(index: usize) -> syn::Ident {
47    syn::Ident::new(
48        &format!("__hydro_singleton_ref_{}", index),
49        Span::call_site(),
50    )
51}
52
53/// Activate the reference capture context. Must be called before `q!()` expansion
54/// that may capture handoff references. Returns a `ClosureExpr` bundling the expression with any
55/// captured references.
56pub fn with_ref_capture(
57    f: impl FnOnce() -> crate::compile::ir::DebugExpr,
58) -> crate::compile::ir::ClosureExpr {
59    CAPTURED_REFS.with(|cell| {
60        let prev = cell.borrow_mut().replace(Vec::new());
61        assert!(
62            prev.is_none(),
63            "nested handoff reference capture scopes are not supported"
64        );
65    });
66    let expr = (f)();
67    let captured_refs = CAPTURED_REFS.with(|cell| cell.borrow_mut().take().unwrap());
68    crate::compile::ir::ClosureExpr::new(expr, captured_refs)
69}
70
71/// Shared registration logic: wraps the IR node in `HydroNode::Reference` if needed,
72/// pushes it to the capture list, and returns the ident to use in the closure body.
73fn register_handoff_ref(
74    ir_node: &RefCell<HydroNode>,
75    is_mut: bool,
76    kind: HandoffRefKind,
77) -> syn::Ident {
78    CAPTURED_REFS.with(|cell| {
79        let mut guard = cell.borrow_mut();
80        let refs = guard.as_mut().expect(
81            "HandoffRef used inside q!() but no reference capture scope is active. \
82             This is a bug — reference capture should be set up by the operator that uses q!().",
83        );
84
85        let index = refs.len();
86        let ident = handoff_ref_ident(index);
87
88        let metadata = ir_node.borrow().metadata().clone();
89
90        // Wrap in HydroNode::Reference for materialization + identity tracking.
91        // If already a Reference node, reuse it.
92        if !matches!(&*ir_node.borrow(), HydroNode::Reference { .. }) {
93            let orig = ir_node.replace(HydroNode::Placeholder);
94            *ir_node.borrow_mut() = HydroNode::Reference {
95                inner: SharedNode(Rc::new(RefCell::new(orig))),
96                kind,
97                access_counter: AccessCounter::new(),
98                metadata: metadata.clone(),
99            };
100        }
101
102        let borrow: std::cell::Ref<'_, HydroNode> = ir_node.borrow();
103        let HydroNode::Reference {
104            inner,
105            access_counter,
106            ..
107        } = &*borrow
108        else {
109            unreachable!()
110        };
111
112        // Compute access group at staging time (code order).
113        let group = access_counter.next_group(is_mut);
114
115        refs.push((
116            HydroNode::Reference {
117                inner: SharedNode(Rc::clone(&inner.0)),
118                kind,
119                access_counter: group,
120                metadata,
121            },
122            is_mut,
123        ));
124
125        ident
126    })
127}
128
129/// Macro to define a handoff reference struct with all necessary trait impls.
130macro_rules! define_handoff_ref {
131    (
132        $(
133            $(#[$meta:meta])*
134            $name:ident, $is_mut:expr, $kind:expr, $output:ty
135        )+
136    ) => {
137        $(
138            $(#[$meta])*
139            pub struct $name<'a, 'slf, T, L, B> {
140                pub(crate) ir_node: &'slf RefCell<HydroNode>,
141                _phantom: PhantomData<(&'a T, L, B)>,
142            }
143
144            impl<'slf, T, L, B> $name<'_, 'slf, T, L, B> {
145                /// Creates a new reference handle from an IR node cell.
146                pub(crate) fn new(ir_node: &'slf RefCell<HydroNode>) -> Self {
147                    Self {
148                        ir_node,
149                        _phantom: PhantomData,
150                    }
151                }
152            }
153
154            impl<T, L, B> Copy for $name<'_, '_, T, L, B> {}
155            impl<T, L, B> Clone for $name<'_, '_, T, L, B> {
156                fn clone(&self) -> Self {
157                    *self
158                }
159            }
160
161            impl<'a, 'slf, T: 'a, L, B> FreeVariableWithContextWithProps<OperatorContext<L, B>, ()>
162                for $name<'a, 'slf, T, L, B>
163            where
164                L: Location<'a>,
165            {
166                type O = $output;
167
168                fn to_tokens(self, _ctx: &OperatorContext<L, B>) -> (QuoteTokens, ()) {
169                    let ident = register_handoff_ref(
170                        self.ir_node,
171                        $is_mut,
172                        $kind,
173                    );
174                    (
175                        QuoteTokens {
176                            prelude: None,
177                            expr: Some(quote!(#ident)),
178                        },
179                        (),
180                    )
181                }
182            }
183        )+
184    };
185}
186
187#[stageleft::export(
188    SingletonRef,
189    SingletonMut,
190    OptionalRef,
191    OptionalMut,
192    StreamRef,
193    StreamMut
194)]
195define_handoff_ref!(
196    /// A shared reference handle to a singleton, resolves to `&T` at runtime.
197    ///
198    /// Created via [`Singleton::by_ref()`](crate::live_collections::Singleton::by_ref).
199    SingletonRef, false, HandoffRefKind::Singleton, &'a T
200
201    /// A mutable reference handle to a singleton, resolves to `&mut T` at runtime.
202    ///
203    /// Created via [`Singleton::by_mut()`](crate::live_collections::Singleton::by_mut).
204    SingletonMut, true, HandoffRefKind::Singleton, &'a mut T
205
206    /// A shared reference handle to an optional, resolves to `&Option<T>` at runtime.
207    ///
208    /// Created via [`Optional::by_ref()`](crate::live_collections::Optional::by_ref).
209    OptionalRef, false, HandoffRefKind::Optional, &'a Option<T>
210
211    /// A mutable reference handle to an optional, resolves to `&mut Option<T>` at runtime.
212    ///
213    /// Created via [`Optional::by_mut()`](crate::live_collections::Optional::by_mut).
214    OptionalMut, true, HandoffRefKind::Optional, &'a mut Option<T>
215
216    /// A shared reference handle to a stream's handoff buffer, resolves to `&Vec<T>` at runtime.
217    ///
218    /// Created via [`Stream::by_ref()`](crate::live_collections::Stream::by_ref).
219    StreamRef, false, HandoffRefKind::Vec, &'a Vec<T>
220
221    /// A mutable reference handle to a stream's handoff buffer, resolves to `&mut Vec<T>` at runtime.
222    ///
223    /// Created via [`Stream::by_mut()`](crate::live_collections::Stream::by_mut).
224    StreamMut, true, HandoffRefKind::Vec, &'a mut Vec<T>
225);
226
227#[cfg(test)]
228#[cfg(feature = "build")]
229mod tests {
230    use stageleft::q;
231
232    use crate::compile::builder::FlowBuilder;
233    use crate::location::Location;
234
235    struct P1 {}
236
237    /// Compile-only test: verifies that `by_ref()` + `q!()` produces valid IR.
238    #[test]
239    fn singleton_by_ref_compiles() {
240        let mut flow = FlowBuilder::new();
241        let node = flow.process::<P1>();
242
243        let my_count = node
244            .source_iter(q!(0..5i32))
245            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
246        let count_ref = my_count.by_ref();
247
248        node.source_iter(q!(1..=3i32))
249            .map(q!(|x| x + *count_ref))
250            .for_each(q!(|_| {}));
251
252        my_count.into_stream().for_each(q!(|_| {}));
253        let _built = flow.finalize();
254    }
255
256    /// Test with a non-Copy type (Vec) to ensure we're borrowing, not copying.
257    #[test]
258    fn singleton_by_ref_non_copy() {
259        let mut flow = FlowBuilder::new();
260        let node = flow.process::<P1>();
261
262        let my_vec = node.source_iter(q!(0..5i32)).fold(
263            q!(|| Vec::<i32>::new()),
264            q!(|acc: &mut Vec<i32>, x| acc.push(x)),
265        );
266        let vec_ref = my_vec.by_ref();
267
268        node.source_iter(q!(1..=3i32))
269            .map(q!(|x| x + vec_ref.len() as i32))
270            .for_each(q!(|_| {}));
271
272        my_vec.into_stream().for_each(q!(|_| {}));
273        let _built = flow.finalize();
274    }
275
276    /// Compile-only: singleton ref inside filter closure.
277    #[test]
278    fn singleton_by_ref_filter() {
279        let mut flow = FlowBuilder::new();
280        let node = flow.process::<P1>();
281
282        let threshold = node
283            .source_iter(q!(0..5i32))
284            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
285        let threshold_ref = threshold.by_ref();
286
287        node.source_iter(q!(1..=10i32))
288            .filter(q!(|x| *x > *threshold_ref))
289            .for_each(q!(|_| {}));
290
291        threshold.into_stream().for_each(q!(|_| {}));
292        let _built = flow.finalize();
293    }
294
295    /// Compile-only: singleton ref inside flat_map closure.
296    #[test]
297    fn singleton_by_ref_flat_map() {
298        let mut flow = FlowBuilder::new();
299        let node = flow.process::<P1>();
300
301        let count = node
302            .source_iter(q!(0..3i32))
303            .fold(q!(|| 0i32), q!(|acc: &mut i32, _| *acc += 1));
304        let count_ref = count.by_ref();
305
306        node.source_iter(q!(1..=2i32))
307            .flat_map_ordered(q!(|x| (0..*count_ref).map(move |i| x + i)))
308            .for_each(q!(|_| {}));
309
310        count.into_stream().for_each(q!(|_| {}));
311        let _built = flow.finalize();
312    }
313
314    /// Compile-only: singleton ref inside inspect closure.
315    #[test]
316    fn singleton_by_ref_inspect() {
317        let mut flow = FlowBuilder::new();
318        let node = flow.process::<P1>();
319
320        let count = node
321            .source_iter(q!(0..5i32))
322            .fold(q!(|| 0i32), q!(|acc: &mut i32, _| *acc += 1));
323        let count_ref = count.by_ref();
324
325        node.source_iter(q!(1..=3i32))
326            .inspect(q!(|x| println!("count={}, x={}", *count_ref, x)))
327            .for_each(q!(|_| {}));
328
329        count.into_stream().for_each(q!(|_| {}));
330        let _built = flow.finalize();
331    }
332
333    /// Compile-only: singleton ref inside partition predicate.
334    #[test]
335    fn singleton_by_ref_partition() {
336        let mut flow = FlowBuilder::new();
337        let node = flow.process::<P1>();
338
339        let threshold = node
340            .source_iter(q!(0..5i32))
341            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
342        let threshold_ref = threshold.by_ref();
343
344        let (above, below) = node
345            .source_iter(q!(1..=10i32))
346            .partition(q!(|x| *x > *threshold_ref));
347
348        above.for_each(q!(|_| {}));
349        below.for_each(q!(|_| {}));
350        threshold.into_stream().for_each(q!(|_| {}));
351        let _built = flow.finalize();
352    }
353
354    /// Compile-only: singleton ref inside partition with downstream operators on both branches.
355    #[test]
356    fn singleton_by_ref_partition_with_downstream_ops() {
357        let mut flow = FlowBuilder::new();
358        let node = flow.process::<P1>();
359
360        let threshold = node
361            .source_iter(q!(0..5i32))
362            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
363        let threshold_ref = threshold.by_ref();
364
365        let (above, below) = node
366            .source_iter(q!(1..=10i32))
367            .partition(q!(|x| *x > *threshold_ref));
368
369        above.map(q!(|x| x * 2)).for_each(q!(|_| {}));
370        below.map(q!(|x| x + 100)).for_each(q!(|_| {}));
371        threshold.into_stream().for_each(q!(|_| {}));
372        let _built = flow.finalize();
373    }
374
375    /// Compile-only test: singleton by_mut.
376    #[test]
377    fn singleton_by_mut_compiles() {
378        let mut flow = FlowBuilder::new();
379        let node = flow.process::<P1>();
380
381        let my_count = node
382            .source_iter(q!(0..5i32))
383            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
384        let count_mut = my_count.by_mut();
385
386        node.source_iter(q!(1..=3i32))
387            .map(q!(|x| {
388                *count_mut += x;
389                x
390            }))
391            .for_each(q!(|_| {}));
392
393        my_count.into_stream().for_each(q!(|_| {}));
394        let _built = flow.finalize();
395    }
396
397    /// Compile-only test: optional by_ref.
398    #[test]
399    fn optional_by_ref_compiles() {
400        let mut flow = FlowBuilder::new();
401        let node = flow.process::<P1>();
402
403        let my_opt = node.source_iter(q!(0..5i32)).reduce(q!(|a, b| *a += b));
404        let opt_ref = my_opt.by_ref();
405
406        node.source_iter(q!(1..=3i32))
407            .map(q!(|x| x + opt_ref.unwrap_or(0)))
408            .for_each(q!(|_| {}));
409
410        my_opt.into_stream().for_each(q!(|_| {}));
411        let _built = flow.finalize();
412    }
413
414    /// Compile-only test: stream by_ref.
415    #[test]
416    fn stream_by_ref_compiles() {
417        let mut flow = FlowBuilder::new();
418        let node = flow.process::<P1>();
419
420        let my_stream = node.source_iter(q!(0..5i32));
421        let stream_ref = my_stream.by_ref();
422
423        node.source_iter(q!(1..=3i32))
424            .map(q!(|x| x + stream_ref.len() as i32))
425            .for_each(q!(|_| {}));
426
427        my_stream.for_each(q!(|_| {}));
428        let _built = flow.finalize();
429    }
430
431    /// Compile-only test: singleton by_mut in filter (TotalOrder).
432    #[test]
433    fn singleton_by_mut_filter() {
434        let mut flow = FlowBuilder::new();
435        let node = flow.process::<P1>();
436
437        let my_count = node
438            .source_iter(q!(0..5i32))
439            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
440        let count_mut = my_count.by_mut();
441
442        node.source_iter(q!(1..=3i32))
443            .filter(q!(|x| {
444                *count_mut += *x;
445                *count_mut > 0
446            }))
447            .for_each(q!(|_| {}));
448
449        my_count.into_stream().for_each(q!(|_| {}));
450        let _built = flow.finalize();
451    }
452
453    /// Compile-only test: singleton by_mut in flat_map_ordered (TotalOrder).
454    #[test]
455    fn singleton_by_mut_flat_map() {
456        let mut flow = FlowBuilder::new();
457        let node = flow.process::<P1>();
458
459        let my_count = node
460            .source_iter(q!(0..5i32))
461            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
462        let count_mut = my_count.by_mut();
463
464        node.source_iter(q!(1..=3i32))
465            .flat_map_ordered(q!(|x| {
466                *count_mut += x;
467                vec![*count_mut]
468            }))
469            .for_each(q!(|_| {}));
470
471        my_count.into_stream().for_each(q!(|_| {}));
472        let _built = flow.finalize();
473    }
474
475    /// Compile-only test: singleton by_mut in filter_map (TotalOrder).
476    #[test]
477    fn singleton_by_mut_filter_map() {
478        let mut flow = FlowBuilder::new();
479        let node = flow.process::<P1>();
480
481        let my_count = node
482            .source_iter(q!(0..5i32))
483            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
484        let count_mut = my_count.by_mut();
485
486        node.source_iter(q!(1..=3i32))
487            .filter_map(q!(|x| {
488                *count_mut += x;
489                Some(*count_mut)
490            }))
491            .for_each(q!(|_| {}));
492
493        my_count.into_stream().for_each(q!(|_| {}));
494        let _built = flow.finalize();
495    }
496
497    /// Compile-only test: singleton by_mut in inspect (TotalOrder).
498    #[test]
499    fn singleton_by_mut_inspect() {
500        let mut flow = FlowBuilder::new();
501        let node = flow.process::<P1>();
502
503        let my_count = node
504            .source_iter(q!(0..5i32))
505            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
506        let count_mut = my_count.by_mut();
507
508        node.source_iter(q!(1..=3i32))
509            .inspect(q!(|x| {
510                *count_mut += *x;
511            }))
512            .for_each(q!(|_| {}));
513
514        my_count.into_stream().for_each(q!(|_| {}));
515        let _built = flow.finalize();
516    }
517
518    /// Compile-only test: singleton by_ref in for_each.
519    #[test]
520    fn singleton_by_ref_for_each() {
521        let mut flow = FlowBuilder::new();
522        let node = flow.process::<P1>();
523
524        let my_count = node
525            .source_iter(q!(0..5i32))
526            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
527        let count_ref = my_count.by_ref();
528
529        node.source_iter(q!(1..=3i32))
530            .for_each(q!(|x| println!("{}", x + *count_ref)));
531
532        my_count.into_stream().for_each(q!(|_| {}));
533        let _built = flow.finalize();
534    }
535
536    /// Compile-only test: singleton by_mut in for_each.
537    #[test]
538    fn singleton_by_mut_for_each() {
539        let mut flow = FlowBuilder::new();
540        let node = flow.process::<P1>();
541
542        let my_count = node
543            .source_iter(q!(0..5i32))
544            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
545        let count_mut = my_count.by_mut();
546
547        node.source_iter(q!(1..=3i32)).for_each(q!(|x| {
548            *count_mut += x;
549        }));
550
551        my_count.into_stream().for_each(q!(|_| {}));
552        let _built = flow.finalize();
553    }
554
555    /// Regression test: a handoff reference whose *only* consumer is a `for_each` closure
556    /// must still be materialized during DFIR emission.
557    ///
558    /// `HydroRoot::ForEach` used to only *look up* captured refs in `built_tees`, assuming
559    /// some node-level operator had already emitted them, and panicked with "ForEach
560    /// singleton ref not found in built_tees" when the `for_each` closure was the sole
561    /// capturer. This test drives the flow through full DFIR emission (which
562    /// `flow.finalize()` alone does not) to cover that path.
563    #[cfg(feature = "deploy")]
564    #[test]
565    fn singleton_by_ref_for_each_sole_consumer_emits() {
566        use crate::live_collections::sliced::sliced;
567        use crate::nondet::nondet;
568
569        let mut flow = FlowBuilder::new();
570        let node = flow.process::<P1>();
571
572        let items = node.source_iter(q!(1..=3i32));
573
574        sliced! {
575            let items = use::batch(items, nondet!(/** test */));
576            let my_count = items
577                .location()
578                .source_iter(q!(0..5i32))
579                .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
580            let count_ref = my_count.by_ref();
581
582            // The for_each closure is the ONLY consumer of `my_count` — no other operator
583            // emits the reference node before this root is processed.
584            items.for_each(q!(|x| println!("{}", x + *count_ref)));
585        };
586
587        let _ = flow
588            .finalize()
589            .with_default_optimize::<crate::deploy::HydroDeploy>()
590            .preview_compile();
591    }
592
593    /// Regression test: same as [`singleton_by_ref_for_each_sole_consumer_emits`], but for
594    /// a mutable reference (`by_mut`) — the common accumulator pattern
595    /// `stream.for_each(q!(|x| *acc_mut += x))`.
596    #[cfg(feature = "deploy")]
597    #[test]
598    fn singleton_by_mut_for_each_sole_consumer_emits() {
599        use crate::live_collections::sliced::sliced;
600        use crate::nondet::nondet;
601
602        let mut flow = FlowBuilder::new();
603        let node = flow.process::<P1>();
604
605        let items = node.source_iter(q!(1..=3i32));
606
607        sliced! {
608            let items = use::batch(items, nondet!(/** test */));
609            let my_count = items
610                .location()
611                .source_iter(q!(0..5i32))
612                .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
613            let count_mut = my_count.by_mut();
614
615            // The for_each closure is the ONLY consumer of `my_count`.
616            items.for_each(q!(|x| {
617                *count_mut += x;
618            }));
619        };
620
621        let _ = flow
622            .finalize()
623            .with_default_optimize::<crate::deploy::HydroDeploy>()
624            .preview_compile();
625    }
626
627    /// Compile-only test: singleton by_ref inside scan closures.
628    #[test]
629    fn singleton_by_ref_scan() {
630        let mut flow = FlowBuilder::new();
631        let node = flow.process::<P1>();
632
633        let offset = node
634            .source_iter(q!(0..5i32))
635            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
636        let offset_ref = offset.by_ref();
637
638        node.source_iter(q!(1..=3i32))
639            .scan(
640                q!(move || *offset_ref),
641                q!(move |acc: &mut i32, x| {
642                    *acc += x + *offset_ref;
643                    Some(*acc)
644                }),
645            )
646            .for_each(q!(|_| {}));
647
648        offset.into_stream().for_each(q!(|_| {}));
649        let _built = flow.finalize();
650    }
651
652    /// Compile-only test: singleton by_ref inside scan_async_blocking closure.
653    #[test]
654    fn singleton_by_ref_scan_async_blocking() {
655        let mut flow = FlowBuilder::new();
656        let node = flow.process::<P1>();
657
658        let offset = node
659            .source_iter(q!(0..5i32))
660            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
661        let offset_ref = offset.by_ref();
662
663        node.source_iter(q!(1..=3i32))
664            .scan_async_blocking(
665                q!(|| 0i32),
666                q!(move |acc: &mut i32, x| {
667                    *acc += x + *offset_ref;
668                    let val = *acc;
669                    async move { Some(val) }
670                }),
671            )
672            .for_each(q!(|_| {}));
673
674        offset.into_stream().for_each(q!(|_| {}));
675        let _built = flow.finalize();
676    }
677
678    /// Compile-only test: singleton by_ref inside generator closure.
679    #[test]
680    fn singleton_by_ref_generator() {
681        use crate::live_collections::keyed_stream::Generate;
682
683        let mut flow = FlowBuilder::new();
684        let node = flow.process::<P1>();
685
686        let threshold = node
687            .source_iter(q!(0..5i32))
688            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
689        let threshold_ref = threshold.by_ref();
690
691        node.source_iter(q!(1..=3i32))
692            .generator(
693                q!(|| 0i32),
694                q!(move |acc: &mut i32, x| {
695                    *acc += x;
696                    if *acc > *threshold_ref {
697                        Generate::Return(*acc)
698                    } else {
699                        Generate::Yield(*acc)
700                    }
701                }),
702            )
703            .for_each(q!(|_| {}));
704
705        threshold.into_stream().for_each(q!(|_| {}));
706        let _built = flow.finalize();
707    }
708
709    /// Compile-only test: singleton by_ref inside keyed scan closure.
710    #[test]
711    fn singleton_by_ref_keyed_scan() {
712        let mut flow = FlowBuilder::new();
713        let node = flow.process::<P1>();
714
715        let offset = node
716            .source_iter(q!(0..5i32))
717            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
718        let offset_ref = offset.by_ref();
719
720        node.source_iter(q!(vec![(0, 1i32), (1, 2i32)]))
721            .into_keyed()
722            .scan(
723                q!(|| 0i32),
724                q!(move |acc: &mut i32, x| {
725                    *acc += x + *offset_ref;
726                    Some(*acc)
727                }),
728            )
729            .entries()
730            .assume_ordering::<crate::live_collections::stream::TotalOrder>(
731                crate::nondet::nondet!(/** test */),
732            )
733            .for_each(q!(|_| {}));
734
735        offset.into_stream().for_each(q!(|_| {}));
736        let _built = flow.finalize();
737    }
738
739    /// Compile-only test: singleton by_ref inside keyed generator closure.
740    #[test]
741    fn singleton_by_ref_keyed_generator() {
742        use crate::live_collections::keyed_stream::Generate;
743
744        let mut flow = FlowBuilder::new();
745        let node = flow.process::<P1>();
746
747        let threshold = node
748            .source_iter(q!(0..5i32))
749            .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
750        let threshold_ref = threshold.by_ref();
751
752        node.source_iter(q!(vec![(0, 1i32), (1, 2i32)]))
753            .into_keyed()
754            .generator(
755                q!(|| 0i32),
756                q!(move |acc: &mut i32, x| {
757                    *acc += x;
758                    if *acc > *threshold_ref {
759                        Generate::Return(*acc)
760                    } else {
761                        Generate::Yield(*acc)
762                    }
763                }),
764            )
765            .entries()
766            .assume_ordering::<crate::live_collections::stream::TotalOrder>(
767                crate::nondet::nondet!(/** test */),
768            )
769            .for_each(q!(|_| {}));
770
771        threshold.into_stream().for_each(q!(|_| {}));
772        let _built = flow.finalize();
773    }
774}