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