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(¬_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(¬_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(¬_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(¬_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}