Skip to main content

vm/compiler/
codegen.rs

1use std::collections::HashMap;
2
3use crate::assembler::Assembler;
4use crate::builtins::BuiltinFunction;
5use crate::{
6    CallableKind, CallablePrototype, CallableTarget, ExportedCallable, FunctionRegion, Program,
7    RootCallableBinding, ScriptFunction, TypeMap, Value, ValueType,
8};
9
10use super::ir::{
11    ClosureExpr, Expr, FunctionDecl, FunctionImpl, LocalSlot, MatchPattern, MatchTypePattern, Stmt,
12    StructDecl, TypeSchema,
13};
14use super::{CompileError, TypingMode, typing};
15
16pub struct Compiler {
17    assembler: Assembler,
18    next_label_id: u32,
19    loop_stack: Vec<LoopContext>,
20    function_impls: HashMap<u16, FunctionImpl>,
21    function_decls: HashMap<u16, FunctionDecl>,
22    struct_schemas: HashMap<String, StructDecl>,
23    host_import_return_types: HashMap<u16, typing::BoundType>,
24    host_import_signatures: HashMap<u16, typing::HostCallableSignature>,
25    call_index_remap: HashMap<u16, u16>,
26
27    callable_bindings: HashMap<LocalSlot, CallableBinding>,
28    enable_local_move_semantics: bool,
29    typing_mode: TypingMode,
30    type_state: typing::LocalTypeState,
31    type_map: TypeMap,
32    root_local_count: usize,
33    frame_local_count: usize,
34    function_slots: HashMap<u16, LocalSlot>,
35    specialized_function_slots: Vec<(u16, Vec<TypeSchema>, LocalSlot)>,
36    function_prototype_ids: HashMap<u16, u32>,
37    script_functions: Vec<ScriptFunction>,
38    callable_prototypes: Vec<CallablePrototype>,
39    function_regions: Vec<FunctionRegion>,
40    root_callable_bindings: Vec<RootCallableBinding>,
41    pending_closures: Vec<(u32, ClosureExpr)>,
42    callable_prototype_bindings: HashMap<LocalSlot, u32>,
43    closure_param_hints: HashMap<u32, Vec<(typing::BoundType, Option<TypeSchema>)>>,
44}
45
46struct LoopContext {
47    continue_label: String,
48    break_label: String,
49}
50
51#[derive(Clone)]
52enum CallableBinding {
53    Closure(ClosureExpr),
54    Function(u16),
55}
56
57impl Default for Compiler {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63impl Compiler {
64    pub fn new() -> Self {
65        Self {
66            assembler: Assembler::new(),
67            next_label_id: 0,
68            loop_stack: Vec::new(),
69            function_impls: HashMap::new(),
70            function_decls: HashMap::new(),
71            struct_schemas: HashMap::new(),
72            host_import_return_types: HashMap::new(),
73            host_import_signatures: HashMap::new(),
74            call_index_remap: HashMap::new(),
75
76            callable_bindings: HashMap::new(),
77            enable_local_move_semantics: false,
78            typing_mode: TypingMode::DynamicHints,
79            type_state: typing::LocalTypeState::default(),
80            type_map: TypeMap::default(),
81            root_local_count: 0,
82            frame_local_count: 0,
83            function_slots: HashMap::new(),
84            specialized_function_slots: Vec::new(),
85            function_prototype_ids: HashMap::new(),
86            script_functions: Vec::new(),
87            callable_prototypes: Vec::new(),
88            function_regions: Vec::new(),
89            root_callable_bindings: Vec::new(),
90            pending_closures: Vec::new(),
91            callable_prototype_bindings: HashMap::new(),
92            closure_param_hints: HashMap::new(),
93        }
94    }
95
96    pub fn set_source(&mut self, source: String) {
97        self.assembler.set_source(source);
98    }
99
100    pub fn add_function_debug(&mut self, func: &FunctionDecl) {
101        self.assembler
102            .add_function(func.name.clone(), func.args.clone());
103    }
104
105    pub fn add_local_debug(
106        &mut self,
107        name: String,
108        index: LocalSlot,
109        declared_line: Option<u32>,
110        last_line: Option<u32>,
111    ) -> Result<(), CompileError> {
112        self.assembler.add_local_with_range(
113            name,
114            local_slot_operand(index)?,
115            declared_line,
116            last_line,
117        );
118        Ok(())
119    }
120
121    pub fn set_root_local_count(&mut self, root_local_count: usize) {
122        self.root_local_count = root_local_count;
123    }
124
125    pub fn set_function_impls(&mut self, function_impls: HashMap<u16, FunctionImpl>) {
126        self.function_impls = function_impls;
127    }
128
129    pub fn set_function_decls(&mut self, function_decls: HashMap<u16, FunctionDecl>) {
130        self.function_decls = function_decls;
131    }
132
133    pub fn set_struct_schemas(&mut self, struct_schemas: HashMap<String, StructDecl>) {
134        self.struct_schemas = struct_schemas;
135    }
136
137    pub(crate) fn set_host_import_return_types(
138        &mut self,
139        host_import_return_types: HashMap<u16, typing::BoundType>,
140    ) {
141        self.host_import_return_types = host_import_return_types;
142    }
143
144    pub(crate) fn set_host_import_signatures(
145        &mut self,
146        host_import_signatures: HashMap<u16, typing::HostCallableSignature>,
147    ) {
148        self.host_import_signatures = host_import_signatures;
149    }
150
151    pub fn set_call_index_remap(&mut self, call_index_remap: HashMap<u16, u16>) {
152        self.call_index_remap = call_index_remap;
153    }
154
155    pub fn set_enable_local_move_semantics(&mut self, enable_local_move_semantics: bool) {
156        self.enable_local_move_semantics = enable_local_move_semantics;
157    }
158
159    pub(crate) fn set_typing_mode(&mut self, typing_mode: TypingMode) {
160        self.typing_mode = typing_mode;
161    }
162
163    pub(crate) fn set_type_inference(&mut self, type_info: typing::TypeInferenceResult) {
164        self.type_map.local_types = type_info.local_types;
165        self.type_map.local_schemas = type_info.local_schemas;
166        self.type_map.callable_slots = type_info.callable_slots;
167        self.type_map.optional_slots = type_info.optional_slots;
168    }
169
170    pub fn compile_program(mut self, stmts: &[Stmt]) -> Result<Program, CompileError> {
171        let named_functions = self.prepare_named_callables()?;
172        self.compile_stmts(stmts)?;
173        self.assembler.ret();
174        let root_end = self.assembler.position();
175        if root_end > 0 {
176            self.function_regions.push(FunctionRegion {
177                start_ip: 0,
178                end_ip: root_end,
179                prototype_id: None,
180            });
181        }
182
183        for function_index in named_functions {
184            self.compile_named_function_body(function_index)?;
185        }
186        let mut closure_index = 0usize;
187        while closure_index < self.pending_closures.len() {
188            let (prototype_id, closure) = self.pending_closures[closure_index].clone();
189            self.compile_closure_body(prototype_id, &closure)?;
190            closure_index += 1;
191        }
192        for prototype in &mut self.callable_prototypes {
193            prototype.frame_local_count = self.frame_local_count;
194        }
195        let mut exported_callables = self
196            .function_decls
197            .values()
198            .filter(|decl| decl.exported)
199            .filter_map(|decl| {
200                self.function_slots
201                    .get(&decl.index)
202                    .copied()
203                    .map(|local_slot| ExportedCallable {
204                        name: decl.name.clone(),
205                        local_slot,
206                    })
207            })
208            .collect::<Vec<_>>();
209        exported_callables.sort_unstable_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
210
211        let mut program = self
212            .assembler
213            .finish_program()
214            .map_err(CompileError::Assembler)?;
215        self.type_map.strict_types = self.typing_mode.is_strict();
216        program.type_map = Some(self.type_map);
217        program.local_count = self.frame_local_count;
218        program.script_functions = self.script_functions;
219        program.callable_prototypes = self.callable_prototypes;
220        program.function_regions = self.function_regions;
221        program.root_callable_bindings = self.root_callable_bindings;
222        program.exported_callables = exported_callables;
223        Ok(program)
224    }
225
226    fn seed_frame_type_state_from_type_map(&mut self) {
227        for index in 0..self.frame_local_count.min(usize::from(u16::MAX) + 1) {
228            let Ok(slot) = LocalSlot::try_from(index) else {
229                break;
230            };
231            let schema = self.type_map.local_schemas.get(index).cloned().flatten();
232            let ty = schema
233                .as_ref()
234                .map(typing::bound_type_from_schema)
235                .unwrap_or_else(|| {
236                    self.type_map
237                        .local_types
238                        .get(index)
239                        .copied()
240                        .map(typing::BoundType::from)
241                        .unwrap_or(typing::BoundType::Unknown)
242                });
243            self.type_state
244                .set_with_schema_origin(slot, ty, schema, false);
245        }
246    }
247
248    fn prepare_named_callables(&mut self) -> Result<Vec<u16>, CompileError> {
249        let mut indices = self.function_impls.keys().copied().collect::<Vec<_>>();
250        indices.sort_unstable();
251        self.frame_local_count = self
252            .root_local_count
253            .checked_add(indices.len())
254            .ok_or(CompileError::LocalSlotOverflow(LocalSlot::MAX))?;
255        if self.frame_local_count > usize::from(u8::MAX) + 1 {
256            return Err(CompileError::LocalSlotOverflow(LocalSlot::MAX));
257        }
258
259        for (position, function_index) in indices.iter().copied().enumerate() {
260            let hidden_slot = LocalSlot::try_from(self.root_local_count + position)
261                .map_err(|_| CompileError::LocalSlotOverflow(LocalSlot::MAX))?;
262            let prototype_id = self.callable_prototypes.len() as u32;
263            let script_function_id = self.script_functions.len() as u32 + position as u32;
264            let function_impl = self
265                .function_impls
266                .get(&function_index)
267                .expect("function index came from implementation map");
268            let decl = self.function_decls.get(&function_index);
269            self.function_slots.insert(function_index, hidden_slot);
270            self.function_prototype_ids
271                .insert(function_index, prototype_id);
272            self.callable_prototypes.push(CallablePrototype {
273                kind: if function_impl.capture_copies.is_empty() {
274                    CallableKind::FunctionItem
275                } else {
276                    CallableKind::Closure
277                },
278                target: CallableTarget::ScriptFunction(script_function_id),
279                arity: function_impl.param_slots.len() as u8,
280                frame_local_count: self.frame_local_count,
281                parameter_slots: function_impl.param_slots.clone(),
282                capture_source_slots: function_impl
283                    .capture_copies
284                    .iter()
285                    .map(|(source, _)| *source)
286                    .collect(),
287                capture_slots: function_impl
288                    .capture_copies
289                    .iter()
290                    .map(|(_, target)| *target)
291                    .collect(),
292                capture_modes: function_impl
293                    .capture_copies
294                    .iter()
295                    .map(|(_, target)| {
296                        super::lifetime::function_capture_binding_mode(function_impl, *target)
297                    })
298                    .collect(),
299                self_slot: Some(hidden_slot),
300                schema: decl.map(|decl| TypeSchema::Callable {
301                    params: decl
302                        .arg_schemas
303                        .iter()
304                        .map(|schema| schema.clone().unwrap_or(TypeSchema::Unknown))
305                        .collect(),
306                    result: Box::new(decl.return_schema.clone().unwrap_or(TypeSchema::Unknown)),
307                }),
308            });
309            if function_impl.capture_copies.is_empty() {
310                self.root_callable_bindings.push(RootCallableBinding {
311                    local_slot: hidden_slot,
312                    prototype_id,
313                });
314            }
315        }
316        Ok(indices)
317    }
318
319    fn compile_named_function_body(&mut self, function_index: u16) -> Result<(), CompileError> {
320        let function_impl = self
321            .function_impls
322            .get(&function_index)
323            .cloned()
324            .expect("prepared function implementation must exist");
325        let prototype_id = self.function_prototype_ids[&function_index];
326        let entry_ip = self.assembler.position();
327        let callable_snapshot = self.callable_bindings.clone();
328        let type_snapshot = self.type_state.clone();
329        let loop_snapshot = std::mem::take(&mut self.loop_stack);
330        self.seed_frame_type_state_from_type_map();
331        if let Some(decl) = self.function_decls.get(&function_index) {
332            for (slot, schema) in function_impl.param_slots.iter().zip(&decl.arg_schemas) {
333                match schema {
334                    Some(schema) => self.type_state.set_with_schema_origin(
335                        *slot,
336                        typing::bound_type_from_schema(schema),
337                        Some(schema.clone()),
338                        true,
339                    ),
340                    None => self.type_state.set_with_schema_origin(
341                        *slot,
342                        typing::BoundType::Unknown,
343                        None,
344                        false,
345                    ),
346                }
347            }
348        }
349        self.compile_stmts(&function_impl.body_stmts)?;
350        self.compile_expr(&function_impl.body_expr)?;
351        self.assembler.ret();
352        self.loop_stack = loop_snapshot;
353        self.callable_bindings = callable_snapshot;
354        self.type_state = type_snapshot;
355        let end_ip = self.assembler.position();
356        self.script_functions
357            .push(ScriptFunction { entry_ip, end_ip });
358        self.function_regions.push(FunctionRegion {
359            start_ip: entry_ip,
360            end_ip,
361            prototype_id: Some(prototype_id),
362        });
363        Ok(())
364    }
365
366    fn compile_closure_body(
367        &mut self,
368        prototype_id: u32,
369        closure: &ClosureExpr,
370    ) -> Result<(), CompileError> {
371        let entry_ip = self.assembler.position();
372        let callable_snapshot = self.callable_bindings.clone();
373        let type_snapshot = self.type_state.clone();
374        let loop_snapshot = std::mem::take(&mut self.loop_stack);
375        self.seed_frame_type_state_from_type_map();
376        if let Some(hints) = self.closure_param_hints.get(&prototype_id).cloned() {
377            for (slot, (ty, schema)) in closure.param_slots.iter().zip(hints) {
378                self.type_state
379                    .set_with_schema_origin(*slot, ty, schema, false);
380            }
381        }
382        self.compile_expr(&closure.body)?;
383        self.assembler.ret();
384        self.loop_stack = loop_snapshot;
385        self.callable_bindings = callable_snapshot;
386        self.type_state = type_snapshot;
387        let end_ip = self.assembler.position();
388        let function_id = self.script_functions.len() as u32;
389        self.script_functions
390            .push(ScriptFunction { entry_ip, end_ip });
391        self.callable_prototypes[prototype_id as usize].target =
392            CallableTarget::ScriptFunction(function_id);
393        self.function_regions.push(FunctionRegion {
394            start_ip: entry_ip,
395            end_ip,
396            prototype_id: Some(prototype_id),
397        });
398        Ok(())
399    }
400
401    fn compile_stmts(&mut self, stmts: &[Stmt]) -> Result<(), CompileError> {
402        for stmt in stmts {
403            self.compile_stmt(stmt)?;
404        }
405        Ok(())
406    }
407
408    fn compile_stmt(&mut self, stmt: &Stmt) -> Result<(), CompileError> {
409        match stmt {
410            Stmt::Noop { line } => {
411                self.assembler.mark_line(*line);
412            }
413            Stmt::Let {
414                index,
415                declared_schema,
416                expr,
417                line,
418            } => {
419                self.assembler.mark_line(*line);
420                self.assign_expr_to_slot(*index, declared_schema.as_ref(), expr)?;
421            }
422            Stmt::Assign {
423                index, expr, line, ..
424            } => {
425                self.assembler.mark_line(*line);
426                self.assign_expr_to_slot(*index, None, expr)?;
427            }
428            Stmt::ClosureLet { line, .. } => {
429                self.assembler.mark_line(*line);
430            }
431            Stmt::FuncDecl {
432                index,
433                has_impl,
434                line,
435                ..
436            } => {
437                self.assembler.mark_line(*line);
438                if *has_impl {
439                    self.emit_named_callable_binding(*index)?;
440                }
441            }
442            Stmt::Expr { expr, line } => {
443                self.assembler.mark_line(*line);
444                self.compile_expr(expr)?;
445            }
446            Stmt::IfElse {
447                condition,
448                then_branch,
449                else_branch,
450                line,
451            } => {
452                self.assembler.mark_line(*line);
453                if matches!(condition, Expr::Bool(true)) {
454                    self.compile_stmts(then_branch)?;
455                    return Ok(());
456                }
457                if matches!(condition, Expr::Bool(false)) {
458                    self.compile_stmts(else_branch)?;
459                    return Ok(());
460                }
461                let callable_snapshot = self.callable_bindings.clone();
462                let type_state_snapshot = self.type_state.clone();
463                let then_refined_type_state =
464                    typing::refine_state_for_condition(&type_state_snapshot, condition, true);
465                let else_refined_type_state =
466                    typing::refine_state_for_condition(&type_state_snapshot, condition, false);
467                let else_label = self.fresh_label("else");
468                let end_label = self.fresh_label("endif");
469                self.compile_scalar_expr(condition)?;
470                self.assembler.brfalse_label(&else_label);
471                self.type_state = then_refined_type_state;
472                self.compile_stmts(then_branch)?;
473                let then_type_state = self.type_state.clone();
474                self.assembler.br_label(&end_label);
475                self.assembler
476                    .label(&else_label)
477                    .map_err(CompileError::Assembler)?;
478                self.callable_bindings = callable_snapshot.clone();
479                self.type_state = else_refined_type_state;
480                self.compile_stmts(else_branch)?;
481                let else_type_state = self.type_state.clone();
482                self.assembler
483                    .label(&end_label)
484                    .map_err(CompileError::Assembler)?;
485                self.callable_bindings = callable_snapshot;
486                self.type_state
487                    .merge_from_branches(&then_type_state, &else_type_state);
488            }
489            Stmt::For {
490                init,
491                condition,
492                post,
493                body,
494                line,
495            } => {
496                let callable_snapshot = self.callable_bindings.clone();
497                self.assembler.mark_line(*line);
498                self.compile_stmt(init)?;
499                let loop_entry_type_state = self.type_state.clone();
500                let stabilized_loop_type_state =
501                    self.stabilize_loop_type_state(&loop_entry_type_state, |iterated| {
502                        let _ = typing::infer_expr_type_with_function_impls_and_imports(
503                            condition,
504                            iterated,
505                            &self.function_impls,
506                            &self.function_decls,
507                            &self.struct_schemas,
508                            &self.host_import_return_types,
509                            &self.host_import_signatures,
510                        );
511                        typing::apply_stmts_with_function_impls_and_imports(
512                            body,
513                            iterated,
514                            &self.function_impls,
515                            &self.function_decls,
516                            &self.struct_schemas,
517                            &self.host_import_return_types,
518                            &self.host_import_signatures,
519                        );
520                        typing::apply_stmts_with_function_impls_and_imports(
521                            std::slice::from_ref(post),
522                            iterated,
523                            &self.function_impls,
524                            &self.function_decls,
525                            &self.struct_schemas,
526                            &self.host_import_return_types,
527                            &self.host_import_signatures,
528                        );
529                    });
530                self.type_state = stabilized_loop_type_state;
531                let start_label = self.fresh_label("for_start");
532                let continue_label = self.fresh_label("for_continue");
533                let end_label = self.fresh_label("for_end");
534                self.assembler
535                    .label(&start_label)
536                    .map_err(CompileError::Assembler)?;
537                self.compile_scalar_expr(condition)?;
538                self.assembler.brfalse_label(&end_label);
539                self.loop_stack.push(LoopContext {
540                    continue_label: continue_label.clone(),
541                    break_label: end_label.clone(),
542                });
543                self.compile_stmts(body)?;
544                self.loop_stack.pop();
545                self.assembler
546                    .label(&continue_label)
547                    .map_err(CompileError::Assembler)?;
548                self.compile_stmt(post)?;
549                self.assembler.br_label(&start_label);
550                self.assembler
551                    .label(&end_label)
552                    .map_err(CompileError::Assembler)?;
553                self.callable_bindings = callable_snapshot;
554                self.type_state = self
555                    .simulate_stmt_type_state(std::slice::from_ref(stmt), &loop_entry_type_state);
556            }
557            Stmt::While {
558                condition,
559                body,
560                line,
561            } => {
562                let callable_snapshot = self.callable_bindings.clone();
563                let loop_entry_type_state = self.type_state.clone();
564                let stabilized_loop_type_state =
565                    self.stabilize_loop_type_state(&loop_entry_type_state, |iterated| {
566                        let _ = typing::infer_expr_type_with_function_impls_and_imports(
567                            condition,
568                            iterated,
569                            &self.function_impls,
570                            &self.function_decls,
571                            &self.struct_schemas,
572                            &self.host_import_return_types,
573                            &self.host_import_signatures,
574                        );
575                        typing::apply_stmts_with_function_impls_and_imports(
576                            body,
577                            iterated,
578                            &self.function_impls,
579                            &self.function_decls,
580                            &self.struct_schemas,
581                            &self.host_import_return_types,
582                            &self.host_import_signatures,
583                        );
584                    });
585                self.assembler.mark_line(*line);
586                self.type_state = stabilized_loop_type_state;
587                let start_label = self.fresh_label("while_start");
588                let end_label = self.fresh_label("while_end");
589                self.assembler
590                    .label(&start_label)
591                    .map_err(CompileError::Assembler)?;
592                self.compile_scalar_expr(condition)?;
593                self.assembler.brfalse_label(&end_label);
594                self.loop_stack.push(LoopContext {
595                    continue_label: start_label.clone(),
596                    break_label: end_label.clone(),
597                });
598                self.compile_stmts(body)?;
599                self.loop_stack.pop();
600                self.assembler.br_label(&start_label);
601                self.assembler
602                    .label(&end_label)
603                    .map_err(CompileError::Assembler)?;
604                self.callable_bindings = callable_snapshot;
605                self.type_state = self
606                    .simulate_stmt_type_state(std::slice::from_ref(stmt), &loop_entry_type_state);
607            }
608            Stmt::Break { line } => {
609                self.assembler.mark_line(*line);
610                let loop_ctx = self
611                    .loop_stack
612                    .last()
613                    .ok_or(CompileError::BreakOutsideLoop)?;
614                self.assembler.br_label(&loop_ctx.break_label);
615            }
616            Stmt::Continue { line } => {
617                self.assembler.mark_line(*line);
618                let loop_ctx = self
619                    .loop_stack
620                    .last()
621                    .ok_or(CompileError::ContinueOutsideLoop)?;
622                self.assembler.br_label(&loop_ctx.continue_label);
623            }
624            Stmt::Drop { index, line } => {
625                self.assembler.mark_line(*line);
626                self.assign_expr_to_slot(*index, None, &Expr::Null)?;
627            }
628        }
629        Ok(())
630    }
631
632    fn compile_expr(&mut self, expr: &Expr) -> Result<(), CompileError> {
633        match expr {
634            Expr::Null => {
635                self.assembler.push_const(Value::Null);
636            }
637            Expr::Int(value) => {
638                self.assembler.push_const(Value::Int(*value));
639            }
640            Expr::Float(value) => {
641                self.assembler.push_const(Value::Float(*value));
642            }
643            Expr::Bool(value) => {
644                self.assembler.push_const(Value::Bool(*value));
645            }
646            Expr::String(value) => {
647                self.assembler.push_const(Value::string(value.clone()));
648            }
649            Expr::Bytes(value) => {
650                self.assembler.push_const(Value::bytes(value.clone()));
651            }
652            Expr::OptionalGet {
653                container,
654                key,
655                container_slot,
656                key_slot,
657            } => {
658                self.compile_optional_get_expr(container, key, *container_slot, *key_slot)?;
659            }
660            Expr::OptionUnwrapOr {
661                value,
662                value_slot,
663                fallback,
664            } => {
665                self.compile_option_unwrap_or_expr(value, *value_slot, fallback)?;
666            }
667            Expr::FunctionRef(index, type_args) => {
668                let slot = self.ensure_function_value_slot(*index, type_args)?;
669                self.emit_copy_ldloc(slot)?;
670            }
671            Expr::Call(index, _, args) => {
672                self.compile_function_call(*index, args)?;
673            }
674            Expr::Closure(closure) => {
675                let _ = self.emit_closure_callable(closure)?;
676            }
677            Expr::ClosureCall(closure, args) => {
678                let prototype_id = self.emit_closure_callable(closure)?;
679                self.record_closure_param_hints(prototype_id, args);
680                self.compile_callvalue_args(args, ValueType::Unknown)?;
681            }
682            Expr::LocalCall(index, _, args) => {
683                if let Some(prototype_id) = self.callable_prototype_bindings.get(index).copied() {
684                    self.record_closure_param_hints(prototype_id, args);
685                }
686                let return_type = self.callable_local_return_type(*index);
687                self.emit_copy_ldloc(*index)?;
688                self.compile_callvalue_args(args, return_type)?;
689            }
690            Expr::Add(lhs, rhs) => {
691                let lhs_ty = self.value_type_of_expr(lhs);
692                let rhs_ty = self.value_type_of_expr(rhs);
693                if is_definitely_string_expr(lhs) {
694                    self.compile_scalar_expr(lhs)?;
695                    self.compile_string_concat_operand(rhs)?;
696                    self.record_operand_types(ValueType::String, ValueType::String);
697                    self.assembler.add();
698                    return Ok(());
699                }
700                if is_definitely_string_expr(rhs) {
701                    self.compile_string_concat_operand(lhs)?;
702                    self.compile_scalar_expr(rhs)?;
703                    self.record_operand_types(ValueType::String, ValueType::String);
704                    self.assembler.add();
705                    return Ok(());
706                }
707                self.compile_scalar_expr(lhs)?;
708                self.compile_scalar_expr(rhs)?;
709                self.record_operand_types(lhs_ty, rhs_ty);
710                self.assembler.add();
711            }
712            Expr::Sub(lhs, rhs) => {
713                let lhs_ty = self.value_type_of_expr(lhs);
714                let rhs_ty = self.value_type_of_expr(rhs);
715                self.compile_scalar_expr(lhs)?;
716                self.compile_scalar_expr(rhs)?;
717                self.record_operand_types(lhs_ty, rhs_ty);
718                self.assembler.sub();
719            }
720            Expr::Mul(lhs, rhs) => {
721                if let Expr::Int(value) = rhs.as_ref()
722                    && let Some(shift) = shift_amount_for_power_of_two(*value)
723                {
724                    self.compile_scalar_expr(lhs)?;
725                    self.assembler.push_const(Value::Int(shift as i64));
726                    self.assembler.shl();
727                } else if let Expr::Int(value) = lhs.as_ref()
728                    && let Some(shift) = shift_amount_for_power_of_two(*value)
729                {
730                    self.compile_scalar_expr(rhs)?;
731                    self.assembler.push_const(Value::Int(shift as i64));
732                    self.assembler.shl();
733                } else {
734                    let lhs_ty = self.value_type_of_expr(lhs);
735                    let rhs_ty = self.value_type_of_expr(rhs);
736                    self.compile_scalar_expr(lhs)?;
737                    self.compile_scalar_expr(rhs)?;
738                    self.record_operand_types(lhs_ty, rhs_ty);
739                    self.assembler.mul();
740                }
741            }
742            Expr::Div(lhs, rhs) => {
743                let lhs_ty = self.value_type_of_expr(lhs);
744                let rhs_ty = self.value_type_of_expr(rhs);
745                self.compile_scalar_expr(lhs)?;
746                self.compile_scalar_expr(rhs)?;
747                self.record_operand_types(lhs_ty, rhs_ty);
748                self.assembler.div();
749            }
750            Expr::Mod(lhs, rhs) => {
751                let lhs_ty = self.value_type_of_expr(lhs);
752                let rhs_ty = self.value_type_of_expr(rhs);
753                self.compile_scalar_expr(lhs)?;
754                self.compile_scalar_expr(rhs)?;
755                self.record_operand_types(lhs_ty, rhs_ty);
756                self.assembler.modulo();
757            }
758            Expr::Neg(inner) => {
759                let inner_ty = self.value_type_of_expr(inner);
760                self.compile_scalar_expr(inner)?;
761                self.record_unary_operand_type(inner_ty);
762                self.assembler.neg();
763            }
764            Expr::Not(inner) => {
765                self.compile_scalar_expr(inner)?;
766                self.assembler.not();
767            }
768            Expr::ToOwned(inner) => {
769                self.compile_scalar_expr(inner)?;
770            }
771            Expr::Borrow(inner) | Expr::BorrowMut(inner) => {
772                self.compile_scalar_expr(inner)?;
773            }
774            Expr::And(lhs, rhs) => {
775                self.compile_short_circuit_and(lhs, rhs)?;
776            }
777            Expr::Or(lhs, rhs) => {
778                self.compile_short_circuit_or(lhs, rhs)?;
779            }
780            Expr::Eq(lhs, rhs) => {
781                let lhs_ty = self.value_type_of_expr(lhs);
782                let rhs_ty = self.value_type_of_expr(rhs);
783                self.compile_scalar_expr(lhs)?;
784                self.compile_scalar_expr(rhs)?;
785                self.record_operand_types(lhs_ty, rhs_ty);
786                self.assembler.ceq();
787            }
788            Expr::Lt(lhs, rhs) => {
789                let lhs_ty = self.value_type_of_expr(lhs);
790                let rhs_ty = self.value_type_of_expr(rhs);
791                self.compile_scalar_expr(lhs)?;
792                self.compile_scalar_expr(rhs)?;
793                self.record_operand_types(lhs_ty, rhs_ty);
794                self.assembler.clt();
795            }
796            Expr::Gt(lhs, rhs) => {
797                let lhs_ty = self.value_type_of_expr(lhs);
798                let rhs_ty = self.value_type_of_expr(rhs);
799                self.compile_scalar_expr(lhs)?;
800                self.compile_scalar_expr(rhs)?;
801                self.record_operand_types(lhs_ty, rhs_ty);
802                self.assembler.cgt();
803            }
804            Expr::Var(index) => {
805                self.emit_copy_ldloc(*index)?;
806            }
807            Expr::MoveVar(index) => {
808                self.emit_move_ldloc(*index)?;
809                self.type_state.set(*index, typing::BoundType::Null);
810            }
811            Expr::MoveField { root, key } => {
812                self.emit_copy_ldloc(*root)?;
813                self.assembler.push_const(Value::string(key.clone()));
814                self.assembler.call(BuiltinFunction::Get.call_index(), 2);
815
816                self.emit_copy_ldloc(*root)?;
817                self.assembler.push_const(Value::string(key.clone()));
818                self.assembler.push_const(Value::Null);
819                self.assembler.call(BuiltinFunction::Set.call_index(), 3);
820                self.emit_stloc(*root)?;
821            }
822            Expr::MoveIndex { root, index } => {
823                self.emit_copy_ldloc(*root)?;
824                self.assembler.push_const(Value::Int(*index));
825                self.assembler.call(BuiltinFunction::Get.call_index(), 2);
826
827                self.emit_copy_ldloc(*root)?;
828                self.assembler.push_const(Value::Int(*index));
829                self.assembler.push_const(Value::Null);
830                self.assembler.call(BuiltinFunction::Set.call_index(), 3);
831                self.emit_stloc(*root)?;
832            }
833            Expr::IfElse {
834                condition,
835                then_expr,
836                else_expr,
837            } => {
838                let callable_snapshot = self.callable_bindings.clone();
839                let type_state_snapshot = self.type_state.clone();
840                self.compile_scalar_expr(condition)?;
841                let else_label = self.fresh_label("if_else");
842                let end_label = self.fresh_label("if_end");
843                self.assembler.brfalse_label(&else_label);
844                self.type_state =
845                    typing::refine_state_for_condition(&type_state_snapshot, condition, true);
846                self.compile_expr(then_expr)?;
847                let then_type_state = self.type_state.clone();
848                self.assembler.br_label(&end_label);
849                self.assembler
850                    .label(&else_label)
851                    .map_err(CompileError::Assembler)?;
852                self.callable_bindings = callable_snapshot.clone();
853                self.type_state =
854                    typing::refine_state_for_condition(&type_state_snapshot, condition, false);
855                self.compile_expr(else_expr)?;
856                let else_type_state = self.type_state.clone();
857                self.assembler
858                    .label(&end_label)
859                    .map_err(CompileError::Assembler)?;
860                self.callable_bindings = callable_snapshot;
861                self.type_state
862                    .merge_from_branches(&then_type_state, &else_type_state);
863            }
864            Expr::Match {
865                value_slot,
866                result_slot,
867                value,
868                arms,
869                default,
870            } => {
871                self.compile_scalar_expr(value)?;
872                self.emit_stloc(*value_slot)?;
873                let callable_snapshot = self.callable_bindings.clone();
874                let match_entry_type_state = self.type_state.clone();
875                let end_label = self.fresh_label("match_end");
876                let mut merged_type_state: Option<typing::LocalTypeState> = None;
877                for (pattern, arm_expr) in arms {
878                    let next_label = self.fresh_label("match_next");
879                    self.callable_bindings = callable_snapshot.clone();
880                    self.type_state = match_entry_type_state.clone();
881                    self.compile_match_pattern_condition(*value_slot, pattern)?;
882                    self.assembler.brfalse_label(&next_label);
883                    self.bind_match_pattern_slot(
884                        pattern,
885                        value,
886                        *value_slot,
887                        &match_entry_type_state,
888                    )?;
889                    self.compile_scalar_expr(arm_expr)?;
890                    self.emit_stloc(*result_slot)?;
891                    let arm_type_state = self.type_state.clone();
892                    merged_type_state = Some(match merged_type_state {
893                        Some(existing) => {
894                            let mut merged = typing::LocalTypeState::default();
895                            merged.merge_from_branches(&existing, &arm_type_state);
896                            merged
897                        }
898                        None => arm_type_state,
899                    });
900                    self.assembler.br_label(&end_label);
901                    self.assembler
902                        .label(&next_label)
903                        .map_err(CompileError::Assembler)?;
904                }
905                self.callable_bindings = callable_snapshot.clone();
906                self.type_state = match_entry_type_state.clone();
907                self.compile_scalar_expr(default)?;
908                self.emit_stloc(*result_slot)?;
909                let default_type_state = self.type_state.clone();
910                self.assembler
911                    .label(&end_label)
912                    .map_err(CompileError::Assembler)?;
913                self.callable_bindings = callable_snapshot;
914                self.type_state = if let Some(existing) = merged_type_state {
915                    let mut merged = typing::LocalTypeState::default();
916                    merged.merge_from_branches(&existing, &default_type_state);
917                    merged
918                } else {
919                    default_type_state
920                };
921                self.emit_copy_ldloc(*result_slot)?;
922            }
923            Expr::Block { stmts, expr } => {
924                self.compile_stmts(stmts)?;
925                self.compile_expr(expr)?;
926            }
927        }
928        Ok(())
929    }
930
931    fn compile_optional_get_expr(
932        &mut self,
933        container: &Expr,
934        key: &Expr,
935        container_slot: LocalSlot,
936        key_slot: LocalSlot,
937    ) -> Result<(), CompileError> {
938        self.compile_scalar_expr(container)?;
939        self.emit_stloc(container_slot)?;
940        self.compile_scalar_expr(key)?;
941        self.emit_stloc(key_slot)?;
942
943        let map_lookup = Expr::IfElse {
944            condition: Box::new(Expr::Call(
945                BuiltinFunction::Has.call_index(),
946                Vec::new(),
947                vec![Expr::Var(container_slot), Expr::Var(key_slot)],
948            )),
949            then_expr: Box::new(Expr::Call(
950                BuiltinFunction::Get.call_index(),
951                Vec::new(),
952                vec![Expr::Var(container_slot), Expr::Var(key_slot)],
953            )),
954            else_expr: Box::new(Expr::Null),
955        };
956        let index_lookup = Expr::IfElse {
957            condition: Box::new(Expr::Eq(
958                Box::new(Expr::Call(
959                    BuiltinFunction::TypeOf.call_index(),
960                    Vec::new(),
961                    vec![Expr::Var(key_slot)],
962                )),
963                Box::new(Expr::String("int".to_string())),
964            )),
965            then_expr: Box::new(Expr::IfElse {
966                condition: Box::new(Expr::Lt(
967                    Box::new(Expr::Var(key_slot)),
968                    Box::new(Expr::Int(0)),
969                )),
970                then_expr: Box::new(Expr::Null),
971                else_expr: Box::new(Expr::IfElse {
972                    condition: Box::new(Expr::Lt(
973                        Box::new(Expr::Var(key_slot)),
974                        Box::new(Expr::Call(
975                            BuiltinFunction::Len.call_index(),
976                            Vec::new(),
977                            vec![Expr::Var(container_slot)],
978                        )),
979                    )),
980                    then_expr: Box::new(Expr::Call(
981                        BuiltinFunction::Get.call_index(),
982                        Vec::new(),
983                        vec![Expr::Var(container_slot), Expr::Var(key_slot)],
984                    )),
985                    else_expr: Box::new(Expr::Null),
986                }),
987            }),
988            else_expr: Box::new(Expr::Null),
989        };
990        let lowered = Expr::IfElse {
991            condition: Box::new(Expr::Eq(
992                Box::new(Expr::Call(
993                    BuiltinFunction::TypeOf.call_index(),
994                    Vec::new(),
995                    vec![Expr::Var(container_slot)],
996                )),
997                Box::new(Expr::String("null".to_string())),
998            )),
999            then_expr: Box::new(Expr::Null),
1000            else_expr: Box::new(Expr::IfElse {
1001                condition: Box::new(Expr::Eq(
1002                    Box::new(Expr::Call(
1003                        BuiltinFunction::TypeOf.call_index(),
1004                        Vec::new(),
1005                        vec![Expr::Var(container_slot)],
1006                    )),
1007                    Box::new(Expr::String("map".to_string())),
1008                )),
1009                then_expr: Box::new(map_lookup),
1010                else_expr: Box::new(Expr::IfElse {
1011                    condition: Box::new(Expr::Eq(
1012                        Box::new(Expr::Call(
1013                            BuiltinFunction::TypeOf.call_index(),
1014                            Vec::new(),
1015                            vec![Expr::Var(container_slot)],
1016                        )),
1017                        Box::new(Expr::String("array".to_string())),
1018                    )),
1019                    then_expr: Box::new(index_lookup.clone()),
1020                    else_expr: Box::new(Expr::IfElse {
1021                        condition: Box::new(Expr::Eq(
1022                            Box::new(Expr::Call(
1023                                BuiltinFunction::TypeOf.call_index(),
1024                                Vec::new(),
1025                                vec![Expr::Var(container_slot)],
1026                            )),
1027                            Box::new(Expr::String("string".to_string())),
1028                        )),
1029                        then_expr: Box::new(index_lookup),
1030                        else_expr: Box::new(Expr::Null),
1031                    }),
1032                }),
1033            }),
1034        };
1035
1036        self.compile_expr(&lowered)
1037    }
1038
1039    fn compile_option_unwrap_or_expr(
1040        &mut self,
1041        value: &Expr,
1042        value_slot: LocalSlot,
1043        fallback: &Expr,
1044    ) -> Result<(), CompileError> {
1045        self.compile_scalar_expr(value)?;
1046        self.emit_stloc(value_slot)?;
1047        let lowered = Expr::IfElse {
1048            condition: Box::new(Expr::Eq(
1049                Box::new(Expr::Call(
1050                    BuiltinFunction::TypeOf.call_index(),
1051                    Vec::new(),
1052                    vec![Expr::Var(value_slot)],
1053                )),
1054                Box::new(Expr::String("null".to_string())),
1055            )),
1056            then_expr: Box::new(fallback.clone()),
1057            else_expr: Box::new(Expr::Var(value_slot)),
1058        };
1059        self.compile_expr(&lowered)
1060    }
1061
1062    fn emit_named_callable_binding(&mut self, index: u16) -> Result<(), CompileError> {
1063        let Some(function_impl) = self.function_impls.get(&index).cloned() else {
1064            return Ok(());
1065        };
1066        if function_impl.capture_copies.is_empty() {
1067            return Ok(());
1068        }
1069        let prototype_id = *self
1070            .function_prototype_ids
1071            .get(&index)
1072            .ok_or(CompileError::CallableUsedAsValue)?;
1073        let slot = *self
1074            .function_slots
1075            .get(&index)
1076            .ok_or(CompileError::CallableUsedAsValue)?;
1077        self.emit_bind_callable(
1078            prototype_id,
1079            function_impl
1080                .capture_copies
1081                .iter()
1082                .map(|(source, _)| *source),
1083        )?;
1084        self.emit_stloc(slot)?;
1085        Ok(())
1086    }
1087
1088    fn callable_binding_from_expr(
1089        &mut self,
1090        expr: &Expr,
1091    ) -> Result<Option<CallableBinding>, CompileError> {
1092        match expr {
1093            Expr::Closure(closure) => Ok(Some(CallableBinding::Closure(closure.clone()))),
1094            Expr::FunctionRef(index, _) => Ok(Some(CallableBinding::Function(*index))),
1095            Expr::Var(index) => Ok(self.callable_bindings.get(index).cloned()),
1096            _ => Ok(None),
1097        }
1098    }
1099
1100    fn assign_expr_to_slot(
1101        &mut self,
1102        slot: LocalSlot,
1103        declared_schema: Option<&TypeSchema>,
1104        expr: &Expr,
1105    ) -> Result<(), CompileError> {
1106        if let Some(callable) = self.callable_binding_from_expr(expr)? {
1107            self.callable_bindings.insert(slot, callable.clone());
1108            match callable {
1109                CallableBinding::Closure(closure) => self.type_state.bind_closure(slot, &closure),
1110                CallableBinding::Function(index) => self.type_state.bind_function(slot, index),
1111            }
1112            if let Expr::Closure(closure) = expr {
1113                let prototype_id = self.emit_closure_callable_with_self(closure, Some(slot))?;
1114                self.callable_prototype_bindings.insert(slot, prototype_id);
1115            } else {
1116                if let Expr::Var(source) | Expr::MoveVar(source) = expr
1117                    && let Some(prototype_id) =
1118                        self.callable_prototype_bindings.get(source).copied()
1119                {
1120                    self.callable_prototype_bindings.insert(slot, prototype_id);
1121                }
1122                self.compile_expr(expr)?;
1123            }
1124            self.emit_stloc(slot)?;
1125            return Ok(());
1126        }
1127        let declared_binding = declared_schema.map(TypeSchema::split_optional).or_else(|| {
1128            self.type_state
1129                .has_declared_schema(slot)
1130                .then(|| {
1131                    (
1132                        self.type_state.schema(slot).cloned(),
1133                        self.type_state.is_optional(slot),
1134                    )
1135                })
1136                .and_then(|(schema, optional)| schema.map(|schema| (schema, optional)))
1137        });
1138        let slot_declared_schema = declared_binding.as_ref().map(|(schema, _)| schema.clone());
1139        let declared_optional = declared_binding
1140            .as_ref()
1141            .map(|(_, optional)| *optional)
1142            .unwrap_or(false);
1143        let optional = typing::expr_is_optional_with_function_impls_and_imports(
1144            expr,
1145            &self.type_state,
1146            &self.function_impls,
1147            &self.function_decls,
1148            &self.struct_schemas,
1149            &self.host_import_return_types,
1150            &self.host_import_signatures,
1151        ) || declared_optional;
1152        let ty = if optional {
1153            typing::infer_optional_expr_inner_type_with_function_impls_and_imports(
1154                expr,
1155                &self.type_state,
1156                &self.function_impls,
1157                &self.function_decls,
1158                &self.struct_schemas,
1159                &self.host_import_return_types,
1160                &self.host_import_signatures,
1161            )
1162        } else {
1163            self.infer_bound_type(expr)
1164        };
1165        self.callable_bindings.remove(&slot);
1166        if !self.try_compile_same_local_collection_rebind(slot, expr)? {
1167            self.compile_scalar_expr(expr)?;
1168        }
1169        self.emit_stloc(slot)?;
1170        let schema = slot_declared_schema.clone().or_else(|| {
1171            if optional {
1172                typing::infer_optional_expr_inner_schema_with_function_impls_and_imports(
1173                    expr,
1174                    &self.type_state,
1175                    &self.function_impls,
1176                    &self.function_decls,
1177                    &self.struct_schemas,
1178                    &self.host_import_return_types,
1179                    &self.host_import_signatures,
1180                )
1181            } else {
1182                typing::infer_expr_schema_with_function_impls_and_imports(
1183                    expr,
1184                    &self.type_state,
1185                    &self.function_impls,
1186                    &self.function_decls,
1187                    &self.struct_schemas,
1188                    &self.host_import_return_types,
1189                    &self.host_import_signatures,
1190                )
1191            }
1192        });
1193        let from_declared_schema =
1194            slot_declared_schema.is_some() || self.type_state.has_declared_schema(slot);
1195        let ty = slot_declared_schema
1196            .as_ref()
1197            .map(typing::bound_type_from_schema)
1198            .unwrap_or(ty);
1199        self.type_state.set_with_optional_schema_origin(
1200            slot,
1201            ty,
1202            schema,
1203            from_declared_schema,
1204            optional,
1205        );
1206        Ok(())
1207    }
1208
1209    fn try_compile_same_local_collection_rebind(
1210        &mut self,
1211        target: LocalSlot,
1212        expr: &Expr,
1213    ) -> Result<bool, CompileError> {
1214        if !self.enable_local_move_semantics {
1215            return Ok(false);
1216        }
1217        let Expr::Call(index, _, args) = expr else {
1218            return Ok(false);
1219        };
1220        let Some(builtin) = BuiltinFunction::from_call_index(*index) else {
1221            return Ok(false);
1222        };
1223        let expected_arity = match builtin {
1224            BuiltinFunction::Set => 3,
1225            BuiltinFunction::ArrayPush => 2,
1226            _ => return Ok(false),
1227        };
1228        if args.len() != expected_arity
1229            || !matches!(args.first(), Some(Expr::Var(source)) if *source == target)
1230        {
1231            return Ok(false);
1232        }
1233
1234        for arg in args {
1235            self.compile_scalar_expr(arg)?;
1236        }
1237        self.assembler.push_const(Value::Null);
1238        self.emit_stloc(target)?;
1239        self.emit_direct_call(*index, args)?;
1240        Ok(true)
1241    }
1242
1243    fn compile_scalar_expr(&mut self, expr: &Expr) -> Result<(), CompileError> {
1244        self.compile_expr(expr)
1245    }
1246
1247    fn compile_short_circuit_and(&mut self, lhs: &Expr, rhs: &Expr) -> Result<(), CompileError> {
1248        let false_label = self.fresh_label("and_false");
1249        let end_label = self.fresh_label("and_end");
1250        self.compile_scalar_expr(lhs)?;
1251        self.assembler.brfalse_label(&false_label);
1252        self.compile_scalar_expr(rhs)?;
1253        self.assembler.br_label(&end_label);
1254        self.assembler
1255            .label(&false_label)
1256            .map_err(CompileError::Assembler)?;
1257        self.assembler.push_const(Value::Bool(false));
1258        self.assembler
1259            .label(&end_label)
1260            .map_err(CompileError::Assembler)?;
1261        Ok(())
1262    }
1263
1264    fn compile_short_circuit_or(&mut self, lhs: &Expr, rhs: &Expr) -> Result<(), CompileError> {
1265        let rhs_label = self.fresh_label("or_rhs");
1266        let end_label = self.fresh_label("or_end");
1267        self.compile_scalar_expr(lhs)?;
1268        self.assembler.brfalse_label(&rhs_label);
1269        self.assembler.push_const(Value::Bool(true));
1270        self.assembler.br_label(&end_label);
1271        self.assembler
1272            .label(&rhs_label)
1273            .map_err(CompileError::Assembler)?;
1274        self.compile_scalar_expr(rhs)?;
1275        self.assembler
1276            .label(&end_label)
1277            .map_err(CompileError::Assembler)?;
1278        Ok(())
1279    }
1280
1281    fn ensure_function_value_slot(
1282        &mut self,
1283        index: u16,
1284        type_args: &[TypeSchema],
1285    ) -> Result<LocalSlot, CompileError> {
1286        if type_args.is_empty()
1287            && self
1288                .function_decls
1289                .get(&index)
1290                .is_some_and(|decl| !decl.type_params.is_empty())
1291        {
1292            let name = self
1293                .function_decls
1294                .get(&index)
1295                .map(|decl| decl.name.as_str())
1296                .unwrap_or("<unknown>");
1297            return Err(CompileError::CallableArgumentTypeMismatch {
1298                line: None,
1299                source_name: None,
1300                detail: format!(
1301                    "generic function value '{name}' requires explicit type arguments or an unambiguous callable context"
1302                ),
1303            });
1304        }
1305        if !type_args.is_empty()
1306            && let Some((_, _, slot)) = self
1307                .specialized_function_slots
1308                .iter()
1309                .find(|(candidate, args, _)| *candidate == index && args == type_args)
1310        {
1311            return Ok(*slot);
1312        }
1313        if type_args.is_empty()
1314            && let Some(slot) = self.function_slots.get(&index)
1315        {
1316            return Ok(*slot);
1317        }
1318        if !type_args.is_empty() && self.function_slots.contains_key(&index) {
1319            return self.ensure_specialized_function_slot(index, type_args);
1320        }
1321
1322        let (target_index, arity) = if let Some(builtin) = BuiltinFunction::from_call_index(index) {
1323            (index, builtin.arity())
1324        } else if let Some(decl) = self.function_decls.get(&index) {
1325            (
1326                self.call_index_remap.get(&index).copied().unwrap_or(index),
1327                decl.args.len() as u8,
1328            )
1329        } else {
1330            return Err(CompileError::CallableUsedAsValue);
1331        };
1332        let slot = self.allocate_hidden_callable_slot()?;
1333        let prototype_id = self.callable_prototypes.len() as u32;
1334        self.callable_prototypes.push(CallablePrototype {
1335            kind: CallableKind::HostFunction,
1336            target: CallableTarget::HostImport(target_index),
1337            arity,
1338            frame_local_count: self.frame_local_count,
1339            parameter_slots: Vec::new(),
1340            capture_source_slots: Vec::new(),
1341            capture_slots: Vec::new(),
1342            capture_modes: Vec::new(),
1343            self_slot: None,
1344            schema: self.instantiated_callable_schema(index, type_args),
1345        });
1346        self.root_callable_bindings.push(RootCallableBinding {
1347            local_slot: slot,
1348            prototype_id,
1349        });
1350        self.function_slots.insert(index, slot);
1351        self.function_prototype_ids.insert(index, prototype_id);
1352        if type_args.is_empty() {
1353            Ok(slot)
1354        } else {
1355            self.ensure_specialized_function_slot(index, type_args)
1356        }
1357    }
1358
1359    fn ensure_specialized_function_slot(
1360        &mut self,
1361        index: u16,
1362        type_args: &[TypeSchema],
1363    ) -> Result<LocalSlot, CompileError> {
1364        let base_prototype_id = *self
1365            .function_prototype_ids
1366            .get(&index)
1367            .ok_or(CompileError::CallableUsedAsValue)?;
1368        if self
1369            .function_impls
1370            .get(&index)
1371            .is_some_and(|function| !function.capture_copies.is_empty())
1372        {
1373            return self
1374                .function_slots
1375                .get(&index)
1376                .copied()
1377                .ok_or(CompileError::CallableUsedAsValue);
1378        }
1379        let slot = self.allocate_hidden_callable_slot()?;
1380        let mut prototype = self.callable_prototypes[base_prototype_id as usize].clone();
1381        prototype.schema = self.instantiated_callable_schema(index, type_args);
1382        prototype.frame_local_count = self.frame_local_count;
1383        let prototype_id = self.callable_prototypes.len() as u32;
1384        self.callable_prototypes.push(prototype);
1385        self.root_callable_bindings.push(RootCallableBinding {
1386            local_slot: slot,
1387            prototype_id,
1388        });
1389        self.specialized_function_slots
1390            .push((index, type_args.to_vec(), slot));
1391        Ok(slot)
1392    }
1393
1394    fn allocate_hidden_callable_slot(&mut self) -> Result<LocalSlot, CompileError> {
1395        let slot = LocalSlot::try_from(self.frame_local_count)
1396            .map_err(|_| CompileError::LocalSlotOverflow(LocalSlot::MAX))?;
1397        let _ = local_slot_operand(slot)?;
1398        self.frame_local_count = self.frame_local_count.saturating_add(1);
1399        Ok(slot)
1400    }
1401
1402    fn instantiated_callable_schema(
1403        &self,
1404        index: u16,
1405        type_args: &[TypeSchema],
1406    ) -> Option<TypeSchema> {
1407        let decl = self.function_decls.get(&index)?;
1408        if decl.type_params.len() != type_args.len() {
1409            return None;
1410        }
1411        let bindings = decl
1412            .type_params
1413            .iter()
1414            .cloned()
1415            .zip(type_args.iter().cloned())
1416            .collect::<HashMap<_, _>>();
1417        Some(TypeSchema::Callable {
1418            params: decl
1419                .arg_schemas
1420                .iter()
1421                .map(|schema| {
1422                    schema
1423                        .as_ref()
1424                        .map(|schema| substitute_type_schema(schema, &bindings))
1425                        .unwrap_or(TypeSchema::Unknown)
1426                })
1427                .collect(),
1428            result: Box::new(
1429                decl.return_schema
1430                    .as_ref()
1431                    .map(|schema| substitute_type_schema(schema, &bindings))
1432                    .unwrap_or(TypeSchema::Unknown),
1433            ),
1434        })
1435    }
1436
1437    fn record_closure_param_hints(&mut self, prototype_id: u32, args: &[Expr]) {
1438        let hints = args
1439            .iter()
1440            .map(|arg| {
1441                let schema = typing::infer_expr_schema_with_function_impls_and_imports(
1442                    arg,
1443                    &self.type_state,
1444                    &self.function_impls,
1445                    &self.function_decls,
1446                    &self.struct_schemas,
1447                    &self.host_import_return_types,
1448                    &self.host_import_signatures,
1449                );
1450                let ty = schema
1451                    .as_ref()
1452                    .map(typing::bound_type_from_schema)
1453                    .unwrap_or_else(|| self.infer_bound_type(arg));
1454                (ty, schema)
1455            })
1456            .collect::<Vec<_>>();
1457
1458        self.closure_param_hints
1459            .entry(prototype_id)
1460            .and_modify(|existing| {
1461                for (index, hint) in hints.iter().enumerate() {
1462                    if let Some(existing) = existing.get_mut(index)
1463                        && existing.0 == typing::BoundType::Unknown
1464                    {
1465                        *existing = hint.clone();
1466                    }
1467                }
1468            })
1469            .or_insert(hints);
1470    }
1471
1472    fn compile_function_call(&mut self, index: u16, args: &[Expr]) -> Result<(), CompileError> {
1473        if self.function_impls.contains_key(&index) {
1474            let slot = *self
1475                .function_slots
1476                .get(&index)
1477                .ok_or(CompileError::CallableUsedAsValue)?;
1478            let return_type = self
1479                .function_decls
1480                .get(&index)
1481                .map(|decl| decl.return_type)
1482                .unwrap_or(ValueType::Unknown);
1483            self.emit_copy_ldloc(slot)?;
1484            return self.compile_callvalue_args(args, return_type);
1485        }
1486        self.compile_direct_call(index, args)
1487    }
1488
1489    fn compile_callvalue_args(
1490        &mut self,
1491        args: &[Expr],
1492        return_type: ValueType,
1493    ) -> Result<(), CompileError> {
1494        for arg in args {
1495            self.compile_scalar_expr(arg)?;
1496        }
1497        let argc = u8::try_from(args.len()).map_err(|_| CompileError::CallArityOverflow)?;
1498        if return_type != ValueType::Unknown {
1499            self.record_operand_types(ValueType::Callable, return_type);
1500        }
1501        self.assembler.call_value(argc);
1502        Ok(())
1503    }
1504
1505    fn emit_closure_callable(&mut self, closure: &ClosureExpr) -> Result<u32, CompileError> {
1506        self.emit_closure_callable_with_self(closure, None)
1507    }
1508
1509    fn emit_closure_callable_with_self(
1510        &mut self,
1511        closure: &ClosureExpr,
1512        binding_slot: Option<LocalSlot>,
1513    ) -> Result<u32, CompileError> {
1514        let prototype_id = self.callable_prototypes.len() as u32;
1515        self.callable_prototypes.push(CallablePrototype {
1516            kind: CallableKind::Closure,
1517            target: CallableTarget::ScriptFunction(u32::MAX),
1518            arity: u8::try_from(closure.param_slots.len())
1519                .map_err(|_| CompileError::CallArityOverflow)?,
1520            frame_local_count: self.frame_local_count,
1521            parameter_slots: closure.param_slots.clone(),
1522            capture_source_slots: closure
1523                .capture_copies
1524                .iter()
1525                .map(|(source, _)| *source)
1526                .collect(),
1527            capture_slots: closure
1528                .capture_copies
1529                .iter()
1530                .map(|(_, target)| *target)
1531                .collect(),
1532            capture_modes: closure
1533                .capture_copies
1534                .iter()
1535                .map(|(_, target)| super::lifetime::closure_capture_binding_mode(closure, *target))
1536                .collect(),
1537            self_slot: binding_slot.and_then(|binding_slot| {
1538                closure
1539                    .capture_copies
1540                    .iter()
1541                    .find_map(|(source, target)| (*source == binding_slot).then_some(*target))
1542            }),
1543            schema: binding_slot.and_then(|slot| {
1544                self.type_map
1545                    .local_schemas
1546                    .get(slot as usize)
1547                    .cloned()
1548                    .flatten()
1549            }),
1550        });
1551        self.pending_closures.push((prototype_id, closure.clone()));
1552        self.emit_bind_callable(
1553            prototype_id,
1554            closure.capture_copies.iter().map(|(source, _)| *source),
1555        )?;
1556        Ok(prototype_id)
1557    }
1558
1559    fn emit_bind_callable(
1560        &mut self,
1561        prototype_id: u32,
1562        capture_slots: impl IntoIterator<Item = LocalSlot>,
1563    ) -> Result<(), CompileError> {
1564        self.assembler
1565            .push_const(Value::Int(i64::from(prototype_id)));
1566        self.assembler
1567            .call(BuiltinFunction::ArrayNew.call_index(), 0);
1568        for source_slot in capture_slots {
1569            self.emit_copy_ldloc(source_slot)?;
1570            self.assembler
1571                .call(BuiltinFunction::ArrayPush.call_index(), 2);
1572        }
1573        self.assembler
1574            .call(BuiltinFunction::BindCallable.call_index(), 2);
1575        Ok(())
1576    }
1577
1578    fn compile_direct_call(&mut self, index: u16, args: &[Expr]) -> Result<(), CompileError> {
1579        for arg in args {
1580            self.compile_scalar_expr(arg)?;
1581        }
1582        self.emit_direct_call(index, args)
1583    }
1584
1585    fn emit_direct_call(&mut self, index: u16, args: &[Expr]) -> Result<(), CompileError> {
1586        let argc = u8::try_from(args.len()).map_err(|_| CompileError::CallArityOverflow)?;
1587        if let Some(builtin) = BuiltinFunction::from_call_index(index) {
1588            debug_assert!(builtin.accepts_arity(argc));
1589            self.record_builtin_call_operand_types(args);
1590            self.assembler.call(index, argc);
1591            return Ok(());
1592        }
1593        let remapped_index = self.call_index_remap.get(&index).copied().unwrap_or(index);
1594        self.assembler.call(remapped_index, argc);
1595        Ok(())
1596    }
1597
1598    fn compile_match_pattern_condition(
1599        &mut self,
1600        value_slot: LocalSlot,
1601        pattern: &MatchPattern,
1602    ) -> Result<(), CompileError> {
1603        match pattern {
1604            MatchPattern::Int(v) => {
1605                self.emit_copy_ldloc(value_slot)?;
1606                self.assembler.push_const(Value::Int(*v));
1607                self.assembler.ceq();
1608            }
1609            MatchPattern::String(v) => {
1610                self.emit_copy_ldloc(value_slot)?;
1611                self.assembler.push_const(Value::string(v.clone()));
1612                self.assembler.ceq();
1613            }
1614            MatchPattern::Bytes(v) => {
1615                self.emit_copy_ldloc(value_slot)?;
1616                self.assembler.push_const(Value::bytes(v.clone()));
1617                self.assembler.ceq();
1618            }
1619            MatchPattern::Null => {
1620                self.emit_copy_ldloc(value_slot)?;
1621                self.assembler.push_const(Value::Null);
1622                self.assembler.ceq();
1623            }
1624            MatchPattern::None => {
1625                self.emit_copy_ldloc(value_slot)?;
1626                self.assembler.push_const(Value::Null);
1627                self.assembler.ceq();
1628            }
1629            MatchPattern::SomeBinding(_) => {
1630                self.emit_copy_ldloc(value_slot)?;
1631                self.assembler.push_const(Value::Null);
1632                self.assembler.ceq();
1633                self.assembler.not();
1634            }
1635            MatchPattern::Type(type_pattern) => {
1636                self.compile_match_type_pattern_condition(value_slot, type_pattern)?;
1637            }
1638        }
1639        Ok(())
1640    }
1641
1642    fn compile_match_type_pattern_condition(
1643        &mut self,
1644        value_slot: LocalSlot,
1645        type_pattern: &MatchTypePattern,
1646    ) -> Result<(), CompileError> {
1647        match type_pattern {
1648            MatchTypePattern::Int => self.compile_type_name_equals(value_slot, "int")?,
1649            MatchTypePattern::Float => self.compile_type_name_equals(value_slot, "float")?,
1650            MatchTypePattern::Bool => self.compile_type_name_equals(value_slot, "bool")?,
1651            MatchTypePattern::String => self.compile_type_name_equals(value_slot, "string")?,
1652            MatchTypePattern::Bytes => self.compile_type_name_equals(value_slot, "bytes")?,
1653            MatchTypePattern::Array => self.compile_type_name_equals(value_slot, "array")?,
1654            MatchTypePattern::Map => self.compile_type_name_equals(value_slot, "map")?,
1655            MatchTypePattern::Number => {
1656                let number_fallback_label = self.fresh_label("match_type_number_fallback");
1657                let number_end_label = self.fresh_label("match_type_number_end");
1658
1659                self.compile_type_name_equals(value_slot, "int")?;
1660                self.assembler.brfalse_label(&number_fallback_label);
1661                self.assembler.push_const(Value::Bool(true));
1662                self.assembler.br_label(&number_end_label);
1663                self.assembler
1664                    .label(&number_fallback_label)
1665                    .map_err(CompileError::Assembler)?;
1666                self.compile_type_name_equals(value_slot, "float")?;
1667                self.assembler
1668                    .label(&number_end_label)
1669                    .map_err(CompileError::Assembler)?;
1670            }
1671        }
1672        Ok(())
1673    }
1674
1675    fn compile_type_name_equals(
1676        &mut self,
1677        value_slot: LocalSlot,
1678        expected: &str,
1679    ) -> Result<(), CompileError> {
1680        self.emit_copy_ldloc(value_slot)?;
1681        self.assembler.call(BuiltinFunction::TypeOf.call_index(), 1);
1682        self.assembler
1683            .push_const(Value::string(expected.to_string()));
1684        self.assembler.ceq();
1685        Ok(())
1686    }
1687
1688    fn bind_match_pattern_slot(
1689        &mut self,
1690        pattern: &MatchPattern,
1691        value: &Expr,
1692        value_slot: LocalSlot,
1693        match_entry_type_state: &typing::LocalTypeState,
1694    ) -> Result<(), CompileError> {
1695        let Some(binding_slot) = pattern.binding_slot() else {
1696            return Ok(());
1697        };
1698        let ty = typing::infer_optional_expr_inner_type_with_function_impls_and_imports(
1699            value,
1700            match_entry_type_state,
1701            &self.function_impls,
1702            &self.function_decls,
1703            &self.struct_schemas,
1704            &self.host_import_return_types,
1705            &self.host_import_signatures,
1706        );
1707        let schema = typing::infer_optional_expr_inner_schema_with_function_impls_and_imports(
1708            value,
1709            match_entry_type_state,
1710            &self.function_impls,
1711            &self.function_decls,
1712            &self.struct_schemas,
1713            &self.host_import_return_types,
1714            &self.host_import_signatures,
1715        );
1716        self.emit_copy_ldloc(value_slot)?;
1717        self.emit_stloc(binding_slot)?;
1718        self.type_state
1719            .set_with_optional_schema_origin(binding_slot, ty, schema, false, false);
1720        Ok(())
1721    }
1722
1723    fn infer_bound_type(&self, expr: &Expr) -> typing::BoundType {
1724        typing::infer_expr_type_with_function_impls_and_imports(
1725            expr,
1726            &self.type_state,
1727            &self.function_impls,
1728            &self.function_decls,
1729            &self.struct_schemas,
1730            &self.host_import_return_types,
1731            &self.host_import_signatures,
1732        )
1733    }
1734
1735    fn simulate_stmt_type_state(
1736        &self,
1737        stmts: &[Stmt],
1738        initial_state: &typing::LocalTypeState,
1739    ) -> typing::LocalTypeState {
1740        let mut state = initial_state.clone();
1741        typing::apply_stmts_with_function_impls_and_imports(
1742            stmts,
1743            &mut state,
1744            &self.function_impls,
1745            &self.function_decls,
1746            &self.struct_schemas,
1747            &self.host_import_return_types,
1748            &self.host_import_signatures,
1749        );
1750        state
1751    }
1752
1753    fn stabilize_loop_type_state<F>(
1754        &self,
1755        initial_state: &typing::LocalTypeState,
1756        mut run_iteration: F,
1757    ) -> typing::LocalTypeState
1758    where
1759        F: FnMut(&mut typing::LocalTypeState),
1760    {
1761        let zero_iteration = initial_state.clone();
1762        let mut first_iteration = initial_state.clone();
1763        run_iteration(&mut first_iteration);
1764        let mut second_iteration = first_iteration.clone();
1765        run_iteration(&mut second_iteration);
1766
1767        let mut stable_iteration = typing::LocalTypeState::default();
1768        stable_iteration.merge_from_branches(&first_iteration, &second_iteration);
1769
1770        let mut stabilized = zero_iteration.clone();
1771        stabilized.merge_from_branches(&zero_iteration, &stable_iteration);
1772        stabilized
1773    }
1774
1775    fn value_type_of_expr(&self, expr: &Expr) -> ValueType {
1776        ValueType::from(self.infer_bound_type(expr))
1777    }
1778
1779    fn callable_local_return_type(&self, slot: LocalSlot) -> ValueType {
1780        match self
1781            .type_map
1782            .local_schemas
1783            .get(slot as usize)
1784            .and_then(|schema| schema.as_ref())
1785        {
1786            Some(TypeSchema::Callable { result, .. }) => result.coarse_value_type(),
1787            _ => ValueType::Unknown,
1788        }
1789    }
1790
1791    fn record_operand_types(&mut self, lhs: ValueType, rhs: ValueType) {
1792        if lhs == ValueType::Unknown || rhs == ValueType::Unknown {
1793            return;
1794        }
1795        self.type_map
1796            .operand_types
1797            .insert(self.assembler.position() as usize, (lhs, rhs));
1798    }
1799
1800    fn record_unary_operand_type(&mut self, operand: ValueType) {
1801        if operand == ValueType::Unknown {
1802            return;
1803        }
1804        self.type_map.operand_types.insert(
1805            self.assembler.position() as usize,
1806            (operand, ValueType::Unknown),
1807        );
1808    }
1809
1810    fn record_builtin_call_operand_types(&mut self, args: &[Expr]) {
1811        if args.is_empty() {
1812            return;
1813        }
1814        let lhs = self.value_type_of_expr(&args[0]);
1815        let rhs = args
1816            .get(1)
1817            .map(|expr| self.value_type_of_expr(expr))
1818            .unwrap_or(ValueType::Unknown);
1819        if lhs == ValueType::Unknown && rhs == ValueType::Unknown {
1820            return;
1821        }
1822        self.type_map
1823            .operand_types
1824            .insert(self.assembler.position() as usize, (lhs, rhs));
1825    }
1826
1827    fn fresh_label(&mut self, prefix: &str) -> String {
1828        let label = format!("{prefix}_{}", self.next_label_id);
1829        self.next_label_id += 1;
1830        label
1831    }
1832
1833    fn emit_move_ldloc(&mut self, slot: LocalSlot) -> Result<(), CompileError> {
1834        let operand = local_slot_operand(slot)?;
1835        self.assembler.ldloc(operand);
1836        self.assembler.push_const(Value::Int(i64::from(operand)));
1837        self.assembler
1838            .call(BuiltinFunction::DetachLocal.call_index(), 1);
1839        Ok(())
1840    }
1841
1842    fn emit_copy_ldloc(&mut self, slot: LocalSlot) -> Result<(), CompileError> {
1843        self.assembler.ldloc(local_slot_operand(slot)?);
1844        Ok(())
1845    }
1846
1847    fn emit_stloc(&mut self, slot: LocalSlot) -> Result<(), CompileError> {
1848        self.assembler.stloc(local_slot_operand(slot)?);
1849        Ok(())
1850    }
1851
1852    fn compile_string_concat_operand(&mut self, expr: &Expr) -> Result<(), CompileError> {
1853        if let Some(value) = eval_const_int_expr(expr) {
1854            self.assembler.push_const(Value::string(value.to_string()));
1855            return Ok(());
1856        }
1857
1858        self.compile_scalar_expr(expr)?;
1859        self.lower_number_to_string_for_concat_top();
1860        Ok(())
1861    }
1862
1863    fn lower_number_to_string_for_concat_top(&mut self) {
1864        let not_int_label = self.fresh_label("concat_not_int");
1865        let not_float_label = self.fresh_label("concat_not_float");
1866        let done_label = self.fresh_label("concat_value_done");
1867
1868        self.assembler.dup();
1869        self.assembler.call(BuiltinFunction::TypeOf.call_index(), 1);
1870        self.assembler.push_const(Value::string("int"));
1871        self.assembler.ceq();
1872        self.assembler.brfalse_label(&not_int_label);
1873        self.assembler
1874            .call(BuiltinFunction::ToString.call_index(), 1);
1875        self.assembler.br_label(&done_label);
1876
1877        self.assembler
1878            .label(&not_int_label)
1879            .expect("compiler-generated label should be valid");
1880        self.assembler.dup();
1881        self.assembler.call(BuiltinFunction::TypeOf.call_index(), 1);
1882        self.assembler.push_const(Value::string("float"));
1883        self.assembler.ceq();
1884        self.assembler.brfalse_label(&not_float_label);
1885        self.assembler
1886            .call(BuiltinFunction::ToString.call_index(), 1);
1887        self.assembler.br_label(&done_label);
1888
1889        self.assembler
1890            .label(&not_float_label)
1891            .expect("compiler-generated label should be valid");
1892        self.assembler
1893            .label(&done_label)
1894            .expect("compiler-generated label should be valid");
1895    }
1896}
1897
1898fn substitute_type_schema(
1899    schema: &TypeSchema,
1900    bindings: &HashMap<String, TypeSchema>,
1901) -> TypeSchema {
1902    match schema {
1903        TypeSchema::GenericParam(name) => bindings
1904            .get(name)
1905            .cloned()
1906            .unwrap_or_else(|| schema.clone()),
1907        TypeSchema::Optional(inner) => {
1908            TypeSchema::Optional(Box::new(substitute_type_schema(inner, bindings)))
1909        }
1910        TypeSchema::Named(name, args) => TypeSchema::Named(
1911            name.clone(),
1912            args.iter()
1913                .map(|schema| substitute_type_schema(schema, bindings))
1914                .collect(),
1915        ),
1916        TypeSchema::Array(inner) => {
1917            TypeSchema::Array(Box::new(substitute_type_schema(inner, bindings)))
1918        }
1919        TypeSchema::ArrayTuple(items) => TypeSchema::ArrayTuple(
1920            items
1921                .iter()
1922                .map(|schema| substitute_type_schema(schema, bindings))
1923                .collect(),
1924        ),
1925        TypeSchema::ArrayTupleRest { prefix, rest } => TypeSchema::ArrayTupleRest {
1926            prefix: prefix
1927                .iter()
1928                .map(|schema| substitute_type_schema(schema, bindings))
1929                .collect(),
1930            rest: Box::new(substitute_type_schema(rest, bindings)),
1931        },
1932        TypeSchema::Map(inner) => {
1933            TypeSchema::Map(Box::new(substitute_type_schema(inner, bindings)))
1934        }
1935        TypeSchema::Object(fields) => TypeSchema::Object(
1936            fields
1937                .iter()
1938                .map(|(name, schema)| (name.clone(), substitute_type_schema(schema, bindings)))
1939                .collect(),
1940        ),
1941        TypeSchema::Callable { params, result } => TypeSchema::Callable {
1942            params: params
1943                .iter()
1944                .map(|schema| substitute_type_schema(schema, bindings))
1945                .collect(),
1946            result: Box::new(substitute_type_schema(result, bindings)),
1947        },
1948        _ => schema.clone(),
1949    }
1950}
1951
1952fn local_slot_operand(index: LocalSlot) -> Result<u8, CompileError> {
1953    u8::try_from(index).map_err(|_| CompileError::LocalSlotOverflow(index))
1954}
1955
1956fn shift_amount_for_power_of_two(value: i64) -> Option<u32> {
1957    if value <= 0 {
1958        return None;
1959    }
1960    let as_u64 = value as u64;
1961    if !as_u64.is_power_of_two() {
1962        return None;
1963    }
1964    Some(as_u64.trailing_zeros())
1965}
1966
1967fn is_definitely_string_expr(expr: &Expr) -> bool {
1968    match expr {
1969        Expr::String(_) => true,
1970        Expr::ToOwned(inner) | Expr::Borrow(inner) | Expr::BorrowMut(inner) => {
1971            is_definitely_string_expr(inner)
1972        }
1973        Expr::Add(lhs, rhs) => {
1974            (is_definitely_string_expr(lhs) && is_definitely_string_expr(rhs))
1975                || (is_definitely_string_expr(lhs) && eval_const_int_expr(rhs).is_some())
1976                || (eval_const_int_expr(lhs).is_some() && is_definitely_string_expr(rhs))
1977        }
1978        _ => false,
1979    }
1980}
1981
1982fn eval_const_int_expr(expr: &Expr) -> Option<i64> {
1983    match expr {
1984        Expr::Int(value) => Some(*value),
1985        Expr::ToOwned(inner) | Expr::Borrow(inner) | Expr::BorrowMut(inner) => {
1986            eval_const_int_expr(inner)
1987        }
1988        Expr::Neg(inner) => eval_const_int_expr(inner)?.checked_neg(),
1989        Expr::Add(lhs, rhs) => eval_const_int_expr(lhs)?.checked_add(eval_const_int_expr(rhs)?),
1990        Expr::Sub(lhs, rhs) => eval_const_int_expr(lhs)?.checked_sub(eval_const_int_expr(rhs)?),
1991        Expr::Mul(lhs, rhs) => eval_const_int_expr(lhs)?.checked_mul(eval_const_int_expr(rhs)?),
1992        Expr::Div(lhs, rhs) => {
1993            let rhs = eval_const_int_expr(rhs)?;
1994            if rhs == 0 {
1995                return None;
1996            }
1997            eval_const_int_expr(lhs)?.checked_div(rhs)
1998        }
1999        _ => None,
2000    }
2001}