1use crate::borrow_checker::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::{BytecodeCompiler, DropKind, ParamPassMode};
11
12pub(crate) fn strip_error_prefix(e: &ShapeError) -> String {
18 let msg = e.to_string();
19 const PREFIXES: &[&str] = &[
21 "Runtime error: ",
22 "Type error: ",
23 "Semantic error: ",
24 "Parse error: ",
25 "VM error: ",
26 "Lexical error: ",
27 ];
28 let mut s = msg.as_str();
29 for _ in 0..3 {
31 let mut stripped = false;
32 for prefix in PREFIXES {
33 if let Some(rest) = s.strip_prefix(prefix) {
34 s = rest;
35 stripped = true;
36 break;
37 }
38 }
39 const COMPTIME_PREFIXES: &[&str] = &[
41 "Comptime block evaluation failed: ",
42 "Comptime handler execution failed: ",
43 "Comptime block directive processing failed: ",
44 ];
45 for prefix in COMPTIME_PREFIXES {
46 if let Some(rest) = s.strip_prefix(prefix) {
47 s = rest;
48 stripped = true;
49 break;
50 }
51 }
52 if !stripped {
53 break;
54 }
55 }
56 s.to_string()
57}
58
59impl BytecodeCompiler {
60 fn scalar_type_name_from_numeric(numeric_type: NumericType) -> &'static str {
61 match numeric_type {
62 NumericType::Int | NumericType::IntWidth(_) => "int",
63 NumericType::Number => "number",
64 NumericType::Decimal => "decimal",
65 }
66 }
67
68 fn array_type_name_from_numeric(numeric_type: NumericType) -> &'static str {
69 match numeric_type {
70 NumericType::Int | NumericType::IntWidth(_) => "Vec<int>",
71 NumericType::Number => "Vec<number>",
72 NumericType::Decimal => "Vec<decimal>",
73 }
74 }
75
76 fn is_array_type_name(type_name: Option<&str>) -> bool {
77 matches!(type_name, Some(name) if name.starts_with("Vec<") && name.ends_with('>'))
78 }
79
80 pub(super) fn tracked_type_name_from_annotation(type_ann: &TypeAnnotation) -> Option<String> {
83 match type_ann {
84 TypeAnnotation::Basic(name) | TypeAnnotation::Reference(name) => Some(name.clone()),
85 TypeAnnotation::Array(inner) => Some(format!("Vec<{}>", inner.to_type_string())),
86 TypeAnnotation::Generic { name, args } if name == "Vec" && args.len() == 1 => {
88 Some(format!("Vec<{}>", args[0].to_type_string()))
89 }
90 TypeAnnotation::Generic { name, args } if name == "Mat" && args.len() == 1 => {
91 Some(format!("Mat<{}>", args[0].to_type_string()))
92 }
93 _ => None,
94 }
95 }
96
97 pub(super) fn mark_slot_as_numeric_array(
102 &mut self,
103 slot: u16,
104 is_local: bool,
105 numeric_type: NumericType,
106 ) {
107 let info =
108 VariableTypeInfo::named(Self::array_type_name_from_numeric(numeric_type).to_string());
109 if is_local {
110 self.type_tracker.set_local_type(slot, info);
111 } else {
112 self.type_tracker.set_binding_type(slot, info);
113 }
114 }
115
116 pub(super) fn mark_slot_as_numeric_scalar(
118 &mut self,
119 slot: u16,
120 is_local: bool,
121 numeric_type: NumericType,
122 ) {
123 let info =
124 VariableTypeInfo::named(Self::scalar_type_name_from_numeric(numeric_type).to_string());
125 if is_local {
126 self.type_tracker.set_local_type(slot, info);
127 } else {
128 self.type_tracker.set_binding_type(slot, info);
129 }
130 }
131
132 pub(super) fn seed_numeric_hint_from_expr(
137 &mut self,
138 expr: &shape_ast::ast::Expr,
139 numeric_type: NumericType,
140 ) {
141 match expr {
142 shape_ast::ast::Expr::Identifier(name, _) => {
143 if let Some(local_idx) = self.resolve_local(name) {
144 self.mark_slot_as_numeric_scalar(local_idx, true, numeric_type);
145 return;
146 }
147 let scoped_name = self
148 .resolve_scoped_module_binding_name(name)
149 .unwrap_or_else(|| name.to_string());
150 if let Some(binding_idx) = self.module_bindings.get(&scoped_name).copied() {
151 self.mark_slot_as_numeric_scalar(binding_idx, false, numeric_type);
152 }
153 }
154 shape_ast::ast::Expr::IndexAccess {
155 object,
156 end_index: None,
157 ..
158 } => {
159 if let shape_ast::ast::Expr::Identifier(name, _) = object.as_ref() {
160 if let Some(local_idx) = self.resolve_local(name) {
161 self.mark_slot_as_numeric_array(local_idx, true, numeric_type);
162 return;
163 }
164 let scoped_name = self
165 .resolve_scoped_module_binding_name(name)
166 .unwrap_or_else(|| name.to_string());
167 if let Some(binding_idx) = self.module_bindings.get(&scoped_name).copied() {
168 self.mark_slot_as_numeric_array(binding_idx, false, numeric_type);
169 }
170 }
171 }
172 _ => {}
173 }
174 }
175
176 fn recover_or_bail_with_null_placeholder(&mut self, err: ShapeError) -> Result<()> {
177 if self.should_recover_compile_diagnostics() {
178 self.errors.push(err);
179 self.emit(Instruction::simple(OpCode::PushNull));
180 Ok(())
181 } else {
182 Err(err)
183 }
184 }
185
186 pub(super) fn compile_expr_as_value_or_placeholder(
187 &mut self,
188 expr: &shape_ast::ast::Expr,
189 ) -> Result<()> {
190 match self.compile_expr(expr) {
191 Ok(()) => Ok(()),
192 Err(err) => self.recover_or_bail_with_null_placeholder(err),
193 }
194 }
195
196 pub(super) fn emit(&mut self, instruction: Instruction) -> usize {
199 let idx = self.program.emit(instruction);
200 if self.current_line > 0 {
202 self.program.debug_info.line_numbers.push((
203 idx,
204 self.current_file_id,
205 self.current_line,
206 ));
207 }
208 idx
209 }
210
211 pub(super) fn emit_bool(&mut self, value: bool) {
213 let const_idx = self.program.add_constant(Constant::Bool(value));
214 self.emit(Instruction::new(
215 OpCode::PushConst,
216 Some(Operand::Const(const_idx)),
217 ));
218 }
219
220 pub(super) fn emit_unit(&mut self) {
222 let const_idx = self.program.add_constant(Constant::Unit);
223 self.emit(Instruction::new(
224 OpCode::PushConst,
225 Some(Operand::Const(const_idx)),
226 ));
227 }
228
229 pub(super) fn emit_jump(&mut self, mut opcode: OpCode, dummy: i32) -> usize {
235 if opcode == OpCode::JumpIfFalse && self.last_instruction_produces_bool() {
236 opcode = OpCode::JumpIfFalseTrusted;
237 }
238 self.emit(Instruction::new(opcode, Some(Operand::Offset(dummy))))
239 }
240
241 fn last_instruction_produces_bool(&self) -> bool {
243 self.program
244 .instructions
245 .last()
246 .map(|instr| {
247 matches!(
248 instr.opcode,
249 OpCode::GtInt
250 | OpCode::GtNumber
251 | OpCode::GtDecimal
252 | OpCode::LtInt
253 | OpCode::LtNumber
254 | OpCode::LtDecimal
255 | OpCode::GteInt
256 | OpCode::GteNumber
257 | OpCode::GteDecimal
258 | OpCode::LteInt
259 | OpCode::LteNumber
260 | OpCode::LteDecimal
261 | OpCode::EqInt
262 | OpCode::EqNumber
263 | OpCode::NeqInt
264 | OpCode::NeqNumber
265 | OpCode::Gt
266 | OpCode::Lt
267 | OpCode::Gte
268 | OpCode::Lte
269 | OpCode::Eq
270 | OpCode::Neq
271 | OpCode::Not
272 | OpCode::GtIntTrusted
273 | OpCode::LtIntTrusted
274 | OpCode::GteIntTrusted
275 | OpCode::LteIntTrusted
276 | OpCode::GtNumberTrusted
277 | OpCode::LtNumberTrusted
278 | OpCode::GteNumberTrusted
279 | OpCode::LteNumberTrusted
280 )
281 })
282 .unwrap_or(false)
283 }
284
285 pub(super) fn patch_jump(&mut self, jump_idx: usize) {
287 let offset = self.program.current_offset() as i32 - jump_idx as i32 - 1;
288 self.program.instructions[jump_idx] = Instruction::new(
289 self.program.instructions[jump_idx].opcode,
290 Some(Operand::Offset(offset)),
291 );
292 }
293
294 pub(super) fn compile_call_args(
301 &mut self,
302 args: &[shape_ast::ast::Expr],
303 expected_param_modes: Option<&[ParamPassMode]>,
304 ) -> Result<Vec<(u16, u16)>> {
305 let saved = self.in_call_args;
306 let saved_mode = self.current_call_arg_borrow_mode;
307 self.in_call_args = true;
308 self.borrow_checker.enter_region();
309 self.call_arg_module_binding_ref_writebacks.push(Vec::new());
310
311 let mut first_error: Option<ShapeError> = None;
312 for (idx, arg) in args.iter().enumerate() {
313 let pass_mode = expected_param_modes
314 .and_then(|modes| modes.get(idx).copied())
315 .unwrap_or(ParamPassMode::ByValue);
316 self.current_call_arg_borrow_mode = match pass_mode {
317 ParamPassMode::ByRefExclusive => Some(BorrowMode::Exclusive),
318 ParamPassMode::ByRefShared => Some(BorrowMode::Shared),
319 ParamPassMode::ByValue => None,
320 };
321
322 let arg_result = match pass_mode {
323 ParamPassMode::ByRefExclusive | ParamPassMode::ByRefShared => {
324 let borrow_mode = if pass_mode.is_exclusive() {
325 BorrowMode::Exclusive
326 } else {
327 BorrowMode::Shared
328 };
329 if matches!(arg, shape_ast::ast::Expr::Reference { .. }) {
330 self.compile_expr(arg)
331 } else {
332 self.compile_implicit_reference_arg(arg, borrow_mode)
333 }
334 }
335 ParamPassMode::ByValue => {
336 if let shape_ast::ast::Expr::Reference { span, .. } = arg {
337 let message = if expected_param_modes.is_some() {
338 "[B0004] unexpected `&` argument: target parameter is not a reference parameter".to_string()
339 } else {
340 "[B0004] cannot pass `&` to a callable value without a declared reference contract; \
341 call a named function with known parameter modes or add an explicit callable type"
342 .to_string()
343 };
344 Err(ShapeError::SemanticError {
345 message,
346 location: Some(self.span_to_source_location(*span)),
347 })
348 } else {
349 self.compile_expr(arg)
350 }
351 }
352 };
353
354 if let Err(err) = arg_result {
355 if self.should_recover_compile_diagnostics() {
356 self.errors.push(err);
357 self.emit(Instruction::simple(OpCode::PushNull));
359 continue;
360 }
361 first_error = Some(err);
362 break;
363 }
364 }
365
366 self.current_call_arg_borrow_mode = saved_mode;
367 self.borrow_checker.exit_region();
368 self.in_call_args = saved;
369 let writebacks = self
370 .call_arg_module_binding_ref_writebacks
371 .pop()
372 .unwrap_or_default();
373 if let Some(err) = first_error {
374 Err(err)
375 } else {
376 Ok(writebacks)
377 }
378 }
379
380 pub(super) fn current_arg_borrow_mode(&self) -> BorrowMode {
381 self.current_call_arg_borrow_mode
382 .unwrap_or(BorrowMode::Exclusive)
383 }
384
385 pub(super) fn record_call_arg_module_binding_writeback(
386 &mut self,
387 local: u16,
388 module_binding: u16,
389 ) {
390 if let Some(stack) = self.call_arg_module_binding_ref_writebacks.last_mut() {
391 stack.push((local, module_binding));
392 }
393 }
394
395 fn compile_implicit_reference_arg(
396 &mut self,
397 arg: &shape_ast::ast::Expr,
398 mode: BorrowMode,
399 ) -> Result<()> {
400 use shape_ast::ast::Expr;
401 match arg {
402 Expr::Identifier(name, span) => self.compile_reference_identifier(name, *span, mode),
403 _ if mode == BorrowMode::Exclusive => Err(ShapeError::SemanticError {
404 message: "[B0004] mutable reference arguments must be simple variables".to_string(),
405 location: Some(self.span_to_source_location(arg.span())),
406 }),
407 _ => {
408 self.compile_expr(arg)?;
409 let temp = self.declare_temp_local("__arg_ref_")?;
410 self.emit(Instruction::new(
411 OpCode::StoreLocal,
412 Some(Operand::Local(temp)),
413 ));
414 let source_loc = self.span_to_source_location(arg.span());
415 self.borrow_checker.create_borrow(
416 temp,
417 temp,
418 mode,
419 arg.span(),
420 Some(source_loc),
421 )?;
422 self.emit(Instruction::new(
423 OpCode::MakeRef,
424 Some(Operand::Local(temp)),
425 ));
426 Ok(())
427 }
428 }
429 }
430
431 pub(super) fn compile_reference_identifier(
432 &mut self,
433 name: &str,
434 span: shape_ast::ast::Span,
435 mode: BorrowMode,
436 ) -> Result<()> {
437 if let Some(local_idx) = self.resolve_local(name) {
438 if mode == BorrowMode::Exclusive && self.const_locals.contains(&local_idx) {
440 return Err(ShapeError::SemanticError {
441 message: format!(
442 "Cannot pass const variable '{}' by exclusive reference",
443 name
444 ),
445 location: Some(self.span_to_source_location(span)),
446 });
447 }
448 if mode == BorrowMode::Exclusive && self.immutable_locals.contains(&local_idx) {
450 return Err(ShapeError::SemanticError {
451 message: format!(
452 "Cannot borrow immutable variable '{}' as exclusive (&mut). Use `let mut` or `var` for mutable bindings",
453 name
454 ),
455 location: Some(self.span_to_source_location(span)),
456 });
457 }
458 if self.ref_locals.contains(&local_idx) {
459 self.emit(Instruction::new(
461 OpCode::LoadLocal,
462 Some(Operand::Local(local_idx)),
463 ));
464 return Ok(());
465 }
466 let source_loc = self.span_to_source_location(span);
467 self.borrow_checker
468 .create_borrow(local_idx, local_idx, mode, span, Some(source_loc))
469 .map_err(|e| match e {
470 ShapeError::SemanticError { message, location } => {
471 let user_msg = message
472 .replace(&format!("(slot {})", local_idx), &format!("'{}'", name));
473 ShapeError::SemanticError {
474 message: user_msg,
475 location,
476 }
477 }
478 other => other,
479 })?;
480 self.emit(Instruction::new(
481 OpCode::MakeRef,
482 Some(Operand::Local(local_idx)),
483 ));
484 Ok(())
485 } else if let Some(scoped_name) = self.resolve_scoped_module_binding_name(name) {
486 let Some(&binding_idx) = self.module_bindings.get(&scoped_name) else {
487 return Err(ShapeError::SemanticError {
488 message: format!(
489 "[B0004] reference argument must be a local or module_binding variable, got '{}'",
490 name
491 ),
492 location: Some(self.span_to_source_location(span)),
493 });
494 };
495 if mode == BorrowMode::Exclusive && self.const_module_bindings.contains(&binding_idx) {
497 return Err(ShapeError::SemanticError {
498 message: format!(
499 "Cannot pass const variable '{}' by exclusive reference",
500 name
501 ),
502 location: Some(self.span_to_source_location(span)),
503 });
504 }
505 if mode == BorrowMode::Exclusive
507 && self.immutable_module_bindings.contains(&binding_idx)
508 {
509 return Err(ShapeError::SemanticError {
510 message: format!(
511 "Cannot borrow immutable variable '{}' as exclusive (&mut). Use `let mut` or `var` for mutable bindings",
512 name
513 ),
514 location: Some(self.span_to_source_location(span)),
515 });
516 }
517 let shadow_local = self.declare_temp_local("__module_binding_ref_shadow_")?;
519 self.emit(Instruction::new(
520 OpCode::LoadModuleBinding,
521 Some(Operand::ModuleBinding(binding_idx)),
522 ));
523 self.emit(Instruction::new(
524 OpCode::StoreLocal,
525 Some(Operand::Local(shadow_local)),
526 ));
527 let source_loc = self.span_to_source_location(span);
528 self.borrow_checker.create_borrow(
529 shadow_local,
530 shadow_local,
531 mode,
532 span,
533 Some(source_loc),
534 )?;
535 self.emit(Instruction::new(
536 OpCode::MakeRef,
537 Some(Operand::Local(shadow_local)),
538 ));
539 self.record_call_arg_module_binding_writeback(shadow_local, binding_idx);
540 Ok(())
541 } else if let Some(func_idx) = self.find_function(name) {
542 let temp = self.declare_temp_local("__fn_ref_")?;
545 let const_idx = self
546 .program
547 .add_constant(Constant::Function(func_idx as u16));
548 self.emit(Instruction::new(
549 OpCode::PushConst,
550 Some(Operand::Const(const_idx)),
551 ));
552 self.emit(Instruction::new(
553 OpCode::StoreLocal,
554 Some(Operand::Local(temp)),
555 ));
556 let source_loc = self.span_to_source_location(span);
557 self.borrow_checker
558 .create_borrow(temp, temp, mode, span, Some(source_loc))?;
559 self.emit(Instruction::new(
560 OpCode::MakeRef,
561 Some(Operand::Local(temp)),
562 ));
563 Ok(())
564 } else {
565 Err(ShapeError::SemanticError {
566 message: format!(
567 "[B0004] reference argument must be a local or module_binding variable, got '{}'",
568 name
569 ),
570 location: Some(self.span_to_source_location(span)),
571 })
572 }
573 }
574
575 pub(super) fn push_scope(&mut self) {
577 self.locals.push(HashMap::new());
578 self.type_tracker.push_scope();
579 self.borrow_checker.enter_region();
580 }
581
582 pub(super) fn pop_scope(&mut self) {
584 self.borrow_checker.exit_region();
585 self.locals.pop();
586 self.type_tracker.pop_scope();
587 }
588
589 pub(super) fn declare_local(&mut self, name: &str) -> Result<u16> {
591 let idx = self.next_local;
592 self.next_local += 1;
593
594 if let Some(scope) = self.locals.last_mut() {
595 scope.insert(name.to_string(), idx);
596 }
597
598 Ok(idx)
599 }
600
601 pub(super) fn resolve_local(&self, name: &str) -> Option<u16> {
603 for scope in self.locals.iter().rev() {
604 if let Some(&idx) = scope.get(name) {
605 return Some(idx);
606 }
607 }
608 None
609 }
610
611 pub(super) fn declare_temp_local(&mut self, prefix: &str) -> Result<u16> {
613 let name = format!("{}{}", prefix, self.next_local);
614 self.declare_local(&name)
615 }
616
617 pub(super) fn set_local_type_info(&mut self, slot: u16, type_name: &str) {
619 let info = if let Some(schema) = self.type_tracker.schema_registry().get(type_name) {
620 VariableTypeInfo::known(schema.id, type_name.to_string())
621 } else {
622 VariableTypeInfo::named(type_name.to_string())
623 };
624 self.type_tracker.set_local_type(slot, info);
625 }
626
627 pub(super) fn set_module_binding_type_info(&mut self, slot: u16, type_name: &str) {
629 let info = if let Some(schema) = self.type_tracker.schema_registry().get(type_name) {
630 VariableTypeInfo::known(schema.id, type_name.to_string())
631 } else {
632 VariableTypeInfo::named(type_name.to_string())
633 };
634 self.type_tracker.set_binding_type(slot, info);
635 }
636
637 pub(super) fn capture_function_local_storage_hints(&mut self, func_idx: usize) {
643 let Some(func) = self.program.functions.get(func_idx) else {
644 return;
645 };
646 let hints: Vec<StorageHint> = (0..func.locals_count)
647 .map(|slot| self.type_tracker.get_local_storage_hint(slot))
648 .collect();
649
650 let has_any_known = hints.iter().any(|h| *h != StorageHint::Unknown);
652 if has_any_known {
653 self.program.functions[func_idx].frame_descriptor = Some(
654 crate::type_tracking::FrameDescriptor::from_slots(hints.clone()),
655 );
656 }
657
658 if self.program.function_local_storage_hints.len() <= func_idx {
659 self.program
660 .function_local_storage_hints
661 .resize(func_idx + 1, Vec::new());
662 }
663 self.program.function_local_storage_hints[func_idx] = hints;
664 }
665
666 pub(super) fn populate_program_storage_hints(&mut self) {
668 let top_hints: Vec<StorageHint> = (0..self.next_local)
669 .map(|slot| self.type_tracker.get_local_storage_hint(slot))
670 .collect();
671 self.program.top_level_local_storage_hints = top_hints.clone();
672
673 let has_any_known = top_hints.iter().any(|h| *h != StorageHint::Unknown);
675 if has_any_known {
676 self.program.top_level_frame =
677 Some(crate::type_tracking::FrameDescriptor::from_slots(top_hints));
678 }
679
680 let mut module_binding_hints = vec![StorageHint::Unknown; self.module_bindings.len()];
681 for &idx in self.module_bindings.values() {
682 if let Some(slot) = module_binding_hints.get_mut(idx as usize) {
683 *slot = self.type_tracker.get_module_binding_storage_hint(idx);
684 }
685 }
686 self.program.module_binding_storage_hints = module_binding_hints;
687
688 if self.program.function_local_storage_hints.len() < self.program.functions.len() {
689 self.program
690 .function_local_storage_hints
691 .resize(self.program.functions.len(), Vec::new());
692 } else if self.program.function_local_storage_hints.len() > self.program.functions.len() {
693 self.program
694 .function_local_storage_hints
695 .truncate(self.program.functions.len());
696 }
697 }
698
699 pub(super) fn propagate_assignment_type_to_slot(
704 &mut self,
705 slot: u16,
706 is_local: bool,
707 allow_number_hint: bool,
708 ) {
709 if let Some(ref info) = self.last_expr_type_info {
710 if info.is_indexed()
711 || info.is_datatable()
712 || info.schema_id.is_some()
713 || Self::is_array_type_name(info.type_name.as_deref())
714 {
715 if is_local {
716 self.type_tracker.set_local_type(slot, info.clone());
717 } else {
718 self.type_tracker.set_binding_type(slot, info.clone());
719 }
720 return;
721 }
722 }
723
724 if let Some(schema_id) = self.last_expr_schema {
725 let schema_name = self
726 .type_tracker
727 .schema_registry()
728 .get_by_id(schema_id)
729 .map(|s| s.name.clone())
730 .unwrap_or_else(|| format!("__anon_{}", schema_id));
731 let info = VariableTypeInfo::known(schema_id, schema_name);
732 if is_local {
733 self.type_tracker.set_local_type(slot, info);
734 } else {
735 self.type_tracker.set_binding_type(slot, info);
736 }
737 return;
738 }
739
740 if let Some(numeric_type) = self.last_expr_numeric_type {
741 let (type_name, hint) = match numeric_type {
742 crate::type_tracking::NumericType::Int => ("int", StorageHint::Int64),
743 crate::type_tracking::NumericType::IntWidth(w) => {
744 use shape_ast::IntWidth;
745 let hint = match w {
746 IntWidth::I8 => StorageHint::Int8,
747 IntWidth::U8 => StorageHint::UInt8,
748 IntWidth::I16 => StorageHint::Int16,
749 IntWidth::U16 => StorageHint::UInt16,
750 IntWidth::I32 => StorageHint::Int32,
751 IntWidth::U32 => StorageHint::UInt32,
752 IntWidth::U64 => StorageHint::UInt64,
753 };
754 (w.type_name(), hint)
755 }
756 crate::type_tracking::NumericType::Number => {
757 if !allow_number_hint {
758 if is_local {
759 self.type_tracker
760 .set_local_type(slot, VariableTypeInfo::unknown());
761 } else {
762 self.type_tracker
763 .set_binding_type(slot, VariableTypeInfo::unknown());
764 }
765 return;
766 }
767 ("number", StorageHint::Float64)
768 }
769 crate::type_tracking::NumericType::Decimal => {
771 if is_local {
772 self.type_tracker
773 .set_local_type(slot, VariableTypeInfo::unknown());
774 } else {
775 self.type_tracker
776 .set_binding_type(slot, VariableTypeInfo::unknown());
777 }
778 return;
779 }
780 };
781 let info = VariableTypeInfo::with_storage(type_name.to_string(), hint);
782 if is_local {
783 self.type_tracker.set_local_type(slot, info);
784 } else {
785 self.type_tracker.set_binding_type(slot, info);
786 }
787 return;
788 }
789
790 if is_local {
792 self.type_tracker
793 .set_local_type(slot, VariableTypeInfo::unknown());
794 } else {
795 self.type_tracker
796 .set_binding_type(slot, VariableTypeInfo::unknown());
797 }
798 }
799
800 pub(super) fn propagate_assignment_type_to_identifier(&mut self, name: &str) {
804 if let Some(local_idx) = self.resolve_local(name) {
805 if self.ref_locals.contains(&local_idx) {
806 return;
807 }
808 self.propagate_assignment_type_to_slot(local_idx, true, true);
809 return;
810 }
811
812 let scoped_name = self
813 .resolve_scoped_module_binding_name(name)
814 .unwrap_or_else(|| name.to_string());
815 let binding_idx = self.get_or_create_module_binding(&scoped_name);
816 self.propagate_assignment_type_to_slot(binding_idx, false, true);
817 }
818
819 pub fn type_tracker(&self) -> &TypeTracker {
821 &self.type_tracker
822 }
823
824 pub fn type_tracker_mut(&mut self) -> &mut TypeTracker {
826 &mut self.type_tracker
827 }
828
829 pub(super) fn resolve_column_index(&self, field: &str) -> Result<u32> {
832 self.program
833 .data_schema
834 .as_ref()
835 .ok_or_else(|| ShapeError::RuntimeError {
836 message: format!(
837 "No data schema provided. Cannot resolve field '{}'. \
838 Hint: Use stdlib/finance to load market data with OHLCV schema.",
839 field
840 ),
841 location: None,
842 })?
843 .get_index(field)
844 .ok_or_else(|| ShapeError::RuntimeError {
845 message: format!(
846 "Unknown column '{}' in data schema. Available columns: {:?}",
847 field,
848 self.program
849 .data_schema
850 .as_ref()
851 .map(|s| &s.column_names)
852 .unwrap_or(&vec![])
853 ),
854 location: None,
855 })
856 }
857
858 pub(super) fn is_data_column(&self, field: &str) -> bool {
860 self.program
861 .data_schema
862 .as_ref()
863 .map(|s| s.get_index(field).is_some())
864 .unwrap_or(false)
865 }
866
867 pub(super) fn collect_outer_scope_vars(&self) -> Vec<String> {
869 let mut names = BTreeSet::new();
870 for scope in &self.locals {
871 for name in scope.keys() {
872 names.insert(name.clone());
873 }
874 }
875 for name in self.module_bindings.keys() {
876 names.insert(name.clone());
877 }
878 names.into_iter().collect()
879 }
880
881 pub(super) fn get_or_create_module_binding(&mut self, name: &str) -> u16 {
883 if let Some(&idx) = self.module_bindings.get(name) {
884 idx
885 } else {
886 let idx = self.next_global;
887 self.next_global += 1;
888 self.module_bindings.insert(name.to_string(), idx);
889 idx
890 }
891 }
892
893 pub(super) fn resolve_scoped_module_binding_name(&self, name: &str) -> Option<String> {
894 if self.module_bindings.contains_key(name) {
895 return Some(name.to_string());
896 }
897 for module_path in self.module_scope_stack.iter().rev() {
898 let candidate = format!("{}::{}", module_path, name);
899 if self.module_bindings.contains_key(&candidate) {
900 return Some(candidate);
901 }
902 }
903 None
904 }
905
906 pub(super) fn resolve_scoped_function_name(&self, name: &str) -> Option<String> {
907 if self.program.functions.iter().any(|f| f.name == name) {
908 return Some(name.to_string());
909 }
910 for module_path in self.module_scope_stack.iter().rev() {
911 let candidate = format!("{}::{}", module_path, name);
912 if self.program.functions.iter().any(|f| f.name == candidate) {
913 return Some(candidate);
914 }
915 }
916 None
917 }
918
919 pub(super) fn find_function(&self, name: &str) -> Option<usize> {
921 if let Some(actual_name) = self.function_aliases.get(name) {
923 if let Some(idx) = self
924 .program
925 .functions
926 .iter()
927 .position(|f| f.name == *actual_name)
928 {
929 return Some(idx);
930 }
931 }
932
933 if let Some(resolved) = self.resolve_scoped_function_name(name) {
935 if let Some(idx) = self
936 .program
937 .functions
938 .iter()
939 .position(|f| f.name == resolved)
940 {
941 return Some(idx);
942 }
943 }
944
945 if let Some(imported) = self.imported_names.get(name) {
950 let original = &imported.original_name;
951 if let Some(idx) = self
953 .program
954 .functions
955 .iter()
956 .position(|f| f.name == *original)
957 {
958 return Some(idx);
959 }
960 if let Some(resolved) = self.resolve_scoped_function_name(original) {
962 if let Some(idx) = self
963 .program
964 .functions
965 .iter()
966 .position(|f| f.name == resolved)
967 {
968 return Some(idx);
969 }
970 }
971 }
972
973 None
974 }
975
976 pub(super) fn resolve_receiver_extend_type(
986 &self,
987 receiver: &shape_ast::ast::Expr,
988 receiver_type_info: &Option<crate::type_tracking::VariableTypeInfo>,
989 _receiver_schema: Option<u32>,
990 ) -> Option<String> {
991 if let Some(numeric) = self.last_expr_numeric_type {
995 return Some(
996 match numeric {
997 crate::type_tracking::NumericType::Int
998 | crate::type_tracking::NumericType::IntWidth(_) => "Int",
999 crate::type_tracking::NumericType::Number => "Number",
1000 crate::type_tracking::NumericType::Decimal => "Decimal",
1001 }
1002 .to_string(),
1003 );
1004 }
1005
1006 if let Some(info) = receiver_type_info {
1008 if let Some(type_name) = &info.type_name {
1009 let base = type_name.split('<').next().unwrap_or(type_name);
1011 return Some(base.to_string());
1012 }
1013 }
1014
1015 match receiver {
1017 shape_ast::ast::Expr::Literal(lit, _) => match lit {
1018 shape_ast::ast::Literal::String(_)
1019 | shape_ast::ast::Literal::FormattedString { .. }
1020 | shape_ast::ast::Literal::ContentString { .. } => Some("String".to_string()),
1021 shape_ast::ast::Literal::Bool(_) => Some("Bool".to_string()),
1022 _ => None,
1023 },
1024 shape_ast::ast::Expr::Array(..) => Some("Vec".to_string()),
1025 _ => None,
1026 }
1027 }
1028
1029 pub(super) fn emit_store_identifier(&mut self, name: &str) -> Result<()> {
1031 if let Some(&upvalue_idx) = self.mutable_closure_captures.get(name) {
1033 self.emit(Instruction::new(
1034 OpCode::StoreClosure,
1035 Some(Operand::Local(upvalue_idx)),
1036 ));
1037 return Ok(());
1038 }
1039 if let Some(local_idx) = self.resolve_local(name) {
1040 if self.ref_locals.contains(&local_idx) {
1041 self.emit(Instruction::new(
1042 OpCode::DerefStore,
1043 Some(Operand::Local(local_idx)),
1044 ));
1045 } else {
1046 self.emit(Instruction::new(
1047 OpCode::StoreLocal,
1048 Some(Operand::Local(local_idx)),
1049 ));
1050 if let Some(type_name) = self
1052 .type_tracker
1053 .get_local_type(local_idx)
1054 .and_then(|info| info.type_name.as_deref())
1055 {
1056 if let Some(w) = shape_ast::IntWidth::from_name(type_name) {
1057 if let Some(last) = self.program.instructions.last_mut() {
1058 if last.opcode == OpCode::StoreLocal {
1059 last.opcode = OpCode::StoreLocalTyped;
1060 last.operand = Some(Operand::TypedLocal(
1061 local_idx,
1062 crate::bytecode::NumericWidth::from_int_width(w),
1063 ));
1064 }
1065 }
1066 }
1067 }
1068 }
1069 } else {
1070 let scoped_name = self
1071 .resolve_scoped_module_binding_name(name)
1072 .unwrap_or_else(|| name.to_string());
1073 let binding_idx = self.get_or_create_module_binding(&scoped_name);
1074 self.emit(Instruction::new(
1075 OpCode::StoreModuleBinding,
1076 Some(Operand::ModuleBinding(binding_idx)),
1077 ));
1078 }
1079 Ok(())
1080 }
1081
1082 pub(super) fn get_builtin_function(&self, name: &str) -> Option<BuiltinFunction> {
1084 match name {
1085 "Some" => Some(BuiltinFunction::SomeCtor),
1087 "Ok" => Some(BuiltinFunction::OkCtor),
1088 "Err" => Some(BuiltinFunction::ErrCtor),
1089 "HashMap" => Some(BuiltinFunction::HashMapCtor),
1090 "Set" => Some(BuiltinFunction::SetCtor),
1091 "Deque" => Some(BuiltinFunction::DequeCtor),
1092 "PriorityQueue" => Some(BuiltinFunction::PriorityQueueCtor),
1093 "Mutex" => Some(BuiltinFunction::MutexCtor),
1094 "Atomic" => Some(BuiltinFunction::AtomicCtor),
1095 "Lazy" => Some(BuiltinFunction::LazyCtor),
1096 "Channel" => Some(BuiltinFunction::ChannelCtor),
1097 "__json_object_get" => Some(BuiltinFunction::JsonObjectGet),
1099 "__json_array_at" => Some(BuiltinFunction::JsonArrayAt),
1100 "__json_object_keys" => Some(BuiltinFunction::JsonObjectKeys),
1101 "__json_array_len" => Some(BuiltinFunction::JsonArrayLen),
1102 "__json_object_len" => Some(BuiltinFunction::JsonObjectLen),
1103 "__intrinsic_vec_abs" => Some(BuiltinFunction::IntrinsicVecAbs),
1104 "__intrinsic_vec_sqrt" => Some(BuiltinFunction::IntrinsicVecSqrt),
1105 "__intrinsic_vec_ln" => Some(BuiltinFunction::IntrinsicVecLn),
1106 "__intrinsic_vec_exp" => Some(BuiltinFunction::IntrinsicVecExp),
1107 "__intrinsic_vec_add" => Some(BuiltinFunction::IntrinsicVecAdd),
1108 "__intrinsic_vec_sub" => Some(BuiltinFunction::IntrinsicVecSub),
1109 "__intrinsic_vec_mul" => Some(BuiltinFunction::IntrinsicVecMul),
1110 "__intrinsic_vec_div" => Some(BuiltinFunction::IntrinsicVecDiv),
1111 "__intrinsic_vec_max" => Some(BuiltinFunction::IntrinsicVecMax),
1112 "__intrinsic_vec_min" => Some(BuiltinFunction::IntrinsicVecMin),
1113 "__intrinsic_vec_select" => Some(BuiltinFunction::IntrinsicVecSelect),
1114 "__intrinsic_matmul_vec" => Some(BuiltinFunction::IntrinsicMatMulVec),
1115 "__intrinsic_matmul_mat" => Some(BuiltinFunction::IntrinsicMatMulMat),
1116
1117 "abs" => Some(BuiltinFunction::Abs),
1119 "min" => Some(BuiltinFunction::Min),
1120 "max" => Some(BuiltinFunction::Max),
1121 "sqrt" => Some(BuiltinFunction::Sqrt),
1122 "ln" => Some(BuiltinFunction::Ln),
1123 "pow" => Some(BuiltinFunction::Pow),
1124 "exp" => Some(BuiltinFunction::Exp),
1125 "log" => Some(BuiltinFunction::Log),
1126 "floor" => Some(BuiltinFunction::Floor),
1127 "ceil" => Some(BuiltinFunction::Ceil),
1128 "round" => Some(BuiltinFunction::Round),
1129 "sin" => Some(BuiltinFunction::Sin),
1130 "cos" => Some(BuiltinFunction::Cos),
1131 "tan" => Some(BuiltinFunction::Tan),
1132 "asin" => Some(BuiltinFunction::Asin),
1133 "acos" => Some(BuiltinFunction::Acos),
1134 "atan" => Some(BuiltinFunction::Atan),
1135 "stddev" => Some(BuiltinFunction::StdDev),
1136 "slice" => Some(BuiltinFunction::Slice),
1137 "push" => Some(BuiltinFunction::Push),
1138 "pop" => Some(BuiltinFunction::Pop),
1139 "first" => Some(BuiltinFunction::First),
1140 "last" => Some(BuiltinFunction::Last),
1141 "zip" => Some(BuiltinFunction::Zip),
1142 "filled" => Some(BuiltinFunction::Filled),
1143 "map" | "__intrinsic_map" => Some(BuiltinFunction::Map),
1144 "filter" | "__intrinsic_filter" => Some(BuiltinFunction::Filter),
1145 "reduce" | "__intrinsic_reduce" => Some(BuiltinFunction::Reduce),
1146 "forEach" => Some(BuiltinFunction::ForEach),
1147 "find" => Some(BuiltinFunction::Find),
1148 "findIndex" => Some(BuiltinFunction::FindIndex),
1149 "some" => Some(BuiltinFunction::Some),
1150 "every" => Some(BuiltinFunction::Every),
1151 "print" => Some(BuiltinFunction::Print),
1152 "format" => Some(BuiltinFunction::Format),
1153 "len" | "count" => Some(BuiltinFunction::Len),
1154 "__intrinsic_snapshot" | "snapshot" => Some(BuiltinFunction::Snapshot),
1156 "exit" => Some(BuiltinFunction::Exit),
1157 "range" => Some(BuiltinFunction::Range),
1158 "is_number" | "isNumber" => Some(BuiltinFunction::IsNumber),
1159 "is_string" | "isString" => Some(BuiltinFunction::IsString),
1160 "is_bool" | "isBool" => Some(BuiltinFunction::IsBool),
1161 "is_array" | "isArray" => Some(BuiltinFunction::IsArray),
1162 "is_object" | "isObject" => Some(BuiltinFunction::IsObject),
1163 "is_data_row" | "isDataRow" => Some(BuiltinFunction::IsDataRow),
1164 "to_string" | "toString" => Some(BuiltinFunction::ToString),
1165 "to_number" | "toNumber" => Some(BuiltinFunction::ToNumber),
1166 "to_bool" | "toBool" => Some(BuiltinFunction::ToBool),
1167 "__into_int" => Some(BuiltinFunction::IntoInt),
1168 "__into_number" => Some(BuiltinFunction::IntoNumber),
1169 "__into_decimal" => Some(BuiltinFunction::IntoDecimal),
1170 "__into_bool" => Some(BuiltinFunction::IntoBool),
1171 "__into_string" => Some(BuiltinFunction::IntoString),
1172 "__try_into_int" => Some(BuiltinFunction::TryIntoInt),
1173 "__try_into_number" => Some(BuiltinFunction::TryIntoNumber),
1174 "__try_into_decimal" => Some(BuiltinFunction::TryIntoDecimal),
1175 "__try_into_bool" => Some(BuiltinFunction::TryIntoBool),
1176 "__try_into_string" => Some(BuiltinFunction::TryIntoString),
1177 "__native_ptr_size" => Some(BuiltinFunction::NativePtrSize),
1178 "__native_ptr_new_cell" => Some(BuiltinFunction::NativePtrNewCell),
1179 "__native_ptr_free_cell" => Some(BuiltinFunction::NativePtrFreeCell),
1180 "__native_ptr_read_ptr" => Some(BuiltinFunction::NativePtrReadPtr),
1181 "__native_ptr_write_ptr" => Some(BuiltinFunction::NativePtrWritePtr),
1182 "__native_table_from_arrow_c" => Some(BuiltinFunction::NativeTableFromArrowC),
1183 "__native_table_from_arrow_c_typed" => {
1184 Some(BuiltinFunction::NativeTableFromArrowCTyped)
1185 }
1186 "__native_table_bind_type" => Some(BuiltinFunction::NativeTableBindType),
1187 "fold" => Some(BuiltinFunction::ControlFold),
1188
1189 "__intrinsic_sum" | "sum" => Some(BuiltinFunction::IntrinsicSum),
1191 "__intrinsic_mean" | "mean" => Some(BuiltinFunction::IntrinsicMean),
1192 "__intrinsic_min" => Some(BuiltinFunction::IntrinsicMin),
1193 "__intrinsic_max" => Some(BuiltinFunction::IntrinsicMax),
1194 "__intrinsic_std" | "std" => Some(BuiltinFunction::IntrinsicStd),
1195 "__intrinsic_variance" | "variance" => Some(BuiltinFunction::IntrinsicVariance),
1196
1197 "__intrinsic_random" => Some(BuiltinFunction::IntrinsicRandom),
1199 "__intrinsic_random_int" => Some(BuiltinFunction::IntrinsicRandomInt),
1200 "__intrinsic_random_seed" => Some(BuiltinFunction::IntrinsicRandomSeed),
1201 "__intrinsic_random_normal" => Some(BuiltinFunction::IntrinsicRandomNormal),
1202 "__intrinsic_random_array" => Some(BuiltinFunction::IntrinsicRandomArray),
1203
1204 "__intrinsic_dist_uniform" => Some(BuiltinFunction::IntrinsicDistUniform),
1206 "__intrinsic_dist_lognormal" => Some(BuiltinFunction::IntrinsicDistLognormal),
1207 "__intrinsic_dist_exponential" => Some(BuiltinFunction::IntrinsicDistExponential),
1208 "__intrinsic_dist_poisson" => Some(BuiltinFunction::IntrinsicDistPoisson),
1209 "__intrinsic_dist_sample_n" => Some(BuiltinFunction::IntrinsicDistSampleN),
1210
1211 "__intrinsic_brownian_motion" => Some(BuiltinFunction::IntrinsicBrownianMotion),
1213 "__intrinsic_gbm" => Some(BuiltinFunction::IntrinsicGbm),
1214 "__intrinsic_ou_process" => Some(BuiltinFunction::IntrinsicOuProcess),
1215 "__intrinsic_random_walk" => Some(BuiltinFunction::IntrinsicRandomWalk),
1216
1217 "__intrinsic_rolling_sum" => Some(BuiltinFunction::IntrinsicRollingSum),
1219 "__intrinsic_rolling_mean" => Some(BuiltinFunction::IntrinsicRollingMean),
1220 "__intrinsic_rolling_std" => Some(BuiltinFunction::IntrinsicRollingStd),
1221 "__intrinsic_rolling_min" => Some(BuiltinFunction::IntrinsicRollingMin),
1222 "__intrinsic_rolling_max" => Some(BuiltinFunction::IntrinsicRollingMax),
1223 "__intrinsic_ema" => Some(BuiltinFunction::IntrinsicEma),
1224 "__intrinsic_linear_recurrence" => Some(BuiltinFunction::IntrinsicLinearRecurrence),
1225
1226 "__intrinsic_shift" => Some(BuiltinFunction::IntrinsicShift),
1228 "__intrinsic_diff" => Some(BuiltinFunction::IntrinsicDiff),
1229 "__intrinsic_pct_change" => Some(BuiltinFunction::IntrinsicPctChange),
1230 "__intrinsic_fillna" => Some(BuiltinFunction::IntrinsicFillna),
1231 "__intrinsic_cumsum" => Some(BuiltinFunction::IntrinsicCumsum),
1232 "__intrinsic_cumprod" => Some(BuiltinFunction::IntrinsicCumprod),
1233 "__intrinsic_clip" => Some(BuiltinFunction::IntrinsicClip),
1234
1235 "__intrinsic_correlation" => Some(BuiltinFunction::IntrinsicCorrelation),
1237 "__intrinsic_covariance" => Some(BuiltinFunction::IntrinsicCovariance),
1238 "__intrinsic_percentile" => Some(BuiltinFunction::IntrinsicPercentile),
1239 "__intrinsic_median" => Some(BuiltinFunction::IntrinsicMedian),
1240
1241 "__intrinsic_char_code" => Some(BuiltinFunction::IntrinsicCharCode),
1243 "__intrinsic_from_char_code" => Some(BuiltinFunction::IntrinsicFromCharCode),
1244
1245 "__intrinsic_series" => Some(BuiltinFunction::IntrinsicSeries),
1247
1248 "reflect" => Some(BuiltinFunction::Reflect),
1250
1251 "sign" => Some(BuiltinFunction::Sign),
1253 "gcd" => Some(BuiltinFunction::Gcd),
1254 "lcm" => Some(BuiltinFunction::Lcm),
1255 "hypot" => Some(BuiltinFunction::Hypot),
1256 "clamp" => Some(BuiltinFunction::Clamp),
1257 "isNaN" | "is_nan" => Some(BuiltinFunction::IsNaN),
1258 "isFinite" | "is_finite" => Some(BuiltinFunction::IsFinite),
1259
1260 _ => None,
1261 }
1262 }
1263
1264 pub(super) fn builtin_requires_arg_count(&self, builtin: BuiltinFunction) -> bool {
1266 matches!(
1267 builtin,
1268 BuiltinFunction::Abs
1269 | BuiltinFunction::Min
1270 | BuiltinFunction::Max
1271 | BuiltinFunction::Sqrt
1272 | BuiltinFunction::Ln
1273 | BuiltinFunction::Pow
1274 | BuiltinFunction::Exp
1275 | BuiltinFunction::Log
1276 | BuiltinFunction::Floor
1277 | BuiltinFunction::Ceil
1278 | BuiltinFunction::Round
1279 | BuiltinFunction::Sin
1280 | BuiltinFunction::Cos
1281 | BuiltinFunction::Tan
1282 | BuiltinFunction::Asin
1283 | BuiltinFunction::Acos
1284 | BuiltinFunction::Atan
1285 | BuiltinFunction::StdDev
1286 | BuiltinFunction::Range
1287 | BuiltinFunction::Slice
1288 | BuiltinFunction::Push
1289 | BuiltinFunction::Pop
1290 | BuiltinFunction::First
1291 | BuiltinFunction::Last
1292 | BuiltinFunction::Zip
1293 | BuiltinFunction::Map
1294 | BuiltinFunction::Filter
1295 | BuiltinFunction::Reduce
1296 | BuiltinFunction::ForEach
1297 | BuiltinFunction::Find
1298 | BuiltinFunction::FindIndex
1299 | BuiltinFunction::Some
1300 | BuiltinFunction::Every
1301 | BuiltinFunction::SomeCtor
1302 | BuiltinFunction::OkCtor
1303 | BuiltinFunction::ErrCtor
1304 | BuiltinFunction::HashMapCtor
1305 | BuiltinFunction::SetCtor
1306 | BuiltinFunction::DequeCtor
1307 | BuiltinFunction::PriorityQueueCtor
1308 | BuiltinFunction::MutexCtor
1309 | BuiltinFunction::AtomicCtor
1310 | BuiltinFunction::LazyCtor
1311 | BuiltinFunction::ChannelCtor
1312 | BuiltinFunction::Print
1313 | BuiltinFunction::Format
1314 | BuiltinFunction::Len
1315 | BuiltinFunction::Snapshot
1317 | BuiltinFunction::ObjectRest
1318 | BuiltinFunction::IsNumber
1319 | BuiltinFunction::IsString
1320 | BuiltinFunction::IsBool
1321 | BuiltinFunction::IsArray
1322 | BuiltinFunction::IsObject
1323 | BuiltinFunction::IsDataRow
1324 | BuiltinFunction::ToString
1325 | BuiltinFunction::ToNumber
1326 | BuiltinFunction::ToBool
1327 | BuiltinFunction::IntoInt
1328 | BuiltinFunction::IntoNumber
1329 | BuiltinFunction::IntoDecimal
1330 | BuiltinFunction::IntoBool
1331 | BuiltinFunction::IntoString
1332 | BuiltinFunction::TryIntoInt
1333 | BuiltinFunction::TryIntoNumber
1334 | BuiltinFunction::TryIntoDecimal
1335 | BuiltinFunction::TryIntoBool
1336 | BuiltinFunction::TryIntoString
1337 | BuiltinFunction::NativePtrSize
1338 | BuiltinFunction::NativePtrNewCell
1339 | BuiltinFunction::NativePtrFreeCell
1340 | BuiltinFunction::NativePtrReadPtr
1341 | BuiltinFunction::NativePtrWritePtr
1342 | BuiltinFunction::NativeTableFromArrowC
1343 | BuiltinFunction::NativeTableFromArrowCTyped
1344 | BuiltinFunction::NativeTableBindType
1345 | BuiltinFunction::ControlFold
1346 | BuiltinFunction::IntrinsicSum
1347 | BuiltinFunction::IntrinsicMean
1348 | BuiltinFunction::IntrinsicMin
1349 | BuiltinFunction::IntrinsicMax
1350 | BuiltinFunction::IntrinsicStd
1351 | BuiltinFunction::IntrinsicVariance
1352 | BuiltinFunction::IntrinsicRandom
1353 | BuiltinFunction::IntrinsicRandomInt
1354 | BuiltinFunction::IntrinsicRandomSeed
1355 | BuiltinFunction::IntrinsicRandomNormal
1356 | BuiltinFunction::IntrinsicRandomArray
1357 | BuiltinFunction::IntrinsicDistUniform
1358 | BuiltinFunction::IntrinsicDistLognormal
1359 | BuiltinFunction::IntrinsicDistExponential
1360 | BuiltinFunction::IntrinsicDistPoisson
1361 | BuiltinFunction::IntrinsicDistSampleN
1362 | BuiltinFunction::IntrinsicBrownianMotion
1363 | BuiltinFunction::IntrinsicGbm
1364 | BuiltinFunction::IntrinsicOuProcess
1365 | BuiltinFunction::IntrinsicRandomWalk
1366 | BuiltinFunction::IntrinsicRollingSum
1367 | BuiltinFunction::IntrinsicRollingMean
1368 | BuiltinFunction::IntrinsicRollingStd
1369 | BuiltinFunction::IntrinsicRollingMin
1370 | BuiltinFunction::IntrinsicRollingMax
1371 | BuiltinFunction::IntrinsicEma
1372 | BuiltinFunction::IntrinsicLinearRecurrence
1373 | BuiltinFunction::IntrinsicShift
1374 | BuiltinFunction::IntrinsicDiff
1375 | BuiltinFunction::IntrinsicPctChange
1376 | BuiltinFunction::IntrinsicFillna
1377 | BuiltinFunction::IntrinsicCumsum
1378 | BuiltinFunction::IntrinsicCumprod
1379 | BuiltinFunction::IntrinsicClip
1380 | BuiltinFunction::IntrinsicCorrelation
1381 | BuiltinFunction::IntrinsicCovariance
1382 | BuiltinFunction::IntrinsicPercentile
1383 | BuiltinFunction::IntrinsicMedian
1384 | BuiltinFunction::IntrinsicCharCode
1385 | BuiltinFunction::IntrinsicFromCharCode
1386 | BuiltinFunction::IntrinsicSeries
1387 | BuiltinFunction::IntrinsicVecAbs
1388 | BuiltinFunction::IntrinsicVecSqrt
1389 | BuiltinFunction::IntrinsicVecLn
1390 | BuiltinFunction::IntrinsicVecExp
1391 | BuiltinFunction::IntrinsicVecAdd
1392 | BuiltinFunction::IntrinsicVecSub
1393 | BuiltinFunction::IntrinsicVecMul
1394 | BuiltinFunction::IntrinsicVecDiv
1395 | BuiltinFunction::IntrinsicVecMax
1396 | BuiltinFunction::IntrinsicVecMin
1397 | BuiltinFunction::IntrinsicVecSelect
1398 | BuiltinFunction::IntrinsicMatMulVec
1399 | BuiltinFunction::IntrinsicMatMulMat
1400 | BuiltinFunction::Sign
1401 | BuiltinFunction::Gcd
1402 | BuiltinFunction::Lcm
1403 | BuiltinFunction::Hypot
1404 | BuiltinFunction::Clamp
1405 | BuiltinFunction::IsNaN
1406 | BuiltinFunction::IsFinite
1407 )
1408 }
1409
1410 pub(super) fn is_known_builtin_method(method: &str) -> bool {
1414 matches!(method,
1416 "map" | "filter" | "reduce" | "forEach" | "find" | "findIndex"
1417 | "some" | "every" | "sort" | "groupBy" | "flatMap"
1418 | "len" | "length" | "first" | "last" | "reverse" | "slice"
1419 | "concat" | "take" | "drop" | "skip"
1420 | "indexOf" | "includes"
1421 | "join" | "flatten" | "unique" | "distinct" | "distinctBy"
1422 | "sum" | "avg" | "min" | "max" | "count"
1423 | "where" | "select" | "orderBy" | "thenBy" | "takeWhile"
1424 | "skipWhile" | "single" | "any" | "all"
1425 | "innerJoin" | "leftJoin" | "crossJoin"
1426 | "union" | "intersect" | "except"
1427 )
1428 || matches!(method,
1430 "columns" | "column" | "head" | "tail" | "mean" | "std"
1431 | "describe" | "aggregate" | "group_by" | "index_by" | "indexBy"
1432 | "simulate" | "toMat" | "to_mat"
1433 )
1434 || matches!(method, "toArray")
1436 || matches!(method, "resample" | "between")
1438 || matches!(method,
1440 "toFixed" | "toInt" | "toNumber" | "to_number" | "floor" | "ceil" | "round"
1441 | "abs" | "sign" | "clamp"
1442 )
1443 || matches!(method,
1445 "toUpperCase" | "toLowerCase" | "trim" | "contains" | "startsWith"
1446 | "endsWith" | "split" | "replace" | "substring" | "charAt"
1447 | "padStart" | "padEnd" | "repeat" | "toString"
1448 )
1449 || matches!(method, "keys" | "values" | "has" | "get" | "set" | "len")
1451 || matches!(method, "type")
1453 }
1454
1455 pub(super) fn try_track_datatable_type(
1460 &mut self,
1461 type_ann: &shape_ast::ast::TypeAnnotation,
1462 slot: u16,
1463 is_local: bool,
1464 ) -> shape_ast::error::Result<()> {
1465 use shape_ast::ast::TypeAnnotation;
1466 if let TypeAnnotation::Generic { name, args } = type_ann {
1467 if name == "Table" && args.len() == 1 {
1468 let inner_name = match &args[0] {
1469 TypeAnnotation::Reference(t) => Some(t.as_str()),
1470 TypeAnnotation::Basic(t) => Some(t.as_str()),
1471 _ => None,
1472 };
1473 if let Some(type_name) = inner_name {
1474 let schema_id = self
1475 .type_tracker
1476 .schema_registry()
1477 .get(type_name)
1478 .map(|s| s.id);
1479 if let Some(sid) = schema_id {
1480 let info = crate::type_tracking::VariableTypeInfo::datatable(
1481 sid,
1482 type_name.to_string(),
1483 );
1484 if is_local {
1485 self.type_tracker.set_local_type(slot, info);
1486 } else {
1487 self.type_tracker.set_binding_type(slot, info);
1488 }
1489 } else {
1490 return Err(shape_ast::error::ShapeError::SemanticError {
1491 message: format!(
1492 "Unknown type '{}' in Table<{}> annotation",
1493 type_name, type_name
1494 ),
1495 location: None,
1496 });
1497 }
1498 }
1499 }
1500 }
1501 Ok(())
1502 }
1503
1504 pub(super) fn is_row_view_variable(&self, name: &str) -> bool {
1506 if let Some(local_idx) = self.resolve_local(name) {
1507 if let Some(info) = self.type_tracker.get_local_type(local_idx) {
1508 return info.is_row_view();
1509 }
1510 }
1511 if let Some(&binding_idx) = self.module_bindings.get(name) {
1512 if let Some(info) = self.type_tracker.get_binding_type(binding_idx) {
1513 return info.is_row_view();
1514 }
1515 }
1516 false
1517 }
1518
1519 pub(super) fn get_row_view_field_names(&self, name: &str) -> Option<Vec<String>> {
1521 let type_name = if let Some(local_idx) = self.resolve_local(name) {
1522 self.type_tracker
1523 .get_local_type(local_idx)
1524 .and_then(|info| {
1525 if info.is_row_view() {
1526 info.type_name.clone()
1527 } else {
1528 None
1529 }
1530 })
1531 } else if let Some(&binding_idx) = self.module_bindings.get(name) {
1532 self.type_tracker
1533 .get_binding_type(binding_idx)
1534 .and_then(|info| {
1535 if info.is_row_view() {
1536 info.type_name.clone()
1537 } else {
1538 None
1539 }
1540 })
1541 } else {
1542 None
1543 };
1544
1545 if let Some(tn) = type_name {
1546 if let Some(schema) = self.type_tracker.schema_registry().get(&tn) {
1547 return Some(schema.field_names().map(|n| n.to_string()).collect());
1548 }
1549 }
1550 None
1551 }
1552
1553 pub(super) fn try_resolve_row_view_column(
1559 &self,
1560 var_name: &str,
1561 field_name: &str,
1562 ) -> Option<u32> {
1563 if let Some(local_idx) = self.resolve_local(var_name) {
1565 return self
1566 .type_tracker
1567 .get_row_view_column_id(local_idx, true, field_name);
1568 }
1569 if let Some(&binding_idx) = self.module_bindings.get(var_name) {
1570 return self
1571 .type_tracker
1572 .get_row_view_column_id(binding_idx, false, field_name);
1573 }
1574 None
1575 }
1576
1577 pub(super) fn row_view_field_opcode(&self, var_name: &str, field_name: &str) -> OpCode {
1582 use shape_runtime::type_schema::FieldType;
1583
1584 let type_name = if let Some(local_idx) = self.resolve_local(var_name) {
1585 self.type_tracker
1586 .get_local_type(local_idx)
1587 .and_then(|info| info.type_name.clone())
1588 } else if let Some(&binding_idx) = self.module_bindings.get(var_name) {
1589 self.type_tracker
1590 .get_binding_type(binding_idx)
1591 .and_then(|info| info.type_name.clone())
1592 } else {
1593 None
1594 };
1595
1596 if let Some(type_name) = type_name {
1597 if let Some(schema) = self.type_tracker.schema_registry().get(&type_name) {
1598 if let Some(field) = schema.get_field(field_name) {
1599 return match field.field_type {
1600 FieldType::F64 => OpCode::LoadColF64,
1601 FieldType::I64 | FieldType::Timestamp => OpCode::LoadColI64,
1602 FieldType::Bool => OpCode::LoadColBool,
1603 FieldType::String => OpCode::LoadColStr,
1604 _ => OpCode::LoadColF64, };
1606 }
1607 }
1608 }
1609 OpCode::LoadColF64 }
1611
1612 pub(super) fn resolve_row_view_field_numeric_type(
1614 &self,
1615 var_name: &str,
1616 field_name: &str,
1617 ) -> Option<crate::type_tracking::NumericType> {
1618 use crate::type_tracking::NumericType;
1619 use shape_runtime::type_schema::FieldType;
1620
1621 let type_name = if let Some(local_idx) = self.resolve_local(var_name) {
1622 self.type_tracker
1623 .get_local_type(local_idx)
1624 .and_then(|info| info.type_name.clone())
1625 } else if let Some(&binding_idx) = self.module_bindings.get(var_name) {
1626 self.type_tracker
1627 .get_binding_type(binding_idx)
1628 .and_then(|info| info.type_name.clone())
1629 } else {
1630 None
1631 };
1632
1633 if let Some(type_name) = type_name {
1634 if let Some(schema) = self.type_tracker.schema_registry().get(&type_name) {
1635 if let Some(field) = schema.get_field(field_name) {
1636 return match field.field_type {
1637 FieldType::F64 => Some(NumericType::Number),
1638 FieldType::I64 | FieldType::Timestamp => Some(NumericType::Int),
1639 FieldType::Decimal => Some(NumericType::Decimal),
1640 _ => None,
1641 };
1642 }
1643 }
1644 }
1645 None
1646 }
1647
1648 pub(super) fn type_annotation_to_field_type(
1650 ann: &shape_ast::ast::TypeAnnotation,
1651 ) -> shape_runtime::type_schema::FieldType {
1652 use shape_ast::ast::TypeAnnotation;
1653 use shape_runtime::type_schema::FieldType;
1654 match ann {
1655 TypeAnnotation::Basic(s) => match s.as_str() {
1656 "number" | "float" | "f64" | "f32" => FieldType::F64,
1657 "i8" => FieldType::I8,
1658 "u8" => FieldType::U8,
1659 "i16" => FieldType::I16,
1660 "u16" => FieldType::U16,
1661 "i32" => FieldType::I32,
1662 "u32" => FieldType::U32,
1663 "u64" => FieldType::U64,
1664 "int" | "i64" | "integer" | "isize" | "usize" | "byte" | "char" => FieldType::I64,
1665 "string" | "str" => FieldType::String,
1666 "decimal" => FieldType::Decimal,
1667 "bool" | "boolean" => FieldType::Bool,
1668 "timestamp" => FieldType::Timestamp,
1669 other => FieldType::Object(other.to_string()),
1674 },
1675 TypeAnnotation::Reference(s) => FieldType::Object(s.clone()),
1676 TypeAnnotation::Array(inner) => {
1677 FieldType::Array(Box::new(Self::type_annotation_to_field_type(inner)))
1678 }
1679 TypeAnnotation::Optional(_) => FieldType::Any, TypeAnnotation::Generic { name, .. } => match name.as_str() {
1681 "HashMap" | "Map" | "Result" | "Option" | "Set" => FieldType::Any,
1683 other => FieldType::Object(other.to_string()),
1685 },
1686 _ => FieldType::Any,
1687 }
1688 }
1689
1690 pub(super) fn eval_annotation_arg(expr: &shape_ast::ast::Expr) -> Option<String> {
1693 use shape_ast::ast::{Expr, Literal};
1694 match expr {
1695 Expr::Literal(Literal::String(s), _) => Some(s.clone()),
1696 Expr::Literal(Literal::Number(n), _) => Some(n.to_string()),
1697 Expr::Literal(Literal::Int(i), _) => Some(i.to_string()),
1698 Expr::Literal(Literal::Bool(b), _) => Some(b.to_string()),
1699 _ => None,
1700 }
1701 }
1702
1703 pub(super) fn get_table_schema_id(
1708 &self,
1709 type_ann: &shape_ast::ast::TypeAnnotation,
1710 ) -> Option<u16> {
1711 use shape_ast::ast::TypeAnnotation;
1712 if let TypeAnnotation::Generic { name, args } = type_ann {
1713 if name == "Table" && args.len() == 1 {
1714 let inner_name = match &args[0] {
1715 TypeAnnotation::Reference(t) | TypeAnnotation::Basic(t) => Some(t.as_str()),
1716 _ => None,
1717 };
1718 if let Some(type_name) = inner_name {
1719 return self
1720 .type_tracker
1721 .schema_registry()
1722 .get(type_name)
1723 .map(|s| s.id as u16);
1724 }
1725 }
1726 }
1727 None
1728 }
1729
1730 pub(super) fn push_drop_scope(&mut self) {
1734 self.drop_locals.push(Vec::new());
1735 }
1736
1737 pub(super) fn pop_drop_scope(&mut self) -> Result<()> {
1740 if let Some(locals) = self.drop_locals.pop() {
1742 for (local_idx, is_async) in locals.into_iter().rev() {
1743 self.emit(Instruction::new(
1744 OpCode::LoadLocal,
1745 Some(Operand::Local(local_idx)),
1746 ));
1747 let opcode = if is_async {
1748 OpCode::DropCallAsync
1749 } else {
1750 OpCode::DropCall
1751 };
1752 self.emit(Instruction::simple(opcode));
1753 }
1754 }
1755 Ok(())
1756 }
1757
1758 pub(super) fn track_drop_local(&mut self, local_idx: u16, is_async: bool) {
1760 if let Some(scope) = self.drop_locals.last_mut() {
1761 scope.push((local_idx, is_async));
1762 }
1763 }
1764
1765 pub(super) fn local_drop_kind(&self, local_idx: u16) -> Option<DropKind> {
1768 let type_name = self
1769 .type_tracker
1770 .get_local_type(local_idx)
1771 .and_then(|info| info.type_name.as_ref())?;
1772 self.drop_type_info.get(type_name).copied()
1773 }
1774
1775 pub(super) fn annotation_drop_kind(&self, type_ann: &TypeAnnotation) -> Option<DropKind> {
1777 let type_name = Self::tracked_type_name_from_annotation(type_ann)?;
1778 self.drop_type_info.get(&type_name).copied()
1779 }
1780
1781 pub(super) fn emit_drops_for_early_exit(&mut self, scopes_to_exit: usize) -> Result<()> {
1784 let total = self.drop_locals.len();
1785 if scopes_to_exit > total {
1786 return Ok(());
1787 }
1788 let mut scopes: Vec<Vec<(u16, bool)>> = Vec::new();
1790 for i in (total - scopes_to_exit..total).rev() {
1791 let locals = self.drop_locals.get(i).cloned().unwrap_or_default();
1792 scopes.push(locals);
1793 }
1794 for locals in scopes {
1796 for (local_idx, is_async) in locals.into_iter().rev() {
1797 self.emit(Instruction::new(
1798 OpCode::LoadLocal,
1799 Some(Operand::Local(local_idx)),
1800 ));
1801 let opcode = if is_async {
1802 OpCode::DropCallAsync
1803 } else {
1804 OpCode::DropCall
1805 };
1806 self.emit(Instruction::simple(opcode));
1807 }
1808 }
1809 Ok(())
1810 }
1811}
1812
1813#[cfg(test)]
1814mod tests {
1815 use super::super::BytecodeCompiler;
1816 use shape_ast::ast::TypeAnnotation;
1817 use shape_runtime::type_schema::FieldType;
1818
1819 #[test]
1820 fn test_type_annotation_to_field_type_array_recursive() {
1821 let ann = TypeAnnotation::Array(Box::new(TypeAnnotation::Basic("int".to_string())));
1822 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
1823 assert_eq!(ft, FieldType::Array(Box::new(FieldType::I64)));
1824 }
1825
1826 #[test]
1827 fn test_type_annotation_to_field_type_optional() {
1828 let ann = TypeAnnotation::Optional(Box::new(TypeAnnotation::Basic("int".to_string())));
1829 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
1830 assert_eq!(ft, FieldType::Any);
1831 }
1832
1833 #[test]
1834 fn test_type_annotation_to_field_type_generic_hashmap() {
1835 let ann = TypeAnnotation::Generic {
1836 name: "HashMap".to_string(),
1837 args: vec![
1838 TypeAnnotation::Basic("string".to_string()),
1839 TypeAnnotation::Basic("int".to_string()),
1840 ],
1841 };
1842 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
1843 assert_eq!(ft, FieldType::Any);
1844 }
1845
1846 #[test]
1847 fn test_type_annotation_to_field_type_generic_user_struct() {
1848 let ann = TypeAnnotation::Generic {
1849 name: "MyContainer".to_string(),
1850 args: vec![TypeAnnotation::Basic("string".to_string())],
1851 };
1852 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
1853 assert_eq!(ft, FieldType::Object("MyContainer".to_string()));
1854 }
1855}