1use super::BorrowMode;
4use crate::bytecode::{BuiltinFunction, Constant, Instruction, OpCode, Operand};
5use crate::type_tracking::{NumericType, StorageHint, TypeTracker, VariableTypeInfo};
6use shape_ast::ast::{Spanned, TypeAnnotation};
7use shape_ast::error::{Result, ShapeError};
8use std::collections::{BTreeSet, HashMap};
9
10use super::{
11 BuiltinNameResolution, BytecodeCompiler, DropKind, ParamPassMode, ResolutionScope,
12};
13
14pub(crate) fn strip_error_prefix(e: &ShapeError) -> String {
20 let msg = e.to_string();
21 const PREFIXES: &[&str] = &[
23 "Runtime error: ",
24 "Type error: ",
25 "Semantic error: ",
26 "Parse error: ",
27 "VM error: ",
28 "Lexical error: ",
29 ];
30 let mut s = msg.as_str();
31 for _ in 0..3 {
33 let mut stripped = false;
34 for prefix in PREFIXES {
35 if let Some(rest) = s.strip_prefix(prefix) {
36 s = rest;
37 stripped = true;
38 break;
39 }
40 }
41 const COMPTIME_PREFIXES: &[&str] = &[
43 "Comptime block evaluation failed: ",
44 "Comptime handler execution failed: ",
45 "Comptime block directive processing failed: ",
46 ];
47 for prefix in COMPTIME_PREFIXES {
48 if let Some(rest) = s.strip_prefix(prefix) {
49 s = rest;
50 stripped = true;
51 break;
52 }
53 }
54 if !stripped {
55 break;
56 }
57 }
58 s.to_string()
59}
60
61
62
63impl BytecodeCompiler {
64 fn scalar_type_name_from_numeric(numeric_type: NumericType) -> &'static str {
65 match numeric_type {
66 NumericType::Int | NumericType::IntWidth(_) => "int",
67 NumericType::Number => "number",
68 NumericType::Decimal => "decimal",
69 }
70 }
71
72 fn array_type_name_from_numeric(numeric_type: NumericType) -> &'static str {
73 match numeric_type {
74 NumericType::Int | NumericType::IntWidth(_) => "Vec<int>",
75 NumericType::Number => "Vec<number>",
76 NumericType::Decimal => "Vec<decimal>",
77 }
78 }
79
80 fn is_array_type_name(type_name: Option<&str>) -> bool {
81 matches!(type_name, Some(name) if name.starts_with("Vec<") && name.ends_with('>'))
82 }
83
84 pub(super) fn tracked_type_name_from_annotation(type_ann: &TypeAnnotation) -> Option<String> {
87 match type_ann {
88 TypeAnnotation::Basic(name) => Some(name.clone()),
89 TypeAnnotation::Reference(name) => Some(name.to_string()),
90 TypeAnnotation::Array(inner) => Some(format!("Vec<{}>", inner.to_type_string())),
91 TypeAnnotation::Generic { name, args } if name == "Vec" && args.len() == 1 => {
93 Some(format!("Vec<{}>", args[0].to_type_string()))
94 }
95 TypeAnnotation::Generic { name, args } if name == "Mat" && args.len() == 1 => {
96 Some(format!("Mat<{}>", args[0].to_type_string()))
97 }
98 _ => None,
99 }
100 }
101
102 pub(super) fn resolve_type_name(&self, name: &str) -> String {
108 if name.contains("::") || self.is_type_known_direct(name) {
110 return name.to_string();
111 }
112 for scope in self.module_scope_stack.iter().rev() {
114 let qualified = format!("{}::{}", scope, name);
115 if self.is_type_known_direct(&qualified) {
116 return qualified;
117 }
118 }
119 if let Some(imported) = self.imported_names.get(name) {
121 if !imported.module_path.is_empty() {
125 let qualified = format!("{}::{}", imported.module_path, imported.original_name);
126 if self.is_type_known_direct(&qualified) {
127 return qualified;
128 }
129 }
130 if self.is_type_known_direct(&imported.original_name) {
132 return imported.original_name.clone();
133 }
134 }
135 for ns in &self.module_namespace_bindings {
137 let qualified = format!("{}::{}", ns, name);
138 if self.is_type_known_direct(&qualified) {
139 return qualified;
140 }
141 if let Some(canonical) = self.graph_namespace_map.get(ns) {
143 let cq = format!("{}::{}", canonical, name);
144 if self.is_type_known_direct(&cq) {
145 return cq;
146 }
147 }
148 }
149 name.to_string()
151 }
152
153 fn is_type_known_direct(&self, name: &str) -> bool {
155 self.struct_types.contains_key(name)
156 || self.type_aliases.contains_key(name)
157 || self
158 .type_inference
159 .env
160 .lookup_type_alias(name)
161 .is_some()
162 || self.type_inference.env.get_enum(name).is_some()
163 || self
164 .type_inference
165 .env
166 .lookup_interface(name)
167 .is_some()
168 || self.type_inference.env.lookup_trait(name).is_some()
169 || self.type_tracker.schema_registry().get(name).is_some()
170 }
171
172 pub(super) fn resolve_trait_name(&self, name: &str) -> (String, String) {
178 let basename = name.rsplit("::").next().unwrap_or(name).to_string();
179 if self.trait_defs.contains_key(name) {
181 return (name.to_string(), basename);
182 }
183 for scope in self.module_scope_stack.iter().rev() {
184 let q = format!("{}::{}", scope, name);
185 if self.trait_defs.contains_key(&q) {
186 return (q, basename);
187 }
188 }
189 if let Some(imported) = self.imported_names.get(name) {
190 if !imported.module_path.is_empty() {
191 let q = format!("{}::{}", imported.module_path, imported.original_name);
192 if self.trait_defs.contains_key(&q) {
193 return (q, basename);
194 }
195 }
196 }
197 for ns in &self.module_namespace_bindings {
198 let q = format!("{}::{}", ns, name);
199 if self.trait_defs.contains_key(&q) {
200 return (q, basename);
201 }
202 if let Some(canonical) = self.graph_namespace_map.get(ns) {
203 let cq = format!("{}::{}", canonical, name);
204 if self.trait_defs.contains_key(&cq) {
205 return (cq, basename);
206 }
207 }
208 }
209 if self.type_inference.env.lookup_trait(name).is_some() {
212 return (name.to_string(), basename);
213 }
214 if self.type_inference.env.lookup_trait(&basename).is_some() {
215 return (basename.clone(), basename);
216 }
217 (name.to_string(), basename)
218 }
219
220 pub(super) fn mark_slot_as_numeric_array(
225 &mut self,
226 slot: u16,
227 is_local: bool,
228 numeric_type: NumericType,
229 ) {
230 let info =
231 VariableTypeInfo::named(Self::array_type_name_from_numeric(numeric_type).to_string());
232 if is_local {
233 self.type_tracker.set_local_type(slot, info);
234 } else {
235 self.type_tracker.set_binding_type(slot, info);
236 }
237 }
238
239 pub(super) fn mark_slot_as_numeric_scalar(
241 &mut self,
242 slot: u16,
243 is_local: bool,
244 numeric_type: NumericType,
245 ) {
246 let info =
247 VariableTypeInfo::named(Self::scalar_type_name_from_numeric(numeric_type).to_string());
248 if is_local {
249 self.type_tracker.set_local_type(slot, info);
250 } else {
251 self.type_tracker.set_binding_type(slot, info);
252 }
253 }
254
255 pub(super) fn seed_numeric_hint_from_expr(
260 &mut self,
261 expr: &shape_ast::ast::Expr,
262 numeric_type: NumericType,
263 ) {
264 match expr {
265 shape_ast::ast::Expr::Identifier(name, _) => {
266 if let Some(local_idx) = self.resolve_local(name) {
267 self.mark_slot_as_numeric_scalar(local_idx, true, numeric_type);
268 return;
269 }
270 let scoped_name = self
271 .resolve_scoped_module_binding_name(name)
272 .unwrap_or_else(|| name.to_string());
273 if let Some(binding_idx) = self.module_bindings.get(&scoped_name).copied() {
274 self.mark_slot_as_numeric_scalar(binding_idx, false, numeric_type);
275 }
276 }
277 shape_ast::ast::Expr::IndexAccess {
278 object,
279 end_index: None,
280 ..
281 } => {
282 if let shape_ast::ast::Expr::Identifier(name, _) = object.as_ref() {
283 if let Some(local_idx) = self.resolve_local(name) {
284 self.mark_slot_as_numeric_array(local_idx, true, numeric_type);
285 return;
286 }
287 let scoped_name = self
288 .resolve_scoped_module_binding_name(name)
289 .unwrap_or_else(|| name.to_string());
290 if let Some(binding_idx) = self.module_bindings.get(&scoped_name).copied() {
291 self.mark_slot_as_numeric_array(binding_idx, false, numeric_type);
292 }
293 }
294 }
295 _ => {}
296 }
297 }
298
299 fn recover_or_bail_with_null_placeholder(&mut self, err: ShapeError) -> Result<()> {
300 if self.should_recover_compile_diagnostics() {
301 self.errors.push(err);
302 self.emit(Instruction::simple(OpCode::PushNull));
303 Ok(())
304 } else {
305 Err(err)
306 }
307 }
308
309 pub(super) fn compile_expr_as_value_or_placeholder(
310 &mut self,
311 expr: &shape_ast::ast::Expr,
312 ) -> Result<()> {
313 match self.compile_expr(expr) {
314 Ok(()) => Ok(()),
315 Err(err) => self.recover_or_bail_with_null_placeholder(err),
316 }
317 }
318
319 pub(super) fn emit(&mut self, instruction: Instruction) -> usize {
322 let idx = self.program.emit(instruction);
323 if self.current_line > 0 {
325 self.program.debug_info.line_numbers.push((
326 idx,
327 self.current_file_id,
328 self.current_line,
329 ));
330 }
331 idx
332 }
333
334 pub(super) fn emit_bool(&mut self, value: bool) {
336 let const_idx = self.program.add_constant(Constant::Bool(value));
337 self.emit(Instruction::new(
338 OpCode::PushConst,
339 Some(Operand::Const(const_idx)),
340 ));
341 }
342
343 pub(super) fn emit_unit(&mut self) {
345 let const_idx = self.program.add_constant(Constant::Unit);
346 self.emit(Instruction::new(
347 OpCode::PushConst,
348 Some(Operand::Const(const_idx)),
349 ));
350 }
351
352 pub(super) fn emit_jump(&mut self, mut opcode: OpCode, dummy: i32) -> usize {
358 if opcode == OpCode::JumpIfFalse && self.last_instruction_produces_bool() {
359 opcode = OpCode::JumpIfFalseTrusted;
360 }
361 self.emit(Instruction::new(opcode, Some(Operand::Offset(dummy))))
362 }
363
364 fn last_instruction_produces_bool(&self) -> bool {
366 self.program
367 .instructions
368 .last()
369 .map(|instr| {
370 matches!(
371 instr.opcode,
372 OpCode::GtInt
373 | OpCode::GtNumber
374 | OpCode::GtDecimal
375 | OpCode::LtInt
376 | OpCode::LtNumber
377 | OpCode::LtDecimal
378 | OpCode::GteInt
379 | OpCode::GteNumber
380 | OpCode::GteDecimal
381 | OpCode::LteInt
382 | OpCode::LteNumber
383 | OpCode::LteDecimal
384 | OpCode::EqInt
385 | OpCode::EqNumber
386 | OpCode::NeqInt
387 | OpCode::NeqNumber
388 | OpCode::Gt
389 | OpCode::Lt
390 | OpCode::Gte
391 | OpCode::Lte
392 | OpCode::Eq
393 | OpCode::Neq
394 | OpCode::Not
395 )
396 })
397 .unwrap_or(false)
398 }
399
400 pub(super) fn patch_jump(&mut self, jump_idx: usize) {
402 let offset = self.program.current_offset() as i32 - jump_idx as i32 - 1;
403 self.program.instructions[jump_idx] = Instruction::new(
404 self.program.instructions[jump_idx].opcode,
405 Some(Operand::Offset(offset)),
406 );
407 }
408
409 pub(super) fn compile_call_args(
416 &mut self,
417 args: &[shape_ast::ast::Expr],
418 expected_param_modes: Option<&[ParamPassMode]>,
419 ) -> Result<Vec<(u16, u16)>> {
420 self.call_arg_module_binding_ref_writebacks.push(Vec::new());
421
422 let mut first_error: Option<ShapeError> = None;
423 for (idx, arg) in args.iter().enumerate() {
424 let pass_mode = expected_param_modes
425 .and_then(|modes| modes.get(idx).copied())
426 .unwrap_or(ParamPassMode::ByValue);
427
428 let arg_result = match pass_mode {
429 ParamPassMode::ByRefExclusive | ParamPassMode::ByRefShared => {
430 let borrow_mode = if pass_mode.is_exclusive() {
431 BorrowMode::Exclusive
432 } else {
433 BorrowMode::Shared
434 };
435 if let shape_ast::ast::Expr::Reference { expr, span, .. } = arg {
436 self.compile_reference_expr(expr, *span, borrow_mode)
437 .map(|_| ())
438 } else {
439 self.compile_implicit_reference_arg(arg, borrow_mode)
440 }
441 }
442 ParamPassMode::ByValue => {
443 if let shape_ast::ast::Expr::Reference { span, .. } = arg {
444 let message = if expected_param_modes.is_some() {
445 "[B0004] unexpected `&` argument: target parameter is not a reference parameter".to_string()
446 } else {
447 "[B0004] cannot pass `&` to a callable value without a declared reference contract; \
448 call a named function with known parameter modes or add an explicit callable type"
449 .to_string()
450 };
451 Err(ShapeError::SemanticError {
452 message,
453 location: Some(self.span_to_source_location(*span)),
454 })
455 } else {
456 self.plan_flexible_binding_escape_from_expr(arg);
457 self.compile_expr(arg)
458 }
459 }
460 };
461
462 if let Err(err) = arg_result {
463 if self.should_recover_compile_diagnostics() {
464 self.errors.push(err);
465 self.emit(Instruction::simple(OpCode::PushNull));
467 continue;
468 }
469 first_error = Some(err);
470 break;
471 }
472 }
473
474 let writebacks = self
475 .call_arg_module_binding_ref_writebacks
476 .pop()
477 .unwrap_or_default();
478 if let Some(err) = first_error {
479 Err(err)
480 } else {
481 Ok(writebacks)
482 }
483 }
484
485 pub(super) fn compile_implicit_reference_arg(
486 &mut self,
487 arg: &shape_ast::ast::Expr,
488 mode: BorrowMode,
489 ) -> Result<()> {
490 use shape_ast::ast::Expr;
491 match arg {
492 Expr::Identifier(name, span) => self
493 .compile_reference_identifier(name, *span, mode)
494 .map(|_| ()),
495 Expr::PropertyAccess {
496 object,
497 property,
498 optional: false,
499 span,
500 } => self
501 .compile_reference_property_access(object, property, *span, mode)
502 .map(|_| ()),
503 Expr::IndexAccess {
504 object,
505 index,
506 end_index: None,
507 span,
508 } => self
509 .compile_reference_index_access(object, index, *span, mode)
510 .map(|_| ()),
511 _ => {
512 self.compile_expr_preserving_refs(arg)?;
513 if let Some(returned_mode) = self.last_expr_reference_mode() {
514 if mode == BorrowMode::Exclusive && returned_mode != BorrowMode::Exclusive {
515 return Err(ShapeError::SemanticError {
516 message:
517 "cannot pass a shared reference result to an exclusive parameter"
518 .to_string(),
519 location: Some(self.span_to_source_location(arg.span())),
520 });
521 }
522 return Ok(());
523 }
524 if mode == BorrowMode::Exclusive {
525 return Err(ShapeError::SemanticError {
526 message:
527 "[B0004] mutable reference arguments must be simple variables or existing exclusive references"
528 .to_string(),
529 location: Some(self.span_to_source_location(arg.span())),
530 });
531 }
532 let temp = self.declare_temp_local("__arg_ref_")?;
533 self.emit(Instruction::new(
534 OpCode::StoreLocal,
535 Some(Operand::Local(temp)),
536 ));
537 self.emit(Instruction::new(
539 OpCode::MakeRef,
540 Some(Operand::Local(temp)),
541 ));
542 Ok(())
543 }
544 }
545 }
546
547 pub(super) fn compile_reference_identifier(
548 &mut self,
549 name: &str,
550 span: shape_ast::ast::Span,
551 mode: BorrowMode,
552 ) -> Result<u32> {
553 if let Some(local_idx) = self.resolve_local(name) {
554 if mode == BorrowMode::Exclusive && self.const_locals.contains(&local_idx) {
556 return Err(ShapeError::SemanticError {
557 message: format!(
558 "Cannot pass const variable '{}' by exclusive reference",
559 name
560 ),
561 location: Some(self.span_to_source_location(span)),
562 });
563 }
564 if self.ref_locals.contains(&local_idx) {
565 self.emit(Instruction::new(
567 OpCode::LoadLocal,
568 Some(Operand::Local(local_idx)),
569 ));
570 return Ok(u32::MAX);
571 }
572 if self.reference_value_locals.contains(&local_idx) {
573 if mode == BorrowMode::Exclusive
574 && !self.exclusive_reference_value_locals.contains(&local_idx)
575 {
576 return Err(ShapeError::SemanticError {
577 message: format!(
578 "Cannot pass shared reference variable '{}' as an exclusive reference",
579 name
580 ),
581 location: Some(self.span_to_source_location(span)),
582 });
583 }
584 self.emit(Instruction::new(
585 OpCode::LoadLocal,
586 Some(Operand::Local(local_idx)),
587 ));
588 return Ok(u32::MAX);
589 }
590 self.emit(Instruction::new(
592 OpCode::MakeRef,
593 Some(Operand::Local(local_idx)),
594 ));
595 Ok(u32::MAX)
596 } else if let Some(scoped_name) = self.resolve_scoped_module_binding_name(name) {
597 let Some(&binding_idx) = self.module_bindings.get(&scoped_name) else {
598 return Err(ShapeError::SemanticError {
599 message: format!(
600 "[B0004] reference argument must be a local or module_binding variable, got '{}'",
601 name
602 ),
603 location: Some(self.span_to_source_location(span)),
604 });
605 };
606 if mode == BorrowMode::Exclusive && self.const_module_bindings.contains(&binding_idx) {
608 return Err(ShapeError::SemanticError {
609 message: format!(
610 "Cannot pass const variable '{}' by exclusive reference",
611 name
612 ),
613 location: Some(self.span_to_source_location(span)),
614 });
615 }
616 if self.reference_value_module_bindings.contains(&binding_idx) {
617 if mode == BorrowMode::Exclusive
618 && !self
619 .exclusive_reference_value_module_bindings
620 .contains(&binding_idx)
621 {
622 return Err(ShapeError::SemanticError {
623 message: format!(
624 "Cannot pass shared reference variable '{}' as an exclusive reference",
625 name
626 ),
627 location: Some(self.span_to_source_location(span)),
628 });
629 }
630 self.emit(Instruction::new(
631 OpCode::LoadModuleBinding,
632 Some(Operand::ModuleBinding(binding_idx)),
633 ));
634 return Ok(u32::MAX);
635 }
636 self.emit(Instruction::new(
638 OpCode::MakeRef,
639 Some(Operand::ModuleBinding(binding_idx)),
640 ));
641 Ok(u32::MAX)
642 } else if let Some(func_idx) = self.find_function(name) {
643 let temp = self.declare_temp_local("__fn_ref_")?;
646 let const_idx = self
647 .program
648 .add_constant(Constant::Function(func_idx as u16));
649 self.emit(Instruction::new(
650 OpCode::PushConst,
651 Some(Operand::Const(const_idx)),
652 ));
653 self.emit(Instruction::new(
654 OpCode::StoreLocal,
655 Some(Operand::Local(temp)),
656 ));
657 self.emit(Instruction::new(
659 OpCode::MakeRef,
660 Some(Operand::Local(temp)),
661 ));
662 Ok(u32::MAX)
663 } else {
664 Err(ShapeError::SemanticError {
665 message: format!(
666 "[B0004] reference argument must be a local or module_binding variable, got '{}'",
667 name
668 ),
669 location: Some(self.span_to_source_location(span)),
670 })
671 }
672 }
673
674 pub(super) fn push_scope(&mut self) {
676 self.locals.push(HashMap::new());
677 self.type_tracker.push_scope();
678 }
679
680 pub(super) fn pop_scope(&mut self) {
682 self.locals.pop();
683 self.type_tracker.pop_scope();
684 }
685
686 pub(super) fn declare_local(&mut self, name: &str) -> Result<u16> {
688 let idx = self.next_local;
689 self.next_local += 1;
690
691 if let Some(scope) = self.locals.last_mut() {
692 scope.insert(name.to_string(), idx);
693 }
694
695 Ok(idx)
696 }
697
698 pub(super) fn resolve_local(&self, name: &str) -> Option<u16> {
700 for scope in self.locals.iter().rev() {
701 if let Some(&idx) = scope.get(name) {
702 return Some(idx);
703 }
704 }
705 None
706 }
707
708 pub(super) fn declare_temp_local(&mut self, prefix: &str) -> Result<u16> {
710 let name = format!("{}{}", prefix, self.next_local);
711 self.declare_local(&name)
712 }
713
714 pub(super) fn set_local_type_info(&mut self, slot: u16, type_name: &str) {
716 let info = if let Some(schema) = self.type_tracker.schema_registry().get(type_name) {
717 VariableTypeInfo::known(schema.id, type_name.to_string())
718 } else {
719 VariableTypeInfo::named(type_name.to_string())
720 };
721 self.type_tracker.set_local_type(slot, info);
722 }
723
724 pub(super) fn set_module_binding_type_info(&mut self, slot: u16, type_name: &str) {
726 let info = if let Some(schema) = self.type_tracker.schema_registry().get(type_name) {
727 VariableTypeInfo::known(schema.id, type_name.to_string())
728 } else {
729 VariableTypeInfo::named(type_name.to_string())
730 };
731 self.type_tracker.set_binding_type(slot, info);
732 }
733
734 pub(super) fn capture_function_local_storage_hints(&mut self, func_idx: usize) {
740 let Some(func) = self.program.functions.get(func_idx) else {
741 return;
742 };
743 let hints: Vec<StorageHint> = (0..func.locals_count)
744 .map(|slot| self.type_tracker.get_local_storage_hint(slot))
745 .collect();
746
747 let has_any_known = hints.iter().any(|h| *h != StorageHint::Unknown);
749 let instr_len = self.program.instructions.len();
750 let code_end = if func.body_length > 0 {
751 (func.entry_point + func.body_length).min(instr_len)
752 } else {
753 instr_len
754 };
755 let has_trusted = if func.entry_point <= code_end && code_end <= instr_len {
756 self.program.instructions[func.entry_point..code_end]
757 .iter()
758 .any(|i| i.opcode.is_trusted())
759 } else {
760 false
761 };
762 if has_any_known || has_trusted {
763 self.program.functions[func_idx].frame_descriptor = Some(
764 crate::type_tracking::FrameDescriptor::from_slots(hints.clone()),
765 );
766 }
767
768 if self.program.function_local_storage_hints.len() <= func_idx {
769 self.program
770 .function_local_storage_hints
771 .resize(func_idx + 1, Vec::new());
772 }
773 self.program.function_local_storage_hints[func_idx] = hints;
774 }
775
776 pub(super) fn populate_program_storage_hints(&mut self) {
778 let top_hints: Vec<StorageHint> = (0..self.next_local)
779 .map(|slot| self.type_tracker.get_local_storage_hint(slot))
780 .collect();
781 self.program.top_level_local_storage_hints = top_hints.clone();
782
783 let has_any_known = top_hints.iter().any(|h| *h != StorageHint::Unknown);
785 let has_trusted = self
786 .program
787 .instructions
788 .iter()
789 .any(|i| i.opcode.is_trusted());
790 if has_any_known || has_trusted {
791 self.program.top_level_frame =
792 Some(crate::type_tracking::FrameDescriptor::from_slots(top_hints));
793 }
794
795 let mut module_binding_hints = vec![StorageHint::Unknown; self.module_bindings.len()];
796 for &idx in self.module_bindings.values() {
797 if let Some(slot) = module_binding_hints.get_mut(idx as usize) {
798 *slot = self.type_tracker.get_module_binding_storage_hint(idx);
799 }
800 }
801 self.program.module_binding_storage_hints = module_binding_hints;
802
803 if self.program.function_local_storage_hints.len() < self.program.functions.len() {
804 self.program
805 .function_local_storage_hints
806 .resize(self.program.functions.len(), Vec::new());
807 } else if self.program.function_local_storage_hints.len() > self.program.functions.len() {
808 self.program
809 .function_local_storage_hints
810 .truncate(self.program.functions.len());
811 }
812 }
813
814 pub(super) fn propagate_assignment_type_to_slot(
819 &mut self,
820 slot: u16,
821 is_local: bool,
822 allow_number_hint: bool,
823 ) {
824 if let Some(ref info) = self.last_expr_type_info {
825 if info.is_indexed()
826 || info.is_datatable()
827 || info.schema_id.is_some()
828 || Self::is_array_type_name(info.type_name.as_deref())
829 {
830 if is_local {
831 self.type_tracker.set_local_type(slot, info.clone());
832 } else {
833 self.type_tracker.set_binding_type(slot, info.clone());
834 }
835 return;
836 }
837 }
838
839 if let Some(schema_id) = self.last_expr_schema {
840 let schema_name = self
841 .type_tracker
842 .schema_registry()
843 .get_by_id(schema_id)
844 .map(|s| s.name.clone())
845 .unwrap_or_else(|| format!("__anon_{}", schema_id));
846 let info = VariableTypeInfo::known(schema_id, schema_name);
847 if is_local {
848 self.type_tracker.set_local_type(slot, info);
849 } else {
850 self.type_tracker.set_binding_type(slot, info);
851 }
852 return;
853 }
854
855 if let Some(numeric_type) = self.last_expr_numeric_type {
856 let (type_name, hint) = match numeric_type {
857 crate::type_tracking::NumericType::Int => ("int", StorageHint::Int64),
858 crate::type_tracking::NumericType::IntWidth(w) => {
859 use shape_ast::IntWidth;
860 let hint = match w {
861 IntWidth::I8 => StorageHint::Int8,
862 IntWidth::U8 => StorageHint::UInt8,
863 IntWidth::I16 => StorageHint::Int16,
864 IntWidth::U16 => StorageHint::UInt16,
865 IntWidth::I32 => StorageHint::Int32,
866 IntWidth::U32 => StorageHint::UInt32,
867 IntWidth::U64 => StorageHint::UInt64,
868 };
869 (w.type_name(), hint)
870 }
871 crate::type_tracking::NumericType::Number => {
872 if !allow_number_hint {
873 if is_local {
874 self.type_tracker
875 .set_local_type(slot, VariableTypeInfo::unknown());
876 } else {
877 self.type_tracker
878 .set_binding_type(slot, VariableTypeInfo::unknown());
879 }
880 return;
881 }
882 ("number", StorageHint::Float64)
883 }
884 crate::type_tracking::NumericType::Decimal => {
886 if is_local {
887 self.type_tracker
888 .set_local_type(slot, VariableTypeInfo::unknown());
889 } else {
890 self.type_tracker
891 .set_binding_type(slot, VariableTypeInfo::unknown());
892 }
893 return;
894 }
895 };
896 let info = VariableTypeInfo::with_storage(type_name.to_string(), hint);
897 if is_local {
898 self.type_tracker.set_local_type(slot, info);
899 } else {
900 self.type_tracker.set_binding_type(slot, info);
901 }
902 return;
903 }
904
905 if is_local {
907 self.type_tracker
908 .set_local_type(slot, VariableTypeInfo::unknown());
909 } else {
910 self.type_tracker
911 .set_binding_type(slot, VariableTypeInfo::unknown());
912 }
913 }
914
915 pub(super) fn propagate_assignment_type_to_identifier(&mut self, name: &str) {
919 if let Some(local_idx) = self.resolve_local(name) {
920 if self.local_binding_is_reference_value(local_idx) {
921 return;
922 }
923 self.propagate_assignment_type_to_slot(local_idx, true, true);
924 return;
925 }
926
927 let scoped_name = self
928 .resolve_scoped_module_binding_name(name)
929 .unwrap_or_else(|| name.to_string());
930 let binding_idx = self.get_or_create_module_binding(&scoped_name);
931 self.propagate_assignment_type_to_slot(binding_idx, false, true);
932 }
933
934 pub(crate) fn resolve_canonical_module_path(&self, local_name: &str) -> Option<String> {
940 self.graph_namespace_map
941 .get(local_name)
942 .or_else(|| self.module_scope_sources.get(local_name))
943 .cloned()
944 }
945
946 pub fn type_tracker(&self) -> &TypeTracker {
947 &self.type_tracker
948 }
949
950 pub fn type_tracker_mut(&mut self) -> &mut TypeTracker {
952 &mut self.type_tracker
953 }
954
955 pub(super) fn resolve_column_index(&self, field: &str) -> Result<u32> {
958 self.program
959 .data_schema
960 .as_ref()
961 .ok_or_else(|| ShapeError::RuntimeError {
962 message: format!(
963 "No data schema provided. Cannot resolve field '{}'. \
964 Hint: Use stdlib/finance to load market data with OHLCV schema.",
965 field
966 ),
967 location: None,
968 })?
969 .get_index(field)
970 .ok_or_else(|| ShapeError::RuntimeError {
971 message: format!(
972 "Unknown column '{}' in data schema. Available columns: {:?}",
973 field,
974 self.program
975 .data_schema
976 .as_ref()
977 .map(|s| &s.column_names)
978 .unwrap_or(&vec![])
979 ),
980 location: None,
981 })
982 }
983
984 pub(super) fn is_data_column(&self, field: &str) -> bool {
986 self.program
987 .data_schema
988 .as_ref()
989 .map(|s| s.get_index(field).is_some())
990 .unwrap_or(false)
991 }
992
993 pub(super) fn collect_outer_scope_vars(&self) -> Vec<String> {
995 let mut names = BTreeSet::new();
996 for scope in &self.locals {
997 for name in scope.keys() {
998 names.insert(name.clone());
999 }
1000 }
1001 for name in self.module_bindings.keys() {
1002 names.insert(name.clone());
1003 }
1004 names.into_iter().collect()
1005 }
1006
1007 pub(super) fn get_or_create_module_binding(&mut self, name: &str) -> u16 {
1009 if let Some(&idx) = self.module_bindings.get(name) {
1010 idx
1011 } else {
1012 let idx = self.next_global;
1013 self.next_global += 1;
1014 self.module_bindings.insert(name.to_string(), idx);
1015 idx
1016 }
1017 }
1018
1019 pub(super) fn resolve_scoped_module_binding_name(&self, name: &str) -> Option<String> {
1020 if crate::module_resolution::is_hidden_annotation_import_module_name(name) {
1021 return None;
1022 }
1023 if self.module_bindings.contains_key(name) {
1024 return Some(name.to_string());
1025 }
1026 for module_path in self.module_scope_stack.iter().rev() {
1027 let candidate = format!("{}::{}", module_path, name);
1028 if self.module_bindings.contains_key(&candidate) {
1029 return Some(candidate);
1030 }
1031 }
1032 None
1033 }
1034
1035 pub(super) fn resolve_scoped_function_name(&self, name: &str) -> Option<String> {
1036 if self.program.functions.iter().any(|f| f.name == name) {
1037 return Some(name.to_string());
1038 }
1039 for module_path in self.module_scope_stack.iter().rev() {
1040 let candidate = format!("{}::{}", module_path, name);
1041 if self.program.functions.iter().any(|f| f.name == candidate) {
1042 return Some(candidate);
1043 }
1044 }
1045 None
1046 }
1047
1048 pub(super) fn find_function(&self, name: &str) -> Option<usize> {
1050 if let Some(actual_name) = self.function_aliases.get(name) {
1052 if let Some(idx) = self
1053 .program
1054 .functions
1055 .iter()
1056 .position(|f| f.name == *actual_name)
1057 {
1058 return Some(idx);
1059 }
1060 }
1061
1062 if let Some(resolved) = self.resolve_scoped_function_name(name) {
1064 if let Some(idx) = self
1065 .program
1066 .functions
1067 .iter()
1068 .position(|f| f.name == resolved)
1069 {
1070 return Some(idx);
1071 }
1072 }
1073
1074 if let Some(imported) = self.imported_names.get(name) {
1079 let original = &imported.original_name;
1080 if let Some(idx) = self
1082 .program
1083 .functions
1084 .iter()
1085 .position(|f| f.name == *original)
1086 {
1087 return Some(idx);
1088 }
1089 if let Some(resolved) = self.resolve_scoped_function_name(original) {
1091 if let Some(idx) = self
1092 .program
1093 .functions
1094 .iter()
1095 .position(|f| f.name == resolved)
1096 {
1097 return Some(idx);
1098 }
1099 }
1100 if !imported.module_path.is_empty() {
1104 let qualified = format!("{}::{}", imported.module_path, original);
1105 if let Some(idx) = self
1106 .program
1107 .functions
1108 .iter()
1109 .position(|f| f.name == qualified)
1110 {
1111 return Some(idx);
1112 }
1113 }
1114 }
1115
1116 None
1117 }
1118
1119 pub(super) fn resolve_receiver_extend_type(
1129 &self,
1130 receiver: &shape_ast::ast::Expr,
1131 receiver_type_info: &Option<crate::type_tracking::VariableTypeInfo>,
1132 _receiver_schema: Option<u32>,
1133 ) -> Option<String> {
1134 if let Some(numeric) = self.last_expr_numeric_type {
1138 return Some(
1139 match numeric {
1140 crate::type_tracking::NumericType::Int
1141 | crate::type_tracking::NumericType::IntWidth(_) => "Int",
1142 crate::type_tracking::NumericType::Number => "Number",
1143 crate::type_tracking::NumericType::Decimal => "Decimal",
1144 }
1145 .to_string(),
1146 );
1147 }
1148
1149 if let Some(info) = receiver_type_info {
1151 if let Some(type_name) = &info.type_name {
1152 let base = type_name.split('<').next().unwrap_or(type_name);
1154 return Some(base.to_string());
1155 }
1156 }
1157
1158 match receiver {
1160 shape_ast::ast::Expr::Literal(lit, _) => match lit {
1161 shape_ast::ast::Literal::String(_)
1162 | shape_ast::ast::Literal::FormattedString { .. }
1163 | shape_ast::ast::Literal::ContentString { .. } => Some("String".to_string()),
1164 shape_ast::ast::Literal::Bool(_) => Some("Bool".to_string()),
1165 _ => None,
1166 },
1167 shape_ast::ast::Expr::Array(..) => Some("Vec".to_string()),
1168 _ => None,
1169 }
1170 }
1171
1172 pub(super) fn emit_store_identifier(&mut self, name: &str) -> Result<()> {
1174 if let Some(&upvalue_idx) = self.mutable_closure_captures.get(name) {
1176 self.emit(Instruction::new(
1177 OpCode::StoreClosure,
1178 Some(Operand::Local(upvalue_idx)),
1179 ));
1180 return Ok(());
1181 }
1182 if let Some(local_idx) = self.resolve_local(name) {
1183 if self.local_binding_is_reference_value(local_idx) {
1184 if !self.local_reference_binding_is_exclusive(local_idx) {
1185 return Err(ShapeError::SemanticError {
1186 message: format!(
1187 "cannot assign through shared reference variable '{}'",
1188 name
1189 ),
1190 location: None,
1191 });
1192 }
1193 self.emit(Instruction::new(
1194 OpCode::DerefStore,
1195 Some(Operand::Local(local_idx)),
1196 ));
1197 } else {
1198 self.emit(Instruction::new(
1199 OpCode::StoreLocal,
1200 Some(Operand::Local(local_idx)),
1201 ));
1202 if let Some(type_name) = self
1204 .type_tracker
1205 .get_local_type(local_idx)
1206 .and_then(|info| info.type_name.as_deref())
1207 {
1208 if let Some(w) = shape_ast::IntWidth::from_name(type_name) {
1209 if let Some(last) = self.program.instructions.last_mut() {
1210 if last.opcode == OpCode::StoreLocal {
1211 last.opcode = OpCode::StoreLocalTyped;
1212 last.operand = Some(Operand::TypedLocal(
1213 local_idx,
1214 crate::bytecode::NumericWidth::from_int_width(w),
1215 ));
1216 }
1217 }
1218 }
1219 }
1220 }
1221 } else {
1222 let scoped_name = self
1223 .resolve_scoped_module_binding_name(name)
1224 .unwrap_or_else(|| name.to_string());
1225 let binding_idx = self.get_or_create_module_binding(&scoped_name);
1226 self.emit(Instruction::new(
1227 OpCode::StoreModuleBinding,
1228 Some(Operand::ModuleBinding(binding_idx)),
1229 ));
1230 if let Some(type_name) = self
1232 .type_tracker
1233 .get_binding_type(binding_idx)
1234 .and_then(|info| info.type_name.as_deref())
1235 {
1236 if let Some(w) = shape_ast::IntWidth::from_name(type_name) {
1237 if let Some(last) = self.program.instructions.last_mut() {
1238 if last.opcode == OpCode::StoreModuleBinding {
1239 last.opcode = OpCode::StoreModuleBindingTyped;
1240 last.operand = Some(Operand::TypedModuleBinding(
1241 binding_idx,
1242 crate::bytecode::NumericWidth::from_int_width(w),
1243 ));
1244 }
1245 }
1246 }
1247 }
1248 }
1249 Ok(())
1250 }
1251
1252 pub(super) fn classify_builtin_function(&self, name: &str) -> Option<BuiltinNameResolution> {
1253 let builtin = match name {
1254 "Some" => BuiltinFunction::SomeCtor,
1256 "Ok" => BuiltinFunction::OkCtor,
1257 "Err" => BuiltinFunction::ErrCtor,
1258 "HashMap" => BuiltinFunction::HashMapCtor,
1259 "Set" => BuiltinFunction::SetCtor,
1260 "Deque" => BuiltinFunction::DequeCtor,
1261 "PriorityQueue" => BuiltinFunction::PriorityQueueCtor,
1262 "Mutex" => BuiltinFunction::MutexCtor,
1263 "Atomic" => BuiltinFunction::AtomicCtor,
1264 "Lazy" => BuiltinFunction::LazyCtor,
1265 "Channel" => BuiltinFunction::ChannelCtor,
1266 "__json_object_get" => BuiltinFunction::JsonObjectGet,
1268 "__json_array_at" => BuiltinFunction::JsonArrayAt,
1269 "__json_object_keys" => BuiltinFunction::JsonObjectKeys,
1270 "__json_array_len" => BuiltinFunction::JsonArrayLen,
1271 "__json_object_len" => BuiltinFunction::JsonObjectLen,
1272 "__intrinsic_vec_abs" => BuiltinFunction::IntrinsicVecAbs,
1273 "__intrinsic_vec_sqrt" => BuiltinFunction::IntrinsicVecSqrt,
1274 "__intrinsic_vec_ln" => BuiltinFunction::IntrinsicVecLn,
1275 "__intrinsic_vec_exp" => BuiltinFunction::IntrinsicVecExp,
1276 "__intrinsic_vec_add" => BuiltinFunction::IntrinsicVecAdd,
1277 "__intrinsic_vec_sub" => BuiltinFunction::IntrinsicVecSub,
1278 "__intrinsic_vec_mul" => BuiltinFunction::IntrinsicVecMul,
1279 "__intrinsic_vec_div" => BuiltinFunction::IntrinsicVecDiv,
1280 "__intrinsic_vec_max" => BuiltinFunction::IntrinsicVecMax,
1281 "__intrinsic_vec_min" => BuiltinFunction::IntrinsicVecMin,
1282 "__intrinsic_vec_select" => BuiltinFunction::IntrinsicVecSelect,
1283 "__intrinsic_matmul_vec" => BuiltinFunction::IntrinsicMatMulVec,
1284 "__intrinsic_matmul_mat" => BuiltinFunction::IntrinsicMatMulMat,
1285
1286 "abs" => BuiltinFunction::Abs,
1288 "min" => BuiltinFunction::Min,
1289 "max" => BuiltinFunction::Max,
1290 "sqrt" => BuiltinFunction::Sqrt,
1291 "ln" => BuiltinFunction::Ln,
1292 "pow" => BuiltinFunction::Pow,
1293 "exp" => BuiltinFunction::Exp,
1294 "log" => BuiltinFunction::Log,
1295 "floor" => BuiltinFunction::Floor,
1296 "ceil" => BuiltinFunction::Ceil,
1297 "round" => BuiltinFunction::Round,
1298 "sin" => BuiltinFunction::Sin,
1299 "cos" => BuiltinFunction::Cos,
1300 "tan" => BuiltinFunction::Tan,
1301 "asin" => BuiltinFunction::Asin,
1302 "acos" => BuiltinFunction::Acos,
1303 "atan" => BuiltinFunction::Atan,
1304 "stddev" => BuiltinFunction::StdDev,
1305 "__intrinsic_map" => BuiltinFunction::Map,
1306 "__intrinsic_filter" => BuiltinFunction::Filter,
1307 "__intrinsic_reduce" => BuiltinFunction::Reduce,
1308 "print" => BuiltinFunction::Print,
1309 "format" => BuiltinFunction::Format,
1310 "len" | "count" => BuiltinFunction::Len,
1311 "__intrinsic_snapshot" | "snapshot" => BuiltinFunction::Snapshot,
1313 "exit" => BuiltinFunction::Exit,
1314 "range" => BuiltinFunction::Range,
1315 "is_number" | "isNumber" => BuiltinFunction::IsNumber,
1316 "is_string" | "isString" => BuiltinFunction::IsString,
1317 "is_bool" | "isBool" => BuiltinFunction::IsBool,
1318 "is_array" | "isArray" => BuiltinFunction::IsArray,
1319 "is_object" | "isObject" => BuiltinFunction::IsObject,
1320 "is_data_row" | "isDataRow" => BuiltinFunction::IsDataRow,
1321 "to_string" | "toString" => BuiltinFunction::ToString,
1322 "to_number" | "toNumber" => BuiltinFunction::ToNumber,
1323 "to_bool" | "toBool" => BuiltinFunction::ToBool,
1324 "__native_ptr_size" => BuiltinFunction::NativePtrSize,
1327 "__native_ptr_new_cell" => BuiltinFunction::NativePtrNewCell,
1328 "__native_ptr_free_cell" => BuiltinFunction::NativePtrFreeCell,
1329 "__native_ptr_read_ptr" => BuiltinFunction::NativePtrReadPtr,
1330 "__native_ptr_write_ptr" => BuiltinFunction::NativePtrWritePtr,
1331 "__native_table_from_arrow_c" => BuiltinFunction::NativeTableFromArrowC,
1332 "__native_table_from_arrow_c_typed" => BuiltinFunction::NativeTableFromArrowCTyped,
1333 "__native_table_bind_type" => BuiltinFunction::NativeTableBindType,
1334 "fold" => BuiltinFunction::ControlFold,
1335
1336 "__intrinsic_sum" => BuiltinFunction::IntrinsicSum,
1338 "__intrinsic_mean" => BuiltinFunction::IntrinsicMean,
1339 "__intrinsic_min" => BuiltinFunction::IntrinsicMin,
1340 "__intrinsic_max" => BuiltinFunction::IntrinsicMax,
1341 "__intrinsic_std" => BuiltinFunction::IntrinsicStd,
1342 "__intrinsic_variance" => BuiltinFunction::IntrinsicVariance,
1343
1344 "__intrinsic_random" => BuiltinFunction::IntrinsicRandom,
1346 "__intrinsic_random_int" => BuiltinFunction::IntrinsicRandomInt,
1347 "__intrinsic_random_seed" => BuiltinFunction::IntrinsicRandomSeed,
1348 "__intrinsic_random_normal" => BuiltinFunction::IntrinsicRandomNormal,
1349 "__intrinsic_random_array" => BuiltinFunction::IntrinsicRandomArray,
1350
1351 "__intrinsic_dist_uniform" => BuiltinFunction::IntrinsicDistUniform,
1353 "__intrinsic_dist_lognormal" => BuiltinFunction::IntrinsicDistLognormal,
1354 "__intrinsic_dist_exponential" => BuiltinFunction::IntrinsicDistExponential,
1355 "__intrinsic_dist_poisson" => BuiltinFunction::IntrinsicDistPoisson,
1356 "__intrinsic_dist_sample_n" => BuiltinFunction::IntrinsicDistSampleN,
1357
1358 "__intrinsic_brownian_motion" => BuiltinFunction::IntrinsicBrownianMotion,
1360 "__intrinsic_gbm" => BuiltinFunction::IntrinsicGbm,
1361 "__intrinsic_ou_process" => BuiltinFunction::IntrinsicOuProcess,
1362 "__intrinsic_random_walk" => BuiltinFunction::IntrinsicRandomWalk,
1363
1364 "__intrinsic_rolling_sum" => BuiltinFunction::IntrinsicRollingSum,
1366 "__intrinsic_rolling_mean" => BuiltinFunction::IntrinsicRollingMean,
1367 "__intrinsic_rolling_std" => BuiltinFunction::IntrinsicRollingStd,
1368 "__intrinsic_rolling_min" => BuiltinFunction::IntrinsicRollingMin,
1369 "__intrinsic_rolling_max" => BuiltinFunction::IntrinsicRollingMax,
1370 "__intrinsic_ema" => BuiltinFunction::IntrinsicEma,
1371 "__intrinsic_linear_recurrence" => BuiltinFunction::IntrinsicLinearRecurrence,
1372
1373 "__intrinsic_shift" => BuiltinFunction::IntrinsicShift,
1375 "__intrinsic_diff" => BuiltinFunction::IntrinsicDiff,
1376 "__intrinsic_pct_change" => BuiltinFunction::IntrinsicPctChange,
1377 "__intrinsic_fillna" => BuiltinFunction::IntrinsicFillna,
1378 "__intrinsic_cumsum" => BuiltinFunction::IntrinsicCumsum,
1379 "__intrinsic_cumprod" => BuiltinFunction::IntrinsicCumprod,
1380 "__intrinsic_clip" => BuiltinFunction::IntrinsicClip,
1381
1382 "__intrinsic_sin" => BuiltinFunction::Sin,
1384 "__intrinsic_cos" => BuiltinFunction::Cos,
1385 "__intrinsic_tan" => BuiltinFunction::Tan,
1386 "__intrinsic_asin" => BuiltinFunction::Asin,
1387 "__intrinsic_acos" => BuiltinFunction::Acos,
1388 "__intrinsic_atan" => BuiltinFunction::Atan,
1389 "__intrinsic_atan2" => BuiltinFunction::IntrinsicAtan2,
1390 "__intrinsic_sinh" => BuiltinFunction::IntrinsicSinh,
1391 "__intrinsic_cosh" => BuiltinFunction::IntrinsicCosh,
1392 "__intrinsic_tanh" => BuiltinFunction::IntrinsicTanh,
1393
1394 "__intrinsic_correlation" => BuiltinFunction::IntrinsicCorrelation,
1396 "__intrinsic_covariance" => BuiltinFunction::IntrinsicCovariance,
1397 "__intrinsic_percentile" => BuiltinFunction::IntrinsicPercentile,
1398 "__intrinsic_median" => BuiltinFunction::IntrinsicMedian,
1399
1400 "__intrinsic_char_code" => BuiltinFunction::IntrinsicCharCode,
1402 "__intrinsic_from_char_code" => BuiltinFunction::IntrinsicFromCharCode,
1403
1404 "__intrinsic_series" => BuiltinFunction::IntrinsicSeries,
1406
1407 "reflect" => BuiltinFunction::Reflect,
1409
1410 "sign" => BuiltinFunction::Sign,
1412 "gcd" => BuiltinFunction::Gcd,
1413 "lcm" => BuiltinFunction::Lcm,
1414 "hypot" => BuiltinFunction::Hypot,
1415 "clamp" => BuiltinFunction::Clamp,
1416 "isNaN" | "is_nan" => BuiltinFunction::IsNaN,
1417 "isFinite" | "is_finite" => BuiltinFunction::IsFinite,
1418 "mat" => BuiltinFunction::MatFromFlat,
1419 _ => return None,
1420 };
1421
1422 let scope = match name {
1423 "Some" | "Ok" | "Err" => ResolutionScope::TypeAssociated,
1424 "print" => ResolutionScope::Prelude,
1425 _ if Self::is_internal_intrinsic_name(name) => ResolutionScope::InternalIntrinsic,
1426 _ => ResolutionScope::ModuleBinding,
1427 };
1428
1429 Some(match scope {
1430 ResolutionScope::InternalIntrinsic => {
1431 BuiltinNameResolution::InternalOnly { builtin, scope }
1432 }
1433 _ => BuiltinNameResolution::Surface { builtin, scope },
1434 })
1435 }
1436
1437 pub(super) fn is_internal_intrinsic_name(name: &str) -> bool {
1438 name.starts_with("__native_")
1439 || name.starts_with("__intrinsic_")
1440 || name.starts_with("__json_")
1441 }
1442
1443 pub(super) const fn variable_scope_summary() -> &'static str {
1444 "Variable names resolve from local scope and module scope."
1445 }
1446
1447 pub(super) const fn function_scope_summary() -> &'static str {
1448 "Function names resolve from module scope, explicit imports, type-associated scope, and the implicit prelude."
1449 }
1450
1451 pub(super) fn undefined_variable_message(&self, name: &str) -> String {
1452 format!(
1453 "Undefined variable: {}. {}",
1454 name,
1455 Self::variable_scope_summary()
1456 )
1457 }
1458
1459 pub(super) fn undefined_function_message(&self, name: &str) -> String {
1460 format!(
1461 "Undefined function: {}. {}",
1462 name,
1463 Self::function_scope_summary()
1464 )
1465 }
1466
1467 pub(super) fn internal_intrinsic_error_message(
1468 &self,
1469 name: &str,
1470 resolution: BuiltinNameResolution,
1471 ) -> String {
1472 format!(
1473 "'{}' resolves to {} and is not available from ordinary user code. Internal intrinsics are reserved for std::* implementations and compiler-generated code.",
1474 name,
1475 resolution.scope().label()
1476 )
1477 }
1478
1479 pub(super) fn builtin_requires_arg_count(&self, builtin: BuiltinFunction) -> bool {
1481 matches!(
1482 builtin,
1483 BuiltinFunction::Abs
1484 | BuiltinFunction::Min
1485 | BuiltinFunction::Max
1486 | BuiltinFunction::Sqrt
1487 | BuiltinFunction::Ln
1488 | BuiltinFunction::Pow
1489 | BuiltinFunction::Exp
1490 | BuiltinFunction::Log
1491 | BuiltinFunction::Floor
1492 | BuiltinFunction::Ceil
1493 | BuiltinFunction::Round
1494 | BuiltinFunction::Sin
1495 | BuiltinFunction::Cos
1496 | BuiltinFunction::Tan
1497 | BuiltinFunction::Asin
1498 | BuiltinFunction::Acos
1499 | BuiltinFunction::Atan
1500 | BuiltinFunction::StdDev
1501 | BuiltinFunction::Range
1502 | BuiltinFunction::Slice
1503 | BuiltinFunction::Push
1504 | BuiltinFunction::Pop
1505 | BuiltinFunction::First
1506 | BuiltinFunction::Last
1507 | BuiltinFunction::Zip
1508 | BuiltinFunction::Map
1509 | BuiltinFunction::Filter
1510 | BuiltinFunction::Reduce
1511 | BuiltinFunction::ForEach
1512 | BuiltinFunction::Find
1513 | BuiltinFunction::FindIndex
1514 | BuiltinFunction::Some
1515 | BuiltinFunction::Every
1516 | BuiltinFunction::SomeCtor
1517 | BuiltinFunction::OkCtor
1518 | BuiltinFunction::ErrCtor
1519 | BuiltinFunction::HashMapCtor
1520 | BuiltinFunction::SetCtor
1521 | BuiltinFunction::DequeCtor
1522 | BuiltinFunction::PriorityQueueCtor
1523 | BuiltinFunction::MutexCtor
1524 | BuiltinFunction::AtomicCtor
1525 | BuiltinFunction::LazyCtor
1526 | BuiltinFunction::ChannelCtor
1527 | BuiltinFunction::Print
1528 | BuiltinFunction::Format
1529 | BuiltinFunction::Len
1530 | BuiltinFunction::Snapshot
1532 | BuiltinFunction::ObjectRest
1533 | BuiltinFunction::IsNumber
1534 | BuiltinFunction::IsString
1535 | BuiltinFunction::IsBool
1536 | BuiltinFunction::IsArray
1537 | BuiltinFunction::IsObject
1538 | BuiltinFunction::IsDataRow
1539 | BuiltinFunction::ToString
1540 | BuiltinFunction::ToNumber
1541 | BuiltinFunction::ToBool
1542 | BuiltinFunction::NativePtrSize
1543 | BuiltinFunction::NativePtrNewCell
1544 | BuiltinFunction::NativePtrFreeCell
1545 | BuiltinFunction::NativePtrReadPtr
1546 | BuiltinFunction::NativePtrWritePtr
1547 | BuiltinFunction::NativeTableFromArrowC
1548 | BuiltinFunction::NativeTableFromArrowCTyped
1549 | BuiltinFunction::NativeTableBindType
1550 | BuiltinFunction::ControlFold
1551 | BuiltinFunction::IntrinsicSum
1552 | BuiltinFunction::IntrinsicMean
1553 | BuiltinFunction::IntrinsicMin
1554 | BuiltinFunction::IntrinsicMax
1555 | BuiltinFunction::IntrinsicStd
1556 | BuiltinFunction::IntrinsicVariance
1557 | BuiltinFunction::IntrinsicRandom
1558 | BuiltinFunction::IntrinsicRandomInt
1559 | BuiltinFunction::IntrinsicRandomSeed
1560 | BuiltinFunction::IntrinsicRandomNormal
1561 | BuiltinFunction::IntrinsicRandomArray
1562 | BuiltinFunction::IntrinsicDistUniform
1563 | BuiltinFunction::IntrinsicDistLognormal
1564 | BuiltinFunction::IntrinsicDistExponential
1565 | BuiltinFunction::IntrinsicDistPoisson
1566 | BuiltinFunction::IntrinsicDistSampleN
1567 | BuiltinFunction::IntrinsicBrownianMotion
1568 | BuiltinFunction::IntrinsicGbm
1569 | BuiltinFunction::IntrinsicOuProcess
1570 | BuiltinFunction::IntrinsicRandomWalk
1571 | BuiltinFunction::IntrinsicRollingSum
1572 | BuiltinFunction::IntrinsicRollingMean
1573 | BuiltinFunction::IntrinsicRollingStd
1574 | BuiltinFunction::IntrinsicRollingMin
1575 | BuiltinFunction::IntrinsicRollingMax
1576 | BuiltinFunction::IntrinsicEma
1577 | BuiltinFunction::IntrinsicLinearRecurrence
1578 | BuiltinFunction::IntrinsicShift
1579 | BuiltinFunction::IntrinsicDiff
1580 | BuiltinFunction::IntrinsicPctChange
1581 | BuiltinFunction::IntrinsicFillna
1582 | BuiltinFunction::IntrinsicCumsum
1583 | BuiltinFunction::IntrinsicCumprod
1584 | BuiltinFunction::IntrinsicClip
1585 | BuiltinFunction::IntrinsicCorrelation
1586 | BuiltinFunction::IntrinsicCovariance
1587 | BuiltinFunction::IntrinsicPercentile
1588 | BuiltinFunction::IntrinsicMedian
1589 | BuiltinFunction::IntrinsicAtan2
1590 | BuiltinFunction::IntrinsicSinh
1591 | BuiltinFunction::IntrinsicCosh
1592 | BuiltinFunction::IntrinsicTanh
1593 | BuiltinFunction::IntrinsicCharCode
1594 | BuiltinFunction::IntrinsicFromCharCode
1595 | BuiltinFunction::IntrinsicSeries
1596 | BuiltinFunction::IntrinsicVecAbs
1597 | BuiltinFunction::IntrinsicVecSqrt
1598 | BuiltinFunction::IntrinsicVecLn
1599 | BuiltinFunction::IntrinsicVecExp
1600 | BuiltinFunction::IntrinsicVecAdd
1601 | BuiltinFunction::IntrinsicVecSub
1602 | BuiltinFunction::IntrinsicVecMul
1603 | BuiltinFunction::IntrinsicVecDiv
1604 | BuiltinFunction::IntrinsicVecMax
1605 | BuiltinFunction::IntrinsicVecMin
1606 | BuiltinFunction::IntrinsicVecSelect
1607 | BuiltinFunction::IntrinsicMatMulVec
1608 | BuiltinFunction::IntrinsicMatMulMat
1609 | BuiltinFunction::Sign
1610 | BuiltinFunction::Gcd
1611 | BuiltinFunction::Lcm
1612 | BuiltinFunction::Hypot
1613 | BuiltinFunction::Clamp
1614 | BuiltinFunction::IsNaN
1615 | BuiltinFunction::IsFinite
1616 | BuiltinFunction::MatFromFlat
1617 )
1618 }
1619
1620 pub(super) fn has_any_user_defined_method(&self, method: &str) -> bool {
1625 let dot_suffix = format!(".{}", method);
1626 let colon_suffix = format!("::{}", method);
1627 self.program
1628 .functions
1629 .iter()
1630 .any(|f| f.name.ends_with(&dot_suffix) || f.name.ends_with(&colon_suffix))
1631 }
1632
1633 pub(super) fn is_known_builtin_method(method: &str) -> bool {
1637 matches!(method,
1639 "map" | "filter" | "reduce" | "forEach" | "find" | "findIndex"
1640 | "some" | "every" | "sort" | "groupBy" | "flatMap"
1641 | "len" | "length" | "first" | "last" | "reverse" | "slice"
1642 | "concat" | "take" | "drop" | "skip"
1643 | "indexOf" | "includes"
1644 | "join" | "flatten" | "unique" | "distinct" | "distinctBy"
1645 | "sum" | "avg" | "min" | "max" | "count"
1646 | "where" | "select" | "orderBy" | "thenBy" | "takeWhile"
1647 | "skipWhile" | "single" | "any" | "all"
1648 | "innerJoin" | "leftJoin" | "crossJoin"
1649 | "union" | "intersect" | "except"
1650 )
1651 || matches!(method,
1653 "columns" | "column" | "head" | "tail" | "mean" | "std"
1654 | "describe" | "aggregate" | "group_by" | "index_by" | "indexBy"
1655 | "simulate" | "toMat" | "to_mat"
1656 )
1657 || matches!(method, "toArray")
1659 || matches!(method, "resample" | "between")
1661 || matches!(method,
1663 "toFixed" | "toInt" | "toNumber" | "to_number" | "floor" | "ceil" | "round"
1664 | "abs" | "sign" | "clamp"
1665 )
1666 || matches!(method,
1668 "toUpperCase" | "toLowerCase" | "trim" | "contains" | "startsWith"
1669 | "endsWith" | "split" | "replace" | "substring" | "charAt"
1670 | "padStart" | "padEnd" | "repeat" | "toString"
1671 )
1672 || matches!(method, "keys" | "values" | "has" | "get" | "set" | "len")
1674 || matches!(method, "format")
1676 || matches!(method, "type")
1678 }
1679
1680 pub(super) fn try_track_datatable_type(
1685 &mut self,
1686 type_ann: &shape_ast::ast::TypeAnnotation,
1687 slot: u16,
1688 is_local: bool,
1689 ) -> shape_ast::error::Result<()> {
1690 use shape_ast::ast::TypeAnnotation;
1691 if let TypeAnnotation::Generic { name, args } = type_ann {
1692 if name == "Table" && args.len() == 1 {
1693 let inner_name = match &args[0] {
1694 TypeAnnotation::Reference(t) => Some(t.as_str()),
1695 TypeAnnotation::Basic(t) => Some(t.as_str()),
1696 _ => None,
1697 };
1698 if let Some(type_name) = inner_name {
1699 let schema_id = self
1700 .type_tracker
1701 .schema_registry()
1702 .get(type_name)
1703 .map(|s| s.id);
1704 if let Some(sid) = schema_id {
1705 let info = crate::type_tracking::VariableTypeInfo::datatable(
1706 sid,
1707 type_name.to_string(),
1708 );
1709 if is_local {
1710 self.type_tracker.set_local_type(slot, info);
1711 } else {
1712 self.type_tracker.set_binding_type(slot, info);
1713 }
1714 } else if type_name.len() == 1 && type_name.chars().next().map_or(false, |c| c.is_ascii_uppercase()) {
1715 } else {
1718 return Err(shape_ast::error::ShapeError::SemanticError {
1719 message: format!(
1720 "Unknown type '{}' in Table<{}> annotation",
1721 type_name, type_name
1722 ),
1723 location: None,
1724 });
1725 }
1726 }
1727 }
1728 }
1729 Ok(())
1730 }
1731
1732 pub(super) fn is_row_view_variable(&self, name: &str) -> bool {
1734 if let Some(local_idx) = self.resolve_local(name) {
1735 if let Some(info) = self.type_tracker.get_local_type(local_idx) {
1736 return info.is_row_view();
1737 }
1738 }
1739 if let Some(&binding_idx) = self.module_bindings.get(name) {
1740 if let Some(info) = self.type_tracker.get_binding_type(binding_idx) {
1741 return info.is_row_view();
1742 }
1743 }
1744 false
1745 }
1746
1747 pub(super) fn get_row_view_field_names(&self, name: &str) -> Option<Vec<String>> {
1749 let type_name = if let Some(local_idx) = self.resolve_local(name) {
1750 self.type_tracker
1751 .get_local_type(local_idx)
1752 .and_then(|info| {
1753 if info.is_row_view() {
1754 info.type_name.clone()
1755 } else {
1756 None
1757 }
1758 })
1759 } else if let Some(&binding_idx) = self.module_bindings.get(name) {
1760 self.type_tracker
1761 .get_binding_type(binding_idx)
1762 .and_then(|info| {
1763 if info.is_row_view() {
1764 info.type_name.clone()
1765 } else {
1766 None
1767 }
1768 })
1769 } else {
1770 None
1771 };
1772
1773 if let Some(tn) = type_name {
1774 if let Some(schema) = self.type_tracker.schema_registry().get(&tn) {
1775 return Some(schema.field_names().map(|n| n.to_string()).collect());
1776 }
1777 }
1778 None
1779 }
1780
1781 pub(super) fn try_resolve_row_view_column(
1787 &self,
1788 var_name: &str,
1789 field_name: &str,
1790 ) -> Option<u32> {
1791 if let Some(local_idx) = self.resolve_local(var_name) {
1793 return self
1794 .type_tracker
1795 .get_row_view_column_id(local_idx, true, field_name);
1796 }
1797 if let Some(&binding_idx) = self.module_bindings.get(var_name) {
1798 return self
1799 .type_tracker
1800 .get_row_view_column_id(binding_idx, false, field_name);
1801 }
1802 None
1803 }
1804
1805 pub(super) fn row_view_field_opcode(&self, var_name: &str, field_name: &str) -> OpCode {
1810 use shape_runtime::type_schema::FieldType;
1811
1812 let type_name = if let Some(local_idx) = self.resolve_local(var_name) {
1813 self.type_tracker
1814 .get_local_type(local_idx)
1815 .and_then(|info| info.type_name.clone())
1816 } else if let Some(&binding_idx) = self.module_bindings.get(var_name) {
1817 self.type_tracker
1818 .get_binding_type(binding_idx)
1819 .and_then(|info| info.type_name.clone())
1820 } else {
1821 None
1822 };
1823
1824 if let Some(type_name) = type_name {
1825 if let Some(schema) = self.type_tracker.schema_registry().get(&type_name) {
1826 if let Some(field) = schema.get_field(field_name) {
1827 return match field.field_type {
1828 FieldType::F64 => OpCode::LoadColF64,
1829 FieldType::I64 | FieldType::Timestamp => OpCode::LoadColI64,
1830 FieldType::Bool => OpCode::LoadColBool,
1831 FieldType::String => OpCode::LoadColStr,
1832 _ => OpCode::LoadColF64, };
1834 }
1835 }
1836 }
1837 OpCode::LoadColF64 }
1839
1840 pub(super) fn resolve_row_view_field_numeric_type(
1842 &self,
1843 var_name: &str,
1844 field_name: &str,
1845 ) -> Option<crate::type_tracking::NumericType> {
1846 use crate::type_tracking::NumericType;
1847 use shape_runtime::type_schema::FieldType;
1848
1849 let type_name = if let Some(local_idx) = self.resolve_local(var_name) {
1850 self.type_tracker
1851 .get_local_type(local_idx)
1852 .and_then(|info| info.type_name.clone())
1853 } else if let Some(&binding_idx) = self.module_bindings.get(var_name) {
1854 self.type_tracker
1855 .get_binding_type(binding_idx)
1856 .and_then(|info| info.type_name.clone())
1857 } else {
1858 None
1859 };
1860
1861 if let Some(type_name) = type_name {
1862 if let Some(schema) = self.type_tracker.schema_registry().get(&type_name) {
1863 if let Some(field) = schema.get_field(field_name) {
1864 return match field.field_type {
1865 FieldType::F64 => Some(NumericType::Number),
1866 FieldType::I64 | FieldType::Timestamp => Some(NumericType::Int),
1867 FieldType::Decimal => Some(NumericType::Decimal),
1868 _ => None,
1869 };
1870 }
1871 }
1872 }
1873 None
1874 }
1875
1876 pub(super) fn type_annotation_to_field_type(
1878 ann: &shape_ast::ast::TypeAnnotation,
1879 ) -> shape_runtime::type_schema::FieldType {
1880 use shape_ast::ast::TypeAnnotation;
1881 use shape_runtime::type_schema::FieldType;
1882 match ann {
1883 TypeAnnotation::Basic(s) => match s.as_str() {
1884 "number" | "float" | "f64" | "f32" => FieldType::F64,
1885 "i8" => FieldType::I8,
1886 "u8" => FieldType::U8,
1887 "i16" => FieldType::I16,
1888 "u16" => FieldType::U16,
1889 "i32" => FieldType::I32,
1890 "u32" => FieldType::U32,
1891 "u64" => FieldType::U64,
1892 "int" | "i64" | "integer" | "isize" | "usize" | "byte" | "char" => FieldType::I64,
1893 "string" | "str" => FieldType::String,
1894 "decimal" => FieldType::Decimal,
1895 "bool" | "boolean" => FieldType::Bool,
1896 "timestamp" => FieldType::Timestamp,
1897 other => FieldType::Object(other.to_string()),
1902 },
1903 TypeAnnotation::Reference(s) => FieldType::Object(s.to_string()),
1904 TypeAnnotation::Array(inner) => {
1905 FieldType::Array(Box::new(Self::type_annotation_to_field_type(inner)))
1906 }
1907 TypeAnnotation::Generic { name, .. } => match name.as_str() {
1908 "HashMap" | "Map" | "Result" | "Option" | "Set" => FieldType::Any,
1910 other => FieldType::Object(other.to_string()),
1912 },
1913 _ => FieldType::Any,
1914 }
1915 }
1916
1917 pub(super) fn eval_annotation_arg(expr: &shape_ast::ast::Expr) -> Option<String> {
1920 use shape_ast::ast::{Expr, Literal};
1921 match expr {
1922 Expr::Literal(Literal::String(s), _) => Some(s.clone()),
1923 Expr::Literal(Literal::Number(n), _) => Some(n.to_string()),
1924 Expr::Literal(Literal::Int(i), _) => Some(i.to_string()),
1925 Expr::Literal(Literal::Bool(b), _) => Some(b.to_string()),
1926 _ => None,
1927 }
1928 }
1929
1930 pub(super) fn get_table_schema_id(
1935 &self,
1936 type_ann: &shape_ast::ast::TypeAnnotation,
1937 ) -> Option<u16> {
1938 use shape_ast::ast::TypeAnnotation;
1939 if let TypeAnnotation::Generic { name, args } = type_ann {
1940 if name == "Table" && args.len() == 1 {
1941 let inner_name = match &args[0] {
1942 TypeAnnotation::Basic(t) => Some(t.as_str()),
1943 TypeAnnotation::Reference(t) => Some(t.as_str()),
1944 _ => None,
1945 };
1946 if let Some(type_name) = inner_name {
1947 return self
1948 .type_tracker
1949 .schema_registry()
1950 .get(type_name)
1951 .map(|s| s.id as u16);
1952 }
1953 }
1954 }
1955 None
1956 }
1957
1958 pub(super) fn push_drop_scope(&mut self) {
1962 self.drop_locals.push(Vec::new());
1963 }
1964
1965 pub(super) fn pop_drop_scope(&mut self) -> Result<()> {
1968 if let Some(locals) = self.drop_locals.pop() {
1970 for (local_idx, is_async) in locals.into_iter().rev() {
1971 self.emit_drop_call_for_local(local_idx, is_async);
1972 }
1973 }
1974 Ok(())
1975 }
1976
1977 fn emit_drop_call_for_local(&mut self, local_idx: u16, is_async: bool) {
1981 let type_name_opt = self
1982 .type_tracker
1983 .get_local_type(local_idx)
1984 .and_then(|info| info.type_name.clone());
1985 self.emit(Instruction::new(
1986 OpCode::LoadLocal,
1987 Some(Operand::Local(local_idx)),
1988 ));
1989 let opcode = if is_async {
1990 OpCode::DropCallAsync
1991 } else {
1992 OpCode::DropCall
1993 };
1994 if let Some(type_name) = type_name_opt {
1995 let str_idx = self.program.add_string(type_name);
1996 self.emit(Instruction::new(opcode, Some(Operand::Property(str_idx))));
1997 } else {
1998 self.emit(Instruction::simple(opcode));
1999 }
2000 }
2001
2002 pub(super) fn emit_drop_call_for_module_binding(&mut self, binding_idx: u16, is_async: bool) {
2005 let type_name_opt = self
2006 .type_tracker
2007 .get_binding_type(binding_idx)
2008 .and_then(|info| info.type_name.clone());
2009 self.emit(Instruction::new(
2010 OpCode::LoadModuleBinding,
2011 Some(Operand::ModuleBinding(binding_idx)),
2012 ));
2013 let opcode = if is_async {
2014 OpCode::DropCallAsync
2015 } else {
2016 OpCode::DropCall
2017 };
2018 if let Some(type_name) = type_name_opt {
2019 let str_idx = self.program.add_string(type_name);
2020 self.emit(Instruction::new(opcode, Some(Operand::Property(str_idx))));
2021 } else {
2022 self.emit(Instruction::simple(opcode));
2023 }
2024 }
2025
2026 pub(super) fn track_drop_local(&mut self, local_idx: u16, is_async: bool) {
2028 if let Some(scope) = self.drop_locals.last_mut() {
2029 scope.push((local_idx, is_async));
2030 }
2031 }
2032
2033 pub(super) fn local_drop_kind(&self, local_idx: u16) -> Option<DropKind> {
2036 let type_name = self
2037 .type_tracker
2038 .get_local_type(local_idx)
2039 .and_then(|info| info.type_name.as_ref())?;
2040 self.drop_type_info.get(type_name).copied()
2041 }
2042
2043 pub(super) fn annotation_drop_kind(&self, type_ann: &TypeAnnotation) -> Option<DropKind> {
2045 let type_name = Self::tracked_type_name_from_annotation(type_ann)?;
2046 self.drop_type_info.get(&type_name).copied()
2047 }
2048
2049 pub(super) fn emit_drops_for_early_exit(&mut self, scopes_to_exit: usize) -> Result<()> {
2052 let total = self.drop_locals.len();
2053 if scopes_to_exit > total {
2054 return Ok(());
2055 }
2056 let mut scopes: Vec<Vec<(u16, bool)>> = Vec::new();
2058 for i in (total - scopes_to_exit..total).rev() {
2059 let locals = self.drop_locals.get(i).cloned().unwrap_or_default();
2060 scopes.push(locals);
2061 }
2062 for locals in scopes {
2064 for (local_idx, is_async) in locals.into_iter().rev() {
2065 self.emit_drop_call_for_local(local_idx, is_async);
2066 }
2067 }
2068 Ok(())
2069 }
2070
2071 pub(super) fn track_drop_module_binding(&mut self, binding_idx: u16, is_async: bool) {
2073 self.drop_module_bindings.push((binding_idx, is_async));
2074 }
2075}
2076
2077
2078#[cfg(test)]
2079mod tests {
2080 use super::super::BytecodeCompiler;
2081 use crate::compiler::ParamPassMode;
2082 use crate::type_tracking::BindingStorageClass;
2083 use shape_ast::ast::{Expr, Span, TypeAnnotation};
2084 use shape_runtime::type_schema::FieldType;
2085
2086 #[test]
2087 fn test_type_annotation_to_field_type_array_recursive() {
2088 let ann = TypeAnnotation::Array(Box::new(TypeAnnotation::Basic("int".to_string())));
2089 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
2090 assert_eq!(ft, FieldType::Array(Box::new(FieldType::I64)));
2091 }
2092
2093 #[test]
2094 fn test_type_annotation_to_field_type_optional() {
2095 let ann = TypeAnnotation::Generic {
2096 name: "Option".into(),
2097 args: vec![TypeAnnotation::Basic("int".to_string())],
2098 };
2099 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
2100 assert_eq!(ft, FieldType::Any);
2101 }
2102
2103 #[test]
2104 fn test_type_annotation_to_field_type_generic_hashmap() {
2105 let ann = TypeAnnotation::Generic {
2106 name: "HashMap".into(),
2107 args: vec![
2108 TypeAnnotation::Basic("string".to_string()),
2109 TypeAnnotation::Basic("int".to_string()),
2110 ],
2111 };
2112 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
2113 assert_eq!(ft, FieldType::Any);
2114 }
2115
2116 #[test]
2117 fn test_type_annotation_to_field_type_generic_user_struct() {
2118 let ann = TypeAnnotation::Generic {
2119 name: "MyContainer".into(),
2120 args: vec![TypeAnnotation::Basic("string".to_string())],
2121 };
2122 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
2123 assert_eq!(ft, FieldType::Object("MyContainer".to_string()));
2124 }
2125
2126 #[test]
2127 fn test_flexible_storage_promotion_is_monotonic() {
2128 let mut compiler = BytecodeCompiler::new();
2129 compiler.push_scope();
2130 let slot = compiler.declare_local("value").expect("declare local");
2131 compiler.type_tracker.set_local_binding_semantics(
2132 slot,
2133 BytecodeCompiler::binding_semantics_for_ownership_class(
2134 crate::type_tracking::BindingOwnershipClass::Flexible,
2135 ),
2136 );
2137
2138 compiler.promote_flexible_binding_storage_for_slot(
2139 slot,
2140 true,
2141 BindingStorageClass::UniqueHeap,
2142 );
2143 assert_eq!(
2144 compiler
2145 .type_tracker
2146 .get_local_binding_semantics(slot)
2147 .map(|semantics| semantics.storage_class),
2148 Some(BindingStorageClass::UniqueHeap)
2149 );
2150
2151 compiler.promote_flexible_binding_storage_for_slot(slot, true, BindingStorageClass::Direct);
2152 assert_eq!(
2153 compiler
2154 .type_tracker
2155 .get_local_binding_semantics(slot)
2156 .map(|semantics| semantics.storage_class),
2157 Some(BindingStorageClass::UniqueHeap)
2158 );
2159
2160 compiler.promote_flexible_binding_storage_for_slot(
2161 slot,
2162 true,
2163 BindingStorageClass::SharedCow,
2164 );
2165 assert_eq!(
2166 compiler
2167 .type_tracker
2168 .get_local_binding_semantics(slot)
2169 .map(|semantics| semantics.storage_class),
2170 Some(BindingStorageClass::SharedCow)
2171 );
2172 }
2173
2174 #[test]
2175 fn test_escape_planner_marks_array_element_identifier_as_unique_heap() {
2176 let mut compiler = BytecodeCompiler::new();
2177 compiler.push_scope();
2178 let slot = compiler.declare_local("value").expect("declare local");
2179 compiler.type_tracker.set_local_binding_semantics(
2180 slot,
2181 BytecodeCompiler::binding_semantics_for_ownership_class(
2182 crate::type_tracking::BindingOwnershipClass::Flexible,
2183 ),
2184 );
2185
2186 let expr = Expr::Array(
2187 vec![Expr::Identifier("value".to_string(), Span::DUMMY)],
2188 Span::DUMMY,
2189 );
2190 compiler.plan_flexible_binding_escape_from_expr(&expr);
2191
2192 assert_eq!(
2193 compiler
2194 .type_tracker
2195 .get_local_binding_semantics(slot)
2196 .map(|semantics| semantics.storage_class),
2197 Some(BindingStorageClass::UniqueHeap)
2198 );
2199 }
2200
2201 #[test]
2202 fn test_escape_planner_marks_if_branch_identifier_as_unique_heap() {
2203 let mut compiler = BytecodeCompiler::new();
2204 compiler.push_scope();
2205 let slot = compiler.declare_local("value").expect("declare local");
2206 compiler.type_tracker.set_local_binding_semantics(
2207 slot,
2208 BytecodeCompiler::binding_semantics_for_ownership_class(
2209 crate::type_tracking::BindingOwnershipClass::Flexible,
2210 ),
2211 );
2212
2213 let expr = Expr::If(
2214 Box::new(shape_ast::ast::IfExpr {
2215 condition: Box::new(Expr::Literal(
2216 shape_ast::ast::Literal::Bool(true),
2217 Span::DUMMY,
2218 )),
2219 then_branch: Box::new(Expr::Identifier("value".to_string(), Span::DUMMY)),
2220 else_branch: None,
2221 }),
2222 Span::DUMMY,
2223 );
2224 compiler.plan_flexible_binding_escape_from_expr(&expr);
2225
2226 assert_eq!(
2227 compiler
2228 .type_tracker
2229 .get_local_binding_semantics(slot)
2230 .map(|semantics| semantics.storage_class),
2231 Some(BindingStorageClass::UniqueHeap)
2232 );
2233 }
2234
2235 #[test]
2236 fn test_escape_planner_marks_async_let_rhs_identifier_as_unique_heap() {
2237 let mut compiler = BytecodeCompiler::new();
2238 compiler.push_scope();
2239 let slot = compiler.declare_local("value").expect("declare local");
2240 compiler.type_tracker.set_local_binding_semantics(
2241 slot,
2242 BytecodeCompiler::binding_semantics_for_ownership_class(
2243 crate::type_tracking::BindingOwnershipClass::Flexible,
2244 ),
2245 );
2246
2247 let expr = Expr::AsyncLet(
2248 Box::new(shape_ast::ast::AsyncLetExpr {
2249 name: "task".to_string(),
2250 expr: Box::new(Expr::Identifier("value".to_string(), Span::DUMMY)),
2251 span: Span::DUMMY,
2252 }),
2253 Span::DUMMY,
2254 );
2255 compiler.plan_flexible_binding_escape_from_expr(&expr);
2256
2257 assert_eq!(
2258 compiler
2259 .type_tracker
2260 .get_local_binding_semantics(slot)
2261 .map(|semantics| semantics.storage_class),
2262 Some(BindingStorageClass::UniqueHeap)
2263 );
2264 }
2265
2266 #[test]
2267 fn test_call_args_mark_by_value_identifier_as_unique_heap() {
2268 let mut compiler = BytecodeCompiler::new();
2269 compiler.push_scope();
2270 let slot = compiler.declare_local("value").expect("declare local");
2271 compiler.type_tracker.set_local_binding_semantics(
2272 slot,
2273 BytecodeCompiler::binding_semantics_for_ownership_class(
2274 crate::type_tracking::BindingOwnershipClass::Flexible,
2275 ),
2276 );
2277
2278 compiler
2279 .compile_call_args(&[Expr::Identifier("value".to_string(), Span::DUMMY)], None)
2280 .expect("call args should compile");
2281
2282 assert_eq!(
2283 compiler
2284 .type_tracker
2285 .get_local_binding_semantics(slot)
2286 .map(|semantics| semantics.storage_class),
2287 Some(BindingStorageClass::UniqueHeap)
2288 );
2289 }
2290
2291 #[test]
2292 fn test_call_args_leave_by_ref_identifier_storage_unchanged() {
2293 let mut compiler = BytecodeCompiler::new();
2294 compiler.push_scope();
2295 let slot = compiler.declare_local("value").expect("declare local");
2296 compiler.type_tracker.set_local_binding_semantics(
2297 slot,
2298 BytecodeCompiler::binding_semantics_for_ownership_class(
2299 crate::type_tracking::BindingOwnershipClass::Flexible,
2300 ),
2301 );
2302
2303 compiler
2304 .compile_call_args(
2305 &[Expr::Identifier("value".to_string(), Span::DUMMY)],
2306 Some(&[ParamPassMode::ByRefShared]),
2307 )
2308 .expect("reference call args should compile");
2309
2310 assert_eq!(
2311 compiler
2312 .type_tracker
2313 .get_local_binding_semantics(slot)
2314 .map(|semantics| semantics.storage_class),
2315 Some(BindingStorageClass::Deferred)
2316 );
2317 }
2318}