tinymist_query/analysis/
definition.rs

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