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 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::Character(..) => 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            MathAttach(attach) => {
387                let base = attach.base().to_untyped().clone();
388                let bottom = attach.bottom().unwrap_or_default().to_untyped().clone();
389                let top = attach.top().unwrap_or_default().to_untyped().clone();
390                self.check_math([base, bottom, top].iter())
391            }
392            MathPrimes(..) => Expr::Type(Ty::Builtin(BuiltinTy::None)),
393            MathFrac(frac) => {
394                let num = frac.num().to_untyped().clone();
395                let denom = frac.denom().to_untyped().clone();
396                self.check_math([num, denom].iter())
397            }
398            MathRoot(root) => self.check(root.radicand()),
399        }
400    }
401
402    fn check_element<T: NativeElement>(&mut self, content: EcoVec<Expr>) -> Expr {
403        let elem = Element::of::<T>();
404        Expr::Element(ElementExpr { elem, content }.into())
405    }
406
407    fn check_let(&mut self, typed: ast::LetBinding) -> Expr {
408        match typed.kind() {
409            ast::LetBindingKind::Closure(..) => {
410                typed.init().map_or_else(none_expr, |expr| self.check(expr))
411            }
412            ast::LetBindingKind::Normal(pat) => {
413                let docs = self.comment_matcher.collect();
414                // Check init expression before pattern checking
415                let body = typed.init().map(|init| self.defer(init));
416
417                let span = pat.span();
418                let decl = Decl::pattern(span).into();
419                self.check_docstring(&decl, docs, DefKind::Variable);
420                let pattern = self.check_pattern(pat);
421                Expr::Let(Interned::new(LetExpr {
422                    span,
423                    pattern,
424                    body,
425                }))
426            }
427        }
428    }
429
430    fn check_closure(&mut self, typed: ast::Closure) -> Expr {
431        let docs = self.comment_matcher.collect();
432        let decl = match typed.name() {
433            Some(name) => Decl::func(name).into(),
434            None => Decl::closure(typed.span()).into(),
435        };
436        self.check_docstring(&decl, docs, DefKind::Function);
437        self.resolve_as(Decl::as_def(&decl, None));
438
439        let (params, body) = self.with_scope(|this| {
440            this.scope_mut()
441                .insert_mut(decl.name().clone(), decl.clone().into());
442            let mut inputs = eco_vec![];
443            let mut names = eco_vec![];
444            let mut spread_left = None;
445            let mut spread_right = None;
446            for arg in typed.params().children() {
447                match arg {
448                    ast::Param::Pos(arg) => {
449                        inputs.push(this.check_pattern(arg));
450                    }
451                    ast::Param::Named(arg) => {
452                        let key: DeclExpr = Decl::var(arg.name()).into();
453                        let val = Pattern::Expr(this.check(arg.expr())).into();
454                        names.push((key.clone(), val));
455
456                        this.resolve_as(Decl::as_def(&key, None));
457                        this.scope_mut().insert_mut(key.name().clone(), key.into());
458                    }
459                    ast::Param::Spread(s) => {
460                        let decl: DeclExpr = if let Some(ident) = s.sink_ident() {
461                            Decl::var(ident).into()
462                        } else {
463                            Decl::spread(s.span()).into()
464                        };
465
466                        let spread = Pattern::Expr(this.check(s.expr())).into();
467                        if inputs.is_empty() {
468                            spread_left = Some((decl.clone(), spread));
469                        } else {
470                            spread_right = Some((decl.clone(), spread));
471                        }
472
473                        this.resolve_as(Decl::as_def(&decl, None));
474                        this.scope_mut()
475                            .insert_mut(decl.name().clone(), decl.into());
476                    }
477                }
478            }
479
480            if inputs.is_empty() {
481                spread_right = spread_left.take();
482            }
483
484            let pattern = PatternSig {
485                pos: inputs,
486                named: names,
487                spread_left,
488                spread_right,
489            };
490
491            (pattern, this.defer(typed.body()))
492        });
493
494        self.scope_mut()
495            .insert_mut(decl.name().clone(), decl.clone().into());
496        Expr::Func(FuncExpr { decl, params, body }.into())
497    }
498
499    fn check_pattern(&mut self, typed: ast::Pattern) -> Interned<Pattern> {
500        match typed {
501            ast::Pattern::Normal(expr) => self.check_pattern_expr(expr),
502            ast::Pattern::Placeholder(..) => Pattern::Expr(Expr::Star).into(),
503            ast::Pattern::Parenthesized(paren_expr) => self.check_pattern(paren_expr.pattern()),
504            ast::Pattern::Destructuring(destructing) => {
505                let mut inputs = eco_vec![];
506                let mut names = eco_vec![];
507                let mut spread_left = None;
508                let mut spread_right = None;
509
510                for item in destructing.items() {
511                    match item {
512                        ast::DestructuringItem::Pattern(pos) => {
513                            inputs.push(self.check_pattern(pos));
514                        }
515                        ast::DestructuringItem::Named(named) => {
516                            let key = Decl::var(named.name()).into();
517                            let val = self.check_pattern(named.pattern());
518                            names.push((key, val));
519                        }
520                        ast::DestructuringItem::Spread(spreading) => {
521                            let decl: DeclExpr = if let Some(ident) = spreading.sink_ident() {
522                                Decl::var(ident).into()
523                            } else {
524                                Decl::spread(spreading.span()).into()
525                            };
526                            let pattern = Pattern::Expr(Expr::Star).into();
527
528                            if inputs.is_empty() {
529                                spread_left = Some((decl.clone(), pattern));
530                            } else {
531                                spread_right = Some((decl.clone(), pattern));
532                            }
533
534                            self.resolve_as(Decl::as_def(&decl, None));
535                            self.scope_mut()
536                                .insert_mut(decl.name().clone(), decl.into());
537                        }
538                    }
539                }
540
541                if inputs.is_empty() {
542                    spread_right = spread_left.take();
543                }
544
545                let pattern = PatternSig {
546                    pos: inputs,
547                    named: names,
548                    spread_left,
549                    spread_right,
550                };
551
552                Pattern::Sig(Box::new(pattern)).into()
553            }
554        }
555    }
556
557    fn check_pattern_expr(&mut self, typed: ast::Expr) -> Interned<Pattern> {
558        match typed {
559            ast::Expr::Ident(ident) => {
560                let decl = Decl::var(ident).into();
561                self.resolve_as(Decl::as_def(&decl, None));
562                self.scope_mut()
563                    .insert_mut(decl.name().clone(), decl.clone().into());
564                Pattern::Simple(decl).into()
565            }
566            ast::Expr::Parenthesized(parenthesized) => self.check_pattern(parenthesized.pattern()),
567            _ => Pattern::Expr(self.check(typed)).into(),
568        }
569    }
570
571    fn check_module_import(&mut self, typed: ast::ModuleImport) -> Expr {
572        let is_wildcard_import = matches!(typed.imports(), Some(ast::Imports::Wildcard));
573
574        let source = typed.source();
575        let mod_expr = self.check_import(source, true, is_wildcard_import);
576        crate::log_debug_ct!("checking import: {source:?} => {mod_expr:?}");
577
578        let mod_var = typed.new_name().map(Decl::module_alias).or_else(|| {
579            typed.imports().is_none().then(|| {
580                let name = match mod_expr.as_ref()? {
581                    Expr::Decl(decl) if matches!(decl.as_ref(), Decl::Module { .. }) => {
582                        decl.name().clone()
583                    }
584                    _ => return None,
585                };
586                // todo: package stem
587                Some(Decl::path_stem(source.to_untyped().clone(), name))
588            })?
589        });
590
591        let creating_mod_var = mod_var.is_some();
592        let mod_var = Interned::new(mod_var.unwrap_or_else(|| Decl::module_import(typed.span())));
593
594        // Create a RefExpr for the module import variable.
595        // - decl: The import variable (e.g., "foo" in "import 'file.typ' as foo")
596        // - step & root: Both point to the module expression (same for imports)
597        // - term: None because module types are complex and not stored here
598        let mod_ref = RefExpr {
599            decl: mod_var.clone(),
600            step: mod_expr.clone(),
601            root: mod_expr.clone(),
602            term: None,
603        };
604        crate::log_debug_ct!("create import variable: {mod_ref:?}");
605        let mod_ref = Interned::new(mod_ref);
606        if creating_mod_var {
607            self.scope_mut()
608                .insert_mut(mod_var.name().clone(), Expr::Ref(mod_ref.clone()));
609        }
610
611        self.resolve_as(mod_ref.clone());
612
613        let fid = mod_expr.as_ref().and_then(|mod_expr| match mod_expr {
614            Expr::Type(Ty::Value(v)) => match &v.val {
615                Value::Module(m) => m.file_id(),
616                _ => None,
617            },
618            Expr::Decl(decl) => {
619                if matches!(decl.as_ref(), Decl::Module { .. }) {
620                    decl.file_id()
621                } else {
622                    None
623                }
624            }
625            _ => None,
626        });
627
628        // Prefetch Type Check Information
629        if let Some(fid) = fid {
630            crate::log_debug_ct!("prefetch type check: {fid:?}");
631            self.ctx.prefetch_type_check(fid);
632        }
633
634        let scope = if let Some(fid) = &fid {
635            Some(ExprScope::Lexical(self.exports_of(*fid)))
636        } else {
637            match &mod_expr {
638                Some(Expr::Type(Ty::Value(v))) => match &v.val {
639                    Value::Module(m) => Some(ExprScope::Module(m.clone())),
640                    Value::Func(func) => {
641                        if func.scope().is_some() {
642                            Some(ExprScope::Func(func.clone()))
643                        } else {
644                            None
645                        }
646                    }
647                    Value::Type(s) => Some(ExprScope::Type(*s)),
648                    _ => None,
649                },
650                _ => None,
651            }
652        };
653
654        let scope = if let Some(scope) = scope {
655            scope
656        } else {
657            log::warn!(
658                "cannot analyze import on: {typed:?}, expr {mod_expr:?}, in file {:?}",
659                typed.span().id()
660            );
661            ExprScope::empty()
662        };
663
664        if let Some(imports) = typed.imports() {
665            match imports {
666                ast::Imports::Wildcard => {
667                    crate::log_debug_ct!("checking wildcard: {mod_expr:?}");
668                    self.push_scope(scope);
669                }
670                ast::Imports::Items(items) => {
671                    let module = Expr::Decl(mod_var.clone());
672                    self.import_decls(&scope, Some(mod_var.clone()), module, items);
673                }
674            }
675        };
676
677        Expr::Import(
678            ImportExpr {
679                source: self.check(source),
680                decl: mod_ref,
681            }
682            .into(),
683        )
684    }
685
686    fn check_import(
687        &mut self,
688        source: ast::Expr,
689        is_import: bool,
690        is_wildcard_import: bool,
691    ) -> Option<Expr> {
692        let src = self.eval_expr(source, InterpretMode::Code);
693        let src_expr = self.fold_expr_and_val(src).or_else(|| {
694            self.ctx
695                .analyze_expr(source.to_untyped())
696                .into_iter()
697                .find_map(|(v, _)| match v {
698                    Value::Str(s) => Some(Expr::Type(Ty::Value(InsTy::new(Value::Str(s))))),
699                    _ => None,
700                })
701        })?;
702
703        crate::log_debug_ct!("checking import source: {src_expr:?}");
704        let const_res = match &src_expr {
705            Expr::Type(Ty::Value(val)) => {
706                self.check_import_source_val(source, &val.val, Some(&src_expr), is_import)
707            }
708            Expr::Decl(decl) if matches!(decl.as_ref(), Decl::Module { .. }) => {
709                return Some(src_expr.clone());
710            }
711
712            _ => None,
713        };
714        const_res
715            .or_else(|| self.check_import_by_def(&src_expr))
716            .or_else(|| is_wildcard_import.then(|| self.check_import_dyn(source, &src_expr))?)
717    }
718
719    fn check_import_dyn(&mut self, source: ast::Expr, src_expr: &Expr) -> Option<Expr> {
720        let src_or_module = self.ctx.analyze_import(source.to_untyped());
721        crate::log_debug_ct!("checking import source dyn: {src_or_module:?}");
722
723        match src_or_module {
724            (_, Some(Value::Module(m))) => {
725                // todo: dyn resolve src_expr
726                match m.file_id() {
727                    Some(fid) => Some(Expr::Decl(
728                        Decl::module_with_name(m.name().unwrap().into(), fid).into(),
729                    )),
730                    None => Some(Expr::Type(Ty::Value(InsTy::new(Value::Module(m))))),
731                }
732            }
733            (_, Some(v)) => Some(Expr::Type(Ty::Value(InsTy::new(v)))),
734            (Some(s), _) => self.check_import_source_val(source, &s, Some(src_expr), true),
735            (None, None) => None,
736        }
737    }
738
739    fn check_import_source_val(
740        &mut self,
741        source: ast::Expr,
742        src: &Value,
743        src_expr: Option<&Expr>,
744        is_import: bool,
745    ) -> Option<Expr> {
746        match &src {
747            _ if src.scope().is_some() => src_expr
748                .cloned()
749                .or_else(|| Some(Expr::Type(Ty::Value(InsTy::new(src.clone()))))),
750            Value::Str(s) => self.check_import_by_str(source, s.as_str(), is_import),
751            _ => None,
752        }
753    }
754
755    fn check_import_by_str(
756        &mut self,
757        source: ast::Expr,
758        src: &str,
759        is_import: bool,
760    ) -> Option<Expr> {
761        let fid = resolve_id_by_path(&self.ctx.world(), self.fid, src)?;
762        let name = Decl::calc_path_stem(src);
763        let module = Expr::Decl(Decl::module_with_name(name.clone(), fid).into());
764
765        let import_path = if is_import {
766            Decl::import_path(source.span(), name)
767        } else {
768            Decl::include_path(source.span(), name)
769        };
770
771        // Create a RefExpr for the import/include path.
772        // - decl: The path declaration (tracks the file path being imported)
773        // - step & root: Both point to the loaded module
774        // - term: None (module types not stored directly)
775        let ref_expr = RefExpr {
776            decl: import_path.into(),
777            step: Some(module.clone()),
778            root: Some(module.clone()),
779            term: None,
780        };
781        self.resolve_as(ref_expr.into());
782        Some(module)
783    }
784
785    fn check_import_by_def(&mut self, src_expr: &Expr) -> Option<Expr> {
786        match src_expr {
787            Expr::Decl(m) if matches!(m.kind(), DefKind::Module) => Some(src_expr.clone()),
788            Expr::Ref(r) => r.root.clone(),
789            _ => None,
790        }
791    }
792
793    fn import_decls(
794        &mut self,
795        scope: &ExprScope,
796        module_decl: Option<DeclExpr>,
797        module: Expr,
798        items: ast::ImportItems,
799    ) {
800        crate::log_debug_ct!("import scope {scope:?}");
801
802        for item in items.iter() {
803            let (path_ast, old, rename) = match item {
804                ast::ImportItem::Simple(path) => {
805                    let old: DeclExpr = Decl::import(path.name()).into();
806                    (path, old, None)
807                }
808                ast::ImportItem::Renamed(renamed) => {
809                    let path = renamed.path();
810                    let old: DeclExpr = Decl::import(path.name()).into();
811                    let new: DeclExpr = Decl::import_alias(renamed.new_name()).into();
812                    (path, old, Some(new))
813                }
814            };
815
816            let item_span = match item {
817                ast::ImportItem::Simple(path) => path.span(),
818                ast::ImportItem::Renamed(renamed) => renamed.span(),
819            };
820
821            if let Some(parent) = module_decl.as_ref() {
822                self.record_module_item(parent, &old, item_span);
823                if let Some(rename_decl) = &rename {
824                    self.record_module_item(parent, rename_decl, item_span);
825                }
826            }
827
828            let mut path = Vec::with_capacity(1);
829            for seg in path_ast.iter() {
830                let seg = Interned::new(Decl::ident_ref(seg));
831                path.push(seg);
832            }
833            // todo: import path
834            let (mut root, val) = match path.last().map(|decl| decl.name()) {
835                Some(name) => scope.get(name),
836                None => (None, None),
837            };
838
839            crate::log_debug_ct!("path {path:?} -> {root:?} {val:?}");
840            if root.is_none() && val.is_none() {
841                let mut sel = module.clone();
842                for seg in path.into_iter() {
843                    sel = Expr::Select(SelectExpr::new(seg, sel));
844                }
845                root = Some(sel)
846            }
847
848            let (root, step) = extract_ref(root);
849
850            // Create RefExpr for the original name in the import.
851            // - decl: The original identifier (e.g., "old" in "import: old as new")
852            // - root: The module or selection expression where the value comes from
853            // - step: Intermediate expression (from extract_ref, handles reference chains)
854            // - term: The type if it was found in the scope
855            let mut ref_expr = Interned::new(RefExpr {
856                decl: old.clone(),
857                root,
858                step,
859                term: val,
860            });
861            self.resolve_as(ref_expr.clone());
862
863            // If renamed, create a second RefExpr for the new name that chains to the old
864            // one. This builds the chain: new -> old -> root
865            if let Some(new) = &rename {
866                // - decl: The new name (e.g., "new" in "import: old as new")
867                // - root: Same as original (ultimate source of the value)
868                // - step: Points to the old name (intermediate link in the chain)
869                // - term: Same type as original
870                ref_expr = Interned::new(RefExpr {
871                    decl: new.clone(),
872                    root: ref_expr.root.clone(),
873                    step: Some(ref_expr.decl.clone().into()),
874                    term: ref_expr.term.clone(),
875                });
876                self.resolve_as(ref_expr.clone());
877            }
878
879            // final resolves
880            let name = rename.as_ref().unwrap_or(&old).name().clone();
881            let expr = Expr::Ref(ref_expr);
882            self.scope_mut().insert_mut(name, expr.clone());
883        }
884    }
885
886    fn record_module_item(&mut self, parent: &DeclExpr, child: &DeclExpr, span: Span) {
887        if self.init_stage || span.is_detached() || span.id() != Some(self.fid) {
888            return;
889        }
890        let Some(item_range) = self.source.range(span) else {
891            return;
892        };
893        let Some(binding_range) = self.source.range(child.span()) else {
894            return;
895        };
896        self.module_items.insert(
897            child.clone(),
898            ModuleItemLayout {
899                parent: parent.clone(),
900                item_range,
901                binding_range,
902            },
903        );
904    }
905
906    fn check_module_include(&mut self, typed: ast::ModuleInclude) -> Expr {
907        let _mod_expr = self.check_import(typed.source(), false, false);
908        let source = self.check(typed.source());
909        Expr::Include(IncludeExpr { source }.into())
910    }
911
912    fn check_array(&mut self, typed: ast::Array) -> Expr {
913        let mut items = vec![];
914        for item in typed.items() {
915            match item {
916                ast::ArrayItem::Pos(item) => {
917                    items.push(ArgExpr::Pos(self.check(item)));
918                }
919                ast::ArrayItem::Spread(s) => {
920                    items.push(ArgExpr::Spread(self.check(s.expr())));
921                }
922            }
923        }
924
925        Expr::Array(ArgsExpr::new(typed.span(), items))
926    }
927
928    fn check_dict(&mut self, typed: ast::Dict) -> Expr {
929        let mut items = vec![];
930        for item in typed.items() {
931            match item {
932                ast::DictItem::Named(item) => {
933                    let key = Decl::ident_ref(item.name()).into();
934                    let val = self.check(item.expr());
935                    items.push(ArgExpr::Named(Box::new((key, val))));
936                }
937                ast::DictItem::Keyed(item) => {
938                    let val = self.check(item.expr());
939                    let key = item.key();
940                    let analyzed = self.const_eval_expr(key);
941                    let analyzed = match &analyzed {
942                        Some(Value::Str(s)) => Some(s),
943                        _ => None,
944                    };
945                    let Some(analyzed) = analyzed else {
946                        let key = self.check(key);
947                        items.push(ArgExpr::NamedRt(Box::new((key, val))));
948                        continue;
949                    };
950                    let key = Decl::str_name(key.to_untyped().clone(), analyzed).into();
951                    items.push(ArgExpr::Named(Box::new((key, val))));
952                }
953                ast::DictItem::Spread(s) => {
954                    items.push(ArgExpr::Spread(self.check(s.expr())));
955                }
956            }
957        }
958
959        Expr::Dict(ArgsExpr::new(typed.span(), items))
960    }
961
962    fn check_args(&mut self, typed: ast::Args) -> Expr {
963        let mut args = vec![];
964        for arg in typed.items() {
965            match arg {
966                ast::Arg::Pos(arg) => {
967                    args.push(ArgExpr::Pos(self.check(arg)));
968                }
969                ast::Arg::Named(arg) => {
970                    let key = Decl::ident_ref(arg.name()).into();
971                    let val = self.check(arg.expr());
972                    args.push(ArgExpr::Named(Box::new((key, val))));
973                }
974                ast::Arg::Spread(s) => {
975                    args.push(ArgExpr::Spread(self.check(s.expr())));
976                }
977            }
978        }
979        Expr::Args(ArgsExpr::new(typed.span(), args))
980    }
981
982    fn check_unary(&mut self, typed: ast::Unary) -> Expr {
983        let op = match typed.op() {
984            ast::UnOp::Pos => UnaryOp::Pos,
985            ast::UnOp::Neg => UnaryOp::Neg,
986            ast::UnOp::Not => UnaryOp::Not,
987        };
988        let lhs = self.check(typed.expr());
989        Expr::Unary(UnInst::new(op, lhs))
990    }
991
992    fn check_binary(&mut self, typed: ast::Binary) -> Expr {
993        let lhs = self.check(typed.lhs());
994        let rhs = self.check(typed.rhs());
995        Expr::Binary(BinInst::new(typed.op(), lhs, rhs))
996    }
997
998    fn check_destruct_assign(&mut self, typed: ast::DestructAssignment) -> Expr {
999        let pat = Expr::Pattern(self.check_pattern(typed.pattern()));
1000        let val = self.check(typed.value());
1001        let inst = BinInst::new(ast::BinOp::Assign, pat, val);
1002        Expr::Binary(inst)
1003    }
1004
1005    fn check_field_access(&mut self, typed: ast::FieldAccess) -> Expr {
1006        let lhs = self.check(typed.target());
1007        let key = Decl::ident_ref(typed.field()).into();
1008        let span = typed.span();
1009        Expr::Select(SelectExpr { lhs, key, span }.into())
1010    }
1011
1012    fn check_func_call(&mut self, typed: ast::FuncCall) -> Expr {
1013        let callee = self.check(typed.callee());
1014        let args = self.check_args(typed.args());
1015        let span = typed.span();
1016        Expr::Apply(ApplyExpr { callee, args, span }.into())
1017    }
1018
1019    fn check_set(&mut self, typed: ast::SetRule) -> Expr {
1020        let target = self.check(typed.target());
1021        let args = self.check_args(typed.args());
1022        let cond = typed.condition().map(|cond| self.check(cond));
1023        Expr::Set(SetExpr { target, args, cond }.into())
1024    }
1025
1026    fn check_show(&mut self, typed: ast::ShowRule) -> Expr {
1027        let selector = typed.selector().map(|selector| self.check(selector));
1028        let edit = self.defer(typed.transform());
1029        Expr::Show(ShowExpr { selector, edit }.into())
1030    }
1031
1032    fn check_conditional(&mut self, typed: ast::Conditional) -> Expr {
1033        let cond = self.check(typed.condition());
1034        let then = self.defer(typed.if_body());
1035        let else_ = typed
1036            .else_body()
1037            .map_or_else(none_expr, |expr| self.defer(expr));
1038        Expr::Conditional(IfExpr { cond, then, else_ }.into())
1039    }
1040
1041    fn check_while_loop(&mut self, typed: ast::WhileLoop) -> Expr {
1042        let cond = self.check(typed.condition());
1043        let body = self.defer(typed.body());
1044        Expr::WhileLoop(WhileExpr { cond, body }.into())
1045    }
1046
1047    fn check_for_loop(&mut self, typed: ast::ForLoop) -> Expr {
1048        self.with_scope(|this| {
1049            let pattern = this.check_pattern(typed.pattern());
1050            let iter = this.check(typed.iterable());
1051            let body = this.defer(typed.body());
1052            Expr::ForLoop(
1053                ForExpr {
1054                    pattern,
1055                    iter,
1056                    body,
1057                }
1058                .into(),
1059            )
1060        })
1061    }
1062
1063    fn check_inline_markup(&mut self, markup: ast::Markup) -> Expr {
1064        self.check_in_mode(markup.to_untyped().children(), InterpretMode::Markup)
1065    }
1066
1067    fn check_markup(&mut self, markup: ast::Markup) -> Expr {
1068        self.with_scope(|this| this.check_inline_markup(markup))
1069    }
1070
1071    fn check_code(&mut self, code: ast::Code) -> Expr {
1072        self.with_scope(|this| {
1073            this.check_in_mode(code.to_untyped().children(), InterpretMode::Code)
1074        })
1075    }
1076
1077    fn check_math(&mut self, children: SyntaxNodeChildren) -> Expr {
1078        self.check_in_mode(children, InterpretMode::Math)
1079    }
1080
1081    fn check_root_scope(&mut self, children: SyntaxNodeChildren) {
1082        self.init_stage = true;
1083        self.check_in_mode(children, InterpretMode::Markup);
1084        self.init_stage = false;
1085    }
1086
1087    fn check_in_mode(&mut self, children: SyntaxNodeChildren, mode: InterpretMode) -> Expr {
1088        let old_mode = self.lexical.mode;
1089        self.lexical.mode = mode;
1090
1091        // collect all comments before the definition
1092        self.comment_matcher.reset();
1093
1094        let mut items = Vec::with_capacity(4);
1095        for n in children {
1096            if let Some(expr) = n.cast::<ast::Expr>() {
1097                items.push(self.check(expr));
1098                self.comment_matcher.reset();
1099                continue;
1100            }
1101            if !self.init_stage && self.comment_matcher.process(n) {
1102                self.comment_matcher.reset();
1103            }
1104        }
1105
1106        self.lexical.mode = old_mode;
1107        Expr::Block(items.into())
1108    }
1109
1110    fn check_label(&mut self, label: ast::Label) -> Expr {
1111        let decl: Interned<Decl> = Decl::label(label.get(), label.span()).into();
1112
1113        self.resolve_as(
1114            RefExpr {
1115                decl: decl.clone(),
1116                step: None,
1117                root: None,
1118                term: None,
1119            }
1120            .into(),
1121        );
1122        Expr::Decl(decl)
1123    }
1124
1125    fn check_ref(&mut self, ref_node: ast::Ref) -> Expr {
1126        let ident = Interned::new(Decl::ref_(ref_node));
1127        let body = ref_node
1128            .supplement()
1129            .map(|block| self.check(ast::Expr::ContentBlock(block)));
1130        let ref_expr = ContentRefExpr {
1131            ident: ident.clone(),
1132            of: None,
1133            body,
1134        };
1135        self.resolve_as(
1136            RefExpr {
1137                decl: ident,
1138                step: None,
1139                root: None,
1140                term: None,
1141            }
1142            .into(),
1143        );
1144        Expr::ContentRef(ref_expr.into())
1145    }
1146
1147    fn check_ident(&mut self, ident: ast::Ident) -> Expr {
1148        self.resolve_ident(Decl::ident_ref(ident).into(), InterpretMode::Code)
1149    }
1150
1151    fn check_math_ident(&mut self, ident: ast::MathIdent) -> Expr {
1152        self.resolve_ident(Decl::math_ident_ref(ident).into(), InterpretMode::Math)
1153    }
1154
1155    fn resolve_as(&mut self, r: Interned<RefExpr>) {
1156        self.resolve_as_(r.decl.span(), r);
1157    }
1158
1159    fn resolve_as_(&mut self, s: Span, r: Interned<RefExpr>) {
1160        self.buffer.push((s, r.clone()));
1161    }
1162
1163    fn resolve_ident(&mut self, decl: DeclExpr, mode: InterpretMode) -> Expr {
1164        let r: Interned<RefExpr> = self.resolve_ident_(decl, mode).into();
1165        let s = r.decl.span();
1166        self.buffer.push((s, r.clone()));
1167        Expr::Ref(r)
1168    }
1169
1170    /// Resolves an identifier to a reference expression.
1171    ///
1172    /// This function looks up an identifier in the lexical scope and creates
1173    /// a `RefExpr` that tracks the resolution chain.
1174    ///
1175    /// # Resolution Process
1176    ///
1177    /// 1. Evaluates the identifier to get its expression and type
1178    ///    (`eval_ident`)
1179    /// 2. If the result is itself a `RefExpr`, extracts its `root` and uses the
1180    ///    RefExpr's `decl` as the `step` (building a reference chain)
1181    /// 3. Otherwise, uses the expression as both `root` and `step`
1182    ///
1183    /// # Field Assignment
1184    ///
1185    /// - `decl`: The identifier being resolved
1186    /// - `root`: The ultimate source of the value (extracted from chain or the
1187    ///   expression itself)
1188    /// - `step`: The immediate resolution (extracted from chain or the
1189    ///   expression itself)
1190    /// - `term`: The resolved type (if available from evaluation)
1191    ///
1192    /// # Example
1193    ///
1194    /// For `let x = 1; let y = x; let z = y`:
1195    /// - Resolving `x` gives: `RefExpr { decl: x, root: None, step: None, term:
1196    ///   Some(int) }`
1197    /// - Resolving `y` gives: `RefExpr { decl: y, root: Some(x), step: Some(x),
1198    ///   term: Some(int) }`
1199    /// - Resolving `z` gives: `RefExpr { decl: z, root: Some(x), step: Some(y),
1200    ///   term: Some(int) }`
1201    fn resolve_ident_(&mut self, decl: DeclExpr, mode: InterpretMode) -> RefExpr {
1202        let (step, val) = self.eval_ident(decl.name(), mode);
1203        let (root, step) = extract_ref(step);
1204
1205        RefExpr {
1206            decl,
1207            root,
1208            step,
1209            term: val,
1210        }
1211    }
1212
1213    fn defer(&mut self, expr: ast::Expr) -> Expr {
1214        if self.init_stage {
1215            Expr::Star
1216        } else {
1217            self.check(expr)
1218        }
1219    }
1220
1221    fn collect_buffer(&mut self) {
1222        let mut resolves = self.resolves.lock();
1223        resolves.extend(self.buffer.drain(..));
1224        drop(resolves);
1225        let mut imports = self.imports.lock();
1226        imports.extend(self.import_buffer.drain(..));
1227    }
1228
1229    fn const_eval_expr(&self, expr: ast::Expr) -> Option<Value> {
1230        SharedContext::const_eval(expr)
1231    }
1232
1233    fn eval_expr(&mut self, expr: ast::Expr, mode: InterpretMode) -> ConcolicExpr {
1234        if let Some(term) = self.const_eval_expr(expr) {
1235            return (None, Some(Ty::Value(InsTy::new(term))));
1236        }
1237        crate::log_debug_ct!("checking expr: {expr:?}");
1238
1239        match expr {
1240            ast::Expr::FieldAccess(field_access) => {
1241                let field = Decl::ident_ref(field_access.field());
1242
1243                let (expr, term) = self.eval_expr(field_access.target(), mode);
1244                let term = term.and_then(|v| {
1245                    // todo: use type select
1246                    // v.select(field.name()).ok()
1247                    match v {
1248                        Ty::Value(val) => {
1249                            Some(Ty::Value(InsTy::new(val.val.field(field.name(), ()).ok()?)))
1250                        }
1251                        _ => None,
1252                    }
1253                });
1254                let sel = expr.map(|expr| Expr::Select(SelectExpr::new(field.into(), expr)));
1255                (sel, term)
1256            }
1257            ast::Expr::Ident(ident) => {
1258                let expr_term = self.eval_ident(&ident.get().into(), mode);
1259                crate::log_debug_ct!("checking expr: {expr:?} -> res: {expr_term:?}");
1260                expr_term
1261            }
1262            _ => (None, None),
1263        }
1264    }
1265
1266    /// Evaluates an identifier by looking it up in the lexical scope.
1267    ///
1268    /// Returns a tuple of `(expression, type)` where:
1269    /// - `expression`: The expression the identifier resolves to (may be a
1270    ///   `RefExpr`)
1271    /// - `type`: The type of the value (if known)
1272    ///
1273    /// # Lookup Order
1274    ///
1275    /// 1. Current scope (`self.lexical.last`) - for block-local variables
1276    /// 2. Parent scopes (`self.lexical.scopes`) - for outer scope variables
1277    /// 3. Global/Math library scope - for built-in functions and constants
1278    /// 4. Special case: "std" module
1279    fn eval_ident(&self, name: &Interned<str>, mode: InterpretMode) -> ConcolicExpr {
1280        let res = self.lexical.last.get(name);
1281        if res.0.is_some() || res.1.is_some() {
1282            return res;
1283        }
1284
1285        for scope in self.lexical.scopes.iter().rev() {
1286            let res = scope.get(name);
1287            if res.0.is_some() || res.1.is_some() {
1288                return res;
1289            }
1290        }
1291
1292        let scope = match mode {
1293            InterpretMode::Math => self.ctx.world().library.math.scope(),
1294            InterpretMode::Markup | InterpretMode::Code => self.ctx.world().library.global.scope(),
1295            _ => return (None, None),
1296        };
1297
1298        let val = scope
1299            .get(name)
1300            .cloned()
1301            .map(|val| Ty::Value(InsTy::new(val.read().clone())));
1302        if let Some(val) = val {
1303            return (None, Some(val));
1304        }
1305
1306        if name.as_ref() == "std" {
1307            let val = Ty::Value(InsTy::new(self.ctx.world().library.std.read().clone()));
1308            return (None, Some(val));
1309        }
1310
1311        (None, None)
1312    }
1313
1314    fn fold_expr_and_val(&mut self, src: ConcolicExpr) -> Option<Expr> {
1315        crate::log_debug_ct!("folding cc: {src:?}");
1316        match src {
1317            (None, Some(val)) => Some(Expr::Type(val)),
1318            (expr, _) => self.fold_expr(expr),
1319        }
1320    }
1321
1322    fn fold_expr(&mut self, expr: Option<Expr>) -> Option<Expr> {
1323        crate::log_debug_ct!("folding cc: {expr:?}");
1324        match expr {
1325            Some(Expr::Decl(decl)) if !decl.is_def() => {
1326                crate::log_debug_ct!("folding decl: {decl:?}");
1327                let (x, y) = self.eval_ident(decl.name(), InterpretMode::Code);
1328                self.fold_expr_and_val((x, y))
1329            }
1330            Some(Expr::Ref(r)) => {
1331                crate::log_debug_ct!("folding ref: {r:?}");
1332                self.fold_expr_and_val((r.root.clone(), r.term.clone()))
1333            }
1334            Some(Expr::Select(r)) => {
1335                let lhs = self.fold_expr(Some(r.lhs.clone()));
1336                crate::log_debug_ct!("folding select: {r:?} ([{lhs:?}].[{:?}])", r.key);
1337                self.syntax_level_select(lhs?, &r.key, r.span)
1338            }
1339            Some(expr) => {
1340                crate::log_debug_ct!("folding expr: {expr:?}");
1341                Some(expr)
1342            }
1343            _ => None,
1344        }
1345    }
1346
1347    fn syntax_level_select(&mut self, lhs: Expr, key: &Interned<Decl>, span: Span) -> Option<Expr> {
1348        match &lhs {
1349            Expr::Decl(decl) => match decl.as_ref() {
1350                Decl::Module(module) => {
1351                    let exports = self.exports_of(module.fid);
1352                    let selected = exports.get(key.name())?;
1353
1354                    let select_ref = Interned::new(RefExpr {
1355                        decl: key.clone(),
1356                        root: Some(lhs.clone()),
1357                        step: Some(selected.clone()),
1358                        term: None,
1359                    });
1360                    self.resolve_as(select_ref.clone());
1361                    self.resolve_as_(span, select_ref);
1362                    Some(selected.clone())
1363                }
1364                _ => None,
1365            },
1366            _ => None,
1367        }
1368    }
1369
1370    fn exports_of(&mut self, fid: TypstFileId) -> LexicalScope {
1371        let imported = self
1372            .ctx
1373            .source_by_id(fid)
1374            .ok()
1375            .and_then(|src| self.ctx.exports_of(&src, self.route))
1376            .unwrap_or_default();
1377        let res = imported.as_ref().deref().clone();
1378        self.import_buffer.push((fid, imported));
1379        res
1380    }
1381}
1382
1383/// Extracts the root and step from a potential reference expression.
1384///
1385/// This is a key helper function for building reference chains. It handles
1386/// the case where an identifier resolves to another reference.
1387///
1388/// # Returns
1389///
1390/// A tuple of `(root, step)`:
1391/// - If `step` is a `RefExpr`: Returns `(ref.root, Some(ref.decl))` -
1392///   propagates the root forward and uses the ref's declaration as the new step
1393/// - Otherwise: Returns `(step, step)` - the expression is both root and step
1394fn extract_ref(step: Option<Expr>) -> (Option<Expr>, Option<Expr>) {
1395    match step {
1396        Some(Expr::Ref(r)) => (r.root.clone(), Some(r.decl.clone().into())),
1397        step => (step.clone(), step),
1398    }
1399}
1400
1401fn none_expr() -> Expr {
1402    Expr::Type(Ty::Builtin(BuiltinTy::None))
1403}
1404
1405#[cfg(test)]
1406mod tests {
1407    #[test]
1408    fn test_expr_size() {
1409        use super::*;
1410        assert!(size_of::<Expr>() <= size_of::<usize>() * 2);
1411    }
1412}