cranpose_core/
source_trace.rs1#[derive(Clone, Debug, PartialEq, Eq)]
5pub struct SourceLocation {
6 pub name: &'static str,
8 pub file: &'static str,
10 pub line: u32,
12 pub manifest_dir: &'static str,
14 #[cfg(all(feature = "inspection", debug_assertions))]
15 recompositions: Option<RecompositionCounter>,
16}
17
18impl SourceLocation {
19 pub fn recompositions(&self) -> Option<u64> {
22 #[cfg(all(feature = "inspection", debug_assertions))]
23 return self
24 .recompositions
25 .as_ref()
26 .map(|counter| counter.0.executions.get().saturating_sub(1));
27 #[cfg(not(all(feature = "inspection", debug_assertions)))]
28 None
29 }
30}
31
32#[cfg(all(feature = "inspection", debug_assertions))]
33#[derive(Clone, Debug)]
34pub(crate) struct RecompositionCounter(std::rc::Rc<Counter>);
35
36#[cfg(all(feature = "inspection", debug_assertions))]
37#[derive(Debug)]
38struct Counter {
39 executions: std::cell::Cell<u64>,
40 instance_id: u64,
41 name: &'static str,
42 file: &'static str,
43 line: u32,
44 manifest_dir: &'static str,
45}
46
47#[derive(Debug, PartialEq, Eq)]
49pub struct RecompositionInfo {
50 pub instance_id: u64,
52 pub name: &'static str,
54 pub file: &'static str,
56 pub line: u32,
58 pub manifest_dir: &'static str,
60 pub recompositions: u64,
62}
63
64pub fn recomposition_snapshot() -> Vec<RecompositionInfo> {
67 #[cfg(all(feature = "inspection", debug_assertions))]
68 {
69 if !crate::thread_state::cells(|cells| cells.track_recompositions.get()) {
70 return Vec::new();
71 }
72 crate::thread_state::runtimes(|state| {
73 let mut registry = state.recomposition_counters.borrow_mut();
74 let mut result = Vec::with_capacity(registry.len());
75 registry.retain(|weak| {
76 let Some(scope) = crate::RecomposeScope::upgrade(weak) else {
77 return false;
78 };
79 if scope.is_effectively_active()
80 && let Some(counter) = scope.inner.recompositions.get()
81 {
82 result.push(RecompositionInfo {
83 instance_id: counter.0.instance_id,
84 name: counter.0.name,
85 file: counter.0.file,
86 line: counter.0.line,
87 manifest_dir: counter.0.manifest_dir,
88 recompositions: counter.0.executions.get().saturating_sub(1),
89 });
90 }
91 true
92 });
93 result
94 })
95 }
96 #[cfg(not(all(feature = "inspection", debug_assertions)))]
97 Vec::new()
98}
99
100#[cfg(all(feature = "inspection", debug_assertions))]
101impl PartialEq for RecompositionCounter {
102 fn eq(&self, other: &Self) -> bool {
103 std::rc::Rc::ptr_eq(&self.0, &other.0)
104 }
105}
106
107#[cfg(all(feature = "inspection", debug_assertions))]
108impl Eq for RecompositionCounter {}
109
110pub fn set_recomposition_tracking(enabled: bool) {
114 #[cfg(all(feature = "inspection", debug_assertions))]
115 crate::thread_state::cells(|cells| cells.track_recompositions.set(enabled));
116 #[cfg(not(all(feature = "inspection", debug_assertions)))]
117 let _ = enabled;
118}
119
120#[cfg(all(feature = "inspection", debug_assertions))]
121fn record_composition(
122 name: &'static str,
123 file: &'static str,
124 line: u32,
125 manifest_dir: &'static str,
126) -> Option<RecompositionCounter> {
127 if !crate::thread_state::cells(|cells| cells.track_recompositions.get())
128 || name.starts_with("__cranpose_call:")
129 {
130 return None;
131 }
132 crate::with_current_composer_opt(|composer| {
133 let scope = composer.current_recompose_scope()?;
134 let counter = scope.inner.recompositions.get_or_init(|| {
135 crate::thread_state::runtimes(|state| {
136 state
137 .recomposition_counters
138 .borrow_mut()
139 .push(scope.downgrade());
140 });
141 let instance_id = crate::thread_state::cells(|cells| {
142 let next = &cells.next_recomposition_instance;
143 let id = next.get().saturating_add(1);
144 next.set(id);
145 id
146 });
147 RecompositionCounter(std::rc::Rc::new(Counter {
148 executions: std::cell::Cell::new(0),
149 instance_id,
150 name,
151 file,
152 line,
153 manifest_dir,
154 }))
155 });
156 counter
157 .0
158 .executions
159 .set(counter.0.executions.get().saturating_add(1));
160 Some(counter.clone())
161 })
162 .flatten()
163}
164
165#[doc(hidden)]
167pub struct SourceScope {
168 #[cfg(feature = "inspection")]
169 depth: usize,
170 marker: std::marker::PhantomData<std::rc::Rc<()>>,
171}
172
173impl Drop for SourceScope {
174 fn drop(&mut self) {
175 #[cfg(feature = "inspection")]
176 crate::thread_state::runtimes(|state| state.source_stack.borrow_mut().truncate(self.depth));
177 }
178}
179
180#[doc(hidden)]
182#[inline]
183pub fn __source_scope(
184 name: &'static str,
185 file: &'static str,
186 line: u32,
187 manifest_dir: &'static str,
188) -> SourceScope {
189 #[cfg(feature = "inspection")]
190 let depth = crate::thread_state::runtimes(|state| {
191 let mut stack = state.source_stack.borrow_mut();
192 let depth = stack.len();
193 stack.push(SourceLocation {
194 name,
195 file,
196 line,
197 manifest_dir,
198 #[cfg(debug_assertions)]
199 recompositions: record_composition(name, file, line, manifest_dir),
200 });
201 depth
202 });
203 #[cfg(not(feature = "inspection"))]
204 let _ = (name, file, line, manifest_dir);
205 SourceScope {
206 #[cfg(feature = "inspection")]
207 depth,
208 marker: std::marker::PhantomData,
209 }
210}
211
212#[cfg(feature = "inspection")]
214pub fn current_source_trace() -> std::rc::Rc<[SourceLocation]> {
215 crate::thread_state::runtimes(|state| std::rc::Rc::from(state.source_stack.borrow().as_slice()))
216}
217
218#[cfg(feature = "inspection")]
219pub(crate) struct SourceContext(Vec<SourceLocation>);
220
221#[cfg(feature = "inspection")]
222impl Drop for SourceContext {
223 fn drop(&mut self) {
224 crate::thread_state::runtimes(|state| {
225 *state.source_stack.borrow_mut() = std::mem::take(&mut self.0);
226 });
227 }
228}
229
230#[cfg(feature = "inspection")]
231pub(crate) fn restore_source_trace(trace: &[SourceLocation]) -> SourceContext {
232 SourceContext(crate::thread_state::runtimes(|state| {
233 state.source_stack.replace(trace.to_vec())
234 }))
235}
236
237#[cfg(all(test, feature = "inspection"))]
238#[path = "tests/source_trace_tests.rs"]
239mod tests;