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