tinymist_query/analysis/
call.rs1use super::Signature;
4use super::prelude::*;
5use crate::analysis::{PrimarySignature, SignatureTarget, analyze_signature};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum ParamKind {
10 Positional,
12 Named,
14 Rest,
16}
17
18#[derive(Debug, Clone)]
20pub struct CallParamInfo {
21 pub kind: ParamKind,
23 pub is_content_block: bool,
25 pub param_name: StrRef,
27}
28
29#[derive(Debug, Clone)]
31pub struct CallInfo {
32 pub signature: Signature,
34 pub arg_mapping: HashMap<SyntaxNode, CallParamInfo>,
37}
38
39#[typst_macros::time(span = node.span())]
42pub fn analyze_call(
43 ctx: &mut LocalContext,
44 source: Source,
45 node: LinkedNode,
46) -> Option<Arc<CallInfo>> {
47 log::trace!("func call found: {node:?}");
48 let call = node.cast::<ast::FuncCall>()?;
49
50 let callee = call.callee();
51 if !callee.hash() && !matches!(callee, ast::Expr::MathIdent(_)) {
53 return None;
54 }
55
56 let callee_node = node.find(callee.span())?;
57 Some(Arc::new(analyze_call_no_cache(
58 ctx,
59 source,
60 callee_node,
61 call.args(),
62 )?))
63}
64
65pub fn analyze_call_no_cache(
68 ctx: &mut LocalContext,
69 source: Source,
70 callee_node: LinkedNode,
71 args: ast::Args<'_>,
72) -> Option<CallInfo> {
73 let signature = analyze_signature(
74 ctx.shared(),
75 SignatureTarget::SyntaxFast(source, callee_node.span()),
76 )?;
77 log::trace!("got signature {signature:?}");
78
79 let mut info = CallInfo {
80 arg_mapping: HashMap::new(),
81 signature: signature.clone(),
82 };
83
84 enum PosState {
85 Init,
86 Pos(usize),
87 Variadic,
88 Final,
89 }
90
91 struct PosBuilder {
92 state: PosState,
93 out_of_arg_list: bool,
94 signature: Arc<PrimarySignature>,
95 }
96
97 impl PosBuilder {
98 fn advance(&mut self, info: &mut CallInfo, arg: Option<SyntaxNode>) {
99 let (kind, param) = match self.state {
100 PosState::Init => {
101 if !self.signature.pos().is_empty() {
102 self.state = PosState::Pos(0);
103 } else if self.signature.has_spread_right() {
104 self.state = PosState::Variadic;
105 } else {
106 self.state = PosState::Final;
107 }
108
109 return;
110 }
111 PosState::Pos(pos) => {
112 if pos + 1 < self.signature.pos_size() {
113 self.state = PosState::Pos(pos + 1);
114 } else if self.signature.has_spread_right() {
115 self.state = PosState::Variadic;
116 } else {
117 self.state = PosState::Final;
118 }
119
120 (ParamKind::Positional, self.signature.get_pos(pos).unwrap())
121 }
122 PosState::Variadic => (ParamKind::Rest, self.signature.rest().unwrap()),
123 PosState::Final => return,
124 };
125
126 if let Some(arg) = arg {
127 let is_content_block =
128 self.out_of_arg_list && arg.kind() == SyntaxKind::ContentBlock;
129 info.arg_mapping.insert(
130 arg,
131 CallParamInfo {
132 kind,
133 is_content_block,
134 param_name: param.name.clone(),
135 },
136 );
137 }
138 }
139
140 fn advance_rest(&mut self, info: &mut CallInfo, arg: Option<SyntaxNode>) {
141 match self.state {
142 PosState::Init => unreachable!(),
143 PosState::Pos(..) => {
145 if self.signature.has_spread_right() {
146 self.state = PosState::Variadic;
147 } else {
148 self.state = PosState::Final;
149 }
150 }
151 PosState::Variadic => {}
152 PosState::Final => return,
153 };
154
155 let Some(rest) = self.signature.rest() else {
156 return;
157 };
158
159 if let Some(arg) = arg {
160 let is_content_block =
161 self.out_of_arg_list && arg.kind() == SyntaxKind::ContentBlock;
162 info.arg_mapping.insert(
163 arg,
164 CallParamInfo {
165 kind: ParamKind::Rest,
166 is_content_block,
167 param_name: rest.name.clone(),
168 },
169 );
170 }
171 }
172
173 fn set_out_of_arg_list(&mut self, o: bool) {
174 self.out_of_arg_list = o;
175 }
176 }
177
178 let mut pos_builder = PosBuilder {
179 state: PosState::Init,
180 out_of_arg_list: true,
181 signature: signature.primary().clone(),
182 };
183 pos_builder.advance(&mut info, None);
184
185 for args in signature.bindings().iter().rev() {
186 for _arg in args.items.iter().filter(|arg| arg.name.is_none()) {
187 pos_builder.advance(&mut info, None);
188 }
189 }
190
191 for node in args.to_untyped().children() {
192 match node.kind() {
193 SyntaxKind::LeftParen => {
194 pos_builder.set_out_of_arg_list(false);
195 continue;
196 }
197 SyntaxKind::RightParen => {
198 pos_builder.set_out_of_arg_list(true);
199 continue;
200 }
201 _ => {}
202 }
203 let arg_tag = node.clone();
204 let Some(arg) = node.cast::<ast::Arg>() else {
205 continue;
206 };
207
208 match arg {
209 ast::Arg::Named(named) => {
210 let n = named.name().get().into();
211
212 if let Some(param) = signature.primary().get_named(&n) {
213 info.arg_mapping.insert(
214 arg_tag,
215 CallParamInfo {
216 kind: ParamKind::Named,
217 is_content_block: false,
218 param_name: param.name.clone(),
219 },
220 );
221 }
222 }
223 ast::Arg::Pos(..) => {
224 pos_builder.advance(&mut info, Some(arg_tag));
225 }
226 ast::Arg::Spread(..) => pos_builder.advance_rest(&mut info, Some(arg_tag)),
227 }
228 }
229
230 Some(info)
231}