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