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