Skip to main content

runmat_runtime/
callsite.rs

1use crate::source_context;
2use runmat_hir::{SourceId, Span};
3use runmat_thread_local::runmat_thread_local;
4use std::cell::RefCell;
5
6#[derive(Debug, Clone)]
7pub struct CallsiteInfo {
8    pub source_id: Option<SourceId>,
9    pub arg_spans: Vec<Span>,
10}
11
12runmat_thread_local! {
13    static CALLSITE_STACK: RefCell<Vec<CallsiteInfo>> = const { RefCell::new(Vec::new()) };
14    static FUNCTION_INPUT_CALLSITE_STACK: RefCell<Vec<CallsiteInfo>> = const { RefCell::new(Vec::new()) };
15}
16
17pub struct CallsiteGuard {
18    did_push: bool,
19}
20
21impl Drop for CallsiteGuard {
22    fn drop(&mut self) {
23        if !self.did_push {
24            return;
25        }
26        CALLSITE_STACK.with(|stack| {
27            let mut stack = stack.borrow_mut();
28            let _ = stack.pop();
29        });
30    }
31}
32
33pub struct FunctionInputCallsiteGuard {
34    did_push: bool,
35}
36
37impl Drop for FunctionInputCallsiteGuard {
38    fn drop(&mut self) {
39        if !self.did_push {
40            return;
41        }
42        FUNCTION_INPUT_CALLSITE_STACK.with(|stack| {
43            let mut stack = stack.borrow_mut();
44            let _ = stack.pop();
45        });
46    }
47}
48
49pub fn push_callsite(source_id: Option<SourceId>, arg_spans: Option<Vec<Span>>) -> CallsiteGuard {
50    let Some(arg_spans) = arg_spans else {
51        return CallsiteGuard { did_push: false };
52    };
53    CALLSITE_STACK.with(|stack| {
54        stack.borrow_mut().push(CallsiteInfo {
55            source_id,
56            arg_spans,
57        });
58    });
59    CallsiteGuard { did_push: true }
60}
61
62pub fn push_function_input_callsite(
63    source_id: Option<SourceId>,
64    arg_spans: Option<Vec<Span>>,
65) -> FunctionInputCallsiteGuard {
66    let Some(arg_spans) = arg_spans else {
67        return FunctionInputCallsiteGuard { did_push: false };
68    };
69    FUNCTION_INPUT_CALLSITE_STACK.with(|stack| {
70        stack.borrow_mut().push(CallsiteInfo {
71            source_id,
72            arg_spans,
73        });
74    });
75    FunctionInputCallsiteGuard { did_push: true }
76}
77
78pub fn current_callsite() -> Option<CallsiteInfo> {
79    CALLSITE_STACK.with(|stack| stack.borrow().last().cloned())
80}
81
82pub fn current_function_input_callsite() -> Option<CallsiteInfo> {
83    FUNCTION_INPUT_CALLSITE_STACK.with(|stack| stack.borrow().last().cloned())
84}
85
86fn clamp_to_char_boundary(s: &str, mut idx: usize) -> usize {
87    if idx > s.len() {
88        idx = s.len();
89    }
90    while idx > 0 && !s.is_char_boundary(idx) {
91        idx -= 1;
92    }
93    idx
94}
95
96fn normalize_label_text(raw: &str) -> String {
97    let collapsed = raw.split_whitespace().collect::<Vec<_>>().join(" ");
98    let trimmed = collapsed.trim();
99    const MAX_LEN: usize = 80;
100    if trimmed.len() <= MAX_LEN {
101        return trimmed.to_string();
102    }
103    // Conservative truncation: keep valid UTF-8 boundaries.
104    let mut end = MAX_LEN;
105    while end > 0 && !trimmed.is_char_boundary(end) {
106        end -= 1;
107    }
108    format!("{}…", &trimmed[..end])
109}
110
111pub fn arg_text(arg_index: usize) -> Option<String> {
112    let callsite = current_callsite()?;
113    arg_text_for_callsite(&callsite, arg_index)
114}
115
116pub fn function_input_arg_text(arg_index: usize) -> Option<String> {
117    let callsite = current_function_input_callsite()?;
118    arg_text_for_callsite(&callsite, arg_index)
119}
120
121fn source_for_callsite(callsite: &CallsiteInfo) -> Option<std::sync::Arc<str>> {
122    callsite
123        .source_id
124        .and_then(source_context::source_info)
125        .map(|source| source.text)
126        .or_else(source_context::current_source)
127}
128
129fn arg_text_for_callsite(callsite: &CallsiteInfo, arg_index: usize) -> Option<String> {
130    let source = source_for_callsite(callsite)?;
131    let span = callsite.arg_spans.get(arg_index)?;
132
133    let start = clamp_to_char_boundary(&source, span.start);
134    let end = clamp_to_char_boundary(&source, span.end);
135    if end <= start {
136        return None;
137    }
138    let raw = &source[start..end];
139    let text = normalize_label_text(raw);
140    if text.is_empty() {
141        None
142    } else {
143        Some(text)
144    }
145}