cranpose_core/
source_trace.rs1#[cfg(feature = "inspection")]
4use std::cell::RefCell;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct SourceLocation {
9 pub name: &'static str,
11 pub file: &'static str,
13 pub line: u32,
15 pub manifest_dir: &'static str,
17 #[cfg(all(feature = "inspection", debug_assertions))]
18 recompositions: Option<RecompositionCounter>,
19}
20
21impl SourceLocation {
22 pub fn recompositions(&self) -> Option<u64> {
25 #[cfg(all(feature = "inspection", debug_assertions))]
26 return self
27 .recompositions
28 .as_ref()
29 .map(|counter| counter.0.executions.get().saturating_sub(1));
30 #[cfg(not(all(feature = "inspection", debug_assertions)))]
31 None
32 }
33}
34
35#[cfg(all(feature = "inspection", debug_assertions))]
36#[derive(Clone, Debug)]
37pub(crate) struct RecompositionCounter(std::rc::Rc<Counter>);
38
39#[cfg(all(feature = "inspection", debug_assertions))]
40#[derive(Debug)]
41struct Counter {
42 executions: std::cell::Cell<u64>,
43 instance_id: u64,
44 name: &'static str,
45 file: &'static str,
46 line: u32,
47 manifest_dir: &'static str,
48}
49
50#[derive(Debug, PartialEq, Eq)]
52pub struct RecompositionInfo {
53 pub instance_id: u64,
55 pub name: &'static str,
57 pub file: &'static str,
59 pub line: u32,
61 pub manifest_dir: &'static str,
63 pub recompositions: u64,
65}
66
67pub fn recomposition_snapshot() -> Vec<RecompositionInfo> {
70 #[cfg(all(feature = "inspection", debug_assertions))]
71 {
72 if !TRACK_RECOMPOSITIONS.with(std::cell::Cell::get) {
73 return Vec::new();
74 }
75 COUNTERS.with(|registry| {
76 let mut registry = registry.borrow_mut();
77 let mut result = Vec::with_capacity(registry.len());
78 registry.retain(|weak| {
79 let Some(scope) = crate::RecomposeScope::upgrade(weak) else {
80 return false;
81 };
82 if scope.is_effectively_active()
83 && let Some(counter) = scope.inner.recompositions.get()
84 {
85 result.push(RecompositionInfo {
86 instance_id: counter.0.instance_id,
87 name: counter.0.name,
88 file: counter.0.file,
89 line: counter.0.line,
90 manifest_dir: counter.0.manifest_dir,
91 recompositions: counter.0.executions.get().saturating_sub(1),
92 });
93 }
94 true
95 });
96 result
97 })
98 }
99 #[cfg(not(all(feature = "inspection", debug_assertions)))]
100 Vec::new()
101}
102
103#[cfg(all(feature = "inspection", debug_assertions))]
104impl PartialEq for RecompositionCounter {
105 fn eq(&self, other: &Self) -> bool {
106 std::rc::Rc::ptr_eq(&self.0, &other.0)
107 }
108}
109
110#[cfg(all(feature = "inspection", debug_assertions))]
111impl Eq for RecompositionCounter {}
112
113#[cfg(all(feature = "inspection", debug_assertions))]
114thread_local! {
115 static TRACK_RECOMPOSITIONS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
116 static NEXT_INSTANCE: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
117 static COUNTERS: RefCell<Vec<std::rc::Weak<crate::RecomposeScopeInner>>> = const { RefCell::new(Vec::new()) };
118}
119
120pub fn set_recomposition_tracking(enabled: bool) {
124 #[cfg(all(feature = "inspection", debug_assertions))]
125 TRACK_RECOMPOSITIONS.with(|tracking| tracking.set(enabled));
126 #[cfg(not(all(feature = "inspection", debug_assertions)))]
127 let _ = enabled;
128}
129
130#[cfg(all(feature = "inspection", debug_assertions))]
131fn record_composition(
132 name: &'static str,
133 file: &'static str,
134 line: u32,
135 manifest_dir: &'static str,
136) -> Option<RecompositionCounter> {
137 if !TRACK_RECOMPOSITIONS.with(std::cell::Cell::get) || name.starts_with("__cranpose_call:") {
138 return None;
139 }
140 crate::with_current_composer_opt(|composer| {
141 let scope = composer.current_recompose_scope()?;
142 let counter = scope.inner.recompositions.get_or_init(|| {
143 COUNTERS.with(|registry| registry.borrow_mut().push(scope.downgrade()));
144 let instance_id = NEXT_INSTANCE.with(|next| {
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#[cfg(feature = "inspection")]
168thread_local! {
169 static STACK: RefCell<Vec<SourceLocation>> = const { RefCell::new(Vec::new()) };
170}
171
172#[doc(hidden)]
174pub struct SourceScope {
175 #[cfg(feature = "inspection")]
176 depth: usize,
177 marker: std::marker::PhantomData<std::rc::Rc<()>>,
178}
179
180impl Drop for SourceScope {
181 fn drop(&mut self) {
182 #[cfg(feature = "inspection")]
183 STACK.with(|stack| stack.borrow_mut().truncate(self.depth));
184 }
185}
186
187#[doc(hidden)]
189#[inline]
190pub fn __source_scope(
191 name: &'static str,
192 file: &'static str,
193 line: u32,
194 manifest_dir: &'static str,
195) -> SourceScope {
196 #[cfg(feature = "inspection")]
197 let depth = STACK.with(|stack| {
198 let mut stack = stack.borrow_mut();
199 let depth = stack.len();
200 stack.push(SourceLocation {
201 name,
202 file,
203 line,
204 manifest_dir,
205 #[cfg(debug_assertions)]
206 recompositions: record_composition(name, file, line, manifest_dir),
207 });
208 depth
209 });
210 #[cfg(not(feature = "inspection"))]
211 let _ = (name, file, line, manifest_dir);
212 SourceScope {
213 #[cfg(feature = "inspection")]
214 depth,
215 marker: std::marker::PhantomData,
216 }
217}
218
219#[cfg(feature = "inspection")]
221pub fn current_source_trace() -> std::rc::Rc<[SourceLocation]> {
222 STACK.with(|stack| std::rc::Rc::from(stack.borrow().as_slice()))
223}
224
225#[cfg(feature = "inspection")]
226pub(crate) struct SourceContext(Vec<SourceLocation>);
227
228#[cfg(feature = "inspection")]
229impl Drop for SourceContext {
230 fn drop(&mut self) {
231 STACK.with(|stack| *stack.borrow_mut() = std::mem::take(&mut self.0));
232 }
233}
234
235#[cfg(feature = "inspection")]
236pub(crate) fn restore_source_trace(trace: &[SourceLocation]) -> SourceContext {
237 SourceContext(STACK.with(|stack| stack.replace(trace.to_vec())))
238}
239
240#[cfg(all(test, feature = "inspection"))]
241#[path = "tests/source_trace_tests.rs"]
242mod tests;