1#[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> {
24 #[cfg(all(feature = "inspection", debug_assertions))]
25 return self
26 .recompositions
27 .as_ref()
28 .map(|counter| counter.0.executions.get().saturating_sub(1));
29 #[cfg(not(all(feature = "inspection", debug_assertions)))]
30 None
31 }
32}
33
34#[cfg(all(feature = "inspection", debug_assertions))]
35#[derive(Clone, Debug)]
36pub(crate) struct RecompositionCounter(std::rc::Rc<Counter>);
37
38#[cfg(all(feature = "inspection", debug_assertions))]
39#[derive(Debug)]
40struct Counter {
41 executions: std::cell::Cell<u64>,
42 instance_id: u64,
43 name: &'static str,
44 file: &'static str,
45 line: u32,
46 manifest_dir: &'static str,
47}
48
49#[derive(Debug, PartialEq, Eq)]
51pub struct RecompositionInfo {
52 pub instance_id: u64,
54 pub name: &'static str,
56 pub file: &'static str,
58 pub line: u32,
60 pub manifest_dir: &'static str,
62 pub recompositions: u64,
64}
65
66pub fn recomposition_snapshot() -> Vec<RecompositionInfo> {
69 #[cfg(all(feature = "inspection", debug_assertions))]
70 {
71 if !crate::thread_state::cells(|cells| cells.track_recompositions.get()) {
72 return Vec::new();
73 }
74 crate::thread_state::runtimes(|state| {
75 let mut registry = state.recomposition_counters.borrow_mut();
76 let mut result = Vec::with_capacity(registry.len());
77 registry.retain(|weak| {
78 let Some(scope) = crate::RecomposeScope::upgrade(weak) else {
79 return false;
80 };
81 if scope.is_effectively_active()
82 && let Some(counter) = scope.inner.recompositions.get()
83 {
84 result.push(RecompositionInfo {
85 instance_id: counter.0.instance_id,
86 name: counter.0.name,
87 file: counter.0.file,
88 line: counter.0.line,
89 manifest_dir: counter.0.manifest_dir,
90 recompositions: counter.0.executions.get().saturating_sub(1),
91 });
92 }
93 true
94 });
95 result
96 })
97 }
98 #[cfg(not(all(feature = "inspection", debug_assertions)))]
99 Vec::new()
100}
101
102#[cfg(all(feature = "inspection", debug_assertions))]
103impl PartialEq for RecompositionCounter {
104 fn eq(&self, other: &Self) -> bool {
105 std::rc::Rc::ptr_eq(&self.0, &other.0)
106 }
107}
108
109#[cfg(all(feature = "inspection", debug_assertions))]
110impl Eq for RecompositionCounter {}
111
112pub fn set_recomposition_tracking(enabled: bool) {
116 #[cfg(all(feature = "inspection", debug_assertions))]
117 crate::thread_state::cells(|cells| cells.track_recompositions.set(enabled));
118 #[cfg(not(all(feature = "inspection", debug_assertions)))]
119 let _ = enabled;
120}
121
122#[cfg(all(feature = "inspection", debug_assertions))]
123fn record_composition(
124 name: &'static str,
125 file: &'static str,
126 line: u32,
127 manifest_dir: &'static str,
128) -> Option<RecompositionCounter> {
129 if !crate::thread_state::cells(|cells| cells.track_recompositions.get())
130 || name.starts_with("__cranpose_call:")
131 {
132 return None;
133 }
134 crate::with_current_composer_opt(|composer| {
135 let scope = composer.current_recompose_scope()?;
136 let counter = scope.inner.recompositions.get_or_init(|| {
137 crate::thread_state::runtimes(|state| {
138 state
139 .recomposition_counters
140 .borrow_mut()
141 .push(scope.downgrade());
142 });
143 let instance_id = crate::thread_state::cells(|cells| {
144 let next = &cells.next_recomposition_instance;
145 let id = next.get().saturating_add(1);
146 next.set(id);
147 id
148 });
149 RecompositionCounter(std::rc::Rc::new(Counter {
150 executions: std::cell::Cell::new(0),
151 instance_id,
152 name,
153 file,
154 line,
155 manifest_dir,
156 }))
157 });
158 counter
159 .0
160 .executions
161 .set(counter.0.executions.get().saturating_add(1));
162 Some(counter.clone())
163 })
164 .flatten()
165}
166
167#[doc(hidden)]
169pub struct SourceScope {
170 #[cfg(feature = "inspection")]
171 depth: usize,
172 marker: std::marker::PhantomData<std::rc::Rc<()>>,
173}
174
175impl Drop for SourceScope {
176 fn drop(&mut self) {
177 #[cfg(feature = "inspection")]
178 crate::thread_state::runtimes(|state| state.source_stack.borrow_mut().truncate(self.depth));
179 }
180}
181
182#[doc(hidden)]
185#[inline]
186pub fn __source_scope(
187 name: &'static str,
188 file: &'static str,
189 line: u32,
190 manifest_dir: &'static str,
191) -> SourceScope {
192 enter_source(name, file, line, manifest_dir, Composes::InOwnScope)
193}
194
195#[doc(hidden)]
198#[inline]
199pub fn __inline_source_scope(
200 name: &'static str,
201 file: &'static str,
202 line: u32,
203 manifest_dir: &'static str,
204) -> SourceScope {
205 enter_source(name, file, line, manifest_dir, Composes::InCallerScope)
206}
207
208#[derive(Clone, Copy)]
211enum Composes {
212 InOwnScope,
213 InCallerScope,
214}
215
216#[inline]
217fn enter_source(
218 name: &'static str,
219 file: &'static str,
220 line: u32,
221 manifest_dir: &'static str,
222 #[cfg_attr(
223 not(all(feature = "inspection", debug_assertions)),
224 expect(unused_variables, reason = "only debug inspection builds count bodies")
225 )]
226 composes: Composes,
227) -> SourceScope {
228 #[cfg(feature = "inspection")]
229 let depth = crate::thread_state::runtimes(|state| {
230 let mut stack = state.source_stack.borrow_mut();
231 let depth = stack.len();
232 stack.push(SourceLocation {
233 name,
234 file,
235 line,
236 manifest_dir,
237 #[cfg(debug_assertions)]
238 recompositions: match composes {
239 Composes::InOwnScope => record_composition(name, file, line, manifest_dir),
240 Composes::InCallerScope => None,
241 },
242 });
243 depth
244 });
245 #[cfg(not(feature = "inspection"))]
246 let _ = (name, file, line, manifest_dir);
247 SourceScope {
248 #[cfg(feature = "inspection")]
249 depth,
250 marker: std::marker::PhantomData,
251 }
252}
253
254#[cfg(feature = "inspection")]
256pub fn current_source_trace() -> std::rc::Rc<[SourceLocation]> {
257 crate::thread_state::runtimes(|state| std::rc::Rc::from(state.source_stack.borrow().as_slice()))
258}
259
260#[cfg(feature = "inspection")]
261pub(crate) struct SourceContext(Vec<SourceLocation>);
262
263#[cfg(feature = "inspection")]
264impl Drop for SourceContext {
265 fn drop(&mut self) {
266 crate::thread_state::runtimes(|state| {
267 *state.source_stack.borrow_mut() = std::mem::take(&mut self.0);
268 });
269 }
270}
271
272#[cfg(feature = "inspection")]
273pub(crate) fn restore_source_trace(trace: &[SourceLocation]) -> SourceContext {
274 SourceContext(crate::thread_state::runtimes(|state| {
275 state.source_stack.replace(trace.to_vec())
276 }))
277}
278
279#[cfg(all(test, feature = "inspection"))]
280#[path = "tests/source_trace_tests.rs"]
281mod tests;