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 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 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}