Skip to main content

hydro_lang/compile/ir/
mod.rs

1use core::panic;
2use std::cell::{Cell, RefCell};
3use std::collections::HashMap;
4#[cfg(feature = "build")]
5use std::collections::HashSet;
6use std::fmt::{Debug, Display};
7use std::hash::{Hash, Hasher};
8use std::ops::Deref;
9use std::rc::Rc;
10
11#[cfg(feature = "build")]
12use dfir_lang::graph::FlatGraphBuilder;
13#[cfg(feature = "build")]
14use proc_macro2::Span;
15use proc_macro2::TokenStream;
16use quote::ToTokens;
17#[cfg(feature = "build")]
18use quote::quote;
19#[cfg(feature = "build")]
20use slotmap::{SecondaryMap, SparseSecondaryMap};
21#[cfg(feature = "build")]
22use syn::parse_quote;
23
24#[cfg(feature = "build")]
25use crate::compile::builder::ClockId;
26#[cfg(feature = "build")]
27use crate::compile::builder::StmtId;
28use crate::compile::builder::{CycleId, ExternalPortId};
29#[cfg(feature = "build")]
30use crate::compile::deploy_provider::{Deploy, Node, RegisterPort};
31#[cfg(feature = "build")]
32use crate::handoff_ref::handoff_ref_ident;
33use crate::location::dynamic::{ClusterConsistency, LocationId};
34use crate::location::{LocationKey, NetworkHint};
35
36pub mod backtrace;
37use backtrace::Backtrace;
38
39/// A closure expression bundled with any singleton references it captures.
40///
41/// When a `q!()` closure captures a `SingletonRef`, the reference is recorded here
42/// alongside the closure's expression. This allows per-closure tracking of singleton
43/// captures, which is important for nodes with multiple closures (e.g. Fold has `init` and `acc`).
44pub struct ClosureExpr {
45    pub(crate) expr: DebugExpr,
46    /// Each entry is `(HydroNode::Reference, is_mut: bool)`.
47    /// The index in the Vec determines the ident name via [`handoff_ref_ident`].
48    /// The `access_counter` was assigned at staging time in code order.
49    pub(crate) singleton_refs: Vec<(HydroNode, bool)>,
50}
51
52impl Clone for ClosureExpr {
53    fn clone(&self) -> Self {
54        Self {
55            expr: self.expr.clone(),
56            singleton_refs: self
57                .singleton_refs
58                .iter()
59                .map(|(node, is_mut)| {
60                    let HydroNode::Reference {
61                        inner,
62                        kind,
63                        access_counter,
64                        metadata,
65                    } = node
66                    else {
67                        panic!("singleton_refs should only contain HydroNode::Reference");
68                    };
69                    (
70                        HydroNode::Reference {
71                            inner: SharedNode(Rc::clone(&inner.0)),
72                            kind: *kind,
73                            access_counter: access_counter.freeze(),
74                            metadata: metadata.clone(),
75                        },
76                        *is_mut,
77                    )
78                })
79                .collect(),
80        }
81    }
82}
83
84impl Hash for ClosureExpr {
85    fn hash<H: Hasher>(&self, state: &mut H) {
86        self.expr.hash(state);
87        // singleton_refs are structural children (like HydroIrMetadata), not
88        // identity-defining. Two closures with the same expr but different
89        // captured refs are the same closure text — the refs only affect codegen.
90    }
91}
92
93impl serde::Serialize for ClosureExpr {
94    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
95        use serde::ser::SerializeStruct;
96        let mut s = serializer.serialize_struct("ClosureExpr", 2)?;
97        s.serialize_field("expr", &self.expr)?;
98        s.serialize_field(
99            "singleton_refs",
100            &SerializableSingletonRefs(&self.singleton_refs),
101        )?;
102        s.end()
103    }
104}
105
106struct SerializableSingletonRefs<'a>(&'a [(HydroNode, bool)]);
107
108impl serde::Serialize for SerializableSingletonRefs<'_> {
109    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
110        use serde::ser::SerializeSeq;
111        let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
112        for (node, is_mut) in self.0.iter() {
113            seq.serialize_element(&(node, is_mut))?;
114        }
115        seq.end()
116    }
117}
118
119impl Debug for ClosureExpr {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        Debug::fmt(&self.expr, f)
122    }
123}
124
125impl Display for ClosureExpr {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        Display::fmt(&self.expr, f)
128    }
129}
130
131impl From<syn::Expr> for ClosureExpr {
132    fn from(expr: syn::Expr) -> Self {
133        Self {
134            expr: DebugExpr(Box::new(expr)),
135            singleton_refs: Vec::new(),
136        }
137    }
138}
139
140impl From<DebugExpr> for ClosureExpr {
141    fn from(expr: DebugExpr) -> Self {
142        Self {
143            expr,
144            singleton_refs: Vec::new(),
145        }
146    }
147}
148
149impl ClosureExpr {
150    pub fn new(expr: DebugExpr, singleton_refs: Vec<(HydroNode, bool)>) -> Self {
151        Self {
152            expr,
153            singleton_refs,
154        }
155    }
156
157    pub fn has_mut_ref(&self) -> bool {
158        self.singleton_refs.iter().any(|(_, is_mut)| *is_mut)
159    }
160
161    pub fn deep_clone(&self, seen_tees: &mut SeenSharedNodes) -> Self {
162        Self {
163            expr: self.expr.clone(),
164            singleton_refs: self
165                .singleton_refs
166                .iter()
167                .map(|(node, is_mut)| (node.deep_clone(seen_tees), *is_mut))
168                .collect(),
169        }
170    }
171
172    pub fn transform_children(
173        &mut self,
174        transform: &mut impl FnMut(&mut HydroNode, &mut SeenSharedNodes),
175        seen_tees: &mut SeenSharedNodes,
176    ) {
177        for (ref_node, _is_mut) in self.singleton_refs.iter_mut() {
178            transform(ref_node, seen_tees);
179        }
180    }
181
182    /// Pop singleton ref idents from the stack and rewrite the closure's token stream,
183    /// replacing local singleton ref idents with `#{N} dfir_ident` or `#{N} mut dfir_ident` references.
184    #[cfg(feature = "build")]
185    pub fn emit_tokens(&self, ident_stack: &mut Vec<syn::Ident>) -> TokenStream {
186        if self.singleton_refs.is_empty() {
187            self.expr.0.to_token_stream()
188        } else {
189            assert!(
190                ident_stack.len() >= self.singleton_refs.len(),
191                "ident_stack has {} entries but expected at least {} for singleton_refs",
192                ident_stack.len(),
193                self.singleton_refs.len()
194            );
195            let ref_idents = ident_stack.drain(ident_stack.len() - self.singleton_refs.len()..);
196
197            let mut let_bindings = Vec::new();
198            for ((i, (ref_node, is_mut)), ref_ident) in
199                self.singleton_refs.iter().enumerate().zip(ref_idents)
200            {
201                let HydroNode::Reference { access_counter, .. } = ref_node else {
202                    panic!("ClosureExpression expected references to `HydroNode::Reference`");
203                };
204                let group = access_counter.frozen_group();
205                // TODO(mingwei): proper spanning?
206                let local_ident = handoff_ref_ident(i);
207                let hash = proc_macro2::Punct::new('#', proc_macro2::Spacing::Alone);
208                let group_lit = proc_macro2::Literal::u32_unsuffixed(group);
209                let mut_token = is_mut.then(|| quote!(mut));
210                let binding = quote! {
211                    let #local_ident = #hash {#group_lit} #mut_token #ref_ident;
212                };
213                let_bindings.push(binding);
214            }
215
216            let expr = &self.expr.0;
217            quote! {
218                {
219                    #( #let_bindings )*
220                    #expr
221                }
222            }
223        }
224    }
225}
226
227/// Wrapper that displays only the tokens of a parsed expr.
228///
229/// Boxes `syn::Type` which is ~240 bytes.
230#[derive(Clone, Hash)]
231pub struct DebugExpr(pub Box<syn::Expr>);
232
233impl serde::Serialize for DebugExpr {
234    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
235        serializer.serialize_str(&self.to_string())
236    }
237}
238
239impl From<syn::Expr> for DebugExpr {
240    fn from(expr: syn::Expr) -> Self {
241        Self(Box::new(expr))
242    }
243}
244
245impl Deref for DebugExpr {
246    type Target = syn::Expr;
247
248    fn deref(&self) -> &Self::Target {
249        &self.0
250    }
251}
252
253impl ToTokens for DebugExpr {
254    fn to_tokens(&self, tokens: &mut TokenStream) {
255        self.0.to_tokens(tokens);
256    }
257}
258
259impl Debug for DebugExpr {
260    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261        write!(f, "{}", self.0.to_token_stream())
262    }
263}
264
265impl Display for DebugExpr {
266    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267        let original = self.0.as_ref().clone();
268        let simplified = simplify_q_macro(original);
269
270        // For now, just use quote formatting without trying to parse as a statement
271        // This avoids the syn::parse_quote! issues entirely
272        write!(f, "q!({})", quote::quote!(#simplified))
273    }
274}
275
276/// Simplify expanded q! macro calls back to q!(...) syntax for better readability
277fn simplify_q_macro(expr: syn::Expr) -> syn::Expr {
278    if let syn::Expr::Call(ref call) = expr && let syn::Expr::Path(path_expr) = call.func.as_ref()
279        // Look for calls to stageleft::runtime_support::fn*
280        && is_stageleft_runtime_support_call(&path_expr.path)
281        && let syn::Expr::Block(b) = &call.args[0]
282        && b.block.stmts.len() == 3
283        && let Some(syn::Stmt::Expr(e, _)) = b.block.stmts.get(2)
284    // skip the first two, which are imports
285    {
286        let mut e = e.clone();
287        while let syn::Expr::Block(ref mut block) = e
288            && block.block.stmts.len() == 1
289            && let syn::Stmt::Expr(inner_e, _) = block.block.stmts.remove(0)
290        {
291            e = inner_e;
292        }
293
294        e
295    } else {
296        expr
297    }
298}
299
300fn is_stageleft_runtime_support_call(path: &syn::Path) -> bool {
301    // Check if this is a call to stageleft::runtime_support::fn*
302    if let Some(last_segment) = path.segments.last() {
303        let fn_name = last_segment.ident.to_string();
304        path.segments.len() > 2
305            && path.segments[0].ident == "stageleft"
306            && path.segments[1].ident == "runtime_support"
307            && fn_name.contains("_type_hint")
308    } else {
309        false
310    }
311}
312
313/// Debug displays the type's tokens.
314///
315/// Boxes `syn::Type` which is ~320 bytes.
316#[derive(Clone, PartialEq, Eq, Hash)]
317pub struct DebugType(pub Box<syn::Type>);
318
319impl From<syn::Type> for DebugType {
320    fn from(t: syn::Type) -> Self {
321        Self(Box::new(t))
322    }
323}
324
325impl Deref for DebugType {
326    type Target = syn::Type;
327
328    fn deref(&self) -> &Self::Target {
329        &self.0
330    }
331}
332
333impl ToTokens for DebugType {
334    fn to_tokens(&self, tokens: &mut TokenStream) {
335        self.0.to_tokens(tokens);
336    }
337}
338
339impl Debug for DebugType {
340    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341        write!(f, "{}", self.0.to_token_stream())
342    }
343}
344
345impl serde::Serialize for DebugType {
346    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
347        serializer.serialize_str(&format!("{}", self.0.to_token_stream()))
348    }
349}
350
351fn serialize_backtrace_as_span<S: serde::Serializer>(
352    backtrace: &Backtrace,
353    serializer: S,
354) -> Result<S::Ok, S::Error> {
355    match backtrace.format_span() {
356        Some(span) => serializer.serialize_some(&span),
357        None => serializer.serialize_none(),
358    }
359}
360
361fn serialize_ident<S: serde::Serializer>(
362    ident: &syn::Ident,
363    serializer: S,
364) -> Result<S::Ok, S::Error> {
365    serializer.serialize_str(&ident.to_string())
366}
367
368pub enum DebugInstantiate {
369    Building,
370    Finalized(Box<DebugInstantiateFinalized>),
371}
372
373impl serde::Serialize for DebugInstantiate {
374    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
375        match self {
376            DebugInstantiate::Building => {
377                serializer.serialize_unit_variant("DebugInstantiate", 0, "Building")
378            }
379            DebugInstantiate::Finalized(_) => {
380                panic!(
381                    "cannot serialize DebugInstantiate::Finalized: contains non-serializable runtime state (closures)"
382                )
383            }
384        }
385    }
386}
387
388#[cfg_attr(
389    not(feature = "build"),
390    expect(
391        dead_code,
392        reason = "sink, source unused without `feature = \"build\"`."
393    )
394)]
395pub struct DebugInstantiateFinalized {
396    sink: syn::Expr,
397    source: syn::Expr,
398    connect_fn: Option<Box<dyn FnOnce()>>,
399}
400
401impl From<DebugInstantiateFinalized> for DebugInstantiate {
402    fn from(f: DebugInstantiateFinalized) -> Self {
403        Self::Finalized(Box::new(f))
404    }
405}
406
407impl Debug for DebugInstantiate {
408    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409        write!(f, "<network instantiate>")
410    }
411}
412
413impl Hash for DebugInstantiate {
414    fn hash<H: Hasher>(&self, _state: &mut H) {
415        // Do nothing
416    }
417}
418
419impl Clone for DebugInstantiate {
420    fn clone(&self) -> Self {
421        match self {
422            DebugInstantiate::Building => DebugInstantiate::Building,
423            DebugInstantiate::Finalized(_) => {
424                panic!("DebugInstantiate::Finalized should not be cloned")
425            }
426        }
427    }
428}
429
430/// Tracks the instantiation state of a `ClusterMembers` source.
431///
432/// During `compile_network`, the first `ClusterMembers` node for a given
433/// `(at_location, target_cluster)` pair is promoted to [`Self::Stream`] and
434/// receives the expression returned by `Deploy::cluster_membership_stream`.
435/// All subsequent nodes for the same pair are set to [`Self::Tee`] so that
436/// during code-gen they simply reference the tee output of the first node
437/// instead of creating a redundant `source_stream`.
438#[derive(Debug, Hash, Clone, serde::Serialize)]
439pub enum ClusterMembersState {
440    /// Not yet instantiated.
441    Uninit,
442    /// The primary instance: holds the stream expression and will emit
443    /// `source_stream(expr) -> tee()` during code-gen.
444    Stream(DebugExpr),
445    /// A secondary instance that references the tee output of the primary.
446    /// Stores `(at_location_root, target_cluster_location)` so that `emit_core`
447    /// can derive the deterministic tee ident without extra state.
448    Tee(LocationId, LocationId),
449}
450
451/// A source in a Hydro graph, where data enters the graph.
452#[derive(Debug, Hash, Clone, serde::Serialize)]
453pub enum HydroSource {
454    Stream(DebugExpr),
455    ExternalNetwork(),
456    Iter(DebugExpr),
457    Spin(),
458    ClusterMembers(LocationId, ClusterMembersState),
459    Embedded(#[serde(serialize_with = "serialize_ident")] syn::Ident),
460    EmbeddedSingleton(#[serde(serialize_with = "serialize_ident")] syn::Ident),
461}
462
463#[cfg(feature = "build")]
464/// A trait that abstracts over elements of DFIR code-gen that differ between production deployment
465/// and simulations.
466///
467/// In particular, this lets the simulator fuse together all locations into one DFIR graph, spit
468/// out separate graphs for each tick, and emit hooks for controlling non-deterministic operators.
469pub trait DfirBuilder {
470    /// Whether the representation of singletons should include intermediate states.
471    fn singleton_intermediates(&self) -> bool;
472
473    /// Gets the DFIR builder for the given location, creating it if necessary.
474    fn get_dfir_mut(&mut self, location: &LocationId) -> &mut FlatGraphBuilder;
475
476    #[expect(clippy::too_many_arguments, reason = "TODO")]
477    fn batch(
478        &mut self,
479        in_ident: syn::Ident,
480        in_location: &LocationId,
481        in_kind: &CollectionKind,
482        out_ident: &syn::Ident,
483        out_location: &LocationId,
484        op_meta: &HydroIrOpMetadata,
485        fold_hooked_idents: &HashSet<String>,
486    );
487    fn yield_from_tick(
488        &mut self,
489        in_ident: syn::Ident,
490        in_location: &LocationId,
491        in_kind: &CollectionKind,
492        out_ident: &syn::Ident,
493        out_location: &LocationId,
494    );
495
496    fn begin_atomic(
497        &mut self,
498        in_ident: syn::Ident,
499        in_location: &LocationId,
500        in_kind: &CollectionKind,
501        out_ident: &syn::Ident,
502        out_location: &LocationId,
503        op_meta: &HydroIrOpMetadata,
504    );
505    fn end_atomic(
506        &mut self,
507        in_ident: syn::Ident,
508        in_location: &LocationId,
509        in_kind: &CollectionKind,
510        out_ident: &syn::Ident,
511    );
512
513    #[expect(clippy::too_many_arguments, reason = "TODO // internal")]
514    fn observe_nondet(
515        &mut self,
516        trusted: bool,
517        location: &LocationId,
518        in_ident: syn::Ident,
519        in_kind: &CollectionKind,
520        out_ident: &syn::Ident,
521        out_kind: &CollectionKind,
522        op_meta: &HydroIrOpMetadata,
523    );
524
525    #[expect(clippy::too_many_arguments, reason = "TODO")]
526    fn merge_ordered(
527        &mut self,
528        location: &LocationId,
529        first_ident: syn::Ident,
530        second_ident: syn::Ident,
531        out_ident: &syn::Ident,
532        in_kind: &CollectionKind,
533        op_meta: &HydroIrOpMetadata,
534        operator_tag: Option<&str>,
535    );
536
537    #[expect(clippy::too_many_arguments, reason = "TODO")]
538    fn create_network(
539        &mut self,
540        from: &LocationId,
541        to: &LocationId,
542        input_ident: syn::Ident,
543        out_ident: &syn::Ident,
544        serialize: Option<&DebugExpr>,
545        sink: syn::Expr,
546        source: syn::Expr,
547        deserialize: Option<&DebugExpr>,
548        external_element_type: Option<&syn::Type>,
549        tag_id: StmtId,
550        networking_info: &crate::networking::NetworkingInfo,
551    );
552
553    fn create_external_source(
554        &mut self,
555        on: &LocationId,
556        source_expr: syn::Expr,
557        out_ident: &syn::Ident,
558        deserialize: Option<&DebugExpr>,
559        tag_id: StmtId,
560    );
561
562    fn create_external_output(
563        &mut self,
564        on: &LocationId,
565        sink_expr: syn::Expr,
566        input_ident: &syn::Ident,
567        serialize: Option<&DebugExpr>,
568        tag_id: StmtId,
569    );
570
571    /// Optionally emit a fold hook that buffers and permutes inputs before the fold.
572    /// Returns the new input ident to use for the fold if a hook was emitted.
573    fn emit_fold_hook(
574        &mut self,
575        location: &LocationId,
576        in_ident: &syn::Ident,
577        in_kind: &CollectionKind,
578        op_meta: &HydroIrOpMetadata,
579    ) -> Option<syn::Ident>;
580
581    /// Inserts necessary code to validate a manual assertion that at this point the
582    /// input live collection is consistent. In production, this is a no-op, but in simulation
583    /// this will (not yet implemented) inject assertions that validate consistency.
584    fn assert_is_consistent(
585        &mut self,
586        trusted: bool,
587        location: &LocationId,
588        in_ident: syn::Ident,
589        out_ident: &syn::Ident,
590    );
591
592    /// Observes non-determinism introduced by a mut closure operating on a non-strict
593    /// (unordered / at-least-once) input. In production this is identity; in simulation
594    /// it delegates to `observe_nondet` with the strict output kind.
595    fn observe_for_mut(
596        &mut self,
597        location: &LocationId,
598        in_ident: syn::Ident,
599        in_kind: &CollectionKind,
600        out_ident: &syn::Ident,
601        op_meta: &HydroIrOpMetadata,
602    );
603
604    fn create_versioned_network_fork(
605        &mut self,
606        channel_id: u32,
607        dest: &LocationId,
608        senders: Vec<(LocationId, syn::Ident, Option<DebugExpr>)>,
609        external_element_type: Option<&syn::Type>,
610        tag_id: StmtId,
611    );
612
613    #[expect(clippy::too_many_arguments, reason = "networking codegen")]
614    fn create_versioned_network(
615        &mut self,
616        channel_id: u32,
617        source: &LocationId,
618        dest: &LocationId,
619        out_ident: &syn::Ident,
620        deserialize: Option<&DebugExpr>,
621        external_element_type: Option<&syn::Type>,
622        tag_id: StmtId,
623    );
624}
625
626#[cfg(feature = "build")]
627impl DfirBuilder for SecondaryMap<LocationKey, FlatGraphBuilder> {
628    fn singleton_intermediates(&self) -> bool {
629        false
630    }
631
632    fn get_dfir_mut(&mut self, location: &LocationId) -> &mut FlatGraphBuilder {
633        self.entry(location.root().key())
634            .expect("location was removed")
635            .or_default()
636    }
637
638    fn batch(
639        &mut self,
640        in_ident: syn::Ident,
641        in_location: &LocationId,
642        in_kind: &CollectionKind,
643        out_ident: &syn::Ident,
644        _out_location: &LocationId,
645        _op_meta: &HydroIrOpMetadata,
646        _fold_hooked_idents: &HashSet<String>,
647    ) {
648        let builder = self.get_dfir_mut(in_location.root());
649        if in_kind.is_bounded()
650            && matches!(
651                in_kind,
652                CollectionKind::Singleton { .. }
653                    | CollectionKind::Optional { .. }
654                    | CollectionKind::KeyedSingleton { .. }
655            )
656        {
657            assert!(in_location.is_top_level());
658            builder.add_dfir(
659                parse_quote! {
660                    #out_ident = #in_ident -> persist::<'static>();
661                },
662                None,
663                None,
664            );
665        } else {
666            builder.add_dfir(
667                parse_quote! {
668                    #out_ident = #in_ident;
669                },
670                None,
671                None,
672            );
673        }
674    }
675
676    fn yield_from_tick(
677        &mut self,
678        in_ident: syn::Ident,
679        in_location: &LocationId,
680        _in_kind: &CollectionKind,
681        out_ident: &syn::Ident,
682        _out_location: &LocationId,
683    ) {
684        let builder = self.get_dfir_mut(in_location.root());
685        builder.add_dfir(
686            parse_quote! {
687                #out_ident = #in_ident;
688            },
689            None,
690            None,
691        );
692    }
693
694    fn begin_atomic(
695        &mut self,
696        in_ident: syn::Ident,
697        in_location: &LocationId,
698        _in_kind: &CollectionKind,
699        out_ident: &syn::Ident,
700        _out_location: &LocationId,
701        _op_meta: &HydroIrOpMetadata,
702    ) {
703        let builder = self.get_dfir_mut(in_location.root());
704        builder.add_dfir(
705            parse_quote! {
706                #out_ident = #in_ident;
707            },
708            None,
709            None,
710        );
711    }
712
713    fn end_atomic(
714        &mut self,
715        in_ident: syn::Ident,
716        in_location: &LocationId,
717        _in_kind: &CollectionKind,
718        out_ident: &syn::Ident,
719    ) {
720        let builder = self.get_dfir_mut(in_location.root());
721        builder.add_dfir(
722            parse_quote! {
723                #out_ident = #in_ident;
724            },
725            None,
726            None,
727        );
728    }
729
730    fn observe_nondet(
731        &mut self,
732        _trusted: bool,
733        location: &LocationId,
734        in_ident: syn::Ident,
735        _in_kind: &CollectionKind,
736        out_ident: &syn::Ident,
737        _out_kind: &CollectionKind,
738        _op_meta: &HydroIrOpMetadata,
739    ) {
740        let builder = self.get_dfir_mut(location);
741        builder.add_dfir(
742            parse_quote! {
743                #out_ident = #in_ident;
744            },
745            None,
746            None,
747        );
748    }
749
750    fn merge_ordered(
751        &mut self,
752        location: &LocationId,
753        first_ident: syn::Ident,
754        second_ident: syn::Ident,
755        out_ident: &syn::Ident,
756        _in_kind: &CollectionKind,
757        _op_meta: &HydroIrOpMetadata,
758        operator_tag: Option<&str>,
759    ) {
760        let builder = self.get_dfir_mut(location);
761        builder.add_dfir(
762            parse_quote! {
763                #out_ident = union();
764                #first_ident -> [0]#out_ident;
765                #second_ident -> [1]#out_ident;
766            },
767            None,
768            operator_tag,
769        );
770    }
771
772    fn create_network(
773        &mut self,
774        from: &LocationId,
775        to: &LocationId,
776        input_ident: syn::Ident,
777        out_ident: &syn::Ident,
778        serialize: Option<&DebugExpr>,
779        sink: syn::Expr,
780        source: syn::Expr,
781        deserialize: Option<&DebugExpr>,
782        _external_element_type: Option<&syn::Type>,
783        tag_id: StmtId,
784        _networking_info: &crate::networking::NetworkingInfo,
785    ) {
786        let sender_builder = self.get_dfir_mut(from);
787        if let Some(serialize_pipeline) = serialize {
788            sender_builder.add_dfir(
789                parse_quote! {
790                    #input_ident -> map(#serialize_pipeline) -> dest_sink(#sink);
791                },
792                None,
793                // operator tag separates send and receive, which otherwise have the same next_stmt_id
794                Some(&format!("send{}", tag_id)),
795            );
796        } else {
797            sender_builder.add_dfir(
798                parse_quote! {
799                    #input_ident -> dest_sink(#sink);
800                },
801                None,
802                Some(&format!("send{}", tag_id)),
803            );
804        }
805
806        let receiver_builder = self.get_dfir_mut(to);
807        if let Some(deserialize_pipeline) = deserialize {
808            receiver_builder.add_dfir(
809                parse_quote! {
810                    #out_ident = source_stream(#source) -> map(#deserialize_pipeline);
811                },
812                None,
813                Some(&format!("recv{}", tag_id)),
814            );
815        } else {
816            receiver_builder.add_dfir(
817                parse_quote! {
818                    #out_ident = source_stream(#source);
819                },
820                None,
821                Some(&format!("recv{}", tag_id)),
822            );
823        }
824    }
825
826    fn create_external_source(
827        &mut self,
828        on: &LocationId,
829        source_expr: syn::Expr,
830        out_ident: &syn::Ident,
831        deserialize: Option<&DebugExpr>,
832        tag_id: StmtId,
833    ) {
834        let receiver_builder = self.get_dfir_mut(on);
835        if let Some(deserialize_pipeline) = deserialize {
836            receiver_builder.add_dfir(
837                parse_quote! {
838                    #out_ident = source_stream(#source_expr) -> map(#deserialize_pipeline);
839                },
840                None,
841                Some(&format!("recv{}", tag_id)),
842            );
843        } else {
844            receiver_builder.add_dfir(
845                parse_quote! {
846                    #out_ident = source_stream(#source_expr);
847                },
848                None,
849                Some(&format!("recv{}", tag_id)),
850            );
851        }
852    }
853
854    fn create_external_output(
855        &mut self,
856        on: &LocationId,
857        sink_expr: syn::Expr,
858        input_ident: &syn::Ident,
859        serialize: Option<&DebugExpr>,
860        tag_id: StmtId,
861    ) {
862        let sender_builder = self.get_dfir_mut(on);
863        if let Some(serialize_fn) = serialize {
864            sender_builder.add_dfir(
865                parse_quote! {
866                    #input_ident -> map(#serialize_fn) -> dest_sink(#sink_expr);
867                },
868                None,
869                // operator tag separates send and receive, which otherwise have the same next_stmt_id
870                Some(&format!("send{}", tag_id)),
871            );
872        } else {
873            sender_builder.add_dfir(
874                parse_quote! {
875                    #input_ident -> dest_sink(#sink_expr);
876                },
877                None,
878                Some(&format!("send{}", tag_id)),
879            );
880        }
881    }
882
883    fn emit_fold_hook(
884        &mut self,
885        _location: &LocationId,
886        _in_ident: &syn::Ident,
887        _in_kind: &CollectionKind,
888        _op_meta: &HydroIrOpMetadata,
889    ) -> Option<syn::Ident> {
890        None
891    }
892
893    fn assert_is_consistent(
894        &mut self,
895        _trusted: bool,
896        location: &LocationId,
897        in_ident: syn::Ident,
898        out_ident: &syn::Ident,
899    ) {
900        let builder = self.get_dfir_mut(location);
901        builder.add_dfir(
902            parse_quote! {
903                #out_ident = #in_ident;
904            },
905            None,
906            None,
907        );
908    }
909
910    fn observe_for_mut(
911        &mut self,
912        location: &LocationId,
913        in_ident: syn::Ident,
914        _in_kind: &CollectionKind,
915        out_ident: &syn::Ident,
916        _op_meta: &HydroIrOpMetadata,
917    ) {
918        let builder = self.get_dfir_mut(location);
919        builder.add_dfir(
920            parse_quote! {
921                #out_ident = #in_ident;
922            },
923            None,
924            None,
925        );
926    }
927
928    fn create_versioned_network_fork(
929        &mut self,
930        _channel_id: u32,
931        _dest: &LocationId,
932        _senders: Vec<(LocationId, syn::Ident, Option<DebugExpr>)>,
933        _external_element_type: Option<&syn::Type>,
934        _tag_id: StmtId,
935    ) {
936        unreachable!(
937            "HydroNode::VersionedNetworkFork is only produced by the multi-version simulator merge \
938             pass and cannot be emitted by the non-simulation builder"
939        );
940    }
941
942    fn create_versioned_network(
943        &mut self,
944        _channel_id: u32,
945        _source: &LocationId,
946        _dest: &LocationId,
947        _out_ident: &syn::Ident,
948        _deserialize: Option<&DebugExpr>,
949        _external_element_type: Option<&syn::Type>,
950        _tag_id: StmtId,
951    ) {
952        unreachable!(
953            "HydroNode::VersionedNetwork is only produced by the multi-version simulator merge \
954             pass and cannot be emitted by the non-simulation builder"
955        );
956    }
957}
958
959#[cfg(feature = "build")]
960pub enum BuildersOrCallback<'a, L, N>
961where
962    L: FnMut(&mut HydroRoot, &mut crate::Counter<StmtId>),
963    N: FnMut(&mut HydroNode, &mut crate::Counter<StmtId>),
964{
965    Builders(&'a mut dyn DfirBuilder),
966    Callback(L, N),
967}
968
969/// An root in a Hydro graph, which is an pipeline that doesn't emit
970/// any downstream values. Traversals over the dataflow graph and
971/// generating DFIR IR start from roots.
972#[derive(Debug, Hash, serde::Serialize)]
973pub enum HydroRoot {
974    ForEach {
975        f: ClosureExpr,
976        input: Box<HydroNode>,
977        op_metadata: HydroIrOpMetadata,
978    },
979    SendExternal {
980        to_external_key: LocationKey,
981        to_port_id: ExternalPortId,
982        to_many: bool,
983        unpaired: bool,
984        serialize_fn: Option<DebugExpr>,
985        instantiate_fn: DebugInstantiate,
986        input: Box<HydroNode>,
987        op_metadata: HydroIrOpMetadata,
988    },
989    DestSink {
990        sink: DebugExpr,
991        input: Box<HydroNode>,
992        op_metadata: HydroIrOpMetadata,
993    },
994    CycleSink {
995        cycle_id: CycleId,
996        input: Box<HydroNode>,
997        op_metadata: HydroIrOpMetadata,
998    },
999    EmbeddedOutput {
1000        #[serde(serialize_with = "serialize_ident")]
1001        ident: syn::Ident,
1002        input: Box<HydroNode>,
1003        op_metadata: HydroIrOpMetadata,
1004    },
1005    Null {
1006        input: Box<HydroNode>,
1007        op_metadata: HydroIrOpMetadata,
1008    },
1009}
1010
1011impl HydroRoot {
1012    #[cfg(feature = "build")]
1013    #[expect(clippy::too_many_arguments, reason = "TODO(internal)")]
1014    pub fn compile_network<'a, D>(
1015        &mut self,
1016        extra_stmts: &mut SparseSecondaryMap<LocationKey, Vec<syn::Stmt>>,
1017        seen_tees: &mut SeenSharedNodes,
1018        seen_cluster_members: &mut HashSet<(LocationId, LocationKey)>,
1019        processes: &SparseSecondaryMap<LocationKey, D::Process>,
1020        clusters: &SparseSecondaryMap<LocationKey, D::Cluster>,
1021        externals: &SparseSecondaryMap<LocationKey, D::External>,
1022        env: &mut D::InstantiateEnv,
1023    ) where
1024        D: Deploy<'a>,
1025    {
1026        let refcell_extra_stmts = RefCell::new(extra_stmts);
1027        let refcell_env = RefCell::new(env);
1028        let refcell_seen_cluster_members = RefCell::new(seen_cluster_members);
1029        self.transform_bottom_up(
1030            &mut |l| {
1031                if let HydroRoot::SendExternal {
1032                    #[cfg(feature = "tokio")]
1033                    input,
1034                    #[cfg(feature = "tokio")]
1035                    to_external_key,
1036                    #[cfg(feature = "tokio")]
1037                    to_port_id,
1038                    #[cfg(feature = "tokio")]
1039                    to_many,
1040                    #[cfg(feature = "tokio")]
1041                    unpaired,
1042                    #[cfg(feature = "tokio")]
1043                    instantiate_fn,
1044                    ..
1045                } = l
1046                {
1047                    #[cfg(feature = "tokio")]
1048                    let ((sink_expr, source_expr), connect_fn) = match instantiate_fn {
1049                        DebugInstantiate::Building => {
1050                            let to_node = externals
1051                                .get(*to_external_key)
1052                                .unwrap_or_else(|| {
1053                                    panic!("A external used in the graph was not instantiated: {}", to_external_key)
1054                                })
1055                                .clone();
1056
1057                            match input.metadata().location_id.root() {
1058                                &LocationId::Process(process_key) => {
1059                                    if *to_many {
1060                                        (
1061                                            (
1062                                                D::e2o_many_sink(format!("{}_{}", *to_external_key, *to_port_id)),
1063                                                parse_quote!(DUMMY),
1064                                            ),
1065                                            Box::new(|| {}) as Box<dyn FnOnce()>,
1066                                        )
1067                                    } else {
1068                                        let from_node = processes
1069                                            .get(process_key)
1070                                            .unwrap_or_else(|| {
1071                                                panic!("A process used in the graph was not instantiated: {}", process_key)
1072                                            })
1073                                            .clone();
1074
1075                                        let sink_port = from_node.next_port();
1076                                        let source_port = to_node.next_port();
1077
1078                                        if *unpaired {
1079                                            use stageleft::quote_type;
1080                                            use tokio_util::codec::LengthDelimitedCodec;
1081
1082                                            to_node.register(*to_port_id, source_port.clone());
1083
1084                                            let _ = D::e2o_source(
1085                                                refcell_extra_stmts.borrow_mut().entry(process_key).expect("location was removed").or_default(),
1086                                                &to_node, &source_port,
1087                                                &from_node, &sink_port,
1088                                                &quote_type::<LengthDelimitedCodec>(),
1089                                                format!("{}_{}", *to_external_key, *to_port_id)
1090                                            );
1091                                        }
1092
1093                                        (
1094                                            (
1095                                                D::o2e_sink(
1096                                                    &from_node,
1097                                                    &sink_port,
1098                                                    &to_node,
1099                                                    &source_port,
1100                                                    format!("{}_{}", *to_external_key, *to_port_id)
1101                                                ),
1102                                                parse_quote!(DUMMY),
1103                                            ),
1104                                            if *unpaired {
1105                                                D::e2o_connect(
1106                                                    &to_node,
1107                                                    &source_port,
1108                                                    &from_node,
1109                                                    &sink_port,
1110                                                    *to_many,
1111                                                    NetworkHint::Auto,
1112                                                )
1113                                            } else {
1114                                                Box::new(|| {}) as Box<dyn FnOnce()>
1115                                            },
1116                                        )
1117                                    }
1118                                }
1119                                LocationId::Cluster(cluster_key) => {
1120                                    let from_node = clusters
1121                                        .get(*cluster_key)
1122                                        .unwrap_or_else(|| {
1123                                            panic!("A cluster used in the graph was not instantiated: {}", cluster_key)
1124                                        })
1125                                        .clone();
1126
1127                                    let sink_port = from_node.next_port();
1128                                    let source_port = to_node.next_port();
1129
1130                                    if *unpaired {
1131                                        to_node.register(*to_port_id, source_port.clone());
1132                                    }
1133
1134                                    (
1135                                        (
1136                                            D::m2e_sink(
1137                                                &from_node,
1138                                                &sink_port,
1139                                                &to_node,
1140                                                &source_port,
1141                                                format!("{}_{}", *to_external_key, *to_port_id)
1142                                            ),
1143                                            parse_quote!(DUMMY),
1144                                        ),
1145                                        Box::new(|| {}) as Box<dyn FnOnce()>,
1146                                    )
1147                                }
1148                                _ => panic!()
1149                            }
1150                        },
1151
1152                        DebugInstantiate::Finalized(_) => panic!("network already finalized"),
1153                    };
1154
1155                    #[cfg(not(feature = "tokio"))]
1156                    {
1157                        panic!("Cannot instantiate external inputs without tokio");
1158                    };
1159
1160                    #[cfg(feature = "tokio")]
1161                    {
1162                        *instantiate_fn = DebugInstantiateFinalized {
1163                            sink: sink_expr,
1164                            source: source_expr,
1165                            connect_fn: Some(connect_fn),
1166                        }
1167                        .into();
1168                    };
1169                } else if let HydroRoot::EmbeddedOutput { ident, input, .. } = l {
1170                    let element_type = match &input.metadata().collection_kind {
1171                        CollectionKind::Stream { element_type, .. } => element_type.0.as_ref().clone(),
1172                        _ => panic!("Embedded output must have Stream collection kind"),
1173                    };
1174                    let location_key = match input.metadata().location_id.root() {
1175                        LocationId::Process(key) | LocationId::Cluster(key) => *key,
1176                        _ => panic!("Embedded output must be on a process or cluster"),
1177                    };
1178                    D::register_embedded_output(
1179                        &mut refcell_env.borrow_mut(),
1180                        location_key,
1181                        ident,
1182                        &element_type,
1183                    );
1184                }
1185            },
1186            &mut |n| {
1187                if let HydroNode::Network {
1188                    name,
1189                    networking_info,
1190                    input,
1191                    instantiate_fn,
1192                    serialize,
1193                    deserialize,
1194                    metadata,
1195                    ..
1196                } = n
1197                {
1198                    let external_types = match (
1199                        serialize.external_element_type(),
1200                        deserialize.external_element_type(),
1201                    ) {
1202                        (Some(input_type), Some(output_type)) => Some((input_type, output_type)),
1203                        _ => None,
1204                    };
1205                    let (sink_expr, source_expr, connect_fn) = match instantiate_fn {
1206                        DebugInstantiate::Building => instantiate_network::<D>(
1207                            &mut refcell_env.borrow_mut(),
1208                            input.metadata().location_id.root(),
1209                            metadata.location_id.root(),
1210                            processes,
1211                            clusters,
1212                            name.as_deref(),
1213                            networking_info,
1214                            external_types,
1215                        ),
1216
1217                        DebugInstantiate::Finalized(_) => panic!("network already finalized"),
1218                    };
1219
1220                    *instantiate_fn = DebugInstantiateFinalized {
1221                        sink: sink_expr,
1222                        source: source_expr,
1223                        connect_fn: Some(connect_fn),
1224                    }
1225                    .into();
1226                } else if let HydroNode::ExternalInput {
1227                    from_external_key,
1228                    from_port_id,
1229                    from_many,
1230                    codec_type,
1231                    port_hint,
1232                    instantiate_fn,
1233                    metadata,
1234                    ..
1235                } = n
1236                {
1237                    let ((sink_expr, source_expr), connect_fn) = match instantiate_fn {
1238                        DebugInstantiate::Building => {
1239                            let from_node = externals
1240                                .get(*from_external_key)
1241                                .unwrap_or_else(|| {
1242                                    panic!(
1243                                        "A external used in the graph was not instantiated: {}",
1244                                        from_external_key,
1245                                    )
1246                                })
1247                                .clone();
1248
1249                            match metadata.location_id.root() {
1250                                &LocationId::Process(process_key) => {
1251                                    let to_node = processes
1252                                        .get(process_key)
1253                                        .unwrap_or_else(|| {
1254                                            panic!("A process used in the graph was not instantiated: {}", process_key)
1255                                        })
1256                                        .clone();
1257
1258                                    let sink_port = from_node.next_port();
1259                                    let source_port = to_node.next_port();
1260
1261                                    from_node.register(*from_port_id, sink_port.clone());
1262
1263                                    (
1264                                        (
1265                                            parse_quote!(DUMMY),
1266                                            if *from_many {
1267                                                D::e2o_many_source(
1268                                                    refcell_extra_stmts.borrow_mut().entry(process_key).expect("location was removed").or_default(),
1269                                                    &to_node, &source_port,
1270                                                    codec_type.0.as_ref(),
1271                                                    format!("{}_{}", *from_external_key, *from_port_id)
1272                                                )
1273                                            } else {
1274                                                D::e2o_source(
1275                                                    refcell_extra_stmts.borrow_mut().entry(process_key).expect("location was removed").or_default(),
1276                                                    &from_node, &sink_port,
1277                                                    &to_node, &source_port,
1278                                                    codec_type.0.as_ref(),
1279                                                    format!("{}_{}", *from_external_key, *from_port_id)
1280                                                )
1281                                            },
1282                                        ),
1283                                        D::e2o_connect(&from_node, &sink_port, &to_node, &source_port, *from_many, *port_hint),
1284                                    )
1285                                }
1286                                LocationId::Cluster(cluster_key) => {
1287                                    let to_node = clusters
1288                                        .get(*cluster_key)
1289                                        .unwrap_or_else(|| {
1290                                            panic!("A cluster used in the graph was not instantiated: {}", cluster_key)
1291                                        })
1292                                        .clone();
1293
1294                                    let sink_port = from_node.next_port();
1295                                    let source_port = to_node.next_port();
1296
1297                                    from_node.register(*from_port_id, sink_port.clone());
1298
1299                                    (
1300                                        (
1301                                            parse_quote!(DUMMY),
1302                                            D::e2m_source(
1303                                                refcell_extra_stmts.borrow_mut().entry(*cluster_key).expect("location was removed").or_default(),
1304                                                &from_node, &sink_port,
1305                                                &to_node, &source_port,
1306                                                codec_type.0.as_ref(),
1307                                                format!("{}_{}", *from_external_key, *from_port_id)
1308                                            ),
1309                                        ),
1310                                        D::e2m_connect(&from_node, &sink_port, &to_node, &source_port, *port_hint),
1311                                    )
1312                                }
1313                                _ => panic!()
1314                            }
1315                        },
1316
1317                        DebugInstantiate::Finalized(_) => panic!("network already finalized"),
1318                    };
1319
1320                    *instantiate_fn = DebugInstantiateFinalized {
1321                        sink: sink_expr,
1322                        source: source_expr,
1323                        connect_fn: Some(connect_fn),
1324                    }
1325                    .into();
1326                } else if let HydroNode::Source { source: HydroSource::Embedded(ident), metadata } = n {
1327                    let element_type = match &metadata.collection_kind {
1328                        CollectionKind::Stream { element_type, .. } => element_type.0.as_ref().clone(),
1329                        _ => panic!("Embedded source must have Stream collection kind"),
1330                    };
1331                    let location_key = match metadata.location_id.root() {
1332                        LocationId::Process(key) | LocationId::Cluster(key) => *key,
1333                        _ => panic!("Embedded source must be on a process or cluster"),
1334                    };
1335                    D::register_embedded_stream_input(
1336                        &mut refcell_env.borrow_mut(),
1337                        location_key,
1338                        ident,
1339                        &element_type,
1340                    );
1341                } else if let HydroNode::Source { source: HydroSource::EmbeddedSingleton(ident), metadata } = n {
1342                    let element_type = match &metadata.collection_kind {
1343                        CollectionKind::Singleton { element_type, .. } => element_type.0.as_ref().clone(),
1344                        _ => panic!("EmbeddedSingleton source must have Singleton collection kind"),
1345                    };
1346                    let location_key = match metadata.location_id.root() {
1347                        LocationId::Process(key) | LocationId::Cluster(key) => *key,
1348                        _ => panic!("EmbeddedSingleton source must be on a process or cluster"),
1349                    };
1350                    D::register_embedded_singleton_input(
1351                        &mut refcell_env.borrow_mut(),
1352                        location_key,
1353                        ident,
1354                        &element_type,
1355                    );
1356                } else if let HydroNode::Source { source: HydroSource::ClusterMembers(location_id, state), metadata } = n {
1357                    match state {
1358                        ClusterMembersState::Uninit => {
1359                            let at_location = metadata.location_id.root().clone();
1360                            let key = (at_location.clone(), location_id.key());
1361                            if refcell_seen_cluster_members.borrow_mut().insert(key) {
1362                                // First occurrence: call cluster_membership_stream and mark as Stream.
1363                                let expr = stageleft::QuotedWithContext::splice_untyped_ctx(
1364                                    D::cluster_membership_stream(&mut refcell_env.borrow_mut(), &at_location, location_id),
1365                                    &(),
1366                                );
1367                                *state = ClusterMembersState::Stream(expr.into());
1368                            } else {
1369                                // Already instantiated for this (at, target) pair: just tee.
1370                                *state = ClusterMembersState::Tee(at_location, location_id.clone());
1371                            }
1372                        }
1373                        ClusterMembersState::Stream(_) | ClusterMembersState::Tee(..) => {
1374                            panic!("cluster members already finalized");
1375                        }
1376                    }
1377                }
1378            },
1379            seen_tees,
1380            false,
1381        );
1382    }
1383
1384    pub fn connect_network(&mut self, seen_tees: &mut SeenSharedNodes) {
1385        self.transform_bottom_up(
1386            &mut |l| {
1387                if let HydroRoot::SendExternal { instantiate_fn, .. } = l {
1388                    match instantiate_fn {
1389                        DebugInstantiate::Building => panic!("network not built"),
1390
1391                        DebugInstantiate::Finalized(finalized) => {
1392                            (finalized.connect_fn.take().unwrap())();
1393                        }
1394                    }
1395                }
1396            },
1397            &mut |n| {
1398                if let HydroNode::Network { instantiate_fn, .. }
1399                | HydroNode::ExternalInput { instantiate_fn, .. } = n
1400                {
1401                    match instantiate_fn {
1402                        DebugInstantiate::Building => panic!("network not built"),
1403
1404                        DebugInstantiate::Finalized(finalized) => {
1405                            (finalized.connect_fn.take().unwrap())();
1406                        }
1407                    }
1408                }
1409            },
1410            seen_tees,
1411            false,
1412        );
1413    }
1414
1415    pub fn transform_bottom_up(
1416        &mut self,
1417        transform_root: &mut impl FnMut(&mut HydroRoot),
1418        transform_node: &mut impl FnMut(&mut HydroNode),
1419        seen_tees: &mut SeenSharedNodes,
1420        check_well_formed: bool,
1421    ) {
1422        self.transform_children(
1423            |n, s| n.transform_bottom_up(transform_node, s, check_well_formed),
1424            seen_tees,
1425        );
1426
1427        transform_root(self);
1428    }
1429
1430    pub fn transform_children(
1431        &mut self,
1432        mut transform: impl FnMut(&mut HydroNode, &mut SeenSharedNodes),
1433        seen_tees: &mut SeenSharedNodes,
1434    ) {
1435        match self {
1436            HydroRoot::ForEach { f, input, .. } => {
1437                f.transform_children(&mut transform, seen_tees);
1438                transform(input, seen_tees);
1439            }
1440            HydroRoot::SendExternal { input, .. }
1441            | HydroRoot::DestSink { input, .. }
1442            | HydroRoot::CycleSink { input, .. }
1443            | HydroRoot::EmbeddedOutput { input, .. }
1444            | HydroRoot::Null { input, .. } => {
1445                transform(input, seen_tees);
1446            }
1447        }
1448    }
1449
1450    pub fn deep_clone(&self, seen_tees: &mut SeenSharedNodes) -> HydroRoot {
1451        match self {
1452            HydroRoot::ForEach {
1453                f,
1454                input,
1455                op_metadata,
1456            } => HydroRoot::ForEach {
1457                f: f.deep_clone(seen_tees),
1458                input: Box::new(input.deep_clone(seen_tees)),
1459                op_metadata: op_metadata.clone(),
1460            },
1461            HydroRoot::SendExternal {
1462                to_external_key,
1463                to_port_id,
1464                to_many,
1465                unpaired,
1466                serialize_fn,
1467                instantiate_fn,
1468                input,
1469                op_metadata,
1470            } => HydroRoot::SendExternal {
1471                to_external_key: *to_external_key,
1472                to_port_id: *to_port_id,
1473                to_many: *to_many,
1474                unpaired: *unpaired,
1475                serialize_fn: serialize_fn.clone(),
1476                instantiate_fn: instantiate_fn.clone(),
1477                input: Box::new(input.deep_clone(seen_tees)),
1478                op_metadata: op_metadata.clone(),
1479            },
1480            HydroRoot::DestSink {
1481                sink,
1482                input,
1483                op_metadata,
1484            } => HydroRoot::DestSink {
1485                sink: sink.clone(),
1486                input: Box::new(input.deep_clone(seen_tees)),
1487                op_metadata: op_metadata.clone(),
1488            },
1489            HydroRoot::CycleSink {
1490                cycle_id,
1491                input,
1492                op_metadata,
1493            } => HydroRoot::CycleSink {
1494                cycle_id: *cycle_id,
1495                input: Box::new(input.deep_clone(seen_tees)),
1496                op_metadata: op_metadata.clone(),
1497            },
1498            HydroRoot::EmbeddedOutput {
1499                ident,
1500                input,
1501                op_metadata,
1502            } => HydroRoot::EmbeddedOutput {
1503                ident: ident.clone(),
1504                input: Box::new(input.deep_clone(seen_tees)),
1505                op_metadata: op_metadata.clone(),
1506            },
1507            HydroRoot::Null { input, op_metadata } => HydroRoot::Null {
1508                input: Box::new(input.deep_clone(seen_tees)),
1509                op_metadata: op_metadata.clone(),
1510            },
1511        }
1512    }
1513
1514    #[cfg(feature = "build")]
1515    pub fn emit(
1516        &mut self,
1517        graph_builders: &mut dyn DfirBuilder,
1518        seen_tees: &mut SeenSharedNodes,
1519        built_tees: &mut HashMap<*const RefCell<HydroNode>, Vec<syn::Ident>>,
1520        next_stmt_id: &mut crate::Counter<StmtId>,
1521        fold_hooked_idents: &mut HashSet<String>,
1522    ) {
1523        self.emit_core(
1524            &mut BuildersOrCallback::<
1525                fn(&mut HydroRoot, &mut crate::Counter<StmtId>),
1526                fn(&mut HydroNode, &mut crate::Counter<StmtId>),
1527            >::Builders(graph_builders),
1528            seen_tees,
1529            built_tees,
1530            next_stmt_id,
1531            fold_hooked_idents,
1532        );
1533    }
1534
1535    #[cfg(feature = "build")]
1536    pub fn emit_core(
1537        &mut self,
1538        builders_or_callback: &mut BuildersOrCallback<
1539            impl FnMut(&mut HydroRoot, &mut crate::Counter<StmtId>),
1540            impl FnMut(&mut HydroNode, &mut crate::Counter<StmtId>),
1541        >,
1542        seen_tees: &mut SeenSharedNodes,
1543        built_tees: &mut HashMap<*const RefCell<HydroNode>, Vec<syn::Ident>>,
1544        next_stmt_id: &mut crate::Counter<StmtId>,
1545        fold_hooked_idents: &mut HashSet<String>,
1546    ) {
1547        match self {
1548            HydroRoot::ForEach { f, input, .. } => {
1549                let input_ident = input.emit_core(
1550                    builders_or_callback,
1551                    seen_tees,
1552                    built_tees,
1553                    next_stmt_id,
1554                    fold_hooked_idents,
1555                );
1556
1557                let stmt_id = next_stmt_id.get_and_increment();
1558
1559                match builders_or_callback {
1560                    BuildersOrCallback::Builders(graph_builders) => {
1561                        let mut ident_stack: Vec<syn::Ident> = Vec::new();
1562
1563                        // Look up each captured ref's ident from built_tees
1564                        for (ref_node, _is_mut) in f.singleton_refs.iter() {
1565                            let HydroNode::Reference { inner, .. } = ref_node else {
1566                                panic!("singleton_refs should only contain HydroNode::Reference");
1567                            };
1568                            let ptr = inner.0.as_ref() as *const RefCell<HydroNode>;
1569                            let idents = built_tees.get(&ptr).expect(
1570                                "ForEach singleton ref not found in built_tees — ref node was not emitted",
1571                            );
1572                            ident_stack.push(idents[0].clone());
1573                        }
1574
1575                        let f_tokens = f.emit_tokens(&mut ident_stack);
1576
1577                        graph_builders
1578                            .get_dfir_mut(&input.metadata().location_id)
1579                            .add_dfir(
1580                                parse_quote! {
1581                                    #input_ident -> for_each(#f_tokens);
1582                                },
1583                                None,
1584                                Some(&stmt_id.to_string()),
1585                            );
1586                    }
1587                    BuildersOrCallback::Callback(leaf_callback, _) => {
1588                        leaf_callback(self, next_stmt_id);
1589                    }
1590                }
1591            }
1592
1593            HydroRoot::SendExternal {
1594                serialize_fn,
1595                instantiate_fn,
1596                input,
1597                ..
1598            } => {
1599                let input_ident = input.emit_core(
1600                    builders_or_callback,
1601                    seen_tees,
1602                    built_tees,
1603                    next_stmt_id,
1604                    fold_hooked_idents,
1605                );
1606
1607                let stmt_id = next_stmt_id.get_and_increment();
1608
1609                match builders_or_callback {
1610                    BuildersOrCallback::Builders(graph_builders) => {
1611                        let (sink_expr, _) = match instantiate_fn {
1612                            DebugInstantiate::Building => (
1613                                syn::parse_quote!(DUMMY_SINK),
1614                                syn::parse_quote!(DUMMY_SOURCE),
1615                            ),
1616
1617                            DebugInstantiate::Finalized(finalized) => {
1618                                (finalized.sink.clone(), finalized.source.clone())
1619                            }
1620                        };
1621
1622                        graph_builders.create_external_output(
1623                            &input.metadata().location_id,
1624                            sink_expr,
1625                            &input_ident,
1626                            serialize_fn.as_ref(),
1627                            stmt_id,
1628                        );
1629                    }
1630                    BuildersOrCallback::Callback(leaf_callback, _) => {
1631                        leaf_callback(self, next_stmt_id);
1632                    }
1633                }
1634            }
1635
1636            HydroRoot::DestSink { sink, input, .. } => {
1637                let input_ident = input.emit_core(
1638                    builders_or_callback,
1639                    seen_tees,
1640                    built_tees,
1641                    next_stmt_id,
1642                    fold_hooked_idents,
1643                );
1644
1645                let stmt_id = next_stmt_id.get_and_increment();
1646
1647                match builders_or_callback {
1648                    BuildersOrCallback::Builders(graph_builders) => {
1649                        graph_builders
1650                            .get_dfir_mut(&input.metadata().location_id)
1651                            .add_dfir(
1652                                parse_quote! {
1653                                    #input_ident -> dest_sink(#sink);
1654                                },
1655                                None,
1656                                Some(&stmt_id.to_string()),
1657                            );
1658                    }
1659                    BuildersOrCallback::Callback(leaf_callback, _) => {
1660                        leaf_callback(self, next_stmt_id);
1661                    }
1662                }
1663            }
1664
1665            HydroRoot::CycleSink {
1666                cycle_id, input, ..
1667            } => {
1668                let input_ident = input.emit_core(
1669                    builders_or_callback,
1670                    seen_tees,
1671                    built_tees,
1672                    next_stmt_id,
1673                    fold_hooked_idents,
1674                );
1675
1676                match builders_or_callback {
1677                    BuildersOrCallback::Builders(graph_builders) => {
1678                        let elem_type: syn::Type = match &input.metadata().collection_kind {
1679                            CollectionKind::KeyedSingleton {
1680                                key_type,
1681                                value_type,
1682                                ..
1683                            }
1684                            | CollectionKind::KeyedStream {
1685                                key_type,
1686                                value_type,
1687                                ..
1688                            } => {
1689                                parse_quote!((#key_type, #value_type))
1690                            }
1691                            CollectionKind::Stream { element_type, .. }
1692                            | CollectionKind::Singleton { element_type, .. }
1693                            | CollectionKind::Optional { element_type, .. } => {
1694                                parse_quote!(#element_type)
1695                            }
1696                        };
1697
1698                        let cycle_id_ident = cycle_id.as_ident();
1699                        graph_builders
1700                            .get_dfir_mut(&input.metadata().location_id)
1701                            .add_dfir(
1702                                parse_quote! {
1703                                    #cycle_id_ident = #input_ident -> identity::<#elem_type>();
1704                                },
1705                                None,
1706                                None,
1707                            );
1708                    }
1709                    // No ID, no callback
1710                    BuildersOrCallback::Callback(_, _) => {}
1711                }
1712            }
1713
1714            HydroRoot::EmbeddedOutput { ident, input, .. } => {
1715                let input_ident = input.emit_core(
1716                    builders_or_callback,
1717                    seen_tees,
1718                    built_tees,
1719                    next_stmt_id,
1720                    fold_hooked_idents,
1721                );
1722
1723                let stmt_id = next_stmt_id.get_and_increment();
1724
1725                match builders_or_callback {
1726                    BuildersOrCallback::Builders(graph_builders) => {
1727                        graph_builders
1728                            .get_dfir_mut(&input.metadata().location_id)
1729                            .add_dfir(
1730                                parse_quote! {
1731                                    #input_ident -> for_each(&mut #ident);
1732                                },
1733                                None,
1734                                Some(&stmt_id.to_string()),
1735                            );
1736                    }
1737                    BuildersOrCallback::Callback(leaf_callback, _) => {
1738                        leaf_callback(self, next_stmt_id);
1739                    }
1740                }
1741            }
1742
1743            HydroRoot::Null { input, .. } => {
1744                let input_ident = input.emit_core(
1745                    builders_or_callback,
1746                    seen_tees,
1747                    built_tees,
1748                    next_stmt_id,
1749                    fold_hooked_idents,
1750                );
1751
1752                let stmt_id = next_stmt_id.get_and_increment();
1753
1754                match builders_or_callback {
1755                    BuildersOrCallback::Builders(graph_builders) => {
1756                        graph_builders
1757                            .get_dfir_mut(&input.metadata().location_id)
1758                            .add_dfir(
1759                                parse_quote! {
1760                                    #input_ident -> for_each(|_| {});
1761                                },
1762                                None,
1763                                Some(&stmt_id.to_string()),
1764                            );
1765                    }
1766                    BuildersOrCallback::Callback(leaf_callback, _) => {
1767                        leaf_callback(self, next_stmt_id);
1768                    }
1769                }
1770            }
1771        }
1772    }
1773
1774    pub fn op_metadata(&self) -> &HydroIrOpMetadata {
1775        match self {
1776            HydroRoot::ForEach { op_metadata, .. }
1777            | HydroRoot::SendExternal { op_metadata, .. }
1778            | HydroRoot::DestSink { op_metadata, .. }
1779            | HydroRoot::CycleSink { op_metadata, .. }
1780            | HydroRoot::EmbeddedOutput { op_metadata, .. }
1781            | HydroRoot::Null { op_metadata, .. } => op_metadata,
1782        }
1783    }
1784
1785    pub fn op_metadata_mut(&mut self) -> &mut HydroIrOpMetadata {
1786        match self {
1787            HydroRoot::ForEach { op_metadata, .. }
1788            | HydroRoot::SendExternal { op_metadata, .. }
1789            | HydroRoot::DestSink { op_metadata, .. }
1790            | HydroRoot::CycleSink { op_metadata, .. }
1791            | HydroRoot::EmbeddedOutput { op_metadata, .. }
1792            | HydroRoot::Null { op_metadata, .. } => op_metadata,
1793        }
1794    }
1795
1796    pub fn input(&self) -> &HydroNode {
1797        match self {
1798            HydroRoot::ForEach { input, .. }
1799            | HydroRoot::SendExternal { input, .. }
1800            | HydroRoot::DestSink { input, .. }
1801            | HydroRoot::CycleSink { input, .. }
1802            | HydroRoot::EmbeddedOutput { input, .. }
1803            | HydroRoot::Null { input, .. } => input,
1804        }
1805    }
1806
1807    pub fn input_metadata(&self) -> &HydroIrMetadata {
1808        self.input().metadata()
1809    }
1810
1811    pub fn print_root(&self) -> String {
1812        match self {
1813            HydroRoot::ForEach { f, .. } => format!("ForEach({:?})", f),
1814            HydroRoot::SendExternal { .. } => "SendExternal".to_owned(),
1815            HydroRoot::DestSink { sink, .. } => format!("DestSink({:?})", sink),
1816            HydroRoot::CycleSink { cycle_id, .. } => format!("CycleSink({})", cycle_id),
1817            HydroRoot::EmbeddedOutput { ident, .. } => {
1818                format!("EmbeddedOutput({})", ident)
1819            }
1820            HydroRoot::Null { .. } => "Null".to_owned(),
1821        }
1822    }
1823
1824    pub fn visit_debug_expr(&mut self, mut transform: impl FnMut(&mut DebugExpr)) {
1825        match self {
1826            HydroRoot::ForEach { f, .. } => {
1827                transform(&mut f.expr);
1828            }
1829            HydroRoot::DestSink { sink, .. } => {
1830                transform(sink);
1831            }
1832            HydroRoot::SendExternal { .. }
1833            | HydroRoot::CycleSink { .. }
1834            | HydroRoot::EmbeddedOutput { .. }
1835            | HydroRoot::Null { .. } => {}
1836        }
1837    }
1838}
1839
1840#[cfg(feature = "build")]
1841fn tick_of(loc: &LocationId) -> Option<ClockId> {
1842    match loc {
1843        LocationId::Tick(id, _) => Some(*id),
1844        LocationId::Atomic(inner) => tick_of(inner),
1845        _ => None,
1846    }
1847}
1848
1849#[cfg(feature = "build")]
1850fn remap_location(loc: &mut LocationId, uf: &mut HashMap<ClockId, ClockId>) {
1851    match loc {
1852        LocationId::Tick(id, inner) => {
1853            *id = uf_find(uf, *id);
1854            remap_location(inner, uf);
1855        }
1856        LocationId::Atomic(inner) => {
1857            remap_location(inner, uf);
1858        }
1859        LocationId::Process(_) | LocationId::Cluster(_) => {}
1860    }
1861}
1862
1863#[cfg(feature = "build")]
1864fn uf_find(parent: &mut HashMap<ClockId, ClockId>, x: ClockId) -> ClockId {
1865    let p = *parent.get(&x).unwrap_or(&x);
1866    if p == x {
1867        return x;
1868    }
1869    let root = uf_find(parent, p);
1870    parent.insert(x, root);
1871    root
1872}
1873
1874#[cfg(feature = "build")]
1875fn uf_union(parent: &mut HashMap<ClockId, ClockId>, a: ClockId, b: ClockId) {
1876    let ra = uf_find(parent, a);
1877    let rb = uf_find(parent, b);
1878    if ra != rb {
1879        parent.insert(ra, rb);
1880    }
1881}
1882
1883/// Traverse the IR to build a union-find that unifies tick IDs connected
1884/// through `Batch` and `YieldConcat` nodes at atomic boundaries, then
1885/// rewrite all `LocationId`s to use the representative tick ID.
1886#[cfg(feature = "build")]
1887pub fn unify_atomic_ticks(ir: &mut [HydroRoot]) {
1888    let mut uf: HashMap<ClockId, ClockId> = HashMap::new();
1889
1890    // Pass 1: collect unifications.
1891    transform_bottom_up(
1892        ir,
1893        &mut |_| {},
1894        &mut |node: &mut HydroNode| match node {
1895            HydroNode::Batch { inner, metadata } | HydroNode::YieldConcat { inner, metadata } => {
1896                if let (Some(a), Some(b)) = (
1897                    tick_of(&inner.metadata().location_id),
1898                    tick_of(&metadata.location_id),
1899                ) {
1900                    uf_union(&mut uf, a, b);
1901                }
1902            }
1903            HydroNode::Chain {
1904                first,
1905                second,
1906                metadata,
1907            }
1908            | HydroNode::ChainFirst {
1909                first,
1910                second,
1911                metadata,
1912            }
1913            | HydroNode::MergeOrdered {
1914                first,
1915                second,
1916                metadata,
1917            } => {
1918                if let (Some(a), Some(b)) = (
1919                    tick_of(&first.metadata().location_id),
1920                    tick_of(&metadata.location_id),
1921                ) {
1922                    uf_union(&mut uf, a, b);
1923                }
1924                if let (Some(a), Some(b)) = (
1925                    tick_of(&second.metadata().location_id),
1926                    tick_of(&metadata.location_id),
1927                ) {
1928                    uf_union(&mut uf, a, b);
1929                }
1930            }
1931            _ => {}
1932        },
1933        false,
1934    );
1935
1936    // Pass 2: rewrite all LocationIds.
1937    transform_bottom_up(
1938        ir,
1939        &mut |_| {},
1940        &mut |node: &mut HydroNode| {
1941            remap_location(&mut node.metadata_mut().location_id, &mut uf);
1942        },
1943        false,
1944    );
1945}
1946
1947#[cfg(feature = "build")]
1948pub fn emit(ir: &mut Vec<HydroRoot>) -> SecondaryMap<LocationKey, FlatGraphBuilder> {
1949    let mut builders = SecondaryMap::new();
1950    let mut seen_tees = HashMap::new();
1951    let mut built_tees = HashMap::new();
1952    let mut next_stmt_id = crate::Counter::<StmtId>::default();
1953    let mut fold_hooked_idents = HashSet::new();
1954    for leaf in ir {
1955        leaf.emit(
1956            &mut builders,
1957            &mut seen_tees,
1958            &mut built_tees,
1959            &mut next_stmt_id,
1960            &mut fold_hooked_idents,
1961        );
1962    }
1963    builders
1964}
1965
1966#[cfg(feature = "build")]
1967pub fn traverse_dfir(
1968    ir: &mut [HydroRoot],
1969    transform_root: impl FnMut(&mut HydroRoot, &mut crate::Counter<StmtId>),
1970    transform_node: impl FnMut(&mut HydroNode, &mut crate::Counter<StmtId>),
1971) {
1972    let mut seen_tees = HashMap::new();
1973    let mut built_tees = HashMap::new();
1974    let mut next_stmt_id = crate::Counter::<StmtId>::default();
1975    let mut fold_hooked_idents = HashSet::new();
1976    let mut callback = BuildersOrCallback::Callback(transform_root, transform_node);
1977    ir.iter_mut().for_each(|leaf| {
1978        leaf.emit_core(
1979            &mut callback,
1980            &mut seen_tees,
1981            &mut built_tees,
1982            &mut next_stmt_id,
1983            &mut fold_hooked_idents,
1984        );
1985    });
1986}
1987
1988pub fn transform_bottom_up(
1989    ir: &mut [HydroRoot],
1990    transform_root: &mut impl FnMut(&mut HydroRoot),
1991    transform_node: &mut impl FnMut(&mut HydroNode),
1992    check_well_formed: bool,
1993) {
1994    let mut seen_tees = HashMap::new();
1995    ir.iter_mut().for_each(|leaf| {
1996        leaf.transform_bottom_up(
1997            transform_root,
1998            transform_node,
1999            &mut seen_tees,
2000            check_well_formed,
2001        );
2002    });
2003}
2004
2005pub fn deep_clone(ir: &[HydroRoot]) -> Vec<HydroRoot> {
2006    let mut seen_tees = HashMap::new();
2007    ir.iter()
2008        .map(|leaf| leaf.deep_clone(&mut seen_tees))
2009        .collect()
2010}
2011
2012type PrintedTees = RefCell<Option<(usize, HashMap<*const RefCell<HydroNode>, usize>)>>;
2013thread_local! {
2014    static PRINTED_TEES: PrintedTees = const { RefCell::new(None) };
2015    /// Tracks shared nodes already serialized so that `SharedNode::serialize`
2016    /// emits the full subtree only once and uses a `"<shared N>"` back-reference
2017    /// on subsequent encounters, preventing infinite loops.
2018    static SERIALIZED_SHARED: PrintedTees
2019        = const { RefCell::new(None) };
2020}
2021
2022pub fn dbg_dedup_tee<T>(f: impl FnOnce() -> T) -> T {
2023    PRINTED_TEES.with(|printed_tees| {
2024        let mut printed_tees_mut = printed_tees.borrow_mut();
2025        *printed_tees_mut = Some((0, HashMap::new()));
2026        drop(printed_tees_mut);
2027
2028        let ret = f();
2029
2030        let mut printed_tees_mut = printed_tees.borrow_mut();
2031        *printed_tees_mut = None;
2032
2033        ret
2034    })
2035}
2036
2037/// Runs `f` with a fresh shared-node deduplication scope for serialization.
2038/// Any `SharedNode` serialized inside `f` will be tracked; the first occurrence
2039/// emits the full subtree while later occurrences emit a `{"$shared_ref": id}`
2040/// back-reference.  The tracking state is restored when `f` returns or panics.
2041pub fn serialize_dedup_shared<T>(f: impl FnOnce() -> T) -> T {
2042    let _guard = SerializedSharedGuard::enter();
2043    f()
2044}
2045
2046/// RAII guard that saves/restores the `SERIALIZED_SHARED` thread-local,
2047/// making `serialize_dedup_shared` re-entrant and panic-safe.
2048struct SerializedSharedGuard {
2049    previous: Option<(usize, HashMap<*const RefCell<HydroNode>, usize>)>,
2050}
2051
2052impl SerializedSharedGuard {
2053    fn enter() -> Self {
2054        let previous = SERIALIZED_SHARED.with(|cell| {
2055            let mut guard = cell.borrow_mut();
2056            guard.replace((0, HashMap::new()))
2057        });
2058        Self { previous }
2059    }
2060}
2061
2062impl Drop for SerializedSharedGuard {
2063    fn drop(&mut self) {
2064        SERIALIZED_SHARED.with(|cell| {
2065            *cell.borrow_mut() = self.previous.take();
2066        });
2067    }
2068}
2069
2070pub struct SharedNode(pub Rc<RefCell<HydroNode>>);
2071
2072impl serde::Serialize for SharedNode {
2073    /// Multiple `SharedNode`s can point to the same underlying `HydroNode` (via
2074    /// `Tee` / `Partition`).  A naïve recursive serialization would revisit the
2075    /// same subtree every time and, if the graph ever contains a cycle, loop
2076    /// forever.
2077    ///
2078    /// We keep a thread-local map (`SERIALIZED_SHARED`) from raw `Rc` pointer →
2079    /// integer id.  The first time we see a pointer we assign it the next id and
2080    /// emit the full subtree as `{"$shared": <id>, "node": …}`.  Every later
2081    /// encounter of the same pointer emits `{"$shared_ref": <id>}`, cutting the
2082    /// recursion.  Requires an active `serialize_dedup_shared` scope.
2083    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2084        SERIALIZED_SHARED.with(|cell| {
2085            let mut guard = cell.borrow_mut();
2086            // (next_id, pointer → assigned_id)
2087            let state = guard.as_mut().ok_or_else(|| {
2088                serde::ser::Error::custom(
2089                    "SharedNode serialization requires an active serialize_dedup_shared scope",
2090                )
2091            })?;
2092            let ptr = self.0.as_ptr() as *const RefCell<HydroNode>;
2093
2094            if let Some(&id) = state.1.get(&ptr) {
2095                drop(guard);
2096                use serde::ser::SerializeMap;
2097                let mut map = serializer.serialize_map(Some(1))?;
2098                map.serialize_entry("$shared_ref", &id)?;
2099                map.end()
2100            } else {
2101                let id = state.0;
2102                state.0 += 1;
2103                state.1.insert(ptr, id);
2104                drop(guard);
2105
2106                use serde::ser::SerializeMap;
2107                let mut map = serializer.serialize_map(Some(2))?;
2108                map.serialize_entry("$shared", &id)?;
2109                map.serialize_entry("node", &*self.0.borrow())?;
2110                map.end()
2111            }
2112        })
2113    }
2114}
2115
2116impl SharedNode {
2117    pub fn as_ptr(&self) -> *const RefCell<HydroNode> {
2118        Rc::as_ptr(&self.0)
2119    }
2120}
2121
2122impl Debug for SharedNode {
2123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2124        PRINTED_TEES.with(|printed_tees| {
2125            let mut printed_tees_mut_borrow = printed_tees.borrow_mut();
2126            let printed_tees_mut = printed_tees_mut_borrow.as_mut();
2127
2128            if let Some(printed_tees_mut) = printed_tees_mut {
2129                if let Some(existing) = printed_tees_mut
2130                    .1
2131                    .get(&(self.0.as_ref() as *const RefCell<HydroNode>))
2132                {
2133                    write!(f, "<shared {}>", existing)
2134                } else {
2135                    let next_id = printed_tees_mut.0;
2136                    printed_tees_mut.0 += 1;
2137                    printed_tees_mut
2138                        .1
2139                        .insert(self.0.as_ref() as *const RefCell<HydroNode>, next_id);
2140                    drop(printed_tees_mut_borrow);
2141                    write!(f, "<shared {}>: ", next_id)?;
2142                    Debug::fmt(&self.0.borrow(), f)
2143                }
2144            } else {
2145                drop(printed_tees_mut_borrow);
2146                write!(f, "<shared>: ")?;
2147                Debug::fmt(&self.0.borrow(), f)
2148            }
2149        })
2150    }
2151}
2152
2153impl Hash for SharedNode {
2154    fn hash<H: Hasher>(&self, state: &mut H) {
2155        self.0.borrow_mut().hash(state);
2156    }
2157}
2158
2159/// A counter for tracking singleton access groups on a `HydroNode::Reference`.
2160///
2161/// Each mutable access increments the counter (before and after) to isolate itself in its own group;
2162/// immutable accesses share the current group.
2163#[derive(Debug)]
2164pub enum AccessCounter {
2165    Counting(Cell<u32>),
2166    Frozen(u32),
2167}
2168
2169impl AccessCounter {
2170    pub fn new() -> Self {
2171        Self::Counting(Cell::new(0))
2172    }
2173
2174    /// Assign the next access group for this reference.
2175    /// Mutable accesses get an isolated group (counter increments before and after).
2176    /// Immutable accesses share the current group.
2177    pub fn next_group(&self, is_mut: bool) -> Self {
2178        let AccessCounter::Counting(count) = self else {
2179            panic!("Cannot count on `AccessCounter::Frozen`");
2180        };
2181        let c = if is_mut {
2182            let c = count.get() + 1;
2183            count.set(c + 1);
2184            c
2185        } else {
2186            count.get()
2187        };
2188        Self::Frozen(c)
2189    }
2190
2191    /// Creates a frozen counter to prevent further counting.
2192    pub fn freeze(&self) -> Self {
2193        Self::Frozen(match self {
2194            Self::Counting(count) => count.get(),
2195            Self::Frozen(count) => *count,
2196        })
2197    }
2198
2199    pub fn frozen_group(&self) -> u32 {
2200        let Self::Frozen(count) = self else {
2201            panic!("`AccessCounter` not frozen");
2202        };
2203        *count
2204    }
2205}
2206
2207impl Default for AccessCounter {
2208    fn default() -> Self {
2209        Self::new()
2210    }
2211}
2212
2213impl Hash for AccessCounter {
2214    fn hash<H: Hasher>(&self, _state: &mut H) {
2215        // Access counter does not participate in hashing — it is runtime bookkeeping.
2216    }
2217}
2218
2219impl serde::Serialize for AccessCounter {
2220    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2221        let count = match self {
2222            AccessCounter::Counting(count) => count.get(),
2223            AccessCounter::Frozen(count) => *count,
2224        };
2225        count.serialize(serializer)
2226    }
2227}
2228
2229#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
2230pub enum BoundKind {
2231    Unbounded,
2232    Bounded,
2233}
2234
2235#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
2236pub enum StreamOrder {
2237    NoOrder,
2238    TotalOrder,
2239}
2240
2241#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
2242pub enum StreamRetry {
2243    AtLeastOnce,
2244    ExactlyOnce,
2245}
2246
2247#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
2248pub enum KeyedSingletonBoundKind {
2249    Unbounded,
2250    MonotonicKeys,
2251    MonotonicValue,
2252    BoundedValue,
2253    Bounded,
2254}
2255
2256#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
2257pub enum SingletonBoundKind {
2258    Unbounded,
2259    Monotonic,
2260    Bounded,
2261}
2262
2263#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize)]
2264pub enum CollectionKind {
2265    Stream {
2266        bound: BoundKind,
2267        order: StreamOrder,
2268        retry: StreamRetry,
2269        element_type: DebugType,
2270    },
2271    Singleton {
2272        bound: SingletonBoundKind,
2273        element_type: DebugType,
2274    },
2275    Optional {
2276        bound: BoundKind,
2277        element_type: DebugType,
2278    },
2279    KeyedStream {
2280        bound: BoundKind,
2281        value_order: StreamOrder,
2282        value_retry: StreamRetry,
2283        key_type: DebugType,
2284        value_type: DebugType,
2285    },
2286    KeyedSingleton {
2287        bound: KeyedSingletonBoundKind,
2288        key_type: DebugType,
2289        value_type: DebugType,
2290    },
2291}
2292
2293impl CollectionKind {
2294    pub fn is_bounded(&self) -> bool {
2295        matches!(
2296            self,
2297            CollectionKind::Stream {
2298                bound: BoundKind::Bounded,
2299                ..
2300            } | CollectionKind::Singleton {
2301                bound: SingletonBoundKind::Bounded,
2302                ..
2303            } | CollectionKind::Optional {
2304                bound: BoundKind::Bounded,
2305                ..
2306            } | CollectionKind::KeyedStream {
2307                bound: BoundKind::Bounded,
2308                ..
2309            } | CollectionKind::KeyedSingleton {
2310                bound: KeyedSingletonBoundKind::Bounded,
2311                ..
2312            }
2313        )
2314    }
2315
2316    /// Returns whether this collection kind is already "strict" (TotalOrder + ExactlyOnce),
2317    /// meaning no non-determinism needs to be observed for mut closures.
2318    pub fn is_strict(&self) -> bool {
2319        match self {
2320            CollectionKind::Stream { order, retry, .. } => {
2321                *order == StreamOrder::TotalOrder && *retry == StreamRetry::ExactlyOnce
2322            }
2323            CollectionKind::KeyedStream {
2324                value_order,
2325                value_retry,
2326                ..
2327            } => {
2328                *value_order == StreamOrder::TotalOrder && *value_retry == StreamRetry::ExactlyOnce
2329            }
2330            // Singletons/Optionals/KeyedSingletons do not have observable
2331            // non-determinism other than snapshots / batching
2332            CollectionKind::Singleton { .. }
2333            | CollectionKind::Optional { .. }
2334            | CollectionKind::KeyedSingleton { .. } => true,
2335        }
2336    }
2337
2338    /// Creates a "strict" version of this kind with TotalOrder and ExactlyOnce.
2339    pub fn strict_kind(&self) -> CollectionKind {
2340        match self {
2341            CollectionKind::Stream {
2342                bound,
2343                element_type,
2344                ..
2345            } => CollectionKind::Stream {
2346                bound: bound.clone(),
2347                order: StreamOrder::TotalOrder,
2348                retry: StreamRetry::ExactlyOnce,
2349                element_type: element_type.clone(),
2350            },
2351            CollectionKind::KeyedStream {
2352                bound,
2353                key_type,
2354                value_type,
2355                ..
2356            } => CollectionKind::KeyedStream {
2357                bound: bound.clone(),
2358                value_order: StreamOrder::TotalOrder,
2359                value_retry: StreamRetry::ExactlyOnce,
2360                key_type: key_type.clone(),
2361                value_type: value_type.clone(),
2362            },
2363            other => other.clone(),
2364        }
2365    }
2366}
2367
2368#[derive(Clone, serde::Serialize)]
2369pub struct HydroIrMetadata {
2370    pub location_id: LocationId,
2371    pub collection_kind: CollectionKind,
2372    pub consistency: Option<ClusterConsistency>,
2373    pub cardinality: Option<usize>,
2374    pub tag: Option<String>,
2375    pub op: HydroIrOpMetadata,
2376}
2377
2378// HydroIrMetadata shouldn't be used to hash or compare
2379impl Hash for HydroIrMetadata {
2380    fn hash<H: Hasher>(&self, _: &mut H) {}
2381}
2382
2383impl PartialEq for HydroIrMetadata {
2384    fn eq(&self, _: &Self) -> bool {
2385        true
2386    }
2387}
2388
2389impl Eq for HydroIrMetadata {}
2390
2391impl Debug for HydroIrMetadata {
2392    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2393        f.debug_struct("HydroIrMetadata")
2394            .field("location_id", &self.location_id)
2395            .field("collection_kind", &self.collection_kind)
2396            .finish()
2397    }
2398}
2399
2400/// Metadata that is specific to the operator itself, rather than its outputs.
2401/// This is available on _both_ inner nodes and roots.
2402#[derive(Clone, serde::Serialize)]
2403pub struct HydroIrOpMetadata {
2404    #[serde(rename = "span", serialize_with = "serialize_backtrace_as_span")]
2405    pub backtrace: Backtrace,
2406    pub cpu_usage: Option<f64>,
2407    pub network_recv_cpu_usage: Option<f64>,
2408    pub id: Option<usize>,
2409}
2410
2411impl HydroIrOpMetadata {
2412    #[expect(
2413        clippy::new_without_default,
2414        reason = "explicit calls to new ensure correct backtrace bounds"
2415    )]
2416    pub fn new() -> HydroIrOpMetadata {
2417        Self::new_with_skip(1)
2418    }
2419
2420    fn new_with_skip(skip_count: usize) -> HydroIrOpMetadata {
2421        HydroIrOpMetadata {
2422            backtrace: Backtrace::get_backtrace(2 + skip_count),
2423            cpu_usage: None,
2424            network_recv_cpu_usage: None,
2425            id: None,
2426        }
2427    }
2428}
2429
2430impl Debug for HydroIrOpMetadata {
2431    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2432        f.debug_struct("HydroIrOpMetadata").finish()
2433    }
2434}
2435
2436impl Hash for HydroIrOpMetadata {
2437    fn hash<H: Hasher>(&self, _: &mut H) {}
2438}
2439
2440/// How a network channel's *sender* prepares each message before it is handed to the transport.
2441///
2442/// A channel's serialization is split into a send half ([`NetworkSend`]) and a receive half
2443/// ([`NetworkRecv`]) so that the multi-version simulation merge can reason about each side
2444/// independently (the sender fork and the receiver are separate IR nodes).
2445#[derive(Debug, Clone, Hash, serde::Serialize)]
2446pub enum NetworkSend {
2447    /// Serialization is performed within the Hydro dataflow using the provided serialize
2448    /// expression. This is how channels using [`crate::networking::Bincode`] are lowered.
2449    Custom { serialize_fn: Option<DebugExpr> },
2450    /// Serialization is left to code outside of Hydro (see [`crate::networking::Embedded`]). The
2451    /// raw `element_type` is passed through unserialized; the only transformation is converting a
2452    /// routing [`crate::location::MemberId`] (the destination cluster `tag`, when demuxing) into
2453    /// the raw `TaglessMemberId` used by the transport. Only supported by the embedded backend.
2454    ///
2455    /// Stored as structured info (rather than a pre-baked expression) so that the code can be
2456    /// synthesized in a post-IR codegen pass.
2457    Embedded {
2458        tag: Option<DebugType>,
2459        element_type: DebugType,
2460    },
2461}
2462
2463/// How a network channel's *receiver* recovers each message from the transport. See
2464/// [`NetworkSend`] for the sender half.
2465#[derive(Debug, Clone, Hash, serde::Serialize)]
2466pub enum NetworkRecv {
2467    /// Deserialization is performed within the Hydro dataflow using the provided deserialize
2468    /// expression. This is how channels using [`crate::networking::Bincode`] are lowered.
2469    Custom { deserialize_fn: Option<DebugExpr> },
2470    /// Deserialization is left to code outside of Hydro (see [`crate::networking::Embedded`]). The
2471    /// raw `element_type` is delivered to the receiver directly, with no transport `Result` to
2472    /// unwrap (the external code that produces the stream decides how to handle faults). The only
2473    /// transformation is converting a `TaglessMemberId` back into a typed
2474    /// [`crate::location::MemberId`] (the sender cluster `tag`, when the receiver is keyed by
2475    /// sender). Only supported by the embedded backend.
2476    Embedded {
2477        tag: Option<DebugType>,
2478        element_type: DebugType,
2479    },
2480}
2481
2482impl NetworkSend {
2483    /// The raw payload type flowing across the channel when serialization is left to external code,
2484    /// or [`None`] when the channel serializes internally.
2485    pub(crate) fn external_element_type(&self) -> Option<&syn::Type> {
2486        match self {
2487            NetworkSend::Custom { .. } => None,
2488            NetworkSend::Embedded { element_type, .. } => Some(&element_type.0),
2489        }
2490    }
2491}
2492
2493impl NetworkRecv {
2494    /// See [`NetworkSend::external_element_type`].
2495    pub(crate) fn external_element_type(&self) -> Option<&syn::Type> {
2496        match self {
2497            NetworkRecv::Custom { .. } => None,
2498            NetworkRecv::Embedded { element_type, .. } => Some(&element_type.0),
2499        }
2500    }
2501}
2502
2503#[cfg(feature = "build")]
2504impl NetworkSend {
2505    /// The expression applied on the sender to prepare each message for the transport, if any.
2506    pub(crate) fn pipeline(&self) -> Option<DebugExpr> {
2507        match self {
2508            NetworkSend::Custom { serialize_fn } => serialize_fn.clone(),
2509            NetworkSend::Embedded { tag, element_type } => {
2510                let root = crate::staging_util::get_this_crate();
2511                let element_type = &element_type.0;
2512                let expr: syn::Expr = if let Some(tag) = tag {
2513                    let tag = &tag.0;
2514                    parse_quote! {
2515                        #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<(#root::__staged::location::MemberId<#tag>, #element_type), _>(
2516                            |(id, data)| (id.into_tagless(), data)
2517                        )
2518                    }
2519                } else {
2520                    parse_quote! {
2521                        #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<#element_type, _>(
2522                            |data| data
2523                        )
2524                    }
2525                };
2526                Some(expr.into())
2527            }
2528        }
2529    }
2530}
2531
2532#[cfg(feature = "build")]
2533impl NetworkRecv {
2534    /// The expression applied on the receiver to recover each message from the transport, if any.
2535    pub(crate) fn pipeline(&self) -> Option<DebugExpr> {
2536        match self {
2537            NetworkRecv::Custom { deserialize_fn } => deserialize_fn.clone(),
2538            // Embedded channels hand the raw payload to the receiver directly (no transport
2539            // `Result`), so the developer's external code decides how to handle serialization
2540            // faults. The only transformation is restoring the typed `MemberId` when the receiver
2541            // is keyed by the sender.
2542            NetworkRecv::Embedded { tag, .. } => {
2543                let tag = tag.as_ref()?;
2544                let root = crate::staging_util::get_this_crate();
2545                let tag = &tag.0;
2546                let expr: syn::Expr = parse_quote! {
2547                    |(id, b)| (#root::__staged::location::MemberId::<#tag>::from_tagless(id as #root::__staged::location::TaglessMemberId), b)
2548                };
2549                Some(expr.into())
2550            }
2551        }
2552    }
2553}
2554
2555/// An intermediate node in a Hydro graph, which consumes data
2556/// from upstream nodes and emits data to downstream nodes.
2557#[derive(Debug, Hash, serde::Serialize)]
2558pub enum HydroNode {
2559    Placeholder,
2560
2561    /// Manually "casts" between two different collection kinds.
2562    ///
2563    /// Using this IR node requires special care, since it bypasses many of Hydro's core
2564    /// correctness checks. In particular, the user must ensure that every possible
2565    /// "interpretation" of the input corresponds to a distinct "interpretation" of the output,
2566    /// where an "interpretation" is a possible output of `ObserveNonDet` applied to the
2567    /// collection. This ensures that the simulator does not miss any possible outputs.
2568    Cast {
2569        inner: Box<HydroNode>,
2570        metadata: HydroIrMetadata,
2571    },
2572
2573    /// Strengthens the guarantees of a stream by non-deterministically selecting a possible
2574    /// interpretation of the input stream.
2575    ///
2576    /// In production, this simply passes through the input, but in simulation, this operator
2577    /// explicitly selects a randomized interpretation.
2578    ObserveNonDet {
2579        inner: Box<HydroNode>,
2580        trusted: bool, // if true, we do not need to simulate non-determinism
2581        metadata: HydroIrMetadata,
2582    },
2583
2584    Source {
2585        source: HydroSource,
2586        metadata: HydroIrMetadata,
2587    },
2588
2589    SingletonSource {
2590        value: DebugExpr,
2591        first_tick_only: bool,
2592        metadata: HydroIrMetadata,
2593    },
2594
2595    CycleSource {
2596        cycle_id: CycleId,
2597        metadata: HydroIrMetadata,
2598    },
2599
2600    Tee {
2601        inner: SharedNode,
2602        metadata: HydroIrMetadata,
2603    },
2604
2605    /// A reference materialization point. Wraps a SharedNode so that:
2606    /// - The pipe output delivers data to one consumer
2607    /// - `#var` references can borrow the value from the slot
2608    ///
2609    /// In DFIR codegen, emits `ident = inner_ident -> singleton()` or `-> optional()` or
2610    /// `-> handoff()` depending on `kind`.
2611    ///
2612    /// Uses the same `built_tees` dedup pattern as `Tee`.
2613    Reference {
2614        inner: SharedNode,
2615        kind: crate::handoff_ref::HandoffRefKind,
2616        access_counter: AccessCounter,
2617        metadata: HydroIrMetadata,
2618    },
2619
2620    Partition {
2621        inner: SharedNode,
2622        f: ClosureExpr,
2623        is_true: bool,
2624        metadata: HydroIrMetadata,
2625    },
2626
2627    BeginAtomic {
2628        inner: Box<HydroNode>,
2629        metadata: HydroIrMetadata,
2630    },
2631
2632    EndAtomic {
2633        inner: Box<HydroNode>,
2634        metadata: HydroIrMetadata,
2635    },
2636
2637    Batch {
2638        inner: Box<HydroNode>,
2639        metadata: HydroIrMetadata,
2640    },
2641
2642    YieldConcat {
2643        inner: Box<HydroNode>,
2644        metadata: HydroIrMetadata,
2645    },
2646
2647    Chain {
2648        first: Box<HydroNode>,
2649        second: Box<HydroNode>,
2650        metadata: HydroIrMetadata,
2651    },
2652
2653    MergeOrdered {
2654        first: Box<HydroNode>,
2655        second: Box<HydroNode>,
2656        metadata: HydroIrMetadata,
2657    },
2658
2659    ChainFirst {
2660        first: Box<HydroNode>,
2661        second: Box<HydroNode>,
2662        metadata: HydroIrMetadata,
2663    },
2664
2665    CrossProduct {
2666        left: Box<HydroNode>,
2667        right: Box<HydroNode>,
2668        metadata: HydroIrMetadata,
2669    },
2670
2671    CrossSingleton {
2672        left: Box<HydroNode>,
2673        right: Box<HydroNode>,
2674        metadata: HydroIrMetadata,
2675    },
2676
2677    Join {
2678        left: Box<HydroNode>,
2679        right: Box<HydroNode>,
2680        metadata: HydroIrMetadata,
2681    },
2682
2683    /// Asymmetric join where the right (build) side is bounded.
2684    /// The build side is accumulated (stratum-delayed) into a hash table,
2685    /// then the left (probe) side streams through preserving its ordering.
2686    JoinHalf {
2687        left: Box<HydroNode>,
2688        right: Box<HydroNode>,
2689        metadata: HydroIrMetadata,
2690    },
2691
2692    Difference {
2693        pos: Box<HydroNode>,
2694        neg: Box<HydroNode>,
2695        metadata: HydroIrMetadata,
2696    },
2697
2698    AntiJoin {
2699        pos: Box<HydroNode>,
2700        neg: Box<HydroNode>,
2701        metadata: HydroIrMetadata,
2702    },
2703
2704    ResolveFutures {
2705        input: Box<HydroNode>,
2706        metadata: HydroIrMetadata,
2707    },
2708    ResolveFuturesBlocking {
2709        input: Box<HydroNode>,
2710        metadata: HydroIrMetadata,
2711    },
2712    ResolveFuturesOrdered {
2713        input: Box<HydroNode>,
2714        metadata: HydroIrMetadata,
2715    },
2716
2717    Map {
2718        f: ClosureExpr,
2719        input: Box<HydroNode>,
2720        metadata: HydroIrMetadata,
2721    },
2722    FlatMap {
2723        f: ClosureExpr,
2724        input: Box<HydroNode>,
2725        metadata: HydroIrMetadata,
2726    },
2727    FlatMapStreamBlocking {
2728        f: ClosureExpr,
2729        input: Box<HydroNode>,
2730        metadata: HydroIrMetadata,
2731    },
2732    Filter {
2733        f: ClosureExpr,
2734        input: Box<HydroNode>,
2735        metadata: HydroIrMetadata,
2736    },
2737    FilterMap {
2738        f: ClosureExpr,
2739        input: Box<HydroNode>,
2740        metadata: HydroIrMetadata,
2741    },
2742
2743    DeferTick {
2744        input: Box<HydroNode>,
2745        metadata: HydroIrMetadata,
2746    },
2747    Enumerate {
2748        input: Box<HydroNode>,
2749        metadata: HydroIrMetadata,
2750    },
2751    Inspect {
2752        f: ClosureExpr,
2753        input: Box<HydroNode>,
2754        metadata: HydroIrMetadata,
2755    },
2756
2757    Unique {
2758        input: Box<HydroNode>,
2759        metadata: HydroIrMetadata,
2760    },
2761
2762    Sort {
2763        input: Box<HydroNode>,
2764        metadata: HydroIrMetadata,
2765    },
2766    Fold {
2767        init: ClosureExpr,
2768        acc: ClosureExpr,
2769        input: Box<HydroNode>,
2770        metadata: HydroIrMetadata,
2771    },
2772
2773    Scan {
2774        init: ClosureExpr,
2775        acc: ClosureExpr,
2776        input: Box<HydroNode>,
2777        metadata: HydroIrMetadata,
2778    },
2779    ScanAsyncBlocking {
2780        init: ClosureExpr,
2781        acc: ClosureExpr,
2782        input: Box<HydroNode>,
2783        metadata: HydroIrMetadata,
2784    },
2785    FoldKeyed {
2786        init: ClosureExpr,
2787        acc: ClosureExpr,
2788        input: Box<HydroNode>,
2789        metadata: HydroIrMetadata,
2790    },
2791
2792    Reduce {
2793        f: ClosureExpr,
2794        input: Box<HydroNode>,
2795        metadata: HydroIrMetadata,
2796    },
2797    ReduceKeyed {
2798        f: ClosureExpr,
2799        input: Box<HydroNode>,
2800        metadata: HydroIrMetadata,
2801    },
2802    ReduceKeyedWatermark {
2803        f: ClosureExpr,
2804        input: Box<HydroNode>,
2805        watermark: Box<HydroNode>,
2806        metadata: HydroIrMetadata,
2807    },
2808
2809    Network {
2810        name: Option<String>,
2811        networking_info: crate::networking::NetworkingInfo,
2812        serialize: NetworkSend,
2813        deserialize: NetworkRecv,
2814        instantiate_fn: DebugInstantiate,
2815        input: Box<HydroNode>,
2816        metadata: HydroIrMetadata,
2817    },
2818
2819    VersionedNetworkFork {
2820        channel_id: u32,
2821        channel_name: String,
2822        senders: Vec<(u32, Box<HydroNode>, NetworkSend)>,
2823        metadata: HydroIrMetadata,
2824    },
2825
2826    VersionedNetwork {
2827        fork: SharedNode,
2828        version: u32,
2829        deserialize: NetworkRecv,
2830        metadata: HydroIrMetadata,
2831    },
2832
2833    ExternalInput {
2834        from_external_key: LocationKey,
2835        from_port_id: ExternalPortId,
2836        from_many: bool,
2837        codec_type: DebugType,
2838        #[serde(skip)]
2839        port_hint: NetworkHint,
2840        instantiate_fn: DebugInstantiate,
2841        deserialize_fn: Option<DebugExpr>,
2842        metadata: HydroIrMetadata,
2843    },
2844
2845    Counter {
2846        tag: String,
2847        duration: DebugExpr,
2848        prefix: String,
2849        input: Box<HydroNode>,
2850        metadata: HydroIrMetadata,
2851    },
2852
2853    AssertIsConsistent {
2854        inner: Box<HydroNode>,
2855        trusted: bool,
2856        metadata: HydroIrMetadata,
2857    },
2858
2859    UnboundSingleton {
2860        inner: Box<HydroNode>,
2861        metadata: HydroIrMetadata,
2862    },
2863}
2864
2865pub type SeenSharedNodes = HashMap<*const RefCell<HydroNode>, Rc<RefCell<HydroNode>>>;
2866pub type SeenSharedNodeLocations = HashMap<*const RefCell<HydroNode>, LocationId>;
2867
2868/// If `f` has a mut singleton ref and `in_kind` is non-strict, emits an
2869/// `observe_for_mut` node and returns the new ident. Otherwise returns
2870/// `in_ident` unchanged. Always consumes a stmt_id when applicable.
2871#[cfg(feature = "build")]
2872fn maybe_observe_for_mut(
2873    f: &ClosureExpr,
2874    in_ident: syn::Ident,
2875    in_location: &LocationId,
2876    in_kind: &CollectionKind,
2877    op_meta: &HydroIrOpMetadata,
2878    builders_or_callback: &mut BuildersOrCallback<
2879        impl FnMut(&mut HydroRoot, &mut crate::Counter<StmtId>),
2880        impl FnMut(&mut HydroNode, &mut crate::Counter<StmtId>),
2881    >,
2882    next_stmt_id: &mut crate::Counter<StmtId>,
2883) -> syn::Ident {
2884    if f.has_mut_ref() && !in_kind.is_strict() {
2885        let observe_stmt_id = next_stmt_id.get_and_increment();
2886        let observe_ident =
2887            syn::Ident::new(&format!("stream_{}", observe_stmt_id), Span::call_site());
2888        if let BuildersOrCallback::Builders(graph_builders) = builders_or_callback {
2889            graph_builders.observe_for_mut(in_location, in_ident, in_kind, &observe_ident, op_meta);
2890        }
2891        observe_ident
2892    } else {
2893        in_ident
2894    }
2895}
2896
2897impl HydroNode {
2898    pub fn transform_bottom_up(
2899        &mut self,
2900        transform: &mut impl FnMut(&mut HydroNode),
2901        seen_tees: &mut SeenSharedNodes,
2902        check_well_formed: bool,
2903    ) {
2904        self.transform_children(
2905            |n, s| n.transform_bottom_up(transform, s, check_well_formed),
2906            seen_tees,
2907        );
2908
2909        transform(self);
2910
2911        let self_location = self.metadata().location_id.root();
2912
2913        if check_well_formed {
2914            match &*self {
2915                HydroNode::Network { .. } => {}
2916                _ => {
2917                    self.input_metadata().iter().for_each(|i| {
2918                        if i.location_id.root() != self_location {
2919                            panic!(
2920                                "Mismatching IR locations, child: {:?} ({:?}) of: {:?} ({:?})",
2921                                i,
2922                                i.location_id.root(),
2923                                self,
2924                                self_location
2925                            )
2926                        }
2927                    });
2928                }
2929            }
2930        }
2931    }
2932
2933    #[inline(always)]
2934    pub fn transform_children(
2935        &mut self,
2936        mut transform: impl FnMut(&mut HydroNode, &mut SeenSharedNodes),
2937        seen_tees: &mut SeenSharedNodes,
2938    ) {
2939        match self {
2940            HydroNode::Placeholder => {
2941                panic!();
2942            }
2943
2944            HydroNode::Source { .. }
2945            | HydroNode::SingletonSource { .. }
2946            | HydroNode::CycleSource { .. }
2947            | HydroNode::ExternalInput { .. } => {}
2948
2949            HydroNode::Tee { inner, .. } | HydroNode::Reference { inner, .. } => {
2950                if let Some(transformed) = seen_tees.get(&inner.as_ptr()) {
2951                    *inner = SharedNode(transformed.clone());
2952                } else {
2953                    let transformed_cell = Rc::new(RefCell::new(HydroNode::Placeholder));
2954                    seen_tees.insert(inner.as_ptr(), transformed_cell.clone());
2955                    let mut orig = inner.0.replace(HydroNode::Placeholder);
2956                    transform(&mut orig, seen_tees);
2957                    *transformed_cell.borrow_mut() = orig;
2958                    *inner = SharedNode(transformed_cell);
2959                }
2960            }
2961
2962            HydroNode::Partition { inner, f, .. } => {
2963                if let Some(transformed) = seen_tees.get(&inner.as_ptr()) {
2964                    *inner = SharedNode(transformed.clone());
2965                } else {
2966                    f.transform_children(&mut transform, seen_tees);
2967                    let transformed_cell = Rc::new(RefCell::new(HydroNode::Placeholder));
2968                    seen_tees.insert(inner.as_ptr(), transformed_cell.clone());
2969                    let mut orig = inner.0.replace(HydroNode::Placeholder);
2970                    transform(&mut orig, seen_tees);
2971                    *transformed_cell.borrow_mut() = orig;
2972                    *inner = SharedNode(transformed_cell);
2973                }
2974            }
2975
2976            HydroNode::Cast { inner, .. }
2977            | HydroNode::ObserveNonDet { inner, .. }
2978            | HydroNode::BeginAtomic { inner, .. }
2979            | HydroNode::EndAtomic { inner, .. }
2980            | HydroNode::Batch { inner, .. }
2981            | HydroNode::YieldConcat { inner, .. }
2982            | HydroNode::UnboundSingleton { inner, .. }
2983            | HydroNode::AssertIsConsistent { inner, .. } => {
2984                transform(inner.as_mut(), seen_tees);
2985            }
2986
2987            HydroNode::Chain { first, second, .. } => {
2988                transform(first.as_mut(), seen_tees);
2989                transform(second.as_mut(), seen_tees);
2990            }
2991
2992            HydroNode::MergeOrdered { first, second, .. } => {
2993                transform(first.as_mut(), seen_tees);
2994                transform(second.as_mut(), seen_tees);
2995            }
2996
2997            HydroNode::ChainFirst { first, second, .. } => {
2998                transform(first.as_mut(), seen_tees);
2999                transform(second.as_mut(), seen_tees);
3000            }
3001
3002            HydroNode::CrossSingleton { left, right, .. }
3003            | HydroNode::CrossProduct { left, right, .. }
3004            | HydroNode::Join { left, right, .. }
3005            | HydroNode::JoinHalf { left, right, .. } => {
3006                transform(left.as_mut(), seen_tees);
3007                transform(right.as_mut(), seen_tees);
3008            }
3009
3010            HydroNode::Difference { pos, neg, .. } | HydroNode::AntiJoin { pos, neg, .. } => {
3011                transform(pos.as_mut(), seen_tees);
3012                transform(neg.as_mut(), seen_tees);
3013            }
3014
3015            HydroNode::Map { f, input, .. } => {
3016                f.transform_children(&mut transform, seen_tees);
3017                transform(input.as_mut(), seen_tees);
3018            }
3019            HydroNode::FlatMap { f, input, .. }
3020            | HydroNode::FlatMapStreamBlocking { f, input, .. }
3021            | HydroNode::Filter { f, input, .. }
3022            | HydroNode::FilterMap { f, input, .. }
3023            | HydroNode::Inspect { f, input, .. }
3024            | HydroNode::Reduce { f, input, .. }
3025            | HydroNode::ReduceKeyed { f, input, .. } => {
3026                f.transform_children(&mut transform, seen_tees);
3027                transform(input.as_mut(), seen_tees);
3028            }
3029            HydroNode::ReduceKeyedWatermark {
3030                f,
3031                input,
3032                watermark,
3033                ..
3034            } => {
3035                f.transform_children(&mut transform, seen_tees);
3036                transform(input.as_mut(), seen_tees);
3037                transform(watermark.as_mut(), seen_tees);
3038            }
3039            HydroNode::Fold {
3040                init, acc, input, ..
3041            }
3042            | HydroNode::Scan {
3043                init, acc, input, ..
3044            }
3045            | HydroNode::ScanAsyncBlocking {
3046                init, acc, input, ..
3047            }
3048            | HydroNode::FoldKeyed {
3049                init, acc, input, ..
3050            } => {
3051                init.transform_children(&mut transform, seen_tees);
3052                acc.transform_children(&mut transform, seen_tees);
3053                transform(input.as_mut(), seen_tees);
3054            }
3055            HydroNode::ResolveFutures { input, .. }
3056            | HydroNode::ResolveFuturesBlocking { input, .. }
3057            | HydroNode::ResolveFuturesOrdered { input, .. }
3058            | HydroNode::Sort { input, .. }
3059            | HydroNode::DeferTick { input, .. }
3060            | HydroNode::Enumerate { input, .. }
3061            | HydroNode::Unique { input, .. }
3062            | HydroNode::Network { input, .. }
3063            | HydroNode::Counter { input, .. } => {
3064                transform(input.as_mut(), seen_tees);
3065            }
3066
3067            HydroNode::VersionedNetworkFork { senders, .. } => {
3068                for (_version, sender, _serialize) in senders.iter_mut() {
3069                    transform(sender.as_mut(), seen_tees);
3070                }
3071            }
3072
3073            HydroNode::VersionedNetwork { fork, .. } => {
3074                if let Some(transformed) = seen_tees.get(&fork.as_ptr()) {
3075                    *fork = SharedNode(transformed.clone());
3076                } else {
3077                    let transformed_cell = Rc::new(RefCell::new(HydroNode::Placeholder));
3078                    seen_tees.insert(fork.as_ptr(), transformed_cell.clone());
3079                    let mut orig = fork.0.replace(HydroNode::Placeholder);
3080                    transform(&mut orig, seen_tees);
3081                    *transformed_cell.borrow_mut() = orig;
3082                    *fork = SharedNode(transformed_cell);
3083                }
3084            }
3085        }
3086    }
3087
3088    pub fn deep_clone(&self, seen_tees: &mut SeenSharedNodes) -> HydroNode {
3089        match self {
3090            HydroNode::Placeholder => HydroNode::Placeholder,
3091            HydroNode::Cast { inner, metadata } => HydroNode::Cast {
3092                inner: Box::new(inner.deep_clone(seen_tees)),
3093                metadata: metadata.clone(),
3094            },
3095            HydroNode::UnboundSingleton { inner, metadata } => HydroNode::UnboundSingleton {
3096                inner: Box::new(inner.deep_clone(seen_tees)),
3097                metadata: metadata.clone(),
3098            },
3099            HydroNode::ObserveNonDet {
3100                inner,
3101                trusted,
3102                metadata,
3103            } => HydroNode::ObserveNonDet {
3104                inner: Box::new(inner.deep_clone(seen_tees)),
3105                trusted: *trusted,
3106                metadata: metadata.clone(),
3107            },
3108            HydroNode::AssertIsConsistent {
3109                inner,
3110                trusted,
3111                metadata,
3112            } => HydroNode::AssertIsConsistent {
3113                inner: Box::new(inner.deep_clone(seen_tees)),
3114                trusted: *trusted,
3115                metadata: metadata.clone(),
3116            },
3117            HydroNode::Source { source, metadata } => HydroNode::Source {
3118                source: source.clone(),
3119                metadata: metadata.clone(),
3120            },
3121            HydroNode::SingletonSource {
3122                value,
3123                first_tick_only,
3124                metadata,
3125            } => HydroNode::SingletonSource {
3126                value: value.clone(),
3127                first_tick_only: *first_tick_only,
3128                metadata: metadata.clone(),
3129            },
3130            HydroNode::CycleSource { cycle_id, metadata } => HydroNode::CycleSource {
3131                cycle_id: *cycle_id,
3132                metadata: metadata.clone(),
3133            },
3134            HydroNode::Tee { inner, metadata }
3135            | HydroNode::Reference {
3136                inner, metadata, ..
3137            } => {
3138                let cloned_inner = if let Some(transformed) = seen_tees.get(&inner.as_ptr()) {
3139                    SharedNode(transformed.clone())
3140                } else {
3141                    let new_rc = Rc::new(RefCell::new(HydroNode::Placeholder));
3142                    seen_tees.insert(inner.as_ptr(), new_rc.clone());
3143                    let cloned = inner.0.borrow().deep_clone(seen_tees);
3144                    *new_rc.borrow_mut() = cloned;
3145                    SharedNode(new_rc)
3146                };
3147                if let HydroNode::Reference {
3148                    kind,
3149                    access_counter,
3150                    ..
3151                } = self
3152                {
3153                    HydroNode::Reference {
3154                        inner: cloned_inner,
3155                        kind: *kind,
3156                        access_counter: access_counter.freeze(),
3157                        metadata: metadata.clone(),
3158                    }
3159                } else {
3160                    HydroNode::Tee {
3161                        inner: cloned_inner,
3162                        metadata: metadata.clone(),
3163                    }
3164                }
3165            }
3166            HydroNode::Partition {
3167                inner,
3168                f,
3169                is_true,
3170                metadata,
3171            } => {
3172                if let Some(transformed) = seen_tees.get(&inner.as_ptr()) {
3173                    HydroNode::Partition {
3174                        inner: SharedNode(transformed.clone()),
3175                        f: f.deep_clone(seen_tees),
3176                        is_true: *is_true,
3177                        metadata: metadata.clone(),
3178                    }
3179                } else {
3180                    let new_rc = Rc::new(RefCell::new(HydroNode::Placeholder));
3181                    seen_tees.insert(inner.as_ptr(), new_rc.clone());
3182                    let cloned = inner.0.borrow().deep_clone(seen_tees);
3183                    *new_rc.borrow_mut() = cloned;
3184                    HydroNode::Partition {
3185                        inner: SharedNode(new_rc),
3186                        f: f.deep_clone(seen_tees),
3187                        is_true: *is_true,
3188                        metadata: metadata.clone(),
3189                    }
3190                }
3191            }
3192            HydroNode::YieldConcat { inner, metadata } => HydroNode::YieldConcat {
3193                inner: Box::new(inner.deep_clone(seen_tees)),
3194                metadata: metadata.clone(),
3195            },
3196            HydroNode::BeginAtomic { inner, metadata } => HydroNode::BeginAtomic {
3197                inner: Box::new(inner.deep_clone(seen_tees)),
3198                metadata: metadata.clone(),
3199            },
3200            HydroNode::EndAtomic { inner, metadata } => HydroNode::EndAtomic {
3201                inner: Box::new(inner.deep_clone(seen_tees)),
3202                metadata: metadata.clone(),
3203            },
3204            HydroNode::Batch { inner, metadata } => HydroNode::Batch {
3205                inner: Box::new(inner.deep_clone(seen_tees)),
3206                metadata: metadata.clone(),
3207            },
3208            HydroNode::Chain {
3209                first,
3210                second,
3211                metadata,
3212            } => HydroNode::Chain {
3213                first: Box::new(first.deep_clone(seen_tees)),
3214                second: Box::new(second.deep_clone(seen_tees)),
3215                metadata: metadata.clone(),
3216            },
3217            HydroNode::MergeOrdered {
3218                first,
3219                second,
3220                metadata,
3221            } => HydroNode::MergeOrdered {
3222                first: Box::new(first.deep_clone(seen_tees)),
3223                second: Box::new(second.deep_clone(seen_tees)),
3224                metadata: metadata.clone(),
3225            },
3226            HydroNode::ChainFirst {
3227                first,
3228                second,
3229                metadata,
3230            } => HydroNode::ChainFirst {
3231                first: Box::new(first.deep_clone(seen_tees)),
3232                second: Box::new(second.deep_clone(seen_tees)),
3233                metadata: metadata.clone(),
3234            },
3235            HydroNode::CrossProduct {
3236                left,
3237                right,
3238                metadata,
3239            } => HydroNode::CrossProduct {
3240                left: Box::new(left.deep_clone(seen_tees)),
3241                right: Box::new(right.deep_clone(seen_tees)),
3242                metadata: metadata.clone(),
3243            },
3244            HydroNode::CrossSingleton {
3245                left,
3246                right,
3247                metadata,
3248            } => HydroNode::CrossSingleton {
3249                left: Box::new(left.deep_clone(seen_tees)),
3250                right: Box::new(right.deep_clone(seen_tees)),
3251                metadata: metadata.clone(),
3252            },
3253            HydroNode::Join {
3254                left,
3255                right,
3256                metadata,
3257            } => HydroNode::Join {
3258                left: Box::new(left.deep_clone(seen_tees)),
3259                right: Box::new(right.deep_clone(seen_tees)),
3260                metadata: metadata.clone(),
3261            },
3262            HydroNode::JoinHalf {
3263                left,
3264                right,
3265                metadata,
3266            } => HydroNode::JoinHalf {
3267                left: Box::new(left.deep_clone(seen_tees)),
3268                right: Box::new(right.deep_clone(seen_tees)),
3269                metadata: metadata.clone(),
3270            },
3271            HydroNode::Difference { pos, neg, metadata } => HydroNode::Difference {
3272                pos: Box::new(pos.deep_clone(seen_tees)),
3273                neg: Box::new(neg.deep_clone(seen_tees)),
3274                metadata: metadata.clone(),
3275            },
3276            HydroNode::AntiJoin { pos, neg, metadata } => HydroNode::AntiJoin {
3277                pos: Box::new(pos.deep_clone(seen_tees)),
3278                neg: Box::new(neg.deep_clone(seen_tees)),
3279                metadata: metadata.clone(),
3280            },
3281            HydroNode::ResolveFutures { input, metadata } => HydroNode::ResolveFutures {
3282                input: Box::new(input.deep_clone(seen_tees)),
3283                metadata: metadata.clone(),
3284            },
3285            HydroNode::ResolveFuturesBlocking { input, metadata } => {
3286                HydroNode::ResolveFuturesBlocking {
3287                    input: Box::new(input.deep_clone(seen_tees)),
3288                    metadata: metadata.clone(),
3289                }
3290            }
3291            HydroNode::ResolveFuturesOrdered { input, metadata } => {
3292                HydroNode::ResolveFuturesOrdered {
3293                    input: Box::new(input.deep_clone(seen_tees)),
3294                    metadata: metadata.clone(),
3295                }
3296            }
3297            HydroNode::Map { f, input, metadata } => HydroNode::Map {
3298                f: f.deep_clone(seen_tees),
3299                input: Box::new(input.deep_clone(seen_tees)),
3300                metadata: metadata.clone(),
3301            },
3302            HydroNode::FlatMap { f, input, metadata } => HydroNode::FlatMap {
3303                f: f.deep_clone(seen_tees),
3304                input: Box::new(input.deep_clone(seen_tees)),
3305                metadata: metadata.clone(),
3306            },
3307            HydroNode::FlatMapStreamBlocking { f, input, metadata } => {
3308                HydroNode::FlatMapStreamBlocking {
3309                    f: f.deep_clone(seen_tees),
3310                    input: Box::new(input.deep_clone(seen_tees)),
3311                    metadata: metadata.clone(),
3312                }
3313            }
3314            HydroNode::Filter { f, input, metadata } => HydroNode::Filter {
3315                f: f.deep_clone(seen_tees),
3316                input: Box::new(input.deep_clone(seen_tees)),
3317                metadata: metadata.clone(),
3318            },
3319            HydroNode::FilterMap { f, input, metadata } => HydroNode::FilterMap {
3320                f: f.deep_clone(seen_tees),
3321                input: Box::new(input.deep_clone(seen_tees)),
3322                metadata: metadata.clone(),
3323            },
3324            HydroNode::DeferTick { input, metadata } => HydroNode::DeferTick {
3325                input: Box::new(input.deep_clone(seen_tees)),
3326                metadata: metadata.clone(),
3327            },
3328            HydroNode::Enumerate { input, metadata } => HydroNode::Enumerate {
3329                input: Box::new(input.deep_clone(seen_tees)),
3330                metadata: metadata.clone(),
3331            },
3332            HydroNode::Inspect { f, input, metadata } => HydroNode::Inspect {
3333                f: f.deep_clone(seen_tees),
3334                input: Box::new(input.deep_clone(seen_tees)),
3335                metadata: metadata.clone(),
3336            },
3337            HydroNode::Unique { input, metadata } => HydroNode::Unique {
3338                input: Box::new(input.deep_clone(seen_tees)),
3339                metadata: metadata.clone(),
3340            },
3341            HydroNode::Sort { input, metadata } => HydroNode::Sort {
3342                input: Box::new(input.deep_clone(seen_tees)),
3343                metadata: metadata.clone(),
3344            },
3345            HydroNode::Fold {
3346                init,
3347                acc,
3348                input,
3349                metadata,
3350            } => HydroNode::Fold {
3351                init: init.deep_clone(seen_tees),
3352                acc: acc.deep_clone(seen_tees),
3353                input: Box::new(input.deep_clone(seen_tees)),
3354                metadata: metadata.clone(),
3355            },
3356            HydroNode::Scan {
3357                init,
3358                acc,
3359                input,
3360                metadata,
3361            } => HydroNode::Scan {
3362                init: init.deep_clone(seen_tees),
3363                acc: acc.deep_clone(seen_tees),
3364                input: Box::new(input.deep_clone(seen_tees)),
3365                metadata: metadata.clone(),
3366            },
3367            HydroNode::ScanAsyncBlocking {
3368                init,
3369                acc,
3370                input,
3371                metadata,
3372            } => HydroNode::ScanAsyncBlocking {
3373                init: init.deep_clone(seen_tees),
3374                acc: acc.deep_clone(seen_tees),
3375                input: Box::new(input.deep_clone(seen_tees)),
3376                metadata: metadata.clone(),
3377            },
3378            HydroNode::FoldKeyed {
3379                init,
3380                acc,
3381                input,
3382                metadata,
3383            } => HydroNode::FoldKeyed {
3384                init: init.deep_clone(seen_tees),
3385                acc: acc.deep_clone(seen_tees),
3386                input: Box::new(input.deep_clone(seen_tees)),
3387                metadata: metadata.clone(),
3388            },
3389            HydroNode::ReduceKeyedWatermark {
3390                f,
3391                input,
3392                watermark,
3393                metadata,
3394            } => HydroNode::ReduceKeyedWatermark {
3395                f: f.deep_clone(seen_tees),
3396                input: Box::new(input.deep_clone(seen_tees)),
3397                watermark: Box::new(watermark.deep_clone(seen_tees)),
3398                metadata: metadata.clone(),
3399            },
3400            HydroNode::Reduce { f, input, metadata } => HydroNode::Reduce {
3401                f: f.deep_clone(seen_tees),
3402                input: Box::new(input.deep_clone(seen_tees)),
3403                metadata: metadata.clone(),
3404            },
3405            HydroNode::ReduceKeyed { f, input, metadata } => HydroNode::ReduceKeyed {
3406                f: f.deep_clone(seen_tees),
3407                input: Box::new(input.deep_clone(seen_tees)),
3408                metadata: metadata.clone(),
3409            },
3410            HydroNode::Network {
3411                name,
3412                networking_info,
3413                serialize,
3414                deserialize,
3415                instantiate_fn,
3416                input,
3417                metadata,
3418            } => HydroNode::Network {
3419                name: name.clone(),
3420                networking_info: networking_info.clone(),
3421                serialize: serialize.clone(),
3422                deserialize: deserialize.clone(),
3423                instantiate_fn: instantiate_fn.clone(),
3424                input: Box::new(input.deep_clone(seen_tees)),
3425                metadata: metadata.clone(),
3426            },
3427            HydroNode::ExternalInput {
3428                from_external_key,
3429                from_port_id,
3430                from_many,
3431                codec_type,
3432                port_hint,
3433                instantiate_fn,
3434                deserialize_fn,
3435                metadata,
3436            } => HydroNode::ExternalInput {
3437                from_external_key: *from_external_key,
3438                from_port_id: *from_port_id,
3439                from_many: *from_many,
3440                codec_type: codec_type.clone(),
3441                port_hint: *port_hint,
3442                instantiate_fn: instantiate_fn.clone(),
3443                deserialize_fn: deserialize_fn.clone(),
3444                metadata: metadata.clone(),
3445            },
3446            HydroNode::Counter {
3447                tag,
3448                duration,
3449                prefix,
3450                input,
3451                metadata,
3452            } => HydroNode::Counter {
3453                tag: tag.clone(),
3454                duration: duration.clone(),
3455                prefix: prefix.clone(),
3456                input: Box::new(input.deep_clone(seen_tees)),
3457                metadata: metadata.clone(),
3458            },
3459            HydroNode::VersionedNetworkFork {
3460                channel_id,
3461                channel_name,
3462                senders,
3463                metadata,
3464            } => HydroNode::VersionedNetworkFork {
3465                channel_id: *channel_id,
3466                channel_name: channel_name.clone(),
3467                senders: senders
3468                    .iter()
3469                    .map(|(version, sender, serialize)| {
3470                        (
3471                            *version,
3472                            Box::new(sender.deep_clone(seen_tees)),
3473                            serialize.clone(),
3474                        )
3475                    })
3476                    .collect(),
3477                metadata: metadata.clone(),
3478            },
3479            HydroNode::VersionedNetwork {
3480                fork,
3481                version,
3482                deserialize,
3483                metadata,
3484            } => {
3485                let cloned_fork = if let Some(transformed) = seen_tees.get(&fork.as_ptr()) {
3486                    SharedNode(transformed.clone())
3487                } else {
3488                    let new_rc = Rc::new(RefCell::new(HydroNode::Placeholder));
3489                    seen_tees.insert(fork.as_ptr(), new_rc.clone());
3490                    let cloned = fork.0.borrow().deep_clone(seen_tees);
3491                    *new_rc.borrow_mut() = cloned;
3492                    SharedNode(new_rc)
3493                };
3494                HydroNode::VersionedNetwork {
3495                    fork: cloned_fork,
3496                    version: *version,
3497                    deserialize: deserialize.clone(),
3498                    metadata: metadata.clone(),
3499                }
3500            }
3501        }
3502    }
3503
3504    #[cfg(feature = "build")]
3505    pub fn emit_core(
3506        &mut self,
3507        builders_or_callback: &mut BuildersOrCallback<
3508            impl FnMut(&mut HydroRoot, &mut crate::Counter<StmtId>),
3509            impl FnMut(&mut HydroNode, &mut crate::Counter<StmtId>),
3510        >,
3511        seen_tees: &mut SeenSharedNodes,
3512        built_tees: &mut HashMap<*const RefCell<HydroNode>, Vec<syn::Ident>>,
3513        next_stmt_id: &mut crate::Counter<StmtId>,
3514        fold_hooked_idents: &mut HashSet<String>,
3515    ) -> syn::Ident {
3516        let mut ident_stack: Vec<syn::Ident> = Vec::new();
3517
3518        self.transform_bottom_up(
3519            &mut |node: &mut HydroNode| {
3520                let out_location = node.metadata().location_id.clone();
3521                match node {
3522                    HydroNode::Placeholder => {
3523                        panic!()
3524                    }
3525
3526                    HydroNode::Cast { .. } => {
3527                        // Cast passes through the input ident unchanged
3528                        // The input ident is already on the stack from processing the child
3529                        let _ = next_stmt_id.get_and_increment();
3530                        match builders_or_callback {
3531                            BuildersOrCallback::Builders(_) => {}
3532                            BuildersOrCallback::Callback(_, node_callback) => {
3533                                node_callback(node, next_stmt_id);
3534                            }
3535                        }
3536                        // input_ident stays on stack as output
3537                    }
3538
3539                    HydroNode::UnboundSingleton { .. } => {
3540                        let inner_ident = ident_stack.pop().unwrap();
3541
3542                        let stmt_id = next_stmt_id.get_and_increment();
3543                        let out_ident =
3544                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3545
3546                        match builders_or_callback {
3547                            BuildersOrCallback::Builders(graph_builders) => {
3548                                if graph_builders.singleton_intermediates() {
3549                                    let builder = graph_builders.get_dfir_mut(&out_location);
3550                                    builder.add_dfir(
3551                                        parse_quote! {
3552                                            #out_ident = #inner_ident;
3553                                        },
3554                                        None,
3555                                        None,
3556                                    );
3557                                } else {
3558                                    let builder = graph_builders.get_dfir_mut(&out_location);
3559                                    builder.add_dfir(
3560                                        parse_quote! {
3561                                            #out_ident = #inner_ident -> persist::<'static>();
3562                                        },
3563                                        None,
3564                                        None,
3565                                    );
3566                                }
3567                            }
3568                            BuildersOrCallback::Callback(_, node_callback) => {
3569                                node_callback(node, next_stmt_id);
3570                            }
3571                        }
3572
3573                        ident_stack.push(out_ident);
3574                    }
3575
3576                    HydroNode::AssertIsConsistent { inner, trusted, .. } => {
3577                        let inner_ident = ident_stack.pop().unwrap();
3578
3579                        let stmt_id = next_stmt_id.get_and_increment();
3580                        let out_ident =
3581                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3582
3583                        match builders_or_callback {
3584                            BuildersOrCallback::Builders(graph_builders) => {
3585                                graph_builders.assert_is_consistent(
3586                                    *trusted,
3587                                    &inner.metadata().location_id,
3588                                    inner_ident,
3589                                    &out_ident,
3590                                );
3591                            }
3592                            BuildersOrCallback::Callback(_, node_callback) => {
3593                                node_callback(node, next_stmt_id);
3594                            }
3595                        }
3596
3597                        ident_stack.push(out_ident);
3598                    }
3599
3600                    HydroNode::ObserveNonDet {
3601                        inner,
3602                        trusted,
3603                        metadata,
3604                        ..
3605                    } => {
3606                        let inner_ident = ident_stack.pop().unwrap();
3607
3608                        let stmt_id = next_stmt_id.get_and_increment();
3609                        let observe_ident =
3610                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3611
3612                        match builders_or_callback {
3613                            BuildersOrCallback::Builders(graph_builders) => {
3614                                graph_builders.observe_nondet(
3615                                    *trusted,
3616                                    &inner.metadata().location_id,
3617                                    inner_ident,
3618                                    &inner.metadata().collection_kind,
3619                                    &observe_ident,
3620                                    &metadata.collection_kind,
3621                                    &metadata.op,
3622                                );
3623                            }
3624                            BuildersOrCallback::Callback(_, node_callback) => {
3625                                node_callback(node, next_stmt_id);
3626                            }
3627                        }
3628
3629                        ident_stack.push(observe_ident);
3630                    }
3631
3632                    HydroNode::Batch {
3633                        inner, metadata, ..
3634                    } => {
3635                        let inner_ident = ident_stack.pop().unwrap();
3636
3637                        let stmt_id = next_stmt_id.get_and_increment();
3638                        let batch_ident =
3639                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3640
3641                        match builders_or_callback {
3642                            BuildersOrCallback::Builders(graph_builders) => {
3643                                graph_builders.batch(
3644                                    inner_ident,
3645                                    &inner.metadata().location_id,
3646                                    &inner.metadata().collection_kind,
3647                                    &batch_ident,
3648                                    &out_location,
3649                                    &metadata.op,
3650                                    fold_hooked_idents,
3651                                );
3652                            }
3653                            BuildersOrCallback::Callback(_, node_callback) => {
3654                                node_callback(node, next_stmt_id);
3655                            }
3656                        }
3657
3658                        ident_stack.push(batch_ident);
3659                    }
3660
3661                    HydroNode::YieldConcat { inner, .. } => {
3662                        let inner_ident = ident_stack.pop().unwrap();
3663
3664                        let stmt_id = next_stmt_id.get_and_increment();
3665                        let yield_ident =
3666                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3667
3668                        match builders_or_callback {
3669                            BuildersOrCallback::Builders(graph_builders) => {
3670                                graph_builders.yield_from_tick(
3671                                    inner_ident,
3672                                    &inner.metadata().location_id,
3673                                    &inner.metadata().collection_kind,
3674                                    &yield_ident,
3675                                    &out_location,
3676                                );
3677                            }
3678                            BuildersOrCallback::Callback(_, node_callback) => {
3679                                node_callback(node, next_stmt_id);
3680                            }
3681                        }
3682
3683                        ident_stack.push(yield_ident);
3684                    }
3685
3686                    HydroNode::BeginAtomic { inner, metadata } => {
3687                        let inner_ident = ident_stack.pop().unwrap();
3688
3689                        let stmt_id = next_stmt_id.get_and_increment();
3690                        let begin_ident =
3691                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3692
3693                        match builders_or_callback {
3694                            BuildersOrCallback::Builders(graph_builders) => {
3695                                graph_builders.begin_atomic(
3696                                    inner_ident,
3697                                    &inner.metadata().location_id,
3698                                    &inner.metadata().collection_kind,
3699                                    &begin_ident,
3700                                    &out_location,
3701                                    &metadata.op,
3702                                );
3703                            }
3704                            BuildersOrCallback::Callback(_, node_callback) => {
3705                                node_callback(node, next_stmt_id);
3706                            }
3707                        }
3708
3709                        ident_stack.push(begin_ident);
3710                    }
3711
3712                    HydroNode::EndAtomic { inner, .. } => {
3713                        let inner_ident = ident_stack.pop().unwrap();
3714
3715                        let stmt_id = next_stmt_id.get_and_increment();
3716                        let end_ident =
3717                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3718
3719                        match builders_or_callback {
3720                            BuildersOrCallback::Builders(graph_builders) => {
3721                                graph_builders.end_atomic(
3722                                    inner_ident,
3723                                    &inner.metadata().location_id,
3724                                    &inner.metadata().collection_kind,
3725                                    &end_ident,
3726                                );
3727                            }
3728                            BuildersOrCallback::Callback(_, node_callback) => {
3729                                node_callback(node, next_stmt_id);
3730                            }
3731                        }
3732
3733                        ident_stack.push(end_ident);
3734                    }
3735
3736                    HydroNode::Source {
3737                        source, metadata, ..
3738                    } => {
3739                        if let HydroSource::ExternalNetwork() = source {
3740                            ident_stack.push(syn::Ident::new("DUMMY", Span::call_site()));
3741                        } else {
3742                            let stmt_id = next_stmt_id.get_and_increment();
3743                            let source_ident =
3744                                syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3745
3746                            let source_stmt = match source {
3747                                HydroSource::Stream(expr) => {
3748                                    debug_assert!(metadata.location_id.is_top_level());
3749                                    parse_quote! {
3750                                        #source_ident = source_stream(#expr);
3751                                    }
3752                                }
3753
3754                                HydroSource::ExternalNetwork() => {
3755                                    unreachable!()
3756                                }
3757
3758                                HydroSource::Iter(expr) => {
3759                                    if metadata.location_id.is_top_level() {
3760                                        parse_quote! {
3761                                            #source_ident = source_iter(#expr);
3762                                        }
3763                                    } else {
3764                                        // TODO(shadaj): a more natural semantics would be to to re-evaluate the expression on each tick
3765                                        parse_quote! {
3766                                            #source_ident = source_iter(#expr) -> persist::<'static>();
3767                                        }
3768                                    }
3769                                }
3770
3771                                HydroSource::Spin() => {
3772                                    debug_assert!(metadata.location_id.is_top_level());
3773                                    parse_quote! {
3774                                        #source_ident = spin();
3775                                    }
3776                                }
3777
3778                                HydroSource::ClusterMembers(target_loc, state) => {
3779                                    debug_assert!(metadata.location_id.is_top_level());
3780
3781                                    let members_tee_ident = syn::Ident::new(
3782                                        &format!(
3783                                            "__cluster_members_tee_{}_{}",
3784                                            metadata.location_id.root().key(),
3785                                            target_loc.key(),
3786                                        ),
3787                                        Span::call_site(),
3788                                    );
3789
3790                                    match state {
3791                                        ClusterMembersState::Stream(d) => {
3792                                            parse_quote! {
3793                                                #members_tee_ident = source_stream(#d) -> tee();
3794                                                #source_ident = #members_tee_ident;
3795                                            }
3796                                        },
3797                                        ClusterMembersState::Uninit => syn::parse_quote! {
3798                                            #source_ident = source_stream(DUMMY);
3799                                        },
3800                                        ClusterMembersState::Tee(..) => parse_quote! {
3801                                            #source_ident = #members_tee_ident;
3802                                        },
3803                                    }
3804                                }
3805
3806                                HydroSource::Embedded(ident) => {
3807                                    parse_quote! {
3808                                        #source_ident = source_stream(#ident);
3809                                    }
3810                                }
3811
3812                                HydroSource::EmbeddedSingleton(ident) => {
3813                                    parse_quote! {
3814                                        #source_ident = source_iter([#ident]);
3815                                    }
3816                                }
3817                            };
3818
3819                            match builders_or_callback {
3820                                BuildersOrCallback::Builders(graph_builders) => {
3821                                    let builder = graph_builders.get_dfir_mut(&out_location);
3822                                    builder.add_dfir(source_stmt, None, Some(&stmt_id.to_string()));
3823                                }
3824                                BuildersOrCallback::Callback(_, node_callback) => {
3825                                    node_callback(node, next_stmt_id);
3826                                }
3827                            }
3828
3829                            ident_stack.push(source_ident);
3830                        }
3831                    }
3832
3833                    HydroNode::SingletonSource { value, first_tick_only, metadata } => {
3834                        let stmt_id = next_stmt_id.get_and_increment();
3835                        let source_ident =
3836                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3837
3838                        match builders_or_callback {
3839                            BuildersOrCallback::Builders(graph_builders) => {
3840                                let builder = graph_builders.get_dfir_mut(&out_location);
3841
3842                                if *first_tick_only {
3843                                    assert!(
3844                                        !metadata.location_id.is_top_level(),
3845                                        "first_tick_only SingletonSource must be inside a tick"
3846                                    );
3847                                }
3848
3849                                if *first_tick_only
3850                                    || (metadata.location_id.is_top_level()
3851                                        && metadata.collection_kind.is_bounded())
3852                                {
3853                                    builder.add_dfir(
3854                                        parse_quote! {
3855                                            #source_ident = source_iter([#value]);
3856                                        },
3857                                        None,
3858                                        Some(&stmt_id.to_string()),
3859                                    );
3860                                } else {
3861                                    builder.add_dfir(
3862                                        parse_quote! {
3863                                            #source_ident = source_iter([#value]) -> persist::<'static>();
3864                                        },
3865                                        None,
3866                                        Some(&stmt_id.to_string()),
3867                                    );
3868                                }
3869                            }
3870                            BuildersOrCallback::Callback(_, node_callback) => {
3871                                node_callback(node, next_stmt_id);
3872                            }
3873                        }
3874
3875                        ident_stack.push(source_ident);
3876                    }
3877
3878                    HydroNode::CycleSource { cycle_id, .. } => {
3879                        let ident = cycle_id.as_ident();
3880
3881                        // consume a stmt id even though we did not emit anything so that we can instrument this
3882                        let _ = next_stmt_id.get_and_increment();
3883
3884                        match builders_or_callback {
3885                            BuildersOrCallback::Builders(_) => {}
3886                            BuildersOrCallback::Callback(_, node_callback) => {
3887                                node_callback(node, next_stmt_id);
3888                            }
3889                        }
3890
3891                        ident_stack.push(ident);
3892                    }
3893
3894                    HydroNode::Tee { inner, .. } => {
3895                        // we consume a stmt id regardless of if we emit the tee() operator,
3896                        // so that during rewrites we touch all recipients of the tee()
3897                        let stmt_id = next_stmt_id.get_and_increment();
3898
3899                        let ret_ident = if let Some(built_idents) =
3900                            built_tees.get(&(inner.0.as_ref() as *const RefCell<HydroNode>))
3901                        {
3902                            match builders_or_callback {
3903                                BuildersOrCallback::Builders(_) => {}
3904                                BuildersOrCallback::Callback(_, node_callback) => {
3905                                    node_callback(node, next_stmt_id);
3906                                }
3907                            }
3908
3909                            built_idents[0].clone()
3910                        } else {
3911                            // The inner node was already processed by transform_bottom_up,
3912                            // so its ident is on the stack
3913                            let inner_ident = ident_stack.pop().unwrap();
3914
3915                            let tee_ident =
3916                                syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3917
3918                            built_tees.insert(
3919                                inner.0.as_ref() as *const RefCell<HydroNode>,
3920                                vec![tee_ident.clone()],
3921                            );
3922
3923                            match builders_or_callback {
3924                                BuildersOrCallback::Builders(graph_builders) => {
3925                                    // NOTE: With `forward_ref`, the fold codegen may not have
3926                                    // run yet when we reach this tee, so `fold_hooked_idents`
3927                                    // might not contain the inner ident. In that case we won't
3928                                    // propagate the "hooked" status to the tee and the
3929                                    // downstream singleton batch will use the normal
3930                                    // `SingletonHook` instead of `PassthroughSingletonHook`.
3931                                    // This is not a soundness issue: the fallback hook still
3932                                    // produces correct behavior, just with a redundant decision
3933                                    // point. TODO(https://github.com/hydro-project/hydro/issues/2856):
3934                                    // fix ordering so forward_ref folds are always processed
3935                                    // before their downstream tees.
3936                                    if fold_hooked_idents.contains(&inner_ident.to_string()) {
3937                                        fold_hooked_idents.insert(tee_ident.to_string());
3938                                    }
3939                                    let builder = graph_builders.get_dfir_mut(&out_location);
3940                                    builder.add_dfir(
3941                                        parse_quote! {
3942                                            #tee_ident = #inner_ident -> tee();
3943                                        },
3944                                        None,
3945                                        Some(&stmt_id.to_string()),
3946                                    );
3947                                }
3948                                BuildersOrCallback::Callback(_, node_callback) => {
3949                                    node_callback(node, next_stmt_id);
3950                                }
3951                            }
3952
3953                            tee_ident
3954                        };
3955
3956                        ident_stack.push(ret_ident);
3957                    }
3958
3959                    HydroNode::Reference { inner, kind, .. } => {
3960                        // we consume a stmt id regardless of if we emit the operator,
3961                        // so that during rewrites we touch all recipients
3962                        let stmt_id = next_stmt_id.get_and_increment();
3963
3964                        let ret_ident = if let Some(built_idents) =
3965                            built_tees.get(&(inner.0.as_ref() as *const RefCell<HydroNode>))
3966                        {
3967                            built_idents[0].clone()
3968                        } else {
3969                            let inner_ident = ident_stack.pop().unwrap();
3970
3971                            let ref_ident =
3972                                syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3973
3974                            built_tees.insert(
3975                                inner.0.as_ref() as *const RefCell<HydroNode>,
3976                                vec![ref_ident.clone()],
3977                            );
3978
3979                            match builders_or_callback {
3980                                BuildersOrCallback::Builders(graph_builders) => {
3981                                    let builder = graph_builders.get_dfir_mut(&out_location);
3982                                    let op_ident = syn::Ident::new(
3983                                        match kind {
3984                                            crate::handoff_ref::HandoffRefKind::Singleton => "singleton",
3985                                            crate::handoff_ref::HandoffRefKind::Optional => "optional",
3986                                            crate::handoff_ref::HandoffRefKind::Vec => "handoff",
3987                                        },
3988                                        Span::call_site(),
3989                                    );
3990                                    builder.add_dfir(
3991                                        parse_quote! {
3992                                            #ref_ident = #inner_ident -> #op_ident();
3993                                        },
3994                                        None,
3995                                        Some(&stmt_id.to_string()),
3996                                    );
3997                                }
3998                                BuildersOrCallback::Callback(_, node_callback) => {
3999                                    node_callback(node, next_stmt_id);
4000                                }
4001                            }
4002
4003                            ref_ident
4004                        };
4005
4006                        ident_stack.push(ret_ident);
4007                    }
4008
4009                    HydroNode::Partition {
4010                        inner, f, is_true, metadata,
4011                    } => {
4012                        let is_true = *is_true; // need to copy early to avoid borrow checking issues with node
4013                        let ptr = inner.0.as_ref() as *const RefCell<HydroNode>;
4014                        let stmt_id = next_stmt_id.get_and_increment();
4015
4016                        let ret_ident = if let Some(built_idents) = built_tees.get(&ptr) {
4017                            match builders_or_callback {
4018                                BuildersOrCallback::Builders(_) => {}
4019                                BuildersOrCallback::Callback(_, node_callback) => {
4020                                    node_callback(node, next_stmt_id);
4021                                }
4022                            }
4023
4024                            let idx = if is_true { 0 } else { 1 };
4025                            built_idents[idx].clone()
4026                        } else {
4027                            // The inner node was already processed by transform_bottom_up,
4028                            // so its ident is on the stack
4029                            let inner_ident = ident_stack.pop().unwrap();
4030                            let f_tokens = f.emit_tokens(&mut ident_stack);
4031
4032                            let inner_ident = {
4033                                let inner_borrow = inner.0.borrow();
4034                                maybe_observe_for_mut(
4035                                    f, inner_ident,
4036                                    &inner_borrow.metadata().location_id,
4037                                    &inner_borrow.metadata().collection_kind,
4038                                    &metadata.op,
4039                                    builders_or_callback, next_stmt_id,
4040                                )
4041                            };
4042
4043                            let partition_ident = syn::Ident::new(
4044                                &format!("stream_{}_partition", stmt_id),
4045                                Span::call_site(),
4046                            );
4047                            let true_ident = syn::Ident::new(
4048                                &format!("stream_{}_true", stmt_id),
4049                                Span::call_site(),
4050                            );
4051                            let false_ident = syn::Ident::new(
4052                                &format!("stream_{}_false", stmt_id),
4053                                Span::call_site(),
4054                            );
4055
4056                            built_tees.insert(
4057                                ptr,
4058                                vec![true_ident.clone(), false_ident.clone()],
4059                            );
4060
4061                            let stmt_id = next_stmt_id.get_and_increment();
4062                            match builders_or_callback {
4063                                BuildersOrCallback::Builders(graph_builders) => {
4064                                    let builder = graph_builders.get_dfir_mut(&out_location);
4065                                    builder.add_dfir(
4066                                        parse_quote! {
4067                                            #partition_ident = #inner_ident -> partition(|__item, __num_outputs| if (#f_tokens)(__item) { 0_usize } else { 1_usize });
4068                                            #true_ident = #partition_ident[0];
4069                                            #false_ident = #partition_ident[1];
4070                                        },
4071                                        None,
4072                                        Some(&stmt_id.to_string()),
4073                                    );
4074                                }
4075                                BuildersOrCallback::Callback(_, node_callback) => {
4076                                    node_callback(node, next_stmt_id);
4077                                }
4078                            }
4079
4080                            if is_true { true_ident } else { false_ident }
4081                        };
4082
4083                        ident_stack.push(ret_ident);
4084                    }
4085
4086                    HydroNode::Chain { .. } => {
4087                        // Children are processed left-to-right, so second is on top
4088                        let second_ident = ident_stack.pop().unwrap();
4089                        let first_ident = ident_stack.pop().unwrap();
4090
4091                        let stmt_id = next_stmt_id.get_and_increment();
4092                        let chain_ident =
4093                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4094
4095                        match builders_or_callback {
4096                            BuildersOrCallback::Builders(graph_builders) => {
4097                                let builder = graph_builders.get_dfir_mut(&out_location);
4098                                builder.add_dfir(
4099                                    parse_quote! {
4100                                        #chain_ident = chain();
4101                                        #first_ident -> [0]#chain_ident;
4102                                        #second_ident -> [1]#chain_ident;
4103                                    },
4104                                    None,
4105                                    Some(&stmt_id.to_string()),
4106                                );
4107                            }
4108                            BuildersOrCallback::Callback(_, node_callback) => {
4109                                node_callback(node, next_stmt_id);
4110                            }
4111                        }
4112
4113                        ident_stack.push(chain_ident);
4114                    }
4115
4116                    HydroNode::MergeOrdered { first, metadata, .. } => {
4117                        let second_ident = ident_stack.pop().unwrap();
4118                        let first_ident = ident_stack.pop().unwrap();
4119
4120                        let stmt_id = next_stmt_id.get_and_increment();
4121                        let merge_ident =
4122                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4123
4124                        match builders_or_callback {
4125                            BuildersOrCallback::Builders(graph_builders) => {
4126                                graph_builders.merge_ordered(
4127                                    &first.metadata().location_id,
4128                                    first_ident,
4129                                    second_ident,
4130                                    &merge_ident,
4131                                    &first.metadata().collection_kind,
4132                                    &metadata.op,
4133                                    Some(&stmt_id.to_string()),
4134                                );
4135                            }
4136                            BuildersOrCallback::Callback(_, node_callback) => {
4137                                node_callback(node, next_stmt_id);
4138                            }
4139                        }
4140
4141                        ident_stack.push(merge_ident);
4142                    }
4143
4144                    HydroNode::ChainFirst { .. } => {
4145                        let second_ident = ident_stack.pop().unwrap();
4146                        let first_ident = ident_stack.pop().unwrap();
4147
4148                        let stmt_id = next_stmt_id.get_and_increment();
4149                        let chain_ident =
4150                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4151
4152                        match builders_or_callback {
4153                            BuildersOrCallback::Builders(graph_builders) => {
4154                                let builder = graph_builders.get_dfir_mut(&out_location);
4155                                builder.add_dfir(
4156                                    parse_quote! {
4157                                        #chain_ident = chain_first_n(1);
4158                                        #first_ident -> [0]#chain_ident;
4159                                        #second_ident -> [1]#chain_ident;
4160                                    },
4161                                    None,
4162                                    Some(&stmt_id.to_string()),
4163                                );
4164                            }
4165                            BuildersOrCallback::Callback(_, node_callback) => {
4166                                node_callback(node, next_stmt_id);
4167                            }
4168                        }
4169
4170                        ident_stack.push(chain_ident);
4171                    }
4172
4173                    HydroNode::CrossSingleton { right, .. } => {
4174                        let right_ident = ident_stack.pop().unwrap();
4175                        let left_ident = ident_stack.pop().unwrap();
4176
4177                        let stmt_id = next_stmt_id.get_and_increment();
4178                        let cross_ident =
4179                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4180
4181                        match builders_or_callback {
4182                            BuildersOrCallback::Builders(graph_builders) => {
4183                                let builder = graph_builders.get_dfir_mut(&out_location);
4184
4185                                if right.metadata().location_id.is_top_level()
4186                                    && right.metadata().collection_kind.is_bounded()
4187                                {
4188                                    builder.add_dfir(
4189                                        parse_quote! {
4190                                            #cross_ident = cross_singleton::<'static>();
4191                                            #left_ident -> [input]#cross_ident;
4192                                            #right_ident -> [single]#cross_ident;
4193                                        },
4194                                        None,
4195                                        Some(&stmt_id.to_string()),
4196                                    );
4197                                } else {
4198                                    builder.add_dfir(
4199                                        parse_quote! {
4200                                            #cross_ident = cross_singleton();
4201                                            #left_ident -> [input]#cross_ident;
4202                                            #right_ident -> [single]#cross_ident;
4203                                        },
4204                                        None,
4205                                        Some(&stmt_id.to_string()),
4206                                    );
4207                                }
4208                            }
4209                            BuildersOrCallback::Callback(_, node_callback) => {
4210                                node_callback(node, next_stmt_id);
4211                            }
4212                        }
4213
4214                        ident_stack.push(cross_ident);
4215                    }
4216
4217                    HydroNode::CrossProduct { .. } | HydroNode::Join { .. } => {
4218                        let operator: syn::Ident = if matches!(node, HydroNode::CrossProduct { .. }) {
4219                            parse_quote!(cross_join_multiset)
4220                        } else {
4221                            parse_quote!(join_multiset)
4222                        };
4223
4224                        let (HydroNode::CrossProduct { left, right, .. }
4225                        | HydroNode::Join { left, right, .. }) = node
4226                        else {
4227                            unreachable!()
4228                        };
4229
4230                        let is_top_level = left.metadata().location_id.is_top_level()
4231                            && right.metadata().location_id.is_top_level();
4232                        let left_lifetime = if left.metadata().location_id.is_top_level() {
4233                            quote!('static)
4234                        } else {
4235                            quote!('tick)
4236                        };
4237
4238                        let right_lifetime = if right.metadata().location_id.is_top_level() {
4239                            quote!('static)
4240                        } else {
4241                            quote!('tick)
4242                        };
4243
4244                        let right_ident = ident_stack.pop().unwrap();
4245                        let left_ident = ident_stack.pop().unwrap();
4246
4247                        let stmt_id = next_stmt_id.get_and_increment();
4248                        let stream_ident =
4249                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4250
4251                        match builders_or_callback {
4252                            BuildersOrCallback::Builders(graph_builders) => {
4253                                let builder = graph_builders.get_dfir_mut(&out_location);
4254                                builder.add_dfir(
4255                                    if is_top_level {
4256                                        // if both inputs are root, the output is expected to have streamy semantics, so we need
4257                                        // a multiset_delta() to negate the replay behavior
4258                                        parse_quote! {
4259                                            #stream_ident = #operator::<#left_lifetime, #right_lifetime>() -> multiset_delta();
4260                                            #left_ident -> [0]#stream_ident;
4261                                            #right_ident -> [1]#stream_ident;
4262                                        }
4263                                    } else {
4264                                        parse_quote! {
4265                                            #stream_ident = #operator::<#left_lifetime, #right_lifetime>();
4266                                            #left_ident -> [0]#stream_ident;
4267                                            #right_ident -> [1]#stream_ident;
4268                                        }
4269                                    }
4270                                    ,
4271                                    None,
4272                                    Some(&stmt_id.to_string()),
4273                                );
4274                            }
4275                            BuildersOrCallback::Callback(_, node_callback) => {
4276                                node_callback(node, next_stmt_id);
4277                            }
4278                        }
4279
4280                        ident_stack.push(stream_ident);
4281                    }
4282
4283                    HydroNode::Difference { .. } | HydroNode::AntiJoin { .. } => {
4284                        let operator: syn::Ident = if matches!(node, HydroNode::Difference { .. }) {
4285                            parse_quote!(difference)
4286                        } else {
4287                            parse_quote!(anti_join)
4288                        };
4289
4290                        let (HydroNode::Difference { neg, .. } | HydroNode::AntiJoin { neg, .. }) =
4291                            node
4292                        else {
4293                            unreachable!()
4294                        };
4295
4296                        let neg_lifetime = if neg.metadata().location_id.is_top_level() {
4297                            quote!('static)
4298                        } else {
4299                            quote!('tick)
4300                        };
4301
4302                        let neg_ident = ident_stack.pop().unwrap();
4303                        let pos_ident = ident_stack.pop().unwrap();
4304
4305                        let stmt_id = next_stmt_id.get_and_increment();
4306                        let stream_ident =
4307                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4308
4309                        match builders_or_callback {
4310                            BuildersOrCallback::Builders(graph_builders) => {
4311                                let builder = graph_builders.get_dfir_mut(&out_location);
4312                                builder.add_dfir(
4313                                    parse_quote! {
4314                                        #stream_ident = #operator::<'tick, #neg_lifetime>();
4315                                        #pos_ident -> [pos]#stream_ident;
4316                                        #neg_ident -> [neg]#stream_ident;
4317                                    },
4318                                    None,
4319                                    Some(&stmt_id.to_string()),
4320                                );
4321                            }
4322                            BuildersOrCallback::Callback(_, node_callback) => {
4323                                node_callback(node, next_stmt_id);
4324                            }
4325                        }
4326
4327                        ident_stack.push(stream_ident);
4328                    }
4329
4330                    HydroNode::JoinHalf { .. } => {
4331                        let HydroNode::JoinHalf { right, .. } = node else {
4332                            unreachable!()
4333                        };
4334
4335                        assert!(
4336                            right.metadata().collection_kind.is_bounded(),
4337                            "JoinHalf requires the right (build) side to be Bounded, got {:?}",
4338                            right.metadata().collection_kind
4339                        );
4340
4341                        let build_lifetime = if right.metadata().location_id.is_top_level() {
4342                            quote!('static)
4343                        } else {
4344                            quote!('tick)
4345                        };
4346
4347                        let build_ident = ident_stack.pop().unwrap();
4348                        let probe_ident = ident_stack.pop().unwrap();
4349
4350                        let stmt_id = next_stmt_id.get_and_increment();
4351                        let stream_ident =
4352                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4353
4354                        match builders_or_callback {
4355                            BuildersOrCallback::Builders(graph_builders) => {
4356                                let builder = graph_builders.get_dfir_mut(&out_location);
4357                                builder.add_dfir(
4358                                    parse_quote! {
4359                                        #stream_ident = join_multiset_half::<#build_lifetime, 'tick>();
4360                                        #probe_ident -> [probe]#stream_ident;
4361                                        #build_ident -> [build]#stream_ident;
4362                                    },
4363                                    None,
4364                                    Some(&stmt_id.to_string()),
4365                                );
4366                            }
4367                            BuildersOrCallback::Callback(_, node_callback) => {
4368                                node_callback(node, next_stmt_id);
4369                            }
4370                        }
4371
4372                        ident_stack.push(stream_ident);
4373                    }
4374
4375                    HydroNode::ResolveFutures { .. } => {
4376                        let input_ident = ident_stack.pop().unwrap();
4377
4378                        let stmt_id = next_stmt_id.get_and_increment();
4379                        let futures_ident =
4380                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4381
4382                        match builders_or_callback {
4383                            BuildersOrCallback::Builders(graph_builders) => {
4384                                let builder = graph_builders.get_dfir_mut(&out_location);
4385                                builder.add_dfir(
4386                                    parse_quote! {
4387                                        #futures_ident = #input_ident -> resolve_futures();
4388                                    },
4389                                    None,
4390                                    Some(&stmt_id.to_string()),
4391                                );
4392                            }
4393                            BuildersOrCallback::Callback(_, node_callback) => {
4394                                node_callback(node, next_stmt_id);
4395                            }
4396                        }
4397
4398                        ident_stack.push(futures_ident);
4399                    }
4400
4401                    HydroNode::ResolveFuturesBlocking { .. } => {
4402                        let input_ident = ident_stack.pop().unwrap();
4403
4404                        let stmt_id = next_stmt_id.get_and_increment();
4405                        let futures_ident =
4406                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4407
4408                        match builders_or_callback {
4409                            BuildersOrCallback::Builders(graph_builders) => {
4410                                let builder = graph_builders.get_dfir_mut(&out_location);
4411                                builder.add_dfir(
4412                                    parse_quote! {
4413                                        #futures_ident = #input_ident -> resolve_futures_blocking();
4414                                    },
4415                                    None,
4416                                    Some(&stmt_id.to_string()),
4417                                );
4418                            }
4419                            BuildersOrCallback::Callback(_, node_callback) => {
4420                                node_callback(node, next_stmt_id);
4421                            }
4422                        }
4423
4424                        ident_stack.push(futures_ident);
4425                    }
4426
4427                    HydroNode::ResolveFuturesOrdered { .. } => {
4428                        let input_ident = ident_stack.pop().unwrap();
4429
4430                        let stmt_id = next_stmt_id.get_and_increment();
4431                        let futures_ident =
4432                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4433
4434                        match builders_or_callback {
4435                            BuildersOrCallback::Builders(graph_builders) => {
4436                                let builder = graph_builders.get_dfir_mut(&out_location);
4437                                builder.add_dfir(
4438                                    parse_quote! {
4439                                        #futures_ident = #input_ident -> resolve_futures_ordered();
4440                                    },
4441                                    None,
4442                                    Some(&stmt_id.to_string()),
4443                                );
4444                            }
4445                            BuildersOrCallback::Callback(_, node_callback) => {
4446                                node_callback(node, next_stmt_id);
4447                            }
4448                        }
4449
4450                        ident_stack.push(futures_ident);
4451                    }
4452
4453                    HydroNode::Map {
4454                        f,
4455                        input,
4456                        metadata,
4457                    } => {
4458                        // Pop input ident (pushed last by transform_children).
4459                        let input_ident = ident_stack.pop().unwrap();
4460                        let f_tokens = f.emit_tokens(&mut ident_stack);
4461
4462                        let input_ident = maybe_observe_for_mut(
4463                            f,
4464                            input_ident,
4465                            &input.metadata().location_id,
4466                            &input.metadata().collection_kind,
4467                            &metadata.op,
4468                            builders_or_callback,
4469                            next_stmt_id,
4470                        );
4471
4472                        let stmt_id = next_stmt_id.get_and_increment();
4473                        let map_ident =
4474                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4475
4476                        match builders_or_callback {
4477                            BuildersOrCallback::Builders(graph_builders) => {
4478                                let builder = graph_builders.get_dfir_mut(&out_location);
4479                                builder.add_dfir(
4480                                    parse_quote! {
4481                                        #map_ident = #input_ident -> map(#f_tokens);
4482                                    },
4483                                    None,
4484                                    Some(&stmt_id.to_string()),
4485                                );
4486                            }
4487                            BuildersOrCallback::Callback(_, node_callback) => {
4488                                node_callback(node, next_stmt_id);
4489                            }
4490                        }
4491
4492                        ident_stack.push(map_ident);
4493                    }
4494
4495                    HydroNode::FlatMap { f, input, metadata } => {
4496                        let input_ident = ident_stack.pop().unwrap();
4497                        let f_tokens = f.emit_tokens(&mut ident_stack);
4498
4499                        let input_ident = maybe_observe_for_mut(
4500                            f, input_ident,
4501                            &input.metadata().location_id,
4502                            &input.metadata().collection_kind,
4503                            &metadata.op,
4504                            builders_or_callback, next_stmt_id,
4505                        );
4506
4507                        let stmt_id = next_stmt_id.get_and_increment();
4508                        let flat_map_ident =
4509                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4510
4511                        match builders_or_callback {
4512                            BuildersOrCallback::Builders(graph_builders) => {
4513                                let builder = graph_builders.get_dfir_mut(&out_location);
4514                                builder.add_dfir(
4515                                    parse_quote! {
4516                                        #flat_map_ident = #input_ident -> flat_map(#f_tokens);
4517                                    },
4518                                    None,
4519                                    Some(&stmt_id.to_string()),
4520                                );
4521                            }
4522                            BuildersOrCallback::Callback(_, node_callback) => {
4523                                node_callback(node, next_stmt_id);
4524                            }
4525                        }
4526
4527                        ident_stack.push(flat_map_ident);
4528                    }
4529
4530                    HydroNode::FlatMapStreamBlocking { f, input, metadata } => {
4531                        let input_ident = ident_stack.pop().unwrap();
4532                        let f_tokens = f.emit_tokens(&mut ident_stack);
4533
4534                        let input_ident = maybe_observe_for_mut(
4535                            f, input_ident,
4536                            &input.metadata().location_id,
4537                            &input.metadata().collection_kind,
4538                            &metadata.op,
4539                            builders_or_callback, next_stmt_id,
4540                        );
4541
4542                        let stmt_id = next_stmt_id.get_and_increment();
4543                        let flat_map_stream_blocking_ident =
4544                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4545
4546                        match builders_or_callback {
4547                            BuildersOrCallback::Builders(graph_builders) => {
4548                                let builder = graph_builders.get_dfir_mut(&out_location);
4549                                builder.add_dfir(
4550                                    parse_quote! {
4551                                        #flat_map_stream_blocking_ident = #input_ident -> flat_map_stream_blocking(#f_tokens);
4552                                    },
4553                                    None,
4554                                    Some(&stmt_id.to_string()),
4555                                );
4556                            }
4557                            BuildersOrCallback::Callback(_, node_callback) => {
4558                                node_callback(node, next_stmt_id);
4559                            }
4560                        }
4561
4562                        ident_stack.push(flat_map_stream_blocking_ident);
4563                    }
4564
4565                    HydroNode::Filter { f, input, metadata } => {
4566                        let input_ident = ident_stack.pop().unwrap();
4567                        let f_tokens = f.emit_tokens(&mut ident_stack);
4568
4569                        let input_ident = maybe_observe_for_mut(
4570                            f, input_ident,
4571                            &input.metadata().location_id,
4572                            &input.metadata().collection_kind,
4573                            &metadata.op,
4574                            builders_or_callback, next_stmt_id,
4575                        );
4576
4577                        let stmt_id = next_stmt_id.get_and_increment();
4578                        let filter_ident =
4579                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4580
4581                        match builders_or_callback {
4582                            BuildersOrCallback::Builders(graph_builders) => {
4583                                let builder = graph_builders.get_dfir_mut(&out_location);
4584                                builder.add_dfir(
4585                                    parse_quote! {
4586                                        #filter_ident = #input_ident -> filter(#f_tokens);
4587                                    },
4588                                    None,
4589                                    Some(&stmt_id.to_string()),
4590                                );
4591                            }
4592                            BuildersOrCallback::Callback(_, node_callback) => {
4593                                node_callback(node, next_stmt_id);
4594                            }
4595                        }
4596
4597                        ident_stack.push(filter_ident);
4598                    }
4599
4600                    HydroNode::FilterMap { f, input, metadata } => {
4601                        let input_ident = ident_stack.pop().unwrap();
4602                        let f_tokens = f.emit_tokens(&mut ident_stack);
4603
4604                        let input_ident = maybe_observe_for_mut(
4605                            f, input_ident,
4606                            &input.metadata().location_id,
4607                            &input.metadata().collection_kind,
4608                            &metadata.op,
4609                            builders_or_callback, next_stmt_id,
4610                        );
4611
4612                        let stmt_id = next_stmt_id.get_and_increment();
4613                        let filter_map_ident =
4614                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4615
4616                        match builders_or_callback {
4617                            BuildersOrCallback::Builders(graph_builders) => {
4618                                let builder = graph_builders.get_dfir_mut(&out_location);
4619                                builder.add_dfir(
4620                                    parse_quote! {
4621                                        #filter_map_ident = #input_ident -> filter_map(#f_tokens);
4622                                    },
4623                                    None,
4624                                    Some(&stmt_id.to_string()),
4625                                );
4626                            }
4627                            BuildersOrCallback::Callback(_, node_callback) => {
4628                                node_callback(node, next_stmt_id);
4629                            }
4630                        }
4631
4632                        ident_stack.push(filter_map_ident);
4633                    }
4634
4635                    HydroNode::Sort { .. } => {
4636                        let input_ident = ident_stack.pop().unwrap();
4637
4638                        let stmt_id = next_stmt_id.get_and_increment();
4639                        let sort_ident =
4640                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4641
4642                        match builders_or_callback {
4643                            BuildersOrCallback::Builders(graph_builders) => {
4644                                let builder = graph_builders.get_dfir_mut(&out_location);
4645                                builder.add_dfir(
4646                                    parse_quote! {
4647                                        #sort_ident = #input_ident -> sort();
4648                                    },
4649                                    None,
4650                                    Some(&stmt_id.to_string()),
4651                                );
4652                            }
4653                            BuildersOrCallback::Callback(_, node_callback) => {
4654                                node_callback(node, next_stmt_id);
4655                            }
4656                        }
4657
4658                        ident_stack.push(sort_ident);
4659                    }
4660
4661                    HydroNode::DeferTick { .. } => {
4662                        let input_ident = ident_stack.pop().unwrap();
4663
4664                        let stmt_id = next_stmt_id.get_and_increment();
4665                        let defer_tick_ident =
4666                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4667
4668                        match builders_or_callback {
4669                            BuildersOrCallback::Builders(graph_builders) => {
4670                                let builder = graph_builders.get_dfir_mut(&out_location);
4671                                builder.add_dfir(
4672                                    parse_quote! {
4673                                        #defer_tick_ident = #input_ident -> defer_tick_lazy();
4674                                    },
4675                                    None,
4676                                    Some(&stmt_id.to_string()),
4677                                );
4678                            }
4679                            BuildersOrCallback::Callback(_, node_callback) => {
4680                                node_callback(node, next_stmt_id);
4681                            }
4682                        }
4683
4684                        ident_stack.push(defer_tick_ident);
4685                    }
4686
4687                    HydroNode::Enumerate { input, .. } => {
4688                        let input_ident = ident_stack.pop().unwrap();
4689
4690                        let stmt_id = next_stmt_id.get_and_increment();
4691                        let enumerate_ident =
4692                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4693
4694                        match builders_or_callback {
4695                            BuildersOrCallback::Builders(graph_builders) => {
4696                                let builder = graph_builders.get_dfir_mut(&out_location);
4697                                let lifetime = if input.metadata().location_id.is_top_level() {
4698                                    quote!('static)
4699                                } else {
4700                                    quote!('tick)
4701                                };
4702                                builder.add_dfir(
4703                                    parse_quote! {
4704                                        #enumerate_ident = #input_ident -> enumerate::<#lifetime>();
4705                                    },
4706                                    None,
4707                                    Some(&stmt_id.to_string()),
4708                                );
4709                            }
4710                            BuildersOrCallback::Callback(_, node_callback) => {
4711                                node_callback(node, next_stmt_id);
4712                            }
4713                        }
4714
4715                        ident_stack.push(enumerate_ident);
4716                    }
4717
4718                    HydroNode::Inspect { f, input, metadata } => {
4719                        let input_ident = ident_stack.pop().unwrap();
4720                        let f_tokens = f.emit_tokens(&mut ident_stack);
4721
4722                        let input_ident = maybe_observe_for_mut(
4723                            f, input_ident,
4724                            &input.metadata().location_id,
4725                            &input.metadata().collection_kind,
4726                            &metadata.op,
4727                            builders_or_callback, next_stmt_id,
4728                        );
4729
4730                        let stmt_id = next_stmt_id.get_and_increment();
4731                        let inspect_ident =
4732                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4733
4734                        match builders_or_callback {
4735                            BuildersOrCallback::Builders(graph_builders) => {
4736                                let builder = graph_builders.get_dfir_mut(&out_location);
4737                                builder.add_dfir(
4738                                    parse_quote! {
4739                                        #inspect_ident = #input_ident -> inspect(#f_tokens);
4740                                    },
4741                                    None,
4742                                    Some(&stmt_id.to_string()),
4743                                );
4744                            }
4745                            BuildersOrCallback::Callback(_, node_callback) => {
4746                                node_callback(node, next_stmt_id);
4747                            }
4748                        }
4749
4750                        ident_stack.push(inspect_ident);
4751                    }
4752
4753                    HydroNode::Unique { input, .. } => {
4754                        let input_ident = ident_stack.pop().unwrap();
4755
4756                        let stmt_id = next_stmt_id.get_and_increment();
4757                        let unique_ident =
4758                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4759
4760                        match builders_or_callback {
4761                            BuildersOrCallback::Builders(graph_builders) => {
4762                                let builder = graph_builders.get_dfir_mut(&out_location);
4763                                let lifetime = if input.metadata().location_id.is_top_level() {
4764                                    quote!('static)
4765                                } else {
4766                                    quote!('tick)
4767                                };
4768
4769                                builder.add_dfir(
4770                                    parse_quote! {
4771                                        #unique_ident = #input_ident -> unique::<#lifetime>();
4772                                    },
4773                                    None,
4774                                    Some(&stmt_id.to_string()),
4775                                );
4776                            }
4777                            BuildersOrCallback::Callback(_, node_callback) => {
4778                                node_callback(node, next_stmt_id);
4779                            }
4780                        }
4781
4782                        ident_stack.push(unique_ident);
4783                    }
4784
4785                    HydroNode::Fold { .. } | HydroNode::FoldKeyed { .. } | HydroNode::Scan { .. } | HydroNode::ScanAsyncBlocking { .. } => {
4786                        let operator: syn::Ident = if let HydroNode::Fold { input, .. } = node {
4787                            if input.metadata().location_id.is_top_level()
4788                                && input.metadata().collection_kind.is_bounded()
4789                            {
4790                                parse_quote!(fold_no_replay)
4791                            } else {
4792                                parse_quote!(fold)
4793                            }
4794                        } else if matches!(node, HydroNode::Scan { .. }) {
4795                            parse_quote!(scan)
4796                        } else if matches!(node, HydroNode::ScanAsyncBlocking { .. }) {
4797                            parse_quote!(scan_async_blocking)
4798                        } else if let HydroNode::FoldKeyed { input, .. } = node {
4799                            if input.metadata().location_id.is_top_level()
4800                                && input.metadata().collection_kind.is_bounded()
4801                            {
4802                                todo!("Fold keyed on a top-level bounded collection is not yet supported")
4803                            } else {
4804                                parse_quote!(fold_keyed)
4805                            }
4806                        } else {
4807                            unreachable!()
4808                        };
4809
4810                        let (HydroNode::Fold { input, .. }
4811                        | HydroNode::FoldKeyed { input, .. }
4812                        | HydroNode::Scan { input, .. }
4813                        | HydroNode::ScanAsyncBlocking { input, .. }) = node
4814                        else {
4815                            unreachable!()
4816                        };
4817
4818                        let lifetime = if input.metadata().location_id.is_top_level() {
4819                            quote!('static)
4820                        } else {
4821                            quote!('tick)
4822                        };
4823
4824                        let input_ident = ident_stack.pop().unwrap();
4825
4826                        let (HydroNode::Fold { init, acc, .. }
4827                        | HydroNode::FoldKeyed { init, acc, .. }
4828                        | HydroNode::Scan { init, acc, .. }
4829                        | HydroNode::ScanAsyncBlocking { init, acc, .. }) = &*node
4830                        else {
4831                            unreachable!()
4832                        };
4833
4834                        let acc_tokens = acc.emit_tokens(&mut ident_stack);
4835                        let init_tokens = init.emit_tokens(&mut ident_stack);
4836
4837                        let stmt_id = next_stmt_id.get_and_increment();
4838                        let fold_ident =
4839                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4840
4841                        match builders_or_callback {
4842                            BuildersOrCallback::Builders(graph_builders) => {
4843                                if matches!(node, HydroNode::Fold { .. })
4844                                    && node.metadata().location_id.is_top_level()
4845                                    && !(matches!(node.metadata().location_id, LocationId::Atomic(_)))
4846                                    && graph_builders.singleton_intermediates()
4847                                    && !node.metadata().collection_kind.is_bounded()
4848                                {
4849                                    let HydroNode::Fold { input, .. } = &*node else { unreachable!() };
4850                                    let hooked_input_ident = graph_builders.emit_fold_hook(
4851                                        &input.metadata().location_id,
4852                                        &input_ident,
4853                                        &input.metadata().collection_kind,
4854                                        &node.metadata().op,
4855                                    );
4856
4857                                    let (effective_input, wrapped_acc) = if let Some(ref hooked) = hooked_input_ident {
4858                                        let acc: syn::Expr = parse_quote!({
4859                                            let mut __inner = #acc_tokens;
4860                                            move |__state, __batch: Vec<_>| {
4861                                                if __batch.is_empty() {
4862                                                    return None;
4863                                                }
4864                                                for __value in __batch {
4865                                                    __inner(__state, __value);
4866                                                }
4867                                                Some(__state.clone())
4868                                            }
4869                                        });
4870                                        (hooked, acc)
4871                                    } else {
4872                                        let acc: syn::Expr = parse_quote!({
4873                                            let mut __inner = #acc_tokens;
4874                                            move |__state, __value| {
4875                                                __inner(__state, __value);
4876                                                Some(__state.clone())
4877                                            }
4878                                        });
4879                                        (&input_ident, acc)
4880                                    };
4881
4882                                    let builder = graph_builders.get_dfir_mut(&out_location);
4883                                    builder.add_dfir(
4884                                        parse_quote! {
4885                                            source_iter([(#init_tokens)()]) -> [0]#fold_ident;
4886                                            #effective_input -> scan::<#lifetime>(#init_tokens, #wrapped_acc) -> [1]#fold_ident;
4887                                            #fold_ident = chain();
4888                                        },
4889                                        None,
4890                                        Some(&stmt_id.to_string()),
4891                                    );
4892
4893                                    if hooked_input_ident.is_some() {
4894                                        fold_hooked_idents.insert(fold_ident.to_string());
4895                                    }
4896                                } else if matches!(node, HydroNode::FoldKeyed { .. })
4897                                    && node.metadata().location_id.is_top_level()
4898                                    && !(matches!(node.metadata().location_id, LocationId::Atomic(_)))
4899                                    && graph_builders.singleton_intermediates()
4900                                    && !node.metadata().collection_kind.is_bounded()
4901                                {
4902                                    let HydroNode::FoldKeyed { input, .. } = &*node else { unreachable!() };
4903                                    let hooked_input_ident = graph_builders.emit_fold_hook(
4904                                        &input.metadata().location_id,
4905                                        &input_ident,
4906                                        &input.metadata().collection_kind,
4907                                        &node.metadata().op,
4908                                    );
4909                                    let builder = graph_builders.get_dfir_mut(&out_location);
4910
4911                                    let wrapped_acc: syn::Expr = parse_quote!({
4912                                        let mut __init = #init_tokens;
4913                                        let mut __inner = #acc_tokens;
4914                                        move |__state, __kv: (_, _)| {
4915                                            // TODO(shadaj): we can avoid the clone when the entry exists
4916                                            let __state = __state
4917                                                .entry(::std::clone::Clone::clone(&__kv.0))
4918                                                .or_insert_with(|| (__init)());
4919                                            __inner(__state, __kv.1);
4920                                            Some((__kv.0, ::std::clone::Clone::clone(&*__state)))
4921                                        }
4922                                    });
4923
4924                                    if let Some(hooked_input_ident) = hooked_input_ident {
4925                                        builder.add_dfir(
4926                                            parse_quote! {
4927                                                #fold_ident = #hooked_input_ident -> flatten() -> scan::<#lifetime>(|| ::std::collections::HashMap::new(), #wrapped_acc);
4928                                            },
4929                                            None,
4930                                            Some(&stmt_id.to_string()),
4931                                        );
4932
4933                                        fold_hooked_idents.insert(fold_ident.to_string());
4934                                    } else {
4935                                        builder.add_dfir(
4936                                            parse_quote! {
4937                                                #fold_ident = #input_ident -> scan::<#lifetime>(|| ::std::collections::HashMap::new(), #wrapped_acc);
4938                                            },
4939                                            None,
4940                                            Some(&stmt_id.to_string()),
4941                                        );
4942                                    }
4943                                } else if (matches!(node, HydroNode::Fold { .. })
4944                                    || matches!(node, HydroNode::FoldKeyed { .. }))
4945                                    && !node.metadata().location_id.is_top_level()
4946                                    && graph_builders.singleton_intermediates()
4947                                {
4948                                    let input_ref = match &*node {
4949                                        HydroNode::Fold { input, .. } => input,
4950                                        HydroNode::FoldKeyed { input, .. } => input,
4951                                        _ => unreachable!(),
4952                                    };
4953                                    let hooked_input_ident = graph_builders.emit_fold_hook(
4954                                        &input_ref.metadata().location_id,
4955                                        &input_ident,
4956                                        &input_ref.metadata().collection_kind,
4957                                        &node.metadata().op,
4958                                    );
4959
4960                                    let actual_input = hooked_input_ident.as_ref().unwrap_or(&input_ident);
4961                                    let builder = graph_builders.get_dfir_mut(&out_location);
4962                                    builder.add_dfir(
4963                                        parse_quote! {
4964                                            #fold_ident = #actual_input -> #operator::<#lifetime>(#init_tokens, #acc_tokens);
4965                                        },
4966                                        None,
4967                                        Some(&stmt_id.to_string()),
4968                                    );
4969                                } else {
4970                                    let builder = graph_builders.get_dfir_mut(&out_location);
4971                                    builder.add_dfir(
4972                                        parse_quote! {
4973                                            #fold_ident = #input_ident -> #operator::<#lifetime>(#init_tokens, #acc_tokens);
4974                                        },
4975                                        None,
4976                                        Some(&stmt_id.to_string()),
4977                                    );
4978                                }
4979                            }
4980                            BuildersOrCallback::Callback(_, node_callback) => {
4981                                node_callback(node, next_stmt_id);
4982                            }
4983                        }
4984
4985                        ident_stack.push(fold_ident);
4986                    }
4987
4988                    HydroNode::Reduce { .. } | HydroNode::ReduceKeyed { .. } => {
4989                        let operator: syn::Ident = if let HydroNode::Reduce { input, .. } = node {
4990                            if input.metadata().location_id.is_top_level()
4991                                && input.metadata().collection_kind.is_bounded()
4992                            {
4993                                parse_quote!(reduce_no_replay)
4994                            } else {
4995                                parse_quote!(reduce)
4996                            }
4997                        } else if let HydroNode::ReduceKeyed { input, .. } = node {
4998                            if input.metadata().location_id.is_top_level()
4999                                && input.metadata().collection_kind.is_bounded()
5000                            {
5001                                todo!(
5002                                    "Calling keyed reduce on a top-level bounded collection is not supported"
5003                                )
5004                            } else {
5005                                parse_quote!(reduce_keyed)
5006                            }
5007                        } else {
5008                            unreachable!()
5009                        };
5010
5011                        let (HydroNode::Reduce { input, .. } | HydroNode::ReduceKeyed { input, .. }) = node
5012                        else {
5013                            unreachable!()
5014                        };
5015
5016                        let lifetime = if input.metadata().location_id.is_top_level() {
5017                            quote!('static)
5018                        } else {
5019                            quote!('tick)
5020                        };
5021
5022                        let input_ident = ident_stack.pop().unwrap();
5023
5024                        let (HydroNode::Reduce { f, .. } | HydroNode::ReduceKeyed { f, .. }) = &*node
5025                        else {
5026                            unreachable!()
5027                        };
5028
5029                        let f_tokens = f.emit_tokens(&mut ident_stack);
5030
5031                        let stmt_id = next_stmt_id.get_and_increment();
5032                        let reduce_ident =
5033                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5034
5035                        match builders_or_callback {
5036                            BuildersOrCallback::Builders(graph_builders) => {
5037                                if matches!(node, HydroNode::Reduce { .. })
5038                                    && node.metadata().location_id.is_top_level()
5039                                    && !(matches!(node.metadata().location_id, LocationId::Atomic(_)))
5040                                    && graph_builders.singleton_intermediates()
5041                                    && !node.metadata().collection_kind.is_bounded()
5042                                {
5043                                    todo!(
5044                                        "Reduce with optional intermediates is not yet supported in simulator"
5045                                    );
5046                                } else if matches!(node, HydroNode::ReduceKeyed { .. })
5047                                    && node.metadata().location_id.is_top_level()
5048                                    && !(matches!(node.metadata().location_id, LocationId::Atomic(_)))
5049                                    && graph_builders.singleton_intermediates()
5050                                    && !node.metadata().collection_kind.is_bounded()
5051                                {
5052                                    todo!(
5053                                        "Reduce keyed with optional intermediates is not yet supported in simulator"
5054                                    );
5055                                } else {
5056                                    let builder = graph_builders.get_dfir_mut(&out_location);
5057                                    builder.add_dfir(
5058                                        parse_quote! {
5059                                            #reduce_ident = #input_ident -> #operator::<#lifetime>(#f_tokens);
5060                                        },
5061                                        None,
5062                                        Some(&stmt_id.to_string()),
5063                                    );
5064                                }
5065                            }
5066                            BuildersOrCallback::Callback(_, node_callback) => {
5067                                node_callback(node, next_stmt_id);
5068                            }
5069                        }
5070
5071                        ident_stack.push(reduce_ident);
5072                    }
5073
5074                    HydroNode::ReduceKeyedWatermark {
5075                        f,
5076                        input,
5077                        metadata,
5078                        ..
5079                    } => {
5080                        let lifetime = if input.metadata().location_id.is_top_level() {
5081                            quote!('static)
5082                        } else {
5083                            quote!('tick)
5084                        };
5085
5086                        // watermark is processed second, so it's on top
5087                        let watermark_ident = ident_stack.pop().unwrap();
5088                        let input_ident = ident_stack.pop().unwrap();
5089                        let f_tokens = f.emit_tokens(&mut ident_stack);
5090
5091                        let stmt_id = next_stmt_id.get_and_increment();
5092                        let chain_ident = syn::Ident::new(
5093                            &format!("reduce_keyed_watermark_chain_{}", stmt_id),
5094                            Span::call_site(),
5095                        );
5096
5097                        let fold_ident =
5098                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5099
5100                        let agg_operator: syn::Ident = if input.metadata().location_id.is_top_level()
5101                            && input.metadata().collection_kind.is_bounded()
5102                        {
5103                            parse_quote!(fold_no_replay)
5104                        } else {
5105                            parse_quote!(fold)
5106                        };
5107
5108                        match builders_or_callback {
5109                            BuildersOrCallback::Builders(graph_builders) => {
5110                                if metadata.location_id.is_top_level()
5111                                    && !(matches!(metadata.location_id, LocationId::Atomic(_)))
5112                                    && graph_builders.singleton_intermediates()
5113                                    && !metadata.collection_kind.is_bounded()
5114                                {
5115                                    todo!(
5116                                        "Reduce keyed watermarked on a top-level bounded collection is not yet supported"
5117                                    )
5118                                } else {
5119                                    let builder = graph_builders.get_dfir_mut(&out_location);
5120                                    builder.add_dfir(
5121                                        parse_quote! {
5122                                            #chain_ident = chain();
5123                                            #input_ident
5124                                                -> map(|x| (Some(x), None))
5125                                                -> [0]#chain_ident;
5126                                            #watermark_ident
5127                                                -> map(|watermark| (None, Some(watermark)))
5128                                                -> [1]#chain_ident;
5129
5130                                            #fold_ident = #chain_ident
5131                                                -> #agg_operator::<#lifetime>(|| (::std::collections::HashMap::new(), None), {
5132                                                    let __reduce_keyed_fn = #f_tokens;
5133                                                    move |(map, opt_curr_watermark), (opt_payload, opt_watermark)| {
5134                                                        if let Some((k, v)) = opt_payload {
5135                                                            if let Some(curr_watermark) = *opt_curr_watermark {
5136                                                                if k < curr_watermark {
5137                                                                    return;
5138                                                                }
5139                                                            }
5140                                                            match map.entry(k) {
5141                                                                ::std::collections::hash_map::Entry::Vacant(e) => {
5142                                                                    e.insert(v);
5143                                                                }
5144                                                                ::std::collections::hash_map::Entry::Occupied(mut e) => {
5145                                                                    __reduce_keyed_fn(e.get_mut(), v);
5146                                                                }
5147                                                            }
5148                                                        } else {
5149                                                            let watermark = opt_watermark.unwrap();
5150                                                            if let Some(curr_watermark) = *opt_curr_watermark {
5151                                                                if watermark <= curr_watermark {
5152                                                                    return;
5153                                                                }
5154                                                            }
5155                                                            map.retain(|k, _| *k >= watermark);
5156                                                            *opt_curr_watermark = Some(watermark);
5157                                                        }
5158                                                    }
5159                                                })
5160                                                -> flat_map(|(map, _curr_watermark)| map);
5161                                        },
5162                                        None,
5163                                        Some(&stmt_id.to_string()),
5164                                    );
5165                                }
5166                            }
5167                            BuildersOrCallback::Callback(_, node_callback) => {
5168                                node_callback(node, next_stmt_id);
5169                            }
5170                        }
5171
5172                        ident_stack.push(fold_ident);
5173                    }
5174
5175                    HydroNode::Network {
5176                        networking_info,
5177                        serialize,
5178                        deserialize,
5179                        instantiate_fn,
5180                        input,
5181                        ..
5182                    } => {
5183                        let input_ident = ident_stack.pop().unwrap();
5184
5185                        let stmt_id = next_stmt_id.get_and_increment();
5186                        let receiver_stream_ident =
5187                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5188
5189                        // For embedded (external) serialization, this synthesizes only the
5190                        // member-id tag conversions (if any) and passes the raw payload through.
5191                        let serialize_pipeline = serialize.pipeline();
5192                        let deserialize_pipeline = deserialize.pipeline();
5193
5194                        match builders_or_callback {
5195                            BuildersOrCallback::Builders(graph_builders) => {
5196                                let (sink_expr, source_expr) = match instantiate_fn {
5197                                    DebugInstantiate::Building => (
5198                                        syn::parse_quote!(DUMMY_SINK),
5199                                        syn::parse_quote!(DUMMY_SOURCE),
5200                                    ),
5201
5202                                    DebugInstantiate::Finalized(finalized) => {
5203                                        (finalized.sink.clone(), finalized.source.clone())
5204                                    }
5205                                };
5206
5207                                graph_builders.create_network(
5208                                    &input.metadata().location_id,
5209                                    &out_location,
5210                                    input_ident,
5211                                    &receiver_stream_ident,
5212                                    serialize_pipeline.as_ref(),
5213                                    sink_expr,
5214                                    source_expr,
5215                                    deserialize_pipeline.as_ref(),
5216                                    serialize.external_element_type(),
5217                                    stmt_id,
5218                                    networking_info,
5219                                );
5220                            }
5221                            BuildersOrCallback::Callback(_, node_callback) => {
5222                                node_callback(node, next_stmt_id);
5223                            }
5224                        }
5225
5226                        ident_stack.push(receiver_stream_ident);
5227                    }
5228
5229                    HydroNode::ExternalInput {
5230                        instantiate_fn,
5231                        deserialize_fn: deserialize_pipeline,
5232                        ..
5233                    } => {
5234                        let stmt_id = next_stmt_id.get_and_increment();
5235                        let receiver_stream_ident =
5236                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5237
5238                        match builders_or_callback {
5239                            BuildersOrCallback::Builders(graph_builders) => {
5240                                let (_, source_expr) = match instantiate_fn {
5241                                    DebugInstantiate::Building => (
5242                                        syn::parse_quote!(DUMMY_SINK),
5243                                        syn::parse_quote!(DUMMY_SOURCE),
5244                                    ),
5245
5246                                    DebugInstantiate::Finalized(finalized) => {
5247                                        (finalized.sink.clone(), finalized.source.clone())
5248                                    }
5249                                };
5250
5251                                graph_builders.create_external_source(
5252                                    &out_location,
5253                                    source_expr,
5254                                    &receiver_stream_ident,
5255                                    deserialize_pipeline.as_ref(),
5256                                    stmt_id,
5257                                );
5258                            }
5259                            BuildersOrCallback::Callback(_, node_callback) => {
5260                                node_callback(node, next_stmt_id);
5261                            }
5262                        }
5263
5264                        ident_stack.push(receiver_stream_ident);
5265                    }
5266
5267                    HydroNode::Counter {
5268                        tag,
5269                        duration,
5270                        prefix,
5271                        ..
5272                    } => {
5273                        let input_ident = ident_stack.pop().unwrap();
5274
5275                        let stmt_id = next_stmt_id.get_and_increment();
5276                        let counter_ident =
5277                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5278
5279                        match builders_or_callback {
5280                            BuildersOrCallback::Builders(graph_builders) => {
5281                                let arg = format!("{}({})", prefix, tag);
5282                                let builder = graph_builders.get_dfir_mut(&out_location);
5283                                builder.add_dfir(
5284                                    parse_quote! {
5285                                        #counter_ident = #input_ident -> _counter(#arg, #duration);
5286                                    },
5287                                    None,
5288                                    Some(&stmt_id.to_string()),
5289                                );
5290                            }
5291                            BuildersOrCallback::Callback(_, node_callback) => {
5292                                node_callback(node, next_stmt_id);
5293                            }
5294                        }
5295
5296                        ident_stack.push(counter_ident);
5297                    }
5298
5299                    HydroNode::VersionedNetworkFork {
5300                        channel_id,
5301                        senders,
5302                        metadata,
5303                        ..
5304                    } => {
5305                        // sender idents are pushed in order of the 'senders' member.
5306                        let split_at = ident_stack.len() - senders.len();
5307                        let sender_idents = ident_stack.split_off(split_at);
5308
5309                        let stmt_id = next_stmt_id.get_and_increment();
5310
5311                        // All senders share the channel, so the raw element type (for embedded
5312                        // serialization) is read from the first sender.
5313                        let external_element_type =
5314                            senders.first().and_then(|(_, _, s)| s.external_element_type());
5315
5316                        match builders_or_callback {
5317                            BuildersOrCallback::Builders(graph_builders) => {
5318                                let sender_args: Vec<(LocationId, syn::Ident, Option<DebugExpr>)> =
5319                                    senders
5320                                        .iter()
5321                                        .zip(sender_idents)
5322                                        .map(|((_version, sender, serialize), ident)| {
5323                                            (
5324                                                sender.metadata().location_id.clone(),
5325                                                ident,
5326                                                serialize.pipeline(),
5327                                            )
5328                                        })
5329                                        .collect();
5330                                graph_builders.create_versioned_network_fork(
5331                                    *channel_id,
5332                                    &metadata.location_id,
5333                                    sender_args,
5334                                    external_element_type,
5335                                    stmt_id,
5336                                );
5337                            }
5338                            BuildersOrCallback::Callback(_, node_callback) => {
5339                                node_callback(node, next_stmt_id);
5340                            }
5341                        }
5342                    }
5343
5344                    HydroNode::VersionedNetwork {
5345                        fork,
5346                        deserialize,
5347                        metadata,
5348                        ..
5349                    } => {
5350                        let stmt_id = next_stmt_id.get_and_increment();
5351                        let receiver_stream_ident =
5352                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5353
5354                        // The wire element type is determined by the channel's *source* kind, which
5355                        // all senders share; read it from the shared fork's first sender.
5356                        let (channel_id, source_loc) = {
5357                            let fork_ref = fork.0.borrow();
5358                            let HydroNode::VersionedNetworkFork {
5359                                channel_id,
5360                                senders,
5361                                ..
5362                            } = &*fork_ref
5363                            else {
5364                                unreachable!("VersionedNetwork.fork must be a VersionedNetworkFork");
5365                            };
5366                            let source_loc = senders
5367                                .first()
5368                                .map(|(_v, sender, _s)| sender.metadata().location_id.clone())
5369                                .expect("a VersionedNetworkFork always has at least one sender");
5370                            (*channel_id, source_loc)
5371                        };
5372
5373                        let deserialize_pipeline = deserialize.pipeline();
5374                        let external_element_type = deserialize.external_element_type();
5375
5376                        match builders_or_callback {
5377                            BuildersOrCallback::Builders(graph_builders) => {
5378                                graph_builders.create_versioned_network(
5379                                    channel_id,
5380                                    &source_loc,
5381                                    &metadata.location_id,
5382                                    &receiver_stream_ident,
5383                                    deserialize_pipeline.as_ref(),
5384                                    external_element_type,
5385                                    stmt_id,
5386                                );
5387                            }
5388                            BuildersOrCallback::Callback(_, node_callback) => {
5389                                node_callback(node, next_stmt_id);
5390                            }
5391                        }
5392
5393                        ident_stack.push(receiver_stream_ident);
5394                    }
5395                }
5396            },
5397            seen_tees,
5398            false,
5399        );
5400
5401        let ret = ident_stack
5402            .pop()
5403            .expect("ident_stack should have exactly one element after traversal");
5404        assert!(
5405            ident_stack.is_empty(),
5406            "ident_stack should be empty after popping the final ident, but has {} remaining element(s). \
5407             This indicates a bug in the code gen: some node pushed idents that were never consumed.",
5408            ident_stack.len()
5409        );
5410        ret
5411    }
5412
5413    pub fn visit_debug_expr(&mut self, mut transform: impl FnMut(&mut DebugExpr)) {
5414        match self {
5415            HydroNode::Placeholder => {
5416                panic!()
5417            }
5418            HydroNode::Cast { .. }
5419            | HydroNode::ObserveNonDet { .. }
5420            | HydroNode::UnboundSingleton { .. }
5421            | HydroNode::AssertIsConsistent { .. } => {}
5422            HydroNode::Source { source, .. } => match source {
5423                HydroSource::Stream(expr) | HydroSource::Iter(expr) => transform(expr),
5424                HydroSource::ExternalNetwork()
5425                | HydroSource::Spin()
5426                | HydroSource::ClusterMembers(_, _)
5427                | HydroSource::Embedded(_)
5428                | HydroSource::EmbeddedSingleton(_) => {} // TODO: what goes here?
5429            },
5430            HydroNode::SingletonSource { value, .. } => {
5431                transform(value);
5432            }
5433            HydroNode::CycleSource { .. }
5434            | HydroNode::Tee { .. }
5435            | HydroNode::Reference { .. }
5436            | HydroNode::YieldConcat { .. }
5437            | HydroNode::BeginAtomic { .. }
5438            | HydroNode::EndAtomic { .. }
5439            | HydroNode::Batch { .. }
5440            | HydroNode::Chain { .. }
5441            | HydroNode::MergeOrdered { .. }
5442            | HydroNode::ChainFirst { .. }
5443            | HydroNode::CrossProduct { .. }
5444            | HydroNode::CrossSingleton { .. }
5445            | HydroNode::ResolveFutures { .. }
5446            | HydroNode::ResolveFuturesBlocking { .. }
5447            | HydroNode::ResolveFuturesOrdered { .. }
5448            | HydroNode::Join { .. }
5449            | HydroNode::JoinHalf { .. }
5450            | HydroNode::Difference { .. }
5451            | HydroNode::AntiJoin { .. }
5452            | HydroNode::DeferTick { .. }
5453            | HydroNode::Enumerate { .. }
5454            | HydroNode::Unique { .. }
5455            | HydroNode::Sort { .. }
5456            | HydroNode::VersionedNetworkFork { .. }
5457            | HydroNode::VersionedNetwork { .. } => {}
5458            HydroNode::Map { f, .. }
5459            | HydroNode::FlatMap { f, .. }
5460            | HydroNode::FlatMapStreamBlocking { f, .. }
5461            | HydroNode::Filter { f, .. }
5462            | HydroNode::FilterMap { f, .. }
5463            | HydroNode::Inspect { f, .. }
5464            | HydroNode::Partition { f, .. }
5465            | HydroNode::Reduce { f, .. }
5466            | HydroNode::ReduceKeyed { f, .. }
5467            | HydroNode::ReduceKeyedWatermark { f, .. } => {
5468                transform(&mut f.expr);
5469            }
5470            HydroNode::Fold { init, acc, .. }
5471            | HydroNode::Scan { init, acc, .. }
5472            | HydroNode::ScanAsyncBlocking { init, acc, .. }
5473            | HydroNode::FoldKeyed { init, acc, .. } => {
5474                transform(&mut init.expr);
5475                transform(&mut acc.expr);
5476            }
5477            HydroNode::Network {
5478                serialize,
5479                deserialize,
5480                ..
5481            } => {
5482                if let NetworkSend::Custom {
5483                    serialize_fn: Some(serialize_fn),
5484                } = serialize
5485                {
5486                    transform(serialize_fn);
5487                }
5488                if let NetworkRecv::Custom {
5489                    deserialize_fn: Some(deserialize_fn),
5490                } = deserialize
5491                {
5492                    transform(deserialize_fn);
5493                }
5494            }
5495            HydroNode::ExternalInput { deserialize_fn, .. } => {
5496                if let Some(deserialize_fn) = deserialize_fn {
5497                    transform(deserialize_fn);
5498                }
5499            }
5500            HydroNode::Counter { duration, .. } => {
5501                transform(duration);
5502            }
5503        }
5504    }
5505
5506    pub fn op_metadata(&self) -> &HydroIrOpMetadata {
5507        &self.metadata().op
5508    }
5509
5510    pub fn metadata(&self) -> &HydroIrMetadata {
5511        match self {
5512            HydroNode::Placeholder => {
5513                panic!()
5514            }
5515            HydroNode::VersionedNetworkFork { metadata, .. }
5516            | HydroNode::VersionedNetwork { metadata, .. } => metadata,
5517            HydroNode::Cast { metadata, .. }
5518            | HydroNode::ObserveNonDet { metadata, .. }
5519            | HydroNode::AssertIsConsistent { metadata, .. }
5520            | HydroNode::UnboundSingleton { metadata, .. }
5521            | HydroNode::Source { metadata, .. }
5522            | HydroNode::SingletonSource { metadata, .. }
5523            | HydroNode::CycleSource { metadata, .. }
5524            | HydroNode::Tee { metadata, .. }
5525            | HydroNode::Reference { metadata, .. }
5526            | HydroNode::Partition { metadata, .. }
5527            | HydroNode::YieldConcat { metadata, .. }
5528            | HydroNode::BeginAtomic { metadata, .. }
5529            | HydroNode::EndAtomic { metadata, .. }
5530            | HydroNode::Batch { metadata, .. }
5531            | HydroNode::Chain { metadata, .. }
5532            | HydroNode::MergeOrdered { metadata, .. }
5533            | HydroNode::ChainFirst { metadata, .. }
5534            | HydroNode::CrossProduct { metadata, .. }
5535            | HydroNode::CrossSingleton { metadata, .. }
5536            | HydroNode::Join { metadata, .. }
5537            | HydroNode::JoinHalf { metadata, .. }
5538            | HydroNode::Difference { metadata, .. }
5539            | HydroNode::AntiJoin { metadata, .. }
5540            | HydroNode::ResolveFutures { metadata, .. }
5541            | HydroNode::ResolveFuturesBlocking { metadata, .. }
5542            | HydroNode::ResolveFuturesOrdered { metadata, .. }
5543            | HydroNode::Map { metadata, .. }
5544            | HydroNode::FlatMap { metadata, .. }
5545            | HydroNode::FlatMapStreamBlocking { metadata, .. }
5546            | HydroNode::Filter { metadata, .. }
5547            | HydroNode::FilterMap { metadata, .. }
5548            | HydroNode::DeferTick { metadata, .. }
5549            | HydroNode::Enumerate { metadata, .. }
5550            | HydroNode::Inspect { metadata, .. }
5551            | HydroNode::Unique { metadata, .. }
5552            | HydroNode::Sort { metadata, .. }
5553            | HydroNode::Scan { metadata, .. }
5554            | HydroNode::ScanAsyncBlocking { metadata, .. }
5555            | HydroNode::Fold { metadata, .. }
5556            | HydroNode::FoldKeyed { metadata, .. }
5557            | HydroNode::Reduce { metadata, .. }
5558            | HydroNode::ReduceKeyed { metadata, .. }
5559            | HydroNode::ReduceKeyedWatermark { metadata, .. }
5560            | HydroNode::ExternalInput { metadata, .. }
5561            | HydroNode::Network { metadata, .. }
5562            | HydroNode::Counter { metadata, .. } => metadata,
5563        }
5564    }
5565
5566    pub fn op_metadata_mut(&mut self) -> &mut HydroIrOpMetadata {
5567        &mut self.metadata_mut().op
5568    }
5569
5570    pub fn metadata_mut(&mut self) -> &mut HydroIrMetadata {
5571        match self {
5572            HydroNode::Placeholder => {
5573                panic!()
5574            }
5575            HydroNode::VersionedNetworkFork { metadata, .. }
5576            | HydroNode::VersionedNetwork { metadata, .. } => metadata,
5577            HydroNode::Cast { metadata, .. }
5578            | HydroNode::ObserveNonDet { metadata, .. }
5579            | HydroNode::AssertIsConsistent { metadata, .. }
5580            | HydroNode::UnboundSingleton { metadata, .. }
5581            | HydroNode::Source { metadata, .. }
5582            | HydroNode::SingletonSource { metadata, .. }
5583            | HydroNode::CycleSource { metadata, .. }
5584            | HydroNode::Tee { metadata, .. }
5585            | HydroNode::Reference { metadata, .. }
5586            | HydroNode::Partition { metadata, .. }
5587            | HydroNode::YieldConcat { metadata, .. }
5588            | HydroNode::BeginAtomic { metadata, .. }
5589            | HydroNode::EndAtomic { metadata, .. }
5590            | HydroNode::Batch { metadata, .. }
5591            | HydroNode::Chain { metadata, .. }
5592            | HydroNode::MergeOrdered { metadata, .. }
5593            | HydroNode::ChainFirst { metadata, .. }
5594            | HydroNode::CrossProduct { metadata, .. }
5595            | HydroNode::CrossSingleton { metadata, .. }
5596            | HydroNode::Join { metadata, .. }
5597            | HydroNode::JoinHalf { metadata, .. }
5598            | HydroNode::Difference { metadata, .. }
5599            | HydroNode::AntiJoin { metadata, .. }
5600            | HydroNode::ResolveFutures { metadata, .. }
5601            | HydroNode::ResolveFuturesBlocking { metadata, .. }
5602            | HydroNode::ResolveFuturesOrdered { metadata, .. }
5603            | HydroNode::Map { metadata, .. }
5604            | HydroNode::FlatMap { metadata, .. }
5605            | HydroNode::FlatMapStreamBlocking { metadata, .. }
5606            | HydroNode::Filter { metadata, .. }
5607            | HydroNode::FilterMap { metadata, .. }
5608            | HydroNode::DeferTick { metadata, .. }
5609            | HydroNode::Enumerate { metadata, .. }
5610            | HydroNode::Inspect { metadata, .. }
5611            | HydroNode::Unique { metadata, .. }
5612            | HydroNode::Sort { metadata, .. }
5613            | HydroNode::Scan { metadata, .. }
5614            | HydroNode::ScanAsyncBlocking { metadata, .. }
5615            | HydroNode::Fold { metadata, .. }
5616            | HydroNode::FoldKeyed { metadata, .. }
5617            | HydroNode::Reduce { metadata, .. }
5618            | HydroNode::ReduceKeyed { metadata, .. }
5619            | HydroNode::ReduceKeyedWatermark { metadata, .. }
5620            | HydroNode::ExternalInput { metadata, .. }
5621            | HydroNode::Network { metadata, .. }
5622            | HydroNode::Counter { metadata, .. } => metadata,
5623        }
5624    }
5625
5626    pub fn input(&self) -> Vec<&HydroNode> {
5627        match self {
5628            HydroNode::Placeholder => {
5629                panic!()
5630            }
5631            HydroNode::Source { .. }
5632            | HydroNode::SingletonSource { .. }
5633            | HydroNode::ExternalInput { .. }
5634            | HydroNode::CycleSource { .. }
5635            | HydroNode::Tee { .. }
5636            | HydroNode::Reference { .. }
5637            | HydroNode::Partition { .. }
5638            | HydroNode::VersionedNetwork { .. } => {
5639                // Tee/Partition/VersionedNetwork find their input in separate special ways
5640                vec![]
5641            }
5642            HydroNode::Cast { inner, .. }
5643            | HydroNode::ObserveNonDet { inner, .. }
5644            | HydroNode::YieldConcat { inner, .. }
5645            | HydroNode::BeginAtomic { inner, .. }
5646            | HydroNode::EndAtomic { inner, .. }
5647            | HydroNode::Batch { inner, .. }
5648            | HydroNode::UnboundSingleton { inner, .. }
5649            | HydroNode::AssertIsConsistent { inner, .. } => {
5650                vec![inner]
5651            }
5652            HydroNode::Chain { first, second, .. } => {
5653                vec![first, second]
5654            }
5655            HydroNode::MergeOrdered { first, second, .. } => {
5656                vec![first, second]
5657            }
5658            HydroNode::ChainFirst { first, second, .. } => {
5659                vec![first, second]
5660            }
5661            HydroNode::CrossProduct { left, right, .. }
5662            | HydroNode::CrossSingleton { left, right, .. }
5663            | HydroNode::Join { left, right, .. }
5664            | HydroNode::JoinHalf { left, right, .. } => {
5665                vec![left, right]
5666            }
5667            HydroNode::Difference { pos, neg, .. } | HydroNode::AntiJoin { pos, neg, .. } => {
5668                vec![pos, neg]
5669            }
5670            HydroNode::Map { input, .. }
5671            | HydroNode::FlatMap { input, .. }
5672            | HydroNode::FlatMapStreamBlocking { input, .. }
5673            | HydroNode::Filter { input, .. }
5674            | HydroNode::FilterMap { input, .. }
5675            | HydroNode::Sort { input, .. }
5676            | HydroNode::DeferTick { input, .. }
5677            | HydroNode::Enumerate { input, .. }
5678            | HydroNode::Inspect { input, .. }
5679            | HydroNode::Unique { input, .. }
5680            | HydroNode::Network { input, .. }
5681            | HydroNode::Counter { input, .. }
5682            | HydroNode::ResolveFutures { input, .. }
5683            | HydroNode::ResolveFuturesBlocking { input, .. }
5684            | HydroNode::ResolveFuturesOrdered { input, .. }
5685            | HydroNode::Fold { input, .. }
5686            | HydroNode::FoldKeyed { input, .. }
5687            | HydroNode::Reduce { input, .. }
5688            | HydroNode::ReduceKeyed { input, .. }
5689            | HydroNode::Scan { input, .. }
5690            | HydroNode::ScanAsyncBlocking { input, .. } => {
5691                vec![input]
5692            }
5693            HydroNode::ReduceKeyedWatermark {
5694                input, watermark, ..
5695            } => {
5696                vec![input, watermark]
5697            }
5698            HydroNode::VersionedNetworkFork { senders, .. } => senders
5699                .iter()
5700                .map(|(_version, sender, _serialize)| sender.as_ref())
5701                .collect(),
5702        }
5703    }
5704
5705    pub fn input_metadata(&self) -> Vec<&HydroIrMetadata> {
5706        self.input()
5707            .iter()
5708            .map(|input_node| input_node.metadata())
5709            .collect()
5710    }
5711
5712    /// Returns `true` if this node is a Tee or Partition whose inner Rc
5713    /// has other live references, meaning the upstream is already driven
5714    /// by another consumer and does not need a Null sink.
5715    pub fn is_shared_with_others(&self) -> bool {
5716        match self {
5717            HydroNode::Tee { inner, .. } | HydroNode::Partition { inner, .. } => {
5718                Rc::strong_count(&inner.0) > 1
5719            }
5720            // A zero-output reference node is valid in DFIR (it drains itself at
5721            // end of tick), so it doesn't need to be driven by another consumer.
5722            HydroNode::Reference { .. } => false,
5723            _ => false,
5724        }
5725    }
5726
5727    pub fn print_root(&self) -> String {
5728        match self {
5729            HydroNode::Placeholder => {
5730                panic!()
5731            }
5732            HydroNode::Cast { .. } => "Cast()".to_owned(),
5733            HydroNode::UnboundSingleton { .. } => "UnboundSingleton()".to_owned(),
5734            HydroNode::ObserveNonDet { .. } => "ObserveNonDet()".to_owned(),
5735            HydroNode::AssertIsConsistent { .. } => "AssertIsConsistent()".to_owned(),
5736            HydroNode::Source { source, .. } => format!("Source({:?})", source),
5737            HydroNode::SingletonSource {
5738                value,
5739                first_tick_only,
5740                ..
5741            } => format!(
5742                "SingletonSource({:?}, first_tick_only={})",
5743                value, first_tick_only
5744            ),
5745            HydroNode::CycleSource { cycle_id, .. } => format!("CycleSource({})", cycle_id),
5746            HydroNode::Tee { inner, .. } => {
5747                format!("Tee({})", inner.0.borrow().print_root())
5748            }
5749            HydroNode::Reference { inner, kind, .. } => {
5750                format!("Reference({:?}, {})", kind, inner.0.borrow().print_root())
5751            }
5752            HydroNode::Partition { f, is_true, .. } => {
5753                format!("Partition({:?}, is_true={})", f, is_true)
5754            }
5755            HydroNode::YieldConcat { .. } => "YieldConcat()".to_owned(),
5756            HydroNode::BeginAtomic { .. } => "BeginAtomic()".to_owned(),
5757            HydroNode::EndAtomic { .. } => "EndAtomic()".to_owned(),
5758            HydroNode::Batch { .. } => "Batch()".to_owned(),
5759            HydroNode::Chain { first, second, .. } => {
5760                format!("Chain({}, {})", first.print_root(), second.print_root())
5761            }
5762            HydroNode::MergeOrdered { first, second, .. } => {
5763                format!(
5764                    "MergeOrdered({}, {})",
5765                    first.print_root(),
5766                    second.print_root()
5767                )
5768            }
5769            HydroNode::ChainFirst { first, second, .. } => {
5770                format!(
5771                    "ChainFirst({}, {})",
5772                    first.print_root(),
5773                    second.print_root()
5774                )
5775            }
5776            HydroNode::CrossProduct { left, right, .. } => {
5777                format!(
5778                    "CrossProduct({}, {})",
5779                    left.print_root(),
5780                    right.print_root()
5781                )
5782            }
5783            HydroNode::CrossSingleton { left, right, .. } => {
5784                format!(
5785                    "CrossSingleton({}, {})",
5786                    left.print_root(),
5787                    right.print_root()
5788                )
5789            }
5790            HydroNode::Join { left, right, .. } => {
5791                format!("Join({}, {})", left.print_root(), right.print_root())
5792            }
5793            HydroNode::JoinHalf { left, right, .. } => {
5794                format!("JoinHalf({}, {})", left.print_root(), right.print_root())
5795            }
5796            HydroNode::Difference { pos, neg, .. } => {
5797                format!("Difference({}, {})", pos.print_root(), neg.print_root())
5798            }
5799            HydroNode::AntiJoin { pos, neg, .. } => {
5800                format!("AntiJoin({}, {})", pos.print_root(), neg.print_root())
5801            }
5802            HydroNode::ResolveFutures { .. } => "ResolveFutures()".to_owned(),
5803            HydroNode::ResolveFuturesBlocking { .. } => "ResolveFuturesBlocking()".to_owned(),
5804            HydroNode::ResolveFuturesOrdered { .. } => "ResolveFuturesOrdered()".to_owned(),
5805            HydroNode::Map { f, .. } => format!("Map({:?})", f),
5806            HydroNode::FlatMap { f, .. } => format!("FlatMap({:?})", f),
5807            HydroNode::FlatMapStreamBlocking { f, .. } => format!("FlatMapStreamBlocking({:?})", f),
5808            HydroNode::Filter { f, .. } => format!("Filter({:?})", f),
5809            HydroNode::FilterMap { f, .. } => format!("FilterMap({:?})", f),
5810            HydroNode::DeferTick { .. } => "DeferTick()".to_owned(),
5811            HydroNode::Enumerate { .. } => "Enumerate()".to_owned(),
5812            HydroNode::Inspect { f, .. } => format!("Inspect({:?})", f),
5813            HydroNode::Unique { .. } => "Unique()".to_owned(),
5814            HydroNode::Sort { .. } => "Sort()".to_owned(),
5815            HydroNode::Fold { init, acc, .. } => format!("Fold({:?}, {:?})", init, acc),
5816            HydroNode::Scan { init, acc, .. } => format!("Scan({:?}, {:?})", init, acc),
5817            HydroNode::ScanAsyncBlocking { init, acc, .. } => {
5818                format!("ScanAsyncBlocking({:?}, {:?})", init, acc)
5819            }
5820            HydroNode::FoldKeyed { init, acc, .. } => format!("FoldKeyed({:?}, {:?})", init, acc),
5821            HydroNode::Reduce { f, .. } => format!("Reduce({:?})", f),
5822            HydroNode::ReduceKeyed { f, .. } => format!("ReduceKeyed({:?})", f),
5823            HydroNode::ReduceKeyedWatermark { f, .. } => format!("ReduceKeyedWatermark({:?})", f),
5824            HydroNode::Network { .. } => "Network()".to_owned(),
5825            HydroNode::ExternalInput { .. } => "ExternalInput()".to_owned(),
5826            HydroNode::Counter { tag, duration, .. } => {
5827                format!("Counter({:?}, {:?})", tag, duration)
5828            }
5829            HydroNode::VersionedNetworkFork {
5830                channel_name,
5831                senders,
5832                ..
5833            } => {
5834                let versions: Vec<u32> = senders.iter().map(|(v, _, _)| *v).collect();
5835                format!(
5836                    "VersionedNetworkFork({}, senders={:?})",
5837                    channel_name, versions
5838                )
5839            }
5840            HydroNode::VersionedNetwork { version, .. } => {
5841                format!("VersionedNetwork(v{})", version)
5842            }
5843        }
5844    }
5845}
5846
5847#[cfg(feature = "build")]
5848#[expect(clippy::too_many_arguments, reason = "networking codegen")]
5849fn instantiate_network<'a, D>(
5850    env: &mut D::InstantiateEnv,
5851    from_location: &LocationId,
5852    to_location: &LocationId,
5853    processes: &SparseSecondaryMap<LocationKey, D::Process>,
5854    clusters: &SparseSecondaryMap<LocationKey, D::Cluster>,
5855    name: Option<&str>,
5856    networking_info: &crate::networking::NetworkingInfo,
5857    external_types: Option<(&syn::Type, &syn::Type)>,
5858) -> (syn::Expr, syn::Expr, Box<dyn FnOnce()>)
5859where
5860    D: Deploy<'a>,
5861{
5862    if external_types.is_some() && !D::SUPPORTS_EXTERNAL_SERIALIZATION {
5863        panic!(
5864            "`.embedded()` serialization leaves serialization to code outside of Hydro and is \
5865             only supported by the embedded deployment backend. Use `.bincode()` (or another \
5866             supported serialization backend) for this deployment target instead."
5867        );
5868    }
5869
5870    let ((sink, source), connect_fn) = match (from_location, to_location) {
5871        (&LocationId::Process(from), &LocationId::Process(to)) => {
5872            let from_node = processes
5873                .get(from)
5874                .unwrap_or_else(|| {
5875                    panic!("A process used in the graph was not instantiated: {}", from)
5876                })
5877                .clone();
5878            let to_node = processes
5879                .get(to)
5880                .unwrap_or_else(|| {
5881                    panic!("A process used in the graph was not instantiated: {}", to)
5882                })
5883                .clone();
5884
5885            let sink_port = from_node.next_port();
5886            let source_port = to_node.next_port();
5887
5888            (
5889                D::o2o_sink_source(
5890                    env,
5891                    &from_node,
5892                    &sink_port,
5893                    &to_node,
5894                    &source_port,
5895                    name,
5896                    networking_info,
5897                    external_types,
5898                ),
5899                D::o2o_connect(&from_node, &sink_port, &to_node, &source_port),
5900            )
5901        }
5902        (&LocationId::Process(from), &LocationId::Cluster(to)) => {
5903            let from_node = processes
5904                .get(from)
5905                .unwrap_or_else(|| {
5906                    panic!("A process used in the graph was not instantiated: {}", from)
5907                })
5908                .clone();
5909            let to_node = clusters
5910                .get(to)
5911                .unwrap_or_else(|| {
5912                    panic!("A cluster used in the graph was not instantiated: {}", to)
5913                })
5914                .clone();
5915
5916            let sink_port = from_node.next_port();
5917            let source_port = to_node.next_port();
5918
5919            (
5920                D::o2m_sink_source(
5921                    env,
5922                    &from_node,
5923                    &sink_port,
5924                    &to_node,
5925                    &source_port,
5926                    name,
5927                    networking_info,
5928                    external_types,
5929                ),
5930                D::o2m_connect(&from_node, &sink_port, &to_node, &source_port),
5931            )
5932        }
5933        (&LocationId::Cluster(from), &LocationId::Process(to)) => {
5934            let from_node = clusters
5935                .get(from)
5936                .unwrap_or_else(|| {
5937                    panic!("A cluster used in the graph was not instantiated: {}", from)
5938                })
5939                .clone();
5940            let to_node = processes
5941                .get(to)
5942                .unwrap_or_else(|| {
5943                    panic!("A process used in the graph was not instantiated: {}", to)
5944                })
5945                .clone();
5946
5947            let sink_port = from_node.next_port();
5948            let source_port = to_node.next_port();
5949
5950            (
5951                D::m2o_sink_source(
5952                    env,
5953                    &from_node,
5954                    &sink_port,
5955                    &to_node,
5956                    &source_port,
5957                    name,
5958                    networking_info,
5959                    external_types,
5960                ),
5961                D::m2o_connect(&from_node, &sink_port, &to_node, &source_port),
5962            )
5963        }
5964        (&LocationId::Cluster(from), &LocationId::Cluster(to)) => {
5965            let from_node = clusters
5966                .get(from)
5967                .unwrap_or_else(|| {
5968                    panic!("A cluster used in the graph was not instantiated: {}", from)
5969                })
5970                .clone();
5971            let to_node = clusters
5972                .get(to)
5973                .unwrap_or_else(|| {
5974                    panic!("A cluster used in the graph was not instantiated: {}", to)
5975                })
5976                .clone();
5977
5978            let sink_port = from_node.next_port();
5979            let source_port = to_node.next_port();
5980
5981            (
5982                D::m2m_sink_source(
5983                    env,
5984                    &from_node,
5985                    &sink_port,
5986                    &to_node,
5987                    &source_port,
5988                    name,
5989                    networking_info,
5990                    external_types,
5991                ),
5992                D::m2m_connect(&from_node, &sink_port, &to_node, &source_port),
5993            )
5994        }
5995        (LocationId::Tick(_, _), _) => panic!(),
5996        (_, LocationId::Tick(_, _)) => panic!(),
5997        (LocationId::Atomic(_), _) => panic!(),
5998        (_, LocationId::Atomic(_)) => panic!(),
5999    };
6000    (sink, source, connect_fn)
6001}
6002
6003#[cfg(test)]
6004mod serde_test;
6005
6006#[cfg(test)]
6007mod test {
6008    use std::mem::size_of;
6009
6010    use stageleft::{QuotedWithContext, q};
6011
6012    use super::*;
6013
6014    #[test]
6015    #[cfg_attr(
6016        not(feature = "build"),
6017        ignore = "expects inclusion of feature-gated fields"
6018    )]
6019    fn hydro_node_size() {
6020        assert_eq!(size_of::<HydroNode>(), 264);
6021    }
6022
6023    #[test]
6024    #[cfg_attr(
6025        not(feature = "build"),
6026        ignore = "expects inclusion of feature-gated fields"
6027    )]
6028    fn hydro_root_size() {
6029        assert_eq!(size_of::<HydroRoot>(), 136);
6030    }
6031
6032    #[test]
6033    fn test_simplify_q_macro_basic() {
6034        // Test basic non-q! expression
6035        let simple_expr: syn::Expr = syn::parse_str("x + y").unwrap();
6036        let result = simplify_q_macro(simple_expr.clone());
6037        assert_eq!(result, simple_expr);
6038    }
6039
6040    #[test]
6041    fn test_simplify_q_macro_actual_stageleft_call() {
6042        // Test a simplified version of what a real stageleft call might look like
6043        let stageleft_call = q!(|x: usize| x + 1).splice_fn1_ctx(&());
6044        let result = simplify_q_macro(stageleft_call);
6045        // This should be processed by our visitor and simplified to q!(...)
6046        // since we detect the stageleft::runtime_support::fn_* pattern
6047        hydro_build_utils::assert_snapshot!(result.to_token_stream().to_string());
6048    }
6049
6050    #[test]
6051    fn test_closure_no_pipe_at_start() {
6052        // Test a closure that does not start with a pipe
6053        let stageleft_call = q!({
6054            let foo = 123;
6055            move |b: usize| b + foo
6056        })
6057        .splice_fn1_ctx(&());
6058        let result = simplify_q_macro(stageleft_call);
6059        hydro_build_utils::assert_snapshot!(result.to_token_stream().to_string());
6060    }
6061}