Skip to main content

cranpose_core/
source_trace.rs

1//! Optional composition origins for development tools.
2
3#[cfg(feature = "inspection")]
4use std::cell::RefCell;
5
6/// A composable definition active when a layout node was emitted.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct SourceLocation {
9    /// Composable function name.
10    pub name: &'static str,
11    /// Source file reported by the compiler.
12    pub file: &'static str,
13    /// One-based declaration line.
14    pub line: u32,
15    /// Package directory reported by the compiler.
16    pub manifest_dir: &'static str,
17}
18
19#[cfg(feature = "inspection")]
20thread_local! {
21    static STACK: RefCell<Vec<SourceLocation>> = const { RefCell::new(Vec::new()) };
22}
23
24/// Restores the composition origin stack when the function returns or unwinds.
25#[doc(hidden)]
26pub struct SourceScope {
27    #[cfg(feature = "inspection")]
28    depth: usize,
29    marker: std::marker::PhantomData<std::rc::Rc<()>>,
30}
31
32impl Drop for SourceScope {
33    fn drop(&mut self) {
34        #[cfg(feature = "inspection")]
35        STACK.with(|stack| stack.borrow_mut().truncate(self.depth));
36    }
37}
38
39/// Used by the composable macro; compiles to an empty guard without inspection.
40#[doc(hidden)]
41#[inline]
42pub fn __source_scope(
43    name: &'static str,
44    file: &'static str,
45    line: u32,
46    manifest_dir: &'static str,
47) -> SourceScope {
48    #[cfg(feature = "inspection")]
49    let depth = STACK.with(|stack| {
50        let mut stack = stack.borrow_mut();
51        let depth = stack.len();
52        stack.push(SourceLocation {
53            name,
54            file,
55            line,
56            manifest_dir,
57        });
58        depth
59    });
60    #[cfg(not(feature = "inspection"))]
61    let _ = (name, file, line, manifest_dir);
62    SourceScope {
63        #[cfg(feature = "inspection")]
64        depth,
65        marker: std::marker::PhantomData,
66    }
67}
68
69/// Captures the active composable definitions, outermost first.
70#[cfg(feature = "inspection")]
71pub fn current_source_trace() -> std::rc::Rc<[SourceLocation]> {
72    STACK.with(|stack| std::rc::Rc::from(stack.borrow().as_slice()))
73}
74
75#[cfg(feature = "inspection")]
76pub(crate) struct SourceContext(Vec<SourceLocation>);
77
78#[cfg(feature = "inspection")]
79impl Drop for SourceContext {
80    fn drop(&mut self) {
81        STACK.with(|stack| *stack.borrow_mut() = std::mem::take(&mut self.0));
82    }
83}
84
85#[cfg(feature = "inspection")]
86pub(crate) fn restore_source_trace(trace: &[SourceLocation]) -> SourceContext {
87    SourceContext(STACK.with(|stack| stack.replace(trace.to_vec())))
88}
89
90#[cfg(all(test, feature = "inspection"))]
91#[path = "tests/source_trace_tests.rs"]
92mod tests;