hydro_lang/
singleton_ref.rs1use std::cell::RefCell;
4use std::marker::PhantomData;
5use std::rc::Rc;
6
7use proc_macro2::Span;
8use quote::quote;
9use stageleft::runtime_support::{FreeVariableWithContextWithProps, QuoteTokens};
10
11use crate::compile::ir::{HydroNode, SharedNode};
12use crate::location::Location;
13
14pub struct SingletonRef<'a, T, L> {
22 pub(crate) node: *const RefCell<HydroNode>,
23 _phantom: PhantomData<(&'a (), T, L)>,
24}
25impl<T, L> SingletonRef<'_, T, L> {
26 pub(crate) fn new(rc_ptr: Rc<RefCell<HydroNode>>) -> Self {
31 let node = Rc::into_raw(rc_ptr);
33 Self {
34 node,
35 _phantom: PhantomData,
36 }
37 }
38}
39
40impl<T, L> Copy for SingletonRef<'_, T, L> {}
41impl<T, L> Clone for SingletonRef<'_, T, L> {
42 fn clone(&self) -> Self {
43 *self
44 }
45}
46
47thread_local! {
50 static SINGLETON_REFS: RefCell<Option<Vec<(syn::Ident, HydroNode)>>> = const { RefCell::new(None) };
51}
52
53pub fn with_singleton_capture<R>(f: impl FnOnce() -> R) -> (R, Vec<(syn::Ident, HydroNode)>) {
56 SINGLETON_REFS.with(|cell| {
57 let prev = cell.borrow_mut().replace(Vec::new());
58 assert!(
59 prev.is_none(),
60 "nested singleton capture scopes are not supported"
61 );
62 });
63 let result = f();
64 let captured = SINGLETON_REFS.with(|cell| cell.borrow_mut().take().unwrap());
65 (result, captured)
66}
67
68static SINGLETON_REF_COUNTER: std::sync::atomic::AtomicUsize =
69 std::sync::atomic::AtomicUsize::new(0);
70
71impl<'a, T: 'a, L> FreeVariableWithContextWithProps<L, ()> for SingletonRef<'a, T, L>
72where
73 L: Location<'a>,
74{
75 type O = &'a T;
76
77 fn to_tokens(self, _ctx: &L) -> (QuoteTokens, ()) {
78 let id = SINGLETON_REF_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
79 let ident = syn::Ident::new(&format!("__hydro_singleton_ref_{}", id), Span::call_site());
80
81 SINGLETON_REFS.with(|cell| {
82 let mut guard = cell.borrow_mut();
83 let refs = guard.as_mut().expect(
84 "SingletonRef used inside q!() but no singleton capture scope is active. \
85 This is a bug — singleton capture should be set up by the operator that uses q!().",
86 );
87 let rc = unsafe { Rc::from_raw(self.node) };
91 let cloned = rc.clone();
92 std::mem::forget(rc); let metadata = cloned.borrow().metadata().clone(); refs.push((
96 ident.clone(),
97 HydroNode::Singleton {
98 inner: SharedNode(cloned),
99 metadata,
100 },
101 ));
102 });
103
104 (
105 QuoteTokens {
106 prelude: None,
107 expr: Some(quote!(#ident)),
108 },
109 (),
110 )
111 }
112}
113
114#[cfg(test)]
115#[cfg(feature = "build")]
116mod tests {
117 use stageleft::q;
118
119 use crate::compile::builder::FlowBuilder;
120 use crate::location::Location;
121
122 struct P1 {}
123
124 #[test]
127 fn singleton_by_ref_compiles() {
128 let mut flow = FlowBuilder::new();
129 let node = flow.process::<P1>();
130
131 let my_count = node
132 .source_iter(q!(0..5i32))
133 .fold(q!(|| 0i32), q!(|acc: &mut i32, x| *acc += x));
134 let count_ref = my_count.by_ref();
135
136 node.source_iter(q!(1..=3i32))
137 .map(q!(|x| x + *count_ref))
138 .for_each(q!(|_| {}));
139
140 my_count.into_stream().for_each(q!(|_| {}));
142
143 let _built = flow.finalize();
145 }
146
147 #[test]
149 fn singleton_by_ref_non_copy() {
150 let mut flow = FlowBuilder::new();
151 let node = flow.process::<P1>();
152
153 let my_vec = node.source_iter(q!(0..5i32)).fold(
154 q!(|| Vec::<i32>::new()),
155 q!(|acc: &mut Vec<i32>, x| acc.push(x)),
156 );
157 let vec_ref = my_vec.by_ref();
158
159 node.source_iter(q!(1..=3i32))
160 .map(q!(|x| x + vec_ref.len() as i32))
161 .for_each(q!(|_| {}));
162
163 my_vec.into_stream().for_each(q!(|_| {}));
165
166 let _built = flow.finalize();
167 }
168}