tinymist_query/syntax/
expr.rs

1use std::ops::DerefMut;
2
3use parking_lot::Mutex;
4use rpds::RedBlackTreeMapSync;
5use rustc_hash::FxHashMap;
6use std::ops::Deref;
7use tinymist_analysis::adt::interner::Interned;
8use tinymist_std::hash::hash128;
9use typst::{
10    foundations::{Element, NativeElement, Type, Value},
11    model::{EmphElem, EnumElem, HeadingElem, ListElem, ParbreakElem, StrongElem, TermsElem},
12    syntax::{Span, SyntaxNode, ast::MathTextKind},
13    text::LinebreakElem,
14    utils::LazyHash,
15};
16
17use crate::{
18    analysis::{QueryStatGuard, SharedContext},
19    docs::DocString,
20    prelude::*,
21    syntax::{DefKind, find_module_level_docs, resolve_id_by_path},
22    ty::{BuiltinTy, InsTy, Ty},
23};
24
25use super::{DocCommentMatcher, InterpretMode, def::*};
26
27/// Maps file identifiers to their lexical scopes for expression analysis
28/// routing.
29pub type ExprRoute = FxHashMap<TypstFileId, Option<Arc<LazyHash<LexicalScope>>>>;
30
31/// Analyzes expressions in a source file and produces expression information.
32///
33/// This is the core function for expression analysis, which powers features
34/// like go-to-definition, hover, and completion. It performs a two-pass
35/// analysis:
36///
37/// 1. **First pass (init_stage)**: Builds the root lexical scope by scanning
38///    top-level definitions without resolving them. This handles forward
39///    references and circular dependencies.
40///
41/// 2. **Second pass**: Performs full expression analysis, resolving
42///    identifiers, tracking imports, and building the expression tree with type
43///    information.
44#[typst_macros::time(span = source.root().span())]
45pub(crate) fn expr_of(
46    ctx: Arc<SharedContext>,
47    source: Source,
48    route: &mut ExprRoute,
49    guard: QueryStatGuard,
50    prev: Option<ExprInfo>,
51) -> ExprInfo {
52    crate::log_debug_ct!("expr_of: {:?}", source.id());
53
54    route.insert(source.id(), None);
55
56    let cache_hit = prev.and_then(|prev| {
57        if prev.source.lines().len_bytes() != source.lines().len_bytes()
58            || hash128(&prev.source) != hash128(&source)
59        {
60            return None;
61        }
62        for (fid, prev_exports) in &prev.imports {
63            let ei = ctx.exports_of(&ctx.source_by_id(*fid).ok()?, route);
64
65            // If there is a cycle, the expression will be stable as the source is
66            // unchanged.
67            if let Some(exports) = ei
68                && (prev_exports.size() != exports.size()
69                    || hash128(&prev_exports) != hash128(&exports))
70            {
71                return None;
72            }
73        }
74
75        Some(prev)
76    });
77
78    if let Some(prev) = cache_hit {
79        route.remove(&source.id());
80        return prev;
81    }
82    guard.miss();
83
84    let revision = ctx.revision();
85
86    let resolves_base = Arc::new(Mutex::new(vec![]));
87    let resolves = resolves_base.clone();
88
89    // todo: cache docs capture
90    let docstrings_base = Arc::new(Mutex::new(FxHashMap::default()));
91    let docstrings = docstrings_base.clone();
92
93    let exprs_base = Arc::new(Mutex::new(FxHashMap::default()));
94    let exprs = exprs_base.clone();
95
96    let imports_base = Arc::new(Mutex::new(FxHashMap::default()));
97    let imports = imports_base.clone();
98
99    let module_docstring = find_module_level_docs(&source)
100        .and_then(|docs| ctx.compute_docstring(source.id(), docs, DefKind::Module))
101        .unwrap_or_default();
102
103    let (exports, root) = {
104        let mut w = ExprWorker {
105            fid: source.id(),
106            ctx,
107            imports,
108            docstrings,
109            exprs,
110            import_buffer: Vec::new(),
111            lexical: LexicalContext::default(),
112            resolves,
113            buffer: vec![],
114            init_stage: true,
115            comment_matcher: DocCommentMatcher::default(),
116            route,
117        };
118
119        // First pass.
120        let root = source.root().cast::<ast::Markup>().unwrap();
121        w.check_root_scope(root.to_untyped().children());
122        let root_scope = Arc::new(LazyHash::new(w.summarize_scope()));
123        w.route.insert(w.fid, Some(root_scope.clone()));
124
125        // Second pass.
126        w.lexical = LexicalContext::default();
127        w.comment_matcher.reset();
128        w.buffer.clear();
129        w.import_buffer.clear();
130        let root = w.check_in_mode(root.to_untyped().children(), InterpretMode::Markup);
131        let root_scope = Arc::new(LazyHash::new(w.summarize_scope()));
132
133        w.collect_buffer();
134        (root_scope, root)
135    };
136
137    let info = ExprInfoRepr {
138        fid: source.id(),
139        revision,
140        source: source.clone(),
141        resolves: HashMap::from_iter(std::mem::take(resolves_base.lock().deref_mut())),
142        module_docstring,
143        docstrings: std::mem::take(docstrings_base.lock().deref_mut()),
144        imports: HashMap::from_iter(std::mem::take(imports_base.lock().deref_mut())),
145        exports,
146        exprs: std::mem::take(exprs_base.lock().deref_mut()),
147        root,
148    };
149    crate::log_debug_ct!("expr_of end {:?}", source.id());
150
151    route.remove(&info.fid);
152    ExprInfo::new(info)
153}
154
155type ConcolicExpr = (Option<Expr>, Option<Ty>);
156type ResolveVec = Vec<(Span, Interned<RefExpr>)>;
157type SyntaxNodeChildren<'a> = std::slice::Iter<'a, SyntaxNode>;
158
159#[derive(Debug, Clone)]
160struct LexicalContext {
161    mode: InterpretMode,
162    scopes: EcoVec<ExprScope>,
163    last: ExprScope,
164}
165
166impl Default for LexicalContext {
167    fn default() -> Self {
168        LexicalContext {
169            mode: InterpretMode::Markup,
170            scopes: eco_vec![],
171            last: ExprScope::Lexical(RedBlackTreeMapSync::default()),
172        }
173    }
174}
175
176/// Worker for processing expressions during source file analysis.
177pub(crate) struct ExprWorker<'a> {
178    fid: TypstFileId,
179    ctx: Arc<SharedContext>,
180    imports: Arc<Mutex<FxHashMap<TypstFileId, Arc<LazyHash<LexicalScope>>>>>,
181    import_buffer: Vec<(TypstFileId, Arc<LazyHash<LexicalScope>>)>,
182    docstrings: Arc<Mutex<FxHashMap<DeclExpr, Arc<DocString>>>>,
183    exprs: Arc<Mutex<FxHashMap<Span, Expr>>>,
184    resolves: Arc<Mutex<ResolveVec>>,
185    buffer: ResolveVec,
186    lexical: LexicalContext,
187    init_stage: bool,
188
189    route: &'a mut ExprRoute,
190    comment_matcher: DocCommentMatcher,
191}
192
193impl ExprWorker<'_> {
194    fn with_scope<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
195        self.lexical.scopes.push(std::mem::replace(
196            &mut self.lexical.last,
197            ExprScope::empty(),
198        ));
199        let len = self.lexical.scopes.len();
200        let result = f(self);
201        self.lexical.scopes.truncate(len);
202        self.lexical.last = self.lexical.scopes.pop().unwrap();
203        result
204    }
205
206    fn push_scope(&mut self, scope: ExprScope) {
207        let last = std::mem::replace(&mut self.lexical.last, scope);
208        if !last.is_empty() {
209            self.lexical.scopes.push(last);
210        }
211    }
212
213    #[must_use]
214    fn scope_mut(&mut self) -> &mut LexicalScope {
215        if matches!(self.lexical.last, ExprScope::Lexical(_)) {
216            return self.lexical_scope_unchecked();
217        }
218        self.lexical.scopes.push(std::mem::replace(
219            &mut self.lexical.last,
220            ExprScope::empty(),
221        ));
222        self.lexical_scope_unchecked()
223    }
224
225    fn lexical_scope_unchecked(&mut self) -> &mut LexicalScope {
226        let scope = &mut self.lexical.last;
227        if let ExprScope::Lexical(scope) = scope {
228            scope
229        } else {
230            unreachable!()
231        }
232    }
233
234    fn check_docstring(&mut self, decl: &DeclExpr, docs: Option<String>, kind: DefKind) {
235        if let Some(docs) = docs {
236            let docstring = self.ctx.compute_docstring(self.fid, docs, kind);
237            if let Some(docstring) = docstring {
238                self.docstrings.lock().insert(decl.clone(), docstring);
239            }
240        }
241    }
242
243    fn summarize_scope(&self) -> LexicalScope {
244        let mut exports = LexicalScope::default();
245        for scope in self
246            .lexical
247            .scopes
248            .iter()
249            .chain(std::iter::once(&self.lexical.last))
250        {
251            scope.merge_into(&mut exports);
252        }
253        exports
254    }
255
256    fn check(&mut self, m: ast::Expr) -> Expr {
257        let s = m.span();
258        let ret = self.do_check(m);
259        self.exprs.lock().insert(s, ret.clone());
260        ret
261    }
262
263    fn do_check(&mut self, m: ast::Expr) -> Expr {
264        use ast::Expr::*;
265        match m {
266            None(_) => Expr::Type(Ty::Builtin(BuiltinTy::None)),
267            Auto(..) => Expr::Type(Ty::Builtin(BuiltinTy::Auto)),
268            Bool(bool) => Expr::Type(Ty::Value(InsTy::new(Value::Bool(bool.get())))),
269            Int(int) => Expr::Type(Ty::Value(InsTy::new(Value::Int(int.get())))),
270            Float(float) => Expr::Type(Ty::Value(InsTy::new(Value::Float(float.get())))),
271            Numeric(numeric) => Expr::Type(Ty::Value(InsTy::new(Value::numeric(numeric.get())))),
272            Str(s) => Expr::Type(Ty::Value(InsTy::new(Value::Str(s.get().into())))),
273
274            Equation(equation) => self.check_math(equation.body().to_untyped().children()),
275            Math(math) => self.check_math(math.to_untyped().children()),
276            CodeBlock(code_block) => self.check_code(code_block.body()),
277            ContentBlock(content_block) => self.check_markup(content_block.body()),
278
279            Ident(ident) => self.check_ident(ident),
280            MathIdent(math_ident) => self.check_math_ident(math_ident),
281            Label(label) => self.check_label(label),
282            Ref(ref_node) => self.check_ref(ref_node),
283
284            LetBinding(let_binding) => self.check_let(let_binding),
285            Closure(closure) => self.check_closure(closure),
286            ModuleImport(module_import) => self.check_module_import(module_import),
287            ModuleInclude(module_include) => self.check_module_include(module_include),
288
289            Parenthesized(paren_expr) => self.check(paren_expr.expr()),
290            Array(array) => self.check_array(array),
291            Dict(dict) => self.check_dict(dict),
292            Unary(unary) => self.check_unary(unary),
293            Binary(binary) => self.check_binary(binary),
294            FieldAccess(field_access) => self.check_field_access(field_access),
295            FuncCall(func_call) => self.check_func_call(func_call),
296            DestructAssignment(destruct_assignment) => {
297                self.check_destruct_assign(destruct_assignment)
298            }
299            SetRule(set_rule) => self.check_set(set_rule),
300            ShowRule(show_rule) => self.check_show(show_rule),
301            Contextual(contextual) => {
302                Expr::Unary(UnInst::new(UnaryOp::Context, self.defer(contextual.body())))
303            }
304            Conditional(conditional) => self.check_conditional(conditional),
305            WhileLoop(while_loop) => self.check_while_loop(while_loop),
306            ForLoop(for_loop) => self.check_for_loop(for_loop),
307            LoopBreak(..) => Expr::Type(Ty::Builtin(BuiltinTy::Break)),
308            LoopContinue(..) => Expr::Type(Ty::Builtin(BuiltinTy::Continue)),
309            FuncReturn(func_return) => Expr::Unary(UnInst::new(
310                UnaryOp::Return,
311                func_return
312                    .body()
313                    .map_or_else(none_expr, |body| self.check(body)),
314            )),
315
316            Text(..) => Expr::Type(Ty::Builtin(BuiltinTy::Content(Some(Element::of::<
317                typst::text::TextElem,
318            >())))),
319            MathText(t) => Expr::Type(Ty::Builtin(BuiltinTy::Content(Some({
320                match t.get() {
321                    MathTextKind::Character(..) => Element::of::<typst::foundations::SymbolElem>(),
322                    MathTextKind::Number(..) => Element::of::<typst::foundations::SymbolElem>(),
323                }
324            })))),
325            Raw(..) => Expr::Type(Ty::Builtin(BuiltinTy::Content(Some(Element::of::<
326                typst::text::RawElem,
327            >())))),
328            Link(..) => Expr::Type(Ty::Builtin(BuiltinTy::Content(Some(Element::of::<
329                typst::model::LinkElem,
330            >())))),
331            Space(..) => Expr::Type(Ty::Builtin(BuiltinTy::Space)),
332            Linebreak(..) => Expr::Type(Ty::Builtin(BuiltinTy::Content(Some(Element::of::<
333                LinebreakElem,
334            >())))),
335            Parbreak(..) => Expr::Type(Ty::Builtin(BuiltinTy::Content(Some(Element::of::<
336                ParbreakElem,
337            >())))),
338            Escape(..) => Expr::Type(Ty::Builtin(BuiltinTy::Content(Some(Element::of::<
339                typst::text::TextElem,
340            >())))),
341            Shorthand(..) => Expr::Type(Ty::Builtin(BuiltinTy::Type(Type::of::<
342                typst::foundations::Symbol,
343            >()))),
344            SmartQuote(..) => Expr::Type(Ty::Builtin(BuiltinTy::Content(Some(Element::of::<
345                typst::text::SmartQuoteElem,
346            >())))),
347
348            Strong(strong) => {
349                let body = self.check_inline_markup(strong.body());
350                self.check_element::<StrongElem>(eco_vec![body])
351            }
352            Emph(emph) => {
353                let body = self.check_inline_markup(emph.body());
354                self.check_element::<EmphElem>(eco_vec![body])
355            }
356            Heading(heading) => {
357                let body = self.check_markup(heading.body());
358                self.check_element::<HeadingElem>(eco_vec![body])
359            }
360            ListItem(item) => {
361                let body = self.check_markup(item.body());
362                self.check_element::<ListElem>(eco_vec![body])
363            }
364            EnumItem(item) => {
365                let body = self.check_markup(item.body());
366                self.check_element::<EnumElem>(eco_vec![body])
367            }
368            TermItem(item) => {
369                let term = self.check_markup(item.term());
370                let description = self.check_markup(item.description());
371                self.check_element::<TermsElem>(eco_vec![term, description])
372            }
373
374            MathAlignPoint(..) => Expr::Type(Ty::Builtin(BuiltinTy::Content(Some(Element::of::<
375                typst::math::AlignPointElem,
376            >(
377            ))))),
378            MathShorthand(..) => Expr::Type(Ty::Builtin(BuiltinTy::Type(Type::of::<
379                typst::foundations::Symbol,
380            >()))),
381            MathDelimited(math_delimited) => {
382                self.check_math(math_delimited.body().to_untyped().children())
383            }
384            MathAttach(attach) => {
385                let base = attach.base().to_untyped().clone();
386                let bottom = attach.bottom().unwrap_or_default().to_untyped().clone();
387                let top = attach.top().unwrap_or_default().to_untyped().clone();
388                self.check_math([base, bottom, top].iter())
389            }
390            MathPrimes(..) => Expr::Type(Ty::Builtin(BuiltinTy::None)),
391            MathFrac(frac) => {
392                let num = frac.num().to_untyped().clone();
393                let denom = frac.denom().to_untyped().clone();
394                self.check_math([num, denom].iter())
395            }
396            MathRoot(root) => self.check(root.radicand()),
397        }
398    }
399
400    fn check_label(&mut self, label: ast::Label) -> Expr {
401        Expr::Decl(Decl::label(label.get(), label.span()).into())
402    }
403
404    fn check_element<T: NativeElement>(&mut self, content: EcoVec<Expr>) -> Expr {
405        let elem = Element::of::<T>();
406        Expr::Element(ElementExpr { elem, content }.into())
407    }
408
409    fn check_let(&mut self, typed: ast::LetBinding) -> Expr {
410        match typed.kind() {
411            ast::LetBindingKind::Closure(..) => {
412                typed.init().map_or_else(none_expr, |expr| self.check(expr))
413            }
414            ast::LetBindingKind::Normal(pat) => {
415                let docs = self.comment_matcher.collect();
416                // Check init expression before pattern checking
417                let body = typed.init().map(|init| self.defer(init));
418
419                let span = pat.span();
420                let decl = Decl::pattern(span).into();
421                self.check_docstring(&decl, docs, DefKind::Variable);
422                let pattern = self.check_pattern(pat);
423                Expr::Let(Interned::new(LetExpr {
424                    span,
425                    pattern,
426                    body,
427                }))
428            }
429        }
430    }
431
432    fn check_closure(&mut self, typed: ast::Closure) -> Expr {
433        let docs = self.comment_matcher.collect();
434        let decl = match typed.name() {
435            Some(name) => Decl::func(name).into(),
436            None => Decl::closure(typed.span()).into(),
437        };
438        self.check_docstring(&decl, docs, DefKind::Function);
439        self.resolve_as(Decl::as_def(&decl, None));
440
441        let (params, body) = self.with_scope(|this| {
442            this.scope_mut()
443                .insert_mut(decl.name().clone(), decl.clone().into());
444            let mut inputs = eco_vec![];
445            let mut names = eco_vec![];
446            let mut spread_left = None;
447            let mut spread_right = None;
448            for arg in typed.params().children() {
449                match arg {
450                    ast::Param::Pos(arg) => {
451                        inputs.push(this.check_pattern(arg));
452                    }
453                    ast::Param::Named(arg) => {
454                        let key: DeclExpr = Decl::var(arg.name()).into();
455                        let val = Pattern::Expr(this.check(arg.expr())).into();
456                        names.push((key.clone(), val));
457
458                        this.resolve_as(Decl::as_def(&key, None));
459                        this.scope_mut().insert_mut(key.name().clone(), key.into());
460                    }
461                    ast::Param::Spread(s) => {
462                        let decl: DeclExpr = if let Some(ident) = s.sink_ident() {
463                            Decl::var(ident).into()
464                        } else {
465                            Decl::spread(s.span()).into()
466                        };
467
468                        let spread = Pattern::Expr(this.check(s.expr())).into();
469                        if inputs.is_empty() {
470                            spread_left = Some((decl.clone(), spread));
471                        } else {
472                            spread_right = Some((decl.clone(), spread));
473                        }
474
475                        this.resolve_as(Decl::as_def(&decl, None));
476                        this.scope_mut()
477                            .insert_mut(decl.name().clone(), decl.into());
478                    }
479                }
480            }
481
482            if inputs.is_empty() {
483                spread_right = spread_left.take();
484            }
485
486            let pattern = PatternSig {
487                pos: inputs,
488                named: names,
489                spread_left,
490                spread_right,
491            };
492
493            (pattern, this.defer(typed.body()))
494        });
495
496        self.scope_mut()
497            .insert_mut(decl.name().clone(), decl.clone().into());
498        Expr::Func(FuncExpr { decl, params, body }.into())
499    }
500
501    fn check_pattern(&mut self, typed: ast::Pattern) -> Interned<Pattern> {
502        match typed {
503            ast::Pattern::Normal(expr) => self.check_pattern_expr(expr),
504            ast::Pattern::Placeholder(..) => Pattern::Expr(Expr::Star).into(),
505            ast::Pattern::Parenthesized(paren_expr) => self.check_pattern(paren_expr.pattern()),
506            ast::Pattern::Destructuring(destructing) => {
507                let mut inputs = eco_vec![];
508                let mut names = eco_vec![];
509                let mut spread_left = None;
510                let mut spread_right = None;
511
512                for item in destructing.items() {
513                    match item {
514                        ast::DestructuringItem::Pattern(pos) => {
515                            inputs.push(self.check_pattern(pos));
516                        }
517                        ast::DestructuringItem::Named(named) => {
518                            let key = Decl::var(named.name()).into();
519                            let val = self.check_pattern_expr(named.expr());
520                            names.push((key, val));
521                        }
522                        ast::DestructuringItem::Spread(spreading) => {
523                            let decl: DeclExpr = if let Some(ident) = spreading.sink_ident() {
524                                Decl::var(ident).into()
525                            } else {
526                                Decl::spread(spreading.span()).into()
527                            };
528
529                            if inputs.is_empty() {
530                                spread_left =
531                                    Some((decl, self.check_pattern_expr(spreading.expr())));
532                            } else {
533                                spread_right =
534                                    Some((decl, self.check_pattern_expr(spreading.expr())));
535                            }
536                        }
537                    }
538                }
539
540                if inputs.is_empty() {
541                    spread_right = spread_left.take();
542                }
543
544                let pattern = PatternSig {
545                    pos: inputs,
546                    named: names,
547                    spread_left,
548                    spread_right,
549                };
550
551                Pattern::Sig(Box::new(pattern)).into()
552            }
553        }
554    }
555
556    fn check_pattern_expr(&mut self, typed: ast::Expr) -> Interned<Pattern> {
557        match typed {
558            ast::Expr::Ident(ident) => {
559                let decl = Decl::var(ident).into();
560                self.resolve_as(Decl::as_def(&decl, None));
561                self.scope_mut()
562                    .insert_mut(decl.name().clone(), decl.clone().into());
563                Pattern::Simple(decl).into()
564            }
565            ast::Expr::Parenthesized(parenthesized) => self.check_pattern(parenthesized.pattern()),
566            _ => Pattern::Expr(self.check(typed)).into(),
567        }
568    }
569
570    fn check_module_import(&mut self, typed: ast::ModuleImport) -> Expr {
571        let is_wildcard_import = matches!(typed.imports(), Some(ast::Imports::Wildcard));
572
573        let source = typed.source();
574        let mod_expr = self.check_import(typed.source(), true, is_wildcard_import);
575        crate::log_debug_ct!("checking import: {source:?} => {mod_expr:?}");
576
577        let mod_var = typed.new_name().map(Decl::module_alias).or_else(|| {
578            typed.imports().is_none().then(|| {
579                let name = match mod_expr.as_ref()? {
580                    Expr::Decl(decl) if matches!(decl.as_ref(), Decl::Module { .. }) => {
581                        decl.name().clone()
582                    }
583                    _ => return None,
584                };
585                // todo: package stem
586                Some(Decl::path_stem(source.to_untyped().clone(), name))
587            })?
588        });
589
590        let creating_mod_var = mod_var.is_some();
591        let mod_var = Interned::new(mod_var.unwrap_or_else(|| Decl::module_import(typed.span())));
592
593        // Create a RefExpr for the module import variable.
594        // - decl: The import variable (e.g., "foo" in "import 'file.typ' as foo")
595        // - step & root: Both point to the module expression (same for imports)
596        // - term: None because module types are complex and not stored here
597        let mod_ref = RefExpr {
598            decl: mod_var.clone(),
599            step: mod_expr.clone(),
600            root: mod_expr.clone(),
601            term: None,
602        };
603        crate::log_debug_ct!("create import variable: {mod_ref:?}");
604        let mod_ref = Interned::new(mod_ref);
605        if creating_mod_var {
606            self.scope_mut()
607                .insert_mut(mod_var.name().clone(), Expr::Ref(mod_ref.clone()));
608        }
609
610        self.resolve_as(mod_ref.clone());
611
612        let fid = mod_expr.as_ref().and_then(|mod_expr| match mod_expr {
613            Expr::Type(Ty::Value(v)) => match &v.val {
614                Value::Module(m) => m.file_id(),
615                _ => None,
616            },
617            Expr::Decl(decl) => {
618                if matches!(decl.as_ref(), Decl::Module { .. }) {
619                    decl.file_id()
620                } else {
621                    None
622                }
623            }
624            _ => None,
625        });
626
627        // Prefetch Type Check Information
628        if let Some(fid) = fid {
629            crate::log_debug_ct!("prefetch type check: {fid:?}");
630            self.ctx.prefetch_type_check(fid);
631        }
632
633        let scope = if let Some(fid) = &fid {
634            Some(ExprScope::Lexical(self.exports_of(*fid)))
635        } else {
636            match &mod_expr {
637                Some(Expr::Type(Ty::Value(v))) => match &v.val {
638                    Value::Module(m) => Some(ExprScope::Module(m.clone())),
639                    Value::Func(func) => {
640                        if func.scope().is_some() {
641                            Some(ExprScope::Func(func.clone()))
642                        } else {
643                            None
644                        }
645                    }
646                    Value::Type(s) => Some(ExprScope::Type(*s)),
647                    _ => None,
648                },
649                _ => None,
650            }
651        };
652
653        let scope = if let Some(scope) = scope {
654            scope
655        } else {
656            log::warn!(
657                "cannot analyze import on: {typed:?}, expr {mod_expr:?}, in file {:?}",
658                typed.span().id()
659            );
660            ExprScope::empty()
661        };
662
663        if let Some(imports) = typed.imports() {
664            match imports {
665                ast::Imports::Wildcard => {
666                    crate::log_debug_ct!("checking wildcard: {mod_expr:?}");
667                    self.push_scope(scope);
668                }
669                ast::Imports::Items(items) => {
670                    let module = Expr::Decl(mod_var.clone());
671                    self.import_decls(&scope, module, items);
672                }
673            }
674        };
675
676        Expr::Import(ImportExpr { decl: mod_ref }.into())
677    }
678
679    fn check_import(
680        &mut self,
681        source: ast::Expr,
682        is_import: bool,
683        is_wildcard_import: bool,
684    ) -> Option<Expr> {
685        let src = self.eval_expr(source, InterpretMode::Code);
686        let src_expr = self.fold_expr_and_val(src).or_else(|| {
687            self.ctx
688                .analyze_expr(source.to_untyped())
689                .into_iter()
690                .find_map(|(v, _)| match v {
691                    Value::Str(s) => Some(Expr::Type(Ty::Value(InsTy::new(Value::Str(s))))),
692                    _ => None,
693                })
694        })?;
695
696        crate::log_debug_ct!("checking import source: {src_expr:?}");
697        let const_res = match &src_expr {
698            Expr::Type(Ty::Value(val)) => {
699                self.check_import_source_val(source, &val.val, Some(&src_expr), is_import)
700            }
701            Expr::Decl(decl) if matches!(decl.as_ref(), Decl::Module { .. }) => {
702                return Some(src_expr.clone());
703            }
704
705            _ => None,
706        };
707        const_res
708            .or_else(|| self.check_import_by_def(&src_expr))
709            .or_else(|| is_wildcard_import.then(|| self.check_import_dyn(source, &src_expr))?)
710    }
711
712    fn check_import_dyn(&mut self, source: ast::Expr, src_expr: &Expr) -> Option<Expr> {
713        let src_or_module = self.ctx.analyze_import(source.to_untyped());
714        crate::log_debug_ct!("checking import source dyn: {src_or_module:?}");
715
716        match src_or_module {
717            (_, Some(Value::Module(m))) => {
718                // todo: dyn resolve src_expr
719                match m.file_id() {
720                    Some(fid) => Some(Expr::Decl(
721                        Decl::module_with_name(m.name().unwrap().into(), fid).into(),
722                    )),
723                    None => Some(Expr::Type(Ty::Value(InsTy::new(Value::Module(m))))),
724                }
725            }
726            (_, Some(v)) => Some(Expr::Type(Ty::Value(InsTy::new(v)))),
727            (Some(s), _) => self.check_import_source_val(source, &s, Some(src_expr), true),
728            (None, None) => None,
729        }
730    }
731
732    fn check_import_source_val(
733        &mut self,
734        source: ast::Expr,
735        src: &Value,
736        src_expr: Option<&Expr>,
737        is_import: bool,
738    ) -> Option<Expr> {
739        match &src {
740            _ if src.scope().is_some() => src_expr
741                .cloned()
742                .or_else(|| Some(Expr::Type(Ty::Value(InsTy::new(src.clone()))))),
743            Value::Str(s) => self.check_import_by_str(source, s.as_str(), is_import),
744            _ => None,
745        }
746    }
747
748    fn check_import_by_str(
749        &mut self,
750        source: ast::Expr,
751        src: &str,
752        is_import: bool,
753    ) -> Option<Expr> {
754        let fid = resolve_id_by_path(&self.ctx.world, self.fid, src)?;
755        let name = Decl::calc_path_stem(src);
756        let module = Expr::Decl(Decl::module_with_name(name.clone(), fid).into());
757
758        let import_path = if is_import {
759            Decl::import_path(source.span(), name)
760        } else {
761            Decl::include_path(source.span(), name)
762        };
763
764        // Create a RefExpr for the import/include path.
765        // - decl: The path declaration (tracks the file path being imported)
766        // - step & root: Both point to the loaded module
767        // - term: None (module types not stored directly)
768        let ref_expr = RefExpr {
769            decl: import_path.into(),
770            step: Some(module.clone()),
771            root: Some(module.clone()),
772            term: None,
773        };
774        self.resolve_as(ref_expr.into());
775        Some(module)
776    }
777
778    fn check_import_by_def(&mut self, src_expr: &Expr) -> Option<Expr> {
779        match src_expr {
780            Expr::Decl(m) if matches!(m.kind(), DefKind::Module) => Some(src_expr.clone()),
781            Expr::Ref(r) => r.root.clone(),
782            _ => None,
783        }
784    }
785
786    fn import_decls(&mut self, scope: &ExprScope, module: Expr, items: ast::ImportItems) {
787        crate::log_debug_ct!("import scope {scope:?}");
788
789        for item in items.iter() {
790            let (path_ast, old, rename) = match item {
791                ast::ImportItem::Simple(path) => {
792                    let old: DeclExpr = Decl::import(path.name()).into();
793                    (path, old, None)
794                }
795                ast::ImportItem::Renamed(renamed) => {
796                    let path = renamed.path();
797                    let old: DeclExpr = Decl::import(path.name()).into();
798                    let new: DeclExpr = Decl::import_alias(renamed.new_name()).into();
799                    (path, old, Some(new))
800                }
801            };
802
803            let mut path = Vec::with_capacity(1);
804            for seg in path_ast.iter() {
805                let seg = Interned::new(Decl::ident_ref(seg));
806                path.push(seg);
807            }
808            // todo: import path
809            let (mut root, val) = match path.last().map(|decl| decl.name()) {
810                Some(name) => scope.get(name),
811                None => (None, None),
812            };
813
814            crate::log_debug_ct!("path {path:?} -> {root:?} {val:?}");
815            if root.is_none() && val.is_none() {
816                let mut sel = module.clone();
817                for seg in path.into_iter() {
818                    sel = Expr::Select(SelectExpr::new(seg, sel));
819                }
820                root = Some(sel)
821            }
822
823            let (root, step) = extract_ref(root);
824
825            // Create RefExpr for the original name in the import.
826            // - decl: The original identifier (e.g., "old" in "import: old as new")
827            // - root: The module or selection expression where the value comes from
828            // - step: Intermediate expression (from extract_ref, handles reference chains)
829            // - term: The type if it was found in the scope
830            let mut ref_expr = Interned::new(RefExpr {
831                decl: old.clone(),
832                root,
833                step,
834                term: val,
835            });
836            self.resolve_as(ref_expr.clone());
837
838            // If renamed, create a second RefExpr for the new name that chains to the old
839            // one. This builds the chain: new -> old -> root
840            if let Some(new) = &rename {
841                // - decl: The new name (e.g., "new" in "import: old as new")
842                // - root: Same as original (ultimate source of the value)
843                // - step: Points to the old name (intermediate link in the chain)
844                // - term: Same type as original
845                ref_expr = Interned::new(RefExpr {
846                    decl: new.clone(),
847                    root: ref_expr.root.clone(),
848                    step: Some(ref_expr.decl.clone().into()),
849                    term: ref_expr.term.clone(),
850                });
851                self.resolve_as(ref_expr.clone());
852            }
853
854            // final resolves
855            let name = rename.as_ref().unwrap_or(&old).name().clone();
856            let expr = Expr::Ref(ref_expr);
857            self.scope_mut().insert_mut(name, expr.clone());
858        }
859    }
860
861    fn check_module_include(&mut self, typed: ast::ModuleInclude) -> Expr {
862        let _mod_expr = self.check_import(typed.source(), false, false);
863        let source = self.check(typed.source());
864        Expr::Include(IncludeExpr { source }.into())
865    }
866
867    fn check_array(&mut self, typed: ast::Array) -> Expr {
868        let mut items = vec![];
869        for item in typed.items() {
870            match item {
871                ast::ArrayItem::Pos(item) => {
872                    items.push(ArgExpr::Pos(self.check(item)));
873                }
874                ast::ArrayItem::Spread(s) => {
875                    items.push(ArgExpr::Spread(self.check(s.expr())));
876                }
877            }
878        }
879
880        Expr::Array(ArgsExpr::new(typed.span(), items))
881    }
882
883    fn check_dict(&mut self, typed: ast::Dict) -> Expr {
884        let mut items = vec![];
885        for item in typed.items() {
886            match item {
887                ast::DictItem::Named(item) => {
888                    let key = Decl::ident_ref(item.name()).into();
889                    let val = self.check(item.expr());
890                    items.push(ArgExpr::Named(Box::new((key, val))));
891                }
892                ast::DictItem::Keyed(item) => {
893                    let val = self.check(item.expr());
894                    let key = item.key();
895                    let analyzed = self.const_eval_expr(key);
896                    let analyzed = match &analyzed {
897                        Some(Value::Str(s)) => Some(s),
898                        _ => None,
899                    };
900                    let Some(analyzed) = analyzed else {
901                        let key = self.check(key);
902                        items.push(ArgExpr::NamedRt(Box::new((key, val))));
903                        continue;
904                    };
905                    let key = Decl::str_name(key.to_untyped().clone(), analyzed).into();
906                    items.push(ArgExpr::Named(Box::new((key, val))));
907                }
908                ast::DictItem::Spread(s) => {
909                    items.push(ArgExpr::Spread(self.check(s.expr())));
910                }
911            }
912        }
913
914        Expr::Dict(ArgsExpr::new(typed.span(), items))
915    }
916
917    fn check_args(&mut self, typed: ast::Args) -> Expr {
918        let mut args = vec![];
919        for arg in typed.items() {
920            match arg {
921                ast::Arg::Pos(arg) => {
922                    args.push(ArgExpr::Pos(self.check(arg)));
923                }
924                ast::Arg::Named(arg) => {
925                    let key = Decl::ident_ref(arg.name()).into();
926                    let val = self.check(arg.expr());
927                    args.push(ArgExpr::Named(Box::new((key, val))));
928                }
929                ast::Arg::Spread(s) => {
930                    args.push(ArgExpr::Spread(self.check(s.expr())));
931                }
932            }
933        }
934        Expr::Args(ArgsExpr::new(typed.span(), args))
935    }
936
937    fn check_unary(&mut self, typed: ast::Unary) -> Expr {
938        let op = match typed.op() {
939            ast::UnOp::Pos => UnaryOp::Pos,
940            ast::UnOp::Neg => UnaryOp::Neg,
941            ast::UnOp::Not => UnaryOp::Not,
942        };
943        let lhs = self.check(typed.expr());
944        Expr::Unary(UnInst::new(op, lhs))
945    }
946
947    fn check_binary(&mut self, typed: ast::Binary) -> Expr {
948        let lhs = self.check(typed.lhs());
949        let rhs = self.check(typed.rhs());
950        Expr::Binary(BinInst::new(typed.op(), lhs, rhs))
951    }
952
953    fn check_destruct_assign(&mut self, typed: ast::DestructAssignment) -> Expr {
954        let pat = Expr::Pattern(self.check_pattern(typed.pattern()));
955        let val = self.check(typed.value());
956        let inst = BinInst::new(ast::BinOp::Assign, pat, val);
957        Expr::Binary(inst)
958    }
959
960    fn check_field_access(&mut self, typed: ast::FieldAccess) -> Expr {
961        let lhs = self.check(typed.target());
962        let key = Decl::ident_ref(typed.field()).into();
963        let span = typed.span();
964        Expr::Select(SelectExpr { lhs, key, span }.into())
965    }
966
967    fn check_func_call(&mut self, typed: ast::FuncCall) -> Expr {
968        let callee = self.check(typed.callee());
969        let args = self.check_args(typed.args());
970        let span = typed.span();
971        Expr::Apply(ApplyExpr { callee, args, span }.into())
972    }
973
974    fn check_set(&mut self, typed: ast::SetRule) -> Expr {
975        let target = self.check(typed.target());
976        let args = self.check_args(typed.args());
977        let cond = typed.condition().map(|cond| self.check(cond));
978        Expr::Set(SetExpr { target, args, cond }.into())
979    }
980
981    fn check_show(&mut self, typed: ast::ShowRule) -> Expr {
982        let selector = typed.selector().map(|selector| self.check(selector));
983        let edit = self.defer(typed.transform());
984        Expr::Show(ShowExpr { selector, edit }.into())
985    }
986
987    fn check_conditional(&mut self, typed: ast::Conditional) -> Expr {
988        let cond = self.check(typed.condition());
989        let then = self.defer(typed.if_body());
990        let else_ = typed
991            .else_body()
992            .map_or_else(none_expr, |expr| self.defer(expr));
993        Expr::Conditional(IfExpr { cond, then, else_ }.into())
994    }
995
996    fn check_while_loop(&mut self, typed: ast::WhileLoop) -> Expr {
997        let cond = self.check(typed.condition());
998        let body = self.defer(typed.body());
999        Expr::WhileLoop(WhileExpr { cond, body }.into())
1000    }
1001
1002    fn check_for_loop(&mut self, typed: ast::ForLoop) -> Expr {
1003        self.with_scope(|this| {
1004            let pattern = this.check_pattern(typed.pattern());
1005            let iter = this.check(typed.iterable());
1006            let body = this.defer(typed.body());
1007            Expr::ForLoop(
1008                ForExpr {
1009                    pattern,
1010                    iter,
1011                    body,
1012                }
1013                .into(),
1014            )
1015        })
1016    }
1017
1018    fn check_inline_markup(&mut self, markup: ast::Markup) -> Expr {
1019        self.check_in_mode(markup.to_untyped().children(), InterpretMode::Markup)
1020    }
1021
1022    fn check_markup(&mut self, markup: ast::Markup) -> Expr {
1023        self.with_scope(|this| this.check_inline_markup(markup))
1024    }
1025
1026    fn check_code(&mut self, code: ast::Code) -> Expr {
1027        self.with_scope(|this| {
1028            this.check_in_mode(code.to_untyped().children(), InterpretMode::Code)
1029        })
1030    }
1031
1032    fn check_math(&mut self, children: SyntaxNodeChildren) -> Expr {
1033        self.check_in_mode(children, InterpretMode::Math)
1034    }
1035
1036    fn check_root_scope(&mut self, children: SyntaxNodeChildren) {
1037        self.init_stage = true;
1038        self.check_in_mode(children, InterpretMode::Markup);
1039        self.init_stage = false;
1040    }
1041
1042    fn check_in_mode(&mut self, children: SyntaxNodeChildren, mode: InterpretMode) -> Expr {
1043        let old_mode = self.lexical.mode;
1044        self.lexical.mode = mode;
1045
1046        // collect all comments before the definition
1047        self.comment_matcher.reset();
1048
1049        let mut items = Vec::with_capacity(4);
1050        for n in children {
1051            if let Some(expr) = n.cast::<ast::Expr>() {
1052                items.push(self.check(expr));
1053                self.comment_matcher.reset();
1054                continue;
1055            }
1056            if !self.init_stage && self.comment_matcher.process(n) {
1057                self.comment_matcher.reset();
1058            }
1059        }
1060
1061        self.lexical.mode = old_mode;
1062        Expr::Block(items.into())
1063    }
1064
1065    fn check_ref(&mut self, ref_node: ast::Ref) -> Expr {
1066        let ident = Interned::new(Decl::ref_(ref_node));
1067        let body = ref_node
1068            .supplement()
1069            .map(|block| self.check(ast::Expr::ContentBlock(block)));
1070        let ref_expr = ContentRefExpr {
1071            ident: ident.clone(),
1072            of: None,
1073            body,
1074        };
1075        self.resolve_as(
1076            RefExpr {
1077                decl: ident,
1078                step: None,
1079                root: None,
1080                term: None,
1081            }
1082            .into(),
1083        );
1084        Expr::ContentRef(ref_expr.into())
1085    }
1086
1087    fn check_ident(&mut self, ident: ast::Ident) -> Expr {
1088        self.resolve_ident(Decl::ident_ref(ident).into(), InterpretMode::Code)
1089    }
1090
1091    fn check_math_ident(&mut self, ident: ast::MathIdent) -> Expr {
1092        self.resolve_ident(Decl::math_ident_ref(ident).into(), InterpretMode::Math)
1093    }
1094
1095    fn resolve_as(&mut self, r: Interned<RefExpr>) {
1096        self.resolve_as_(r.decl.span(), r);
1097    }
1098
1099    fn resolve_as_(&mut self, s: Span, r: Interned<RefExpr>) {
1100        self.buffer.push((s, r.clone()));
1101    }
1102
1103    fn resolve_ident(&mut self, decl: DeclExpr, mode: InterpretMode) -> Expr {
1104        let r: Interned<RefExpr> = self.resolve_ident_(decl, mode).into();
1105        let s = r.decl.span();
1106        self.buffer.push((s, r.clone()));
1107        Expr::Ref(r)
1108    }
1109
1110    /// Resolves an identifier to a reference expression.
1111    ///
1112    /// This function looks up an identifier in the lexical scope and creates
1113    /// a `RefExpr` that tracks the resolution chain.
1114    ///
1115    /// # Resolution Process
1116    ///
1117    /// 1. Evaluates the identifier to get its expression and type
1118    ///    (`eval_ident`)
1119    /// 2. If the result is itself a `RefExpr`, extracts its `root` and uses the
1120    ///    RefExpr's `decl` as the `step` (building a reference chain)
1121    /// 3. Otherwise, uses the expression as both `root` and `step`
1122    ///
1123    /// # Field Assignment
1124    ///
1125    /// - `decl`: The identifier being resolved
1126    /// - `root`: The ultimate source of the value (extracted from chain or the
1127    ///   expression itself)
1128    /// - `step`: The immediate resolution (extracted from chain or the
1129    ///   expression itself)
1130    /// - `term`: The resolved type (if available from evaluation)
1131    ///
1132    /// # Example
1133    ///
1134    /// For `let x = 1; let y = x; let z = y`:
1135    /// - Resolving `x` gives: `RefExpr { decl: x, root: None, step: None, term:
1136    ///   Some(int) }`
1137    /// - Resolving `y` gives: `RefExpr { decl: y, root: Some(x), step: Some(x),
1138    ///   term: Some(int) }`
1139    /// - Resolving `z` gives: `RefExpr { decl: z, root: Some(x), step: Some(y),
1140    ///   term: Some(int) }`
1141    fn resolve_ident_(&mut self, decl: DeclExpr, mode: InterpretMode) -> RefExpr {
1142        let (step, val) = self.eval_ident(decl.name(), mode);
1143        let (root, step) = extract_ref(step);
1144
1145        RefExpr {
1146            decl,
1147            root,
1148            step,
1149            term: val,
1150        }
1151    }
1152
1153    fn defer(&mut self, expr: ast::Expr) -> Expr {
1154        if self.init_stage {
1155            Expr::Star
1156        } else {
1157            self.check(expr)
1158        }
1159    }
1160
1161    fn collect_buffer(&mut self) {
1162        let mut resolves = self.resolves.lock();
1163        resolves.extend(self.buffer.drain(..));
1164        drop(resolves);
1165        let mut imports = self.imports.lock();
1166        imports.extend(self.import_buffer.drain(..));
1167    }
1168
1169    fn const_eval_expr(&self, expr: ast::Expr) -> Option<Value> {
1170        SharedContext::const_eval(expr)
1171    }
1172
1173    fn eval_expr(&mut self, expr: ast::Expr, mode: InterpretMode) -> ConcolicExpr {
1174        if let Some(term) = self.const_eval_expr(expr) {
1175            return (None, Some(Ty::Value(InsTy::new(term))));
1176        }
1177        crate::log_debug_ct!("checking expr: {expr:?}");
1178
1179        match expr {
1180            ast::Expr::FieldAccess(field_access) => {
1181                let field = Decl::ident_ref(field_access.field());
1182
1183                let (expr, term) = self.eval_expr(field_access.target(), mode);
1184                let term = term.and_then(|v| {
1185                    // todo: use type select
1186                    // v.select(field.name()).ok()
1187                    match v {
1188                        Ty::Value(val) => {
1189                            Some(Ty::Value(InsTy::new(val.val.field(field.name(), ()).ok()?)))
1190                        }
1191                        _ => None,
1192                    }
1193                });
1194                let sel = expr.map(|expr| Expr::Select(SelectExpr::new(field.into(), expr)));
1195                (sel, term)
1196            }
1197            ast::Expr::Ident(ident) => {
1198                let expr_term = self.eval_ident(&ident.get().into(), mode);
1199                crate::log_debug_ct!("checking expr: {expr:?} -> res: {expr_term:?}");
1200                expr_term
1201            }
1202            _ => (None, None),
1203        }
1204    }
1205
1206    /// Evaluates an identifier by looking it up in the lexical scope.
1207    ///
1208    /// Returns a tuple of `(expression, type)` where:
1209    /// - `expression`: The expression the identifier resolves to (may be a
1210    ///   `RefExpr`)
1211    /// - `type`: The type of the value (if known)
1212    ///
1213    /// # Lookup Order
1214    ///
1215    /// 1. Current scope (`self.lexical.last`) - for block-local variables
1216    /// 2. Parent scopes (`self.lexical.scopes`) - for outer scope variables
1217    /// 3. Global/Math library scope - for built-in functions and constants
1218    /// 4. Special case: "std" module
1219    fn eval_ident(&self, name: &Interned<str>, mode: InterpretMode) -> ConcolicExpr {
1220        let res = self.lexical.last.get(name);
1221        if res.0.is_some() || res.1.is_some() {
1222            return res;
1223        }
1224
1225        for scope in self.lexical.scopes.iter().rev() {
1226            let res = scope.get(name);
1227            if res.0.is_some() || res.1.is_some() {
1228                return res;
1229            }
1230        }
1231
1232        let scope = match mode {
1233            InterpretMode::Math => self.ctx.world.library.math.scope(),
1234            InterpretMode::Markup | InterpretMode::Code => self.ctx.world.library.global.scope(),
1235            _ => return (None, None),
1236        };
1237
1238        let val = scope
1239            .get(name)
1240            .cloned()
1241            .map(|val| Ty::Value(InsTy::new(val.read().clone())));
1242        if let Some(val) = val {
1243            return (None, Some(val));
1244        }
1245
1246        if name.as_ref() == "std" {
1247            let val = Ty::Value(InsTy::new(self.ctx.world.library.std.read().clone()));
1248            return (None, Some(val));
1249        }
1250
1251        (None, None)
1252    }
1253
1254    fn fold_expr_and_val(&mut self, src: ConcolicExpr) -> Option<Expr> {
1255        crate::log_debug_ct!("folding cc: {src:?}");
1256        match src {
1257            (None, Some(val)) => Some(Expr::Type(val)),
1258            (expr, _) => self.fold_expr(expr),
1259        }
1260    }
1261
1262    fn fold_expr(&mut self, expr: Option<Expr>) -> Option<Expr> {
1263        crate::log_debug_ct!("folding cc: {expr:?}");
1264        match expr {
1265            Some(Expr::Decl(decl)) if !decl.is_def() => {
1266                crate::log_debug_ct!("folding decl: {decl:?}");
1267                let (x, y) = self.eval_ident(decl.name(), InterpretMode::Code);
1268                self.fold_expr_and_val((x, y))
1269            }
1270            Some(Expr::Ref(r)) => {
1271                crate::log_debug_ct!("folding ref: {r:?}");
1272                self.fold_expr_and_val((r.root.clone(), r.term.clone()))
1273            }
1274            Some(Expr::Select(r)) => {
1275                let lhs = self.fold_expr(Some(r.lhs.clone()));
1276                crate::log_debug_ct!("folding select: {r:?} ([{lhs:?}].[{:?}])", r.key);
1277                self.syntax_level_select(lhs?, &r.key, r.span)
1278            }
1279            Some(expr) => {
1280                crate::log_debug_ct!("folding expr: {expr:?}");
1281                Some(expr)
1282            }
1283            _ => None,
1284        }
1285    }
1286
1287    fn syntax_level_select(&mut self, lhs: Expr, key: &Interned<Decl>, span: Span) -> Option<Expr> {
1288        match &lhs {
1289            Expr::Decl(decl) => match decl.as_ref() {
1290                Decl::Module(module) => {
1291                    let exports = self.exports_of(module.fid);
1292                    let selected = exports.get(key.name())?;
1293
1294                    let select_ref = Interned::new(RefExpr {
1295                        decl: key.clone(),
1296                        root: Some(lhs.clone()),
1297                        step: Some(selected.clone()),
1298                        term: None,
1299                    });
1300                    self.resolve_as(select_ref.clone());
1301                    self.resolve_as_(span, select_ref);
1302                    Some(selected.clone())
1303                }
1304                _ => None,
1305            },
1306            _ => None,
1307        }
1308    }
1309
1310    fn exports_of(&mut self, fid: TypstFileId) -> LexicalScope {
1311        let imported = self
1312            .ctx
1313            .source_by_id(fid)
1314            .ok()
1315            .and_then(|src| self.ctx.exports_of(&src, self.route))
1316            .unwrap_or_default();
1317        let res = imported.as_ref().deref().clone();
1318        self.import_buffer.push((fid, imported));
1319        res
1320    }
1321}
1322
1323/// Extracts the root and step from a potential reference expression.
1324///
1325/// This is a key helper function for building reference chains. It handles
1326/// the case where an identifier resolves to another reference.
1327///
1328/// # Returns
1329///
1330/// A tuple of `(root, step)`:
1331/// - If `step` is a `RefExpr`: Returns `(ref.root, Some(ref.decl))` -
1332///   propagates the root forward and uses the ref's declaration as the new step
1333/// - Otherwise: Returns `(step, step)` - the expression is both root and step
1334fn extract_ref(step: Option<Expr>) -> (Option<Expr>, Option<Expr>) {
1335    match step {
1336        Some(Expr::Ref(r)) => (r.root.clone(), Some(r.decl.clone().into())),
1337        step => (step.clone(), step),
1338    }
1339}
1340
1341fn none_expr() -> Expr {
1342    Expr::Type(Ty::Builtin(BuiltinTy::None))
1343}
1344
1345#[cfg(test)]
1346mod tests {
1347    #[test]
1348    fn test_expr_size() {
1349        use super::*;
1350        assert!(size_of::<Expr>() <= size_of::<usize>() * 2);
1351    }
1352}