tinymist_query/analysis/
definition.rs

1//! Linked definition analysis
2
3use 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/// A linked definition in the source code
11#[derive(Debug, Clone, Hash, PartialEq, Eq)]
12pub struct Definition {
13    /// The declaration identifier of the definition.
14    pub decl: DeclExpr,
15    /// A possible instance of the definition.
16    pub term: Option<Ty>,
17}
18
19impl Definition {
20    /// Creates a definition
21    pub fn new(decl: DeclExpr, term: Option<Ty>) -> Self {
22        Self { decl, term }
23    }
24
25    /// Creates a definition according to some term
26    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    /// The name of the definition.
32    pub fn name(&self) -> &Interned<str> {
33        self.decl.name()
34    }
35
36    /// Gets file location of the definition.
37    pub fn file_id(&self) -> Option<TypstFileId> {
38        self.decl.file_id()
39    }
40
41    /// Gets name range of the definition.
42    pub fn name_range(&self, ctx: &SharedContext) -> Option<Range<usize>> {
43        self.decl.name_range(ctx)
44    }
45
46    /// Gets full range of the definition.
47    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    /// Gets name range of the item.
62    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// todo: field definition
86/// Finds the definition of a symbol.
87#[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        // todo: field access
95        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::At { node: _ }
128        | SyntaxClass::Normal(..) => None,
129    }
130}
131
132fn find_ident_definition(
133    ctx: &Arc<SharedContext>,
134    source: &Source,
135    use_site: VarClass,
136) -> Option<Definition> {
137    // Lexical reference
138    let ident_store = use_site.clone();
139    let ident_ref = match ident_store.node().cast::<ast::Expr>()? {
140        ast::Expr::Ident(ident) => ident.span(),
141        ast::Expr::MathIdent(ident) => ident.span(),
142        ast::Expr::FieldAccess(field_access) => return field_definition(ctx, field_access),
143        _ => {
144            crate::log_debug_ct!("unsupported kind {kind:?}", kind = use_site.node().kind());
145            Span::detached()
146        }
147    };
148
149    DefResolver::new(ctx, source)?.of_span(ident_ref)
150}
151
152fn field_definition(ctx: &Arc<SharedContext>, node: ast::FieldAccess) -> Option<Definition> {
153    let span = node.span();
154    let ty = ctx.type_of_span(span)?;
155    crate::log_debug_ct!("find_field_definition[{span:?}]: {ty:?}");
156
157    // todo multiple sources
158    let mut srcs = ty.sources();
159    srcs.sort();
160    crate::log_debug_ct!("check type signature of ty: {ty:?} => {srcs:?}");
161    let type_var = srcs.into_iter().next()?;
162    match type_var {
163        DocSource::Var(v) => {
164            crate::log_debug_ct!("field var: {:?} {:?}", v.def, v.def.span());
165            Some(Definition::new(v.def.clone(), None))
166        }
167        DocSource::Ins(v) if !v.span().is_detached() => {
168            let s = v.span();
169            let source = ctx.source_by_id(s.id()?).ok()?;
170            DefResolver::new(ctx, &source)?.of_span(s)
171        }
172        DocSource::Ins(ins) => value_to_def(ins.val.clone(), || Some(node.field().get().into())),
173        DocSource::Builtin(..) => None,
174    }
175}
176
177fn bib_definition(
178    ctx: &Arc<SharedContext>,
179    introspector: &Introspector,
180    key: &str,
181) -> Option<Definition> {
182    let bib_info = ctx.analyze_bib(introspector)?;
183
184    let entry = bib_info.entries.get(key)?;
185    crate::log_debug_ct!("find_bib_definition: {key} => {entry:?}");
186
187    // todo: rename with regard to string format: yaml-key/bib etc.
188    let decl = Decl::bib_entry(
189        key.into(),
190        entry.file_id,
191        entry.name_range.clone(),
192        Some(entry.range.clone()),
193    );
194    Some(Definition::new(decl.into(), None))
195}
196
197fn ref_definition(
198    introspector: &Introspector,
199    name: &str,
200    ref_expr: ast::Expr,
201) -> Option<Definition> {
202    // if it is a label, we put the selection range to itself
203    let (decl, ty) = match ref_expr {
204        ast::Expr::Label(label) => (Decl::label(name, label.span()), None),
205        ast::Expr::Ref(..) => {
206            let sel = Selector::Label(Label::construct(name.into()).ok()?);
207            let elem = introspector.query_first(&sel)?;
208            let span = elem.labelled_at();
209            let decl = if !span.is_detached() {
210                Decl::label(name, span)
211            } else {
212                // otherwise, it is estimated to the span of the pointed content
213                Decl::content(elem.span())
214            };
215            (decl, Some(Ty::Value(InsTy::new(Value::Content(elem)))))
216        }
217        _ => return None,
218    };
219
220    Some(Definition::new(decl.into(), ty))
221}
222
223/// The call of a function with calling convention identified.
224#[derive(Debug, Clone)]
225pub enum CallConvention {
226    /// A static function.
227    Static(Func),
228    /// A method call with a this.
229    Method(Value, Func),
230    /// A function call by with binding.
231    With(Func),
232    /// A function call by where binding.
233    Where(Func),
234}
235
236impl CallConvention {
237    /// Get the function pointer of the call.
238    pub fn method_this(&self) -> Option<&Value> {
239        match self {
240            CallConvention::Static(_) => None,
241            CallConvention::Method(this, _) => Some(this),
242            CallConvention::With(_) => None,
243            CallConvention::Where(_) => None,
244        }
245    }
246
247    /// Get the function pointer of the call.
248    pub fn callee(self) -> Func {
249        match self {
250            CallConvention::Static(func) => func,
251            CallConvention::Method(_, func) => func,
252            CallConvention::With(func) => func,
253            CallConvention::Where(func) => func,
254        }
255    }
256}
257
258/// Resolve a call target to a function or a method with a this.
259pub fn resolve_call_target(ctx: &Arc<SharedContext>, node: &SyntaxNode) -> Option<CallConvention> {
260    let callee = (|| {
261        let source = ctx.source_by_id(node.span().id()?).ok()?;
262        let def = ctx.def_of_span(&source, node.span())?;
263        let func_ptr = match def.term.and_then(|val| val.value()) {
264            Some(Value::Func(func)) => Some(func),
265            Some(Value::Type(ty)) => ty.constructor().ok(),
266            _ => None,
267        }?;
268
269        Some((None, func_ptr))
270    })();
271    let callee = callee.or_else(|| {
272        let values = ctx.analyze_expr(node);
273
274        if let Some(access) = node.cast::<ast::FieldAccess>() {
275            let target = access.target();
276            let field = access.field().get();
277            let values = ctx.analyze_expr(target.to_untyped());
278            if let Some((this, func_ptr)) = values.into_iter().find_map(|(this, _styles)| {
279                if let Some(Value::Func(func)) = this.ty().scope().get(field).map(|b| b.read()) {
280                    return Some((this, func.clone()));
281                }
282
283                None
284            }) {
285                return Some((Some(this), func_ptr));
286            }
287        }
288
289        if let Some(func) = values.into_iter().find_map(|v| v.0.to_func()) {
290            return Some((None, func));
291        };
292
293        None
294    })?;
295
296    let (this, func_ptr) = callee;
297    Some(match this {
298        Some(Value::Func(func)) if is_same_native_func(*WITH_FUNC, &func_ptr) => {
299            CallConvention::With(func)
300        }
301        Some(Value::Func(func)) if is_same_native_func(*WHERE_FUNC, &func_ptr) => {
302            CallConvention::Where(func)
303        }
304        Some(this) => CallConvention::Method(this, func_ptr),
305        None => CallConvention::Static(func_ptr),
306    })
307}
308
309fn is_same_native_func(x: Option<&Func>, y: &Func) -> bool {
310    let Some(x) = x else {
311        return false;
312    };
313
314    use typst::foundations::func::Repr;
315    match (x.inner(), y.inner()) {
316        (Repr::Native(x), Repr::Native(y)) => x == y,
317        (Repr::Element(x), Repr::Element(y)) => x == y,
318        _ => false,
319    }
320}
321
322static WITH_FUNC: LazyLock<Option<&'static Func>> = LazyLock::new(|| {
323    let fn_ty = Type::of::<Func>();
324    let bind = fn_ty.scope().get("with")?;
325    let Value::Func(func) = bind.read() else {
326        return None;
327    };
328    Some(func)
329});
330
331static WHERE_FUNC: LazyLock<Option<&'static Func>> = LazyLock::new(|| {
332    let fn_ty = Type::of::<Func>();
333    let bind = fn_ty.scope().get("where")?;
334    let Value::Func(func) = bind.read() else {
335        return None;
336    };
337    Some(func)
338});
339
340fn value_to_def(value: Value, name: impl FnOnce() -> Option<StrRef>) -> Option<Definition> {
341    let val = Ty::Value(InsTy::new(value.clone()));
342    Some(match value {
343        Value::Func(func) => {
344            let name = func.name().map(|name| name.into()).or_else(name)?;
345            let mut s = SyntaxNode::leaf(SyntaxKind::Ident, &name);
346            s.synthesize(func.span());
347
348            let decl = Decl::func(s.cast().unwrap());
349            Definition::new(decl.into(), Some(val))
350        }
351        Value::Module(module) => {
352            Definition::new_var(Interned::new_str(module.name().unwrap()), val)
353        }
354        _v => Definition::new_var(name()?, val),
355    })
356}
357
358struct DefResolver {
359    ei: ExprInfo,
360}
361
362impl DefResolver {
363    fn new(ctx: &Arc<SharedContext>, source: &Source) -> Option<Self> {
364        let ei = ctx.expr_stage(source);
365        Some(Self { ei })
366    }
367
368    fn of_span(&mut self, span: Span) -> Option<Definition> {
369        if span.is_detached() {
370            return None;
371        }
372
373        let resolved = self.ei.resolves.get(&span).cloned()?;
374        match (&resolved.root, &resolved.term) {
375            (Some(expr), term) => self.of_expr(expr, term.as_ref()),
376            (None, Some(term)) => self.of_term(term),
377            (None, None) => None,
378        }
379    }
380
381    fn of_expr(&mut self, expr: &Expr, term: Option<&Ty>) -> Option<Definition> {
382        crate::log_debug_ct!("of_expr: {expr:?}");
383
384        match expr {
385            Expr::Decl(decl) => self.of_decl(decl, term),
386            Expr::Ref(resolved) => {
387                self.of_expr(resolved.root.as_ref()?, resolved.term.as_ref().or(term))
388            }
389            _ => None,
390        }
391    }
392
393    fn of_term(&mut self, term: &Ty) -> Option<Definition> {
394        crate::log_debug_ct!("of_term: {term:?}");
395
396        // Get the type of the type node
397        let better_def = match term {
398            Ty::Value(v) => value_to_def(v.val.clone(), || None),
399            // Ty::Var(..) => DeclKind::Var,
400            // Ty::Func(..) => DeclKind::Func,
401            // Ty::With(..) => DeclKind::Func,
402            _ => None,
403        };
404
405        better_def.or_else(|| {
406            let constant = Decl::constant(Span::detached());
407            Some(Definition::new(constant.into(), Some(term.clone())))
408        })
409    }
410
411    fn of_decl(&mut self, decl: &Interned<Decl>, term: Option<&Ty>) -> Option<Definition> {
412        crate::log_debug_ct!("of_decl: {decl:?}");
413
414        // todo:
415        match decl.as_ref() {
416            Decl::Import(..) | Decl::ImportAlias(..) => {
417                let next = self.of_span(decl.span());
418                Some(next.unwrap_or_else(|| Definition::new(decl.clone(), term.cloned())))
419            }
420            _ => Some(Definition::new(decl.clone(), term.cloned())),
421        }
422    }
423}