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}
15
16pub struct CallsiteGuard {
17    did_push: bool,
18}
19
20impl Drop for CallsiteGuard {
21    fn drop(&mut self) {
22        if !self.did_push {
23            return;
24        }
25        CALLSITE_STACK.with(|stack| {
26            let mut stack = stack.borrow_mut();
27            let _ = stack.pop();
28        });
29    }
30}
31
32pub fn push_callsite(source_id: Option<SourceId>, arg_spans: Option<Vec<Span>>) -> CallsiteGuard {
33    let Some(arg_spans) = arg_spans else {
34        return CallsiteGuard { did_push: false };
35    };
36    CALLSITE_STACK.with(|stack| {
37        stack.borrow_mut().push(CallsiteInfo {
38            source_id,
39            arg_spans,
40        });
41    });
42    CallsiteGuard { did_push: true }
43}
44
45pub fn current_callsite() -> Option<CallsiteInfo> {
46    CALLSITE_STACK.with(|stack| stack.borrow().last().cloned())
47}
48
49fn clamp_to_char_boundary(s: &str, mut idx: usize) -> usize {
50    if idx > s.len() {
51        idx = s.len();
52    }
53    while idx > 0 && !s.is_char_boundary(idx) {
54        idx -= 1;
55    }
56    idx
57}
58
59fn normalize_label_text(raw: &str) -> String {
60    let collapsed = raw.split_whitespace().collect::<Vec<_>>().join(" ");
61    let trimmed = collapsed.trim();
62    const MAX_LEN: usize = 80;
63    if trimmed.len() <= MAX_LEN {
64        return trimmed.to_string();
65    }
66    // Conservative truncation: keep valid UTF-8 boundaries.
67    let mut end = MAX_LEN;
68    while end > 0 && !trimmed.is_char_boundary(end) {
69        end -= 1;
70    }
71    format!("{}…", &trimmed[..end])
72}
73
74pub fn arg_text(arg_index: usize) -> Option<String> {
75    let callsite = current_callsite()?;
76    let source = source_context::current_source()?;
77    let span = callsite.arg_spans.get(arg_index)?;
78
79    let start = clamp_to_char_boundary(&source, span.start);
80    let end = clamp_to_char_boundary(&source, span.end);
81    if end <= start {
82        return None;
83    }
84    let raw = &source[start..end];
85    let text = normalize_label_text(raw);
86    if text.is_empty() {
87        None
88    } else {
89        Some(text)
90    }
91}