1use tinymist_std::typst::TypstDocument;
4use typst::foundations::{Label, Selector, Type};
5use typst::introspection::Introspector;
6
7use super::{InsTy, SharedContext, prelude::*};
8use crate::syntax::{Decl, DeclExpr, Expr, ExprInfo, SyntaxClass, VarClass};
9use crate::ty::DocSource;
10
11#[derive(Debug, Clone, Hash, PartialEq, Eq)]
13pub struct Definition {
14 pub decl: DeclExpr,
16 pub term: Option<Ty>,
18}
19
20impl Definition {
21 pub fn new(decl: DeclExpr, term: Option<Ty>) -> Self {
23 Self { decl, term }
24 }
25
26 pub fn new_var(name: Interned<str>, term: Ty) -> Self {
28 let decl = Decl::lit_(name);
29 Self::new(decl.into(), Some(term))
30 }
31
32 pub fn name(&self) -> &Interned<str> {
34 self.decl.name()
35 }
36
37 pub fn file_id(&self) -> Option<TypstFileId> {
39 self.decl.file_id()
40 }
41
42 pub fn name_range(&self, ctx: &SharedContext) -> Option<Range<usize>> {
44 self.decl.name_range(ctx)
45 }
46
47 pub fn full_range(&self) -> Option<Range<usize>> {
49 self.decl.full_range()
50 }
51
52 pub(crate) fn value(&self) -> Option<Value> {
53 self.term.as_ref()?.value()
54 }
55
56 pub(crate) fn from_value(value: Value, name: impl FnOnce() -> Option<StrRef>) -> Option<Self> {
57 value_to_def(value, name)
58 }
59}
60
61trait HasNameRange {
62 fn name_range(&self, ctx: &SharedContext) -> Option<Range<usize>>;
64}
65
66impl HasNameRange for Decl {
67 fn name_range(&self, ctx: &SharedContext) -> Option<Range<usize>> {
68 if let Decl::BibEntry(decl) = self {
69 return Some(decl.at.1.clone());
70 }
71
72 if !self.is_def() {
73 return None;
74 }
75
76 let span = self.span();
77 if let Some(range) = span.range() {
78 return Some(range.clone());
79 }
80
81 let src = ctx.source_by_id(self.file_id()?).ok()?;
82 src.range(span)
83 }
84}
85
86#[typst_macros::time(span = syntax.node().span())]
89pub fn definition(
90 ctx: &Arc<SharedContext>,
91 source: &Source,
92 document: Option<&TypstDocument>,
93 syntax: SyntaxClass,
94) -> Option<Definition> {
95 match syntax {
96 SyntaxClass::VarAccess(node) => find_ident_definition(ctx, source, node),
98 SyntaxClass::Callee(node) => find_ident_definition(ctx, source, VarClass::Ident(node)),
99 SyntaxClass::ImportPath(path) | SyntaxClass::IncludePath(path) => {
100 DefResolver::new(ctx, source)?.of_span(path.span())
101 }
102 SyntaxClass::Label {
103 node,
104 is_error: false,
105 }
106 | SyntaxClass::Ref {
107 node,
108 suffix_colon: false,
109 } => {
110 let ref_expr: ast::Expr = node.cast()?;
111 let name = match ref_expr {
112 ast::Expr::Ref(r) => r.target(),
113 ast::Expr::Label(r) => r.get(),
114 _ => return None,
115 };
116
117 let introspector = &document?.introspector();
118 bib_definition(ctx, introspector, name)
119 .or_else(|| ref_definition(introspector, name, ref_expr))
120 }
121 SyntaxClass::Label {
122 node: _,
123 is_error: true,
124 }
125 | SyntaxClass::Ref {
126 node: _,
127 suffix_colon: true,
128 }
129 | SyntaxClass::Normal(..) => None,
130 }
131}
132
133fn find_ident_definition(
134 ctx: &Arc<SharedContext>,
135 source: &Source,
136 use_site: VarClass,
137) -> Option<Definition> {
138 let ident_store = use_site.clone();
140 let ident_ref = match ident_store.node().cast::<ast::Expr>()? {
141 ast::Expr::Ident(ident) => ident.span(),
142 ast::Expr::MathIdent(ident) => ident.span(),
143 ast::Expr::FieldAccess(field_access) => return field_definition(ctx, field_access),
144 _ => {
145 crate::log_debug_ct!("unsupported kind {kind:?}", kind = use_site.node().kind());
146 Span::detached()
147 }
148 };
149
150 DefResolver::new(ctx, source)?.of_span(ident_ref)
151}
152
153fn field_definition(ctx: &Arc<SharedContext>, node: ast::FieldAccess) -> Option<Definition> {
154 let span = node.span();
155 let ty = ctx.type_of_span(span)?;
156 crate::log_debug_ct!("find_field_definition[{span:?}]: {ty:?}");
157
158 let mut srcs = ty.sources();
160 srcs.sort();
161 crate::log_debug_ct!("check type signature of ty: {ty:?} => {srcs:?}");
162 let type_var = srcs.into_iter().next()?;
163 match type_var {
164 DocSource::Var(v) => {
165 crate::log_debug_ct!("field var: {:?} {:?}", v.def, v.def.span());
166 Some(Definition::new(v.def.clone(), None))
167 }
168 DocSource::Ins(v) if !v.span().is_detached() => {
169 let s = v.span();
170 let source = ctx.source_by_id(s.id()?).ok()?;
171 DefResolver::new(ctx, &source)?.of_span(s)
172 }
173 DocSource::Ins(ins) => value_to_def(ins.val.clone(), || Some(node.field().get().into())),
174 DocSource::Builtin(..) => None,
175 }
176}
177
178fn bib_definition(
179 ctx: &Arc<SharedContext>,
180 introspector: &Introspector,
181 key: &str,
182) -> Option<Definition> {
183 let bib_info = ctx.analyze_bib(introspector)?;
184
185 let entry = bib_info.entries.get(key)?;
186 crate::log_debug_ct!("find_bib_definition: {key} => {entry:?}");
187
188 let decl = Decl::bib_entry(
190 key.into(),
191 entry.file_id,
192 entry.name_range.clone(),
193 Some(entry.range.clone()),
194 );
195 Some(Definition::new(decl.into(), None))
196}
197
198fn ref_definition(
199 introspector: &Introspector,
200 name: &str,
201 ref_expr: ast::Expr,
202) -> Option<Definition> {
203 let (decl, ty) = match ref_expr {
205 ast::Expr::Label(label) => (Decl::label(name, label.span()), None),
206 ast::Expr::Ref(..) => {
207 let sel = Selector::Label(Label::construct(name.into()).ok()?);
208 let elem = introspector.query_first(&sel)?;
209 let span = elem.labelled_at();
210 let decl = if !span.is_detached() {
211 Decl::label(name, span)
212 } else {
213 Decl::content(elem.span())
215 };
216 (decl, Some(Ty::Value(InsTy::new(Value::Content(elem)))))
217 }
218 _ => return None,
219 };
220
221 Some(Definition::new(decl.into(), ty))
222}
223
224#[derive(Debug, Clone)]
226pub enum CallConvention {
227 Static(Func),
229 Method(Value, Func),
231 With(Func),
233 Where(Func),
235}
236
237impl CallConvention {
238 pub fn method_this(&self) -> Option<&Value> {
240 match self {
241 CallConvention::Static(_) => None,
242 CallConvention::Method(this, _) => Some(this),
243 CallConvention::With(_) => None,
244 CallConvention::Where(_) => None,
245 }
246 }
247
248 pub fn callee(self) -> Func {
250 match self {
251 CallConvention::Static(func) => func,
252 CallConvention::Method(_, func) => func,
253 CallConvention::With(func) => func,
254 CallConvention::Where(func) => func,
255 }
256 }
257}
258
259pub fn resolve_call_target(ctx: &Arc<SharedContext>, node: &SyntaxNode) -> Option<CallConvention> {
261 let callee = (|| {
262 let source = ctx.source_by_id(node.span().id()?).ok()?;
263 let def = ctx.def_of_span(&source, None, node.span())?;
264 let func_ptr = match def.term.and_then(|val| val.value()) {
265 Some(Value::Func(func)) => Some(func),
266 Some(Value::Type(ty)) => ty.constructor().ok(),
267 _ => None,
268 }?;
269
270 Some((None, func_ptr))
271 })();
272 let callee = callee.or_else(|| {
273 let values = ctx.analyze_expr(node);
274
275 if let Some(access) = node.cast::<ast::FieldAccess>() {
276 let target = access.target();
277 let field = access.field().get();
278 let values = ctx.analyze_expr(target.to_untyped());
279 if let Some((this, func_ptr)) = values.into_iter().find_map(|(this, _styles)| {
280 if let Some(Value::Func(func)) = this.ty().scope().get(field).map(|b| b.read()) {
281 return Some((this, func.clone()));
282 }
283
284 None
285 }) {
286 return Some((Some(this), func_ptr));
287 }
288 }
289
290 if let Some(func) = values.into_iter().find_map(|v| v.0.to_func()) {
291 return Some((None, func));
292 };
293
294 None
295 })?;
296
297 let (this, func_ptr) = callee;
298 Some(match this {
299 Some(Value::Func(func)) if is_same_native_func(*WITH_FUNC, &func_ptr) => {
300 CallConvention::With(func)
301 }
302 Some(Value::Func(func)) if is_same_native_func(*WHERE_FUNC, &func_ptr) => {
303 CallConvention::Where(func)
304 }
305 Some(this) => CallConvention::Method(this, func_ptr),
306 None => CallConvention::Static(func_ptr),
307 })
308}
309
310fn is_same_native_func(x: Option<&Func>, y: &Func) -> bool {
311 let Some(x) = x else {
312 return false;
313 };
314
315 use typst::foundations::func::Repr;
316 match (x.inner(), y.inner()) {
317 (Repr::Native(x), Repr::Native(y)) => x == y,
318 (Repr::Element(x), Repr::Element(y)) => x == y,
319 _ => false,
320 }
321}
322
323static WITH_FUNC: LazyLock<Option<&'static Func>> = LazyLock::new(|| {
324 let fn_ty = Type::of::<Func>();
325 let bind = fn_ty.scope().get("with")?;
326 let Value::Func(func) = bind.read() else {
327 return None;
328 };
329 Some(func)
330});
331
332static WHERE_FUNC: LazyLock<Option<&'static Func>> = LazyLock::new(|| {
333 let fn_ty = Type::of::<Func>();
334 let bind = fn_ty.scope().get("where")?;
335 let Value::Func(func) = bind.read() else {
336 return None;
337 };
338 Some(func)
339});
340
341fn value_to_def(value: Value, name: impl FnOnce() -> Option<StrRef>) -> Option<Definition> {
342 let val = Ty::Value(InsTy::new(value.clone()));
343 Some(match value {
344 Value::Func(func) => {
345 let name = func.name().map(|name| name.into()).or_else(name)?;
346 let mut s = SyntaxNode::leaf(SyntaxKind::Ident, &name);
347 s.synthesize(func.span());
348
349 let decl = Decl::func(s.cast().unwrap());
350 Definition::new(decl.into(), Some(val))
351 }
352 Value::Module(module) => {
353 Definition::new_var(Interned::new_str(module.name().unwrap()), val)
354 }
355 _v => Definition::new_var(name()?, val),
356 })
357}
358
359struct DefResolver {
360 ei: ExprInfo,
361}
362
363impl DefResolver {
364 fn new(ctx: &Arc<SharedContext>, source: &Source) -> Option<Self> {
365 let ei = ctx.expr_stage(source);
366 Some(Self { ei })
367 }
368
369 fn of_span(&mut self, span: Span) -> Option<Definition> {
370 if span.is_detached() {
371 return None;
372 }
373
374 let resolved = self.ei.resolves.get(&span).cloned()?;
375 match (&resolved.root, &resolved.term) {
376 (Some(expr), term) => self.of_expr(expr, term.as_ref()),
377 (None, Some(term)) => self.of_term(term),
378 (None, None) => None,
379 }
380 }
381
382 fn of_expr(&mut self, expr: &Expr, term: Option<&Ty>) -> Option<Definition> {
383 crate::log_debug_ct!("of_expr: {expr:?}");
384
385 match expr {
386 Expr::Decl(decl) => self.of_decl(decl, term),
387 Expr::Ref(resolved) => {
388 self.of_expr(resolved.root.as_ref()?, resolved.term.as_ref().or(term))
389 }
390 _ => None,
391 }
392 }
393
394 fn of_term(&mut self, term: &Ty) -> Option<Definition> {
395 crate::log_debug_ct!("of_term: {term:?}");
396
397 let better_def = match term {
399 Ty::Value(v) => value_to_def(v.val.clone(), || None),
400 _ => None,
404 };
405
406 better_def.or_else(|| {
407 let constant = Decl::constant(Span::detached());
408 Some(Definition::new(constant.into(), Some(term.clone())))
409 })
410 }
411
412 fn of_decl(&mut self, decl: &Interned<Decl>, term: Option<&Ty>) -> Option<Definition> {
413 crate::log_debug_ct!("of_decl: {decl:?}");
414
415 match decl.as_ref() {
417 Decl::Import(..) | Decl::ImportAlias(..) => {
418 let next = self.of_span(decl.span());
419 Some(next.unwrap_or_else(|| Definition::new(decl.clone(), term.cloned())))
420 }
421 _ => Some(Definition::new(decl.clone(), term.cloned())),
422 }
423 }
424}