runmat_runtime/
callsite.rs1use 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 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}