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 self.ref_locals.contains(&local_idx) {
449 self.emit(Instruction::new(
451 OpCode::LoadLocal,
452 Some(Operand::Local(local_idx)),
453 ));
454 return Ok(());
455 }
456 let source_loc = self.span_to_source_location(span);
457 self.borrow_checker
458 .create_borrow(local_idx, local_idx, mode, span, Some(source_loc))
459 .map_err(|e| match e {
460 ShapeError::SemanticError { message, location } => {
461 let user_msg = message
462 .replace(&format!("(slot {})", local_idx), &format!("'{}'", name));
463 ShapeError::SemanticError {
464 message: user_msg,
465 location,
466 }
467 }
468 other => other,
469 })?;
470 self.emit(Instruction::new(
471 OpCode::MakeRef,
472 Some(Operand::Local(local_idx)),
473 ));
474 Ok(())
475 } else if let Some(scoped_name) = self.resolve_scoped_module_binding_name(name) {
476 let Some(&binding_idx) = self.module_bindings.get(&scoped_name) else {
477 return Err(ShapeError::SemanticError {
478 message: format!(
479 "[B0004] reference argument must be a local or module_binding variable, got '{}'",
480 name
481 ),
482 location: Some(self.span_to_source_location(span)),
483 });
484 };
485 if mode == BorrowMode::Exclusive && self.const_module_bindings.contains(&binding_idx) {
487 return Err(ShapeError::SemanticError {
488 message: format!(
489 "Cannot pass const variable '{}' by exclusive reference",
490 name
491 ),
492 location: Some(self.span_to_source_location(span)),
493 });
494 }
495 let shadow_local = self.declare_temp_local("__module_binding_ref_shadow_")?;
497 self.emit(Instruction::new(
498 OpCode::LoadModuleBinding,
499 Some(Operand::ModuleBinding(binding_idx)),
500 ));
501 self.emit(Instruction::new(
502 OpCode::StoreLocal,
503 Some(Operand::Local(shadow_local)),
504 ));
505 let source_loc = self.span_to_source_location(span);
506 self.borrow_checker.create_borrow(
507 shadow_local,
508 shadow_local,
509 mode,
510 span,
511 Some(source_loc),
512 )?;
513 self.emit(Instruction::new(
514 OpCode::MakeRef,
515 Some(Operand::Local(shadow_local)),
516 ));
517 self.record_call_arg_module_binding_writeback(shadow_local, binding_idx);
518 Ok(())
519 } else if let Some(func_idx) = self.find_function(name) {
520 let temp = self.declare_temp_local("__fn_ref_")?;
523 let const_idx = self
524 .program
525 .add_constant(Constant::Function(func_idx as u16));
526 self.emit(Instruction::new(
527 OpCode::PushConst,
528 Some(Operand::Const(const_idx)),
529 ));
530 self.emit(Instruction::new(
531 OpCode::StoreLocal,
532 Some(Operand::Local(temp)),
533 ));
534 let source_loc = self.span_to_source_location(span);
535 self.borrow_checker
536 .create_borrow(temp, temp, mode, span, Some(source_loc))?;
537 self.emit(Instruction::new(
538 OpCode::MakeRef,
539 Some(Operand::Local(temp)),
540 ));
541 Ok(())
542 } else {
543 Err(ShapeError::SemanticError {
544 message: format!(
545 "[B0004] reference argument must be a local or module_binding variable, got '{}'",
546 name
547 ),
548 location: Some(self.span_to_source_location(span)),
549 })
550 }
551 }
552
553 pub(super) fn push_scope(&mut self) {
555 self.locals.push(HashMap::new());
556 self.type_tracker.push_scope();
557 self.borrow_checker.enter_region();
558 }
559
560 pub(super) fn pop_scope(&mut self) {
562 self.borrow_checker.exit_region();
563 self.locals.pop();
564 self.type_tracker.pop_scope();
565 }
566
567 pub(super) fn declare_local(&mut self, name: &str) -> Result<u16> {
569 let idx = self.next_local;
570 self.next_local += 1;
571
572 if let Some(scope) = self.locals.last_mut() {
573 scope.insert(name.to_string(), idx);
574 }
575
576 Ok(idx)
577 }
578
579 pub(super) fn resolve_local(&self, name: &str) -> Option<u16> {
581 for scope in self.locals.iter().rev() {
582 if let Some(&idx) = scope.get(name) {
583 return Some(idx);
584 }
585 }
586 None
587 }
588
589 pub(super) fn declare_temp_local(&mut self, prefix: &str) -> Result<u16> {
591 let name = format!("{}{}", prefix, self.next_local);
592 self.declare_local(&name)
593 }
594
595 pub(super) fn set_local_type_info(&mut self, slot: u16, type_name: &str) {
597 let info = if let Some(schema) = self.type_tracker.schema_registry().get(type_name) {
598 VariableTypeInfo::known(schema.id, type_name.to_string())
599 } else {
600 VariableTypeInfo::named(type_name.to_string())
601 };
602 self.type_tracker.set_local_type(slot, info);
603 }
604
605 pub(super) fn set_module_binding_type_info(&mut self, slot: u16, type_name: &str) {
607 let info = if let Some(schema) = self.type_tracker.schema_registry().get(type_name) {
608 VariableTypeInfo::known(schema.id, type_name.to_string())
609 } else {
610 VariableTypeInfo::named(type_name.to_string())
611 };
612 self.type_tracker.set_binding_type(slot, info);
613 }
614
615 pub(super) fn capture_function_local_storage_hints(&mut self, func_idx: usize) {
621 let Some(func) = self.program.functions.get(func_idx) else {
622 return;
623 };
624 let hints: Vec<StorageHint> = (0..func.locals_count)
625 .map(|slot| self.type_tracker.get_local_storage_hint(slot))
626 .collect();
627
628 let has_any_known = hints.iter().any(|h| *h != StorageHint::Unknown);
630 let code_end = if func.body_length > 0 {
631 func.entry_point + func.body_length
632 } else {
633 self.program.instructions.len()
634 };
635 let has_trusted = self.program.instructions[func.entry_point..code_end]
636 .iter()
637 .any(|i| i.opcode.is_trusted());
638 if has_any_known || has_trusted {
639 self.program.functions[func_idx].frame_descriptor = Some(
640 crate::type_tracking::FrameDescriptor::from_slots(hints.clone()),
641 );
642 }
643
644 if self.program.function_local_storage_hints.len() <= func_idx {
645 self.program
646 .function_local_storage_hints
647 .resize(func_idx + 1, Vec::new());
648 }
649 self.program.function_local_storage_hints[func_idx] = hints;
650 }
651
652 pub(super) fn populate_program_storage_hints(&mut self) {
654 let top_hints: Vec<StorageHint> = (0..self.next_local)
655 .map(|slot| self.type_tracker.get_local_storage_hint(slot))
656 .collect();
657 self.program.top_level_local_storage_hints = top_hints.clone();
658
659 let has_any_known = top_hints.iter().any(|h| *h != StorageHint::Unknown);
661 let has_trusted = self.program.instructions
662 .iter()
663 .any(|i| i.opcode.is_trusted());
664 if has_any_known || has_trusted {
665 self.program.top_level_frame =
666 Some(crate::type_tracking::FrameDescriptor::from_slots(top_hints));
667 }
668
669 let mut module_binding_hints = vec![StorageHint::Unknown; self.module_bindings.len()];
670 for &idx in self.module_bindings.values() {
671 if let Some(slot) = module_binding_hints.get_mut(idx as usize) {
672 *slot = self.type_tracker.get_module_binding_storage_hint(idx);
673 }
674 }
675 self.program.module_binding_storage_hints = module_binding_hints;
676
677 if self.program.function_local_storage_hints.len() < self.program.functions.len() {
678 self.program
679 .function_local_storage_hints
680 .resize(self.program.functions.len(), Vec::new());
681 } else if self.program.function_local_storage_hints.len() > self.program.functions.len() {
682 self.program
683 .function_local_storage_hints
684 .truncate(self.program.functions.len());
685 }
686 }
687
688 pub(super) fn propagate_assignment_type_to_slot(
693 &mut self,
694 slot: u16,
695 is_local: bool,
696 allow_number_hint: bool,
697 ) {
698 if let Some(ref info) = self.last_expr_type_info {
699 if info.is_indexed()
700 || info.is_datatable()
701 || info.schema_id.is_some()
702 || Self::is_array_type_name(info.type_name.as_deref())
703 {
704 if is_local {
705 self.type_tracker.set_local_type(slot, info.clone());
706 } else {
707 self.type_tracker.set_binding_type(slot, info.clone());
708 }
709 return;
710 }
711 }
712
713 if let Some(schema_id) = self.last_expr_schema {
714 let schema_name = self
715 .type_tracker
716 .schema_registry()
717 .get_by_id(schema_id)
718 .map(|s| s.name.clone())
719 .unwrap_or_else(|| format!("__anon_{}", schema_id));
720 let info = VariableTypeInfo::known(schema_id, schema_name);
721 if is_local {
722 self.type_tracker.set_local_type(slot, info);
723 } else {
724 self.type_tracker.set_binding_type(slot, info);
725 }
726 return;
727 }
728
729 if let Some(numeric_type) = self.last_expr_numeric_type {
730 let (type_name, hint) = match numeric_type {
731 crate::type_tracking::NumericType::Int => ("int", StorageHint::Int64),
732 crate::type_tracking::NumericType::IntWidth(w) => {
733 use shape_ast::IntWidth;
734 let hint = match w {
735 IntWidth::I8 => StorageHint::Int8,
736 IntWidth::U8 => StorageHint::UInt8,
737 IntWidth::I16 => StorageHint::Int16,
738 IntWidth::U16 => StorageHint::UInt16,
739 IntWidth::I32 => StorageHint::Int32,
740 IntWidth::U32 => StorageHint::UInt32,
741 IntWidth::U64 => StorageHint::UInt64,
742 };
743 (w.type_name(), hint)
744 }
745 crate::type_tracking::NumericType::Number => {
746 if !allow_number_hint {
747 if is_local {
748 self.type_tracker
749 .set_local_type(slot, VariableTypeInfo::unknown());
750 } else {
751 self.type_tracker
752 .set_binding_type(slot, VariableTypeInfo::unknown());
753 }
754 return;
755 }
756 ("number", StorageHint::Float64)
757 }
758 crate::type_tracking::NumericType::Decimal => {
760 if is_local {
761 self.type_tracker
762 .set_local_type(slot, VariableTypeInfo::unknown());
763 } else {
764 self.type_tracker
765 .set_binding_type(slot, VariableTypeInfo::unknown());
766 }
767 return;
768 }
769 };
770 let info = VariableTypeInfo::with_storage(type_name.to_string(), hint);
771 if is_local {
772 self.type_tracker.set_local_type(slot, info);
773 } else {
774 self.type_tracker.set_binding_type(slot, info);
775 }
776 return;
777 }
778
779 if is_local {
781 self.type_tracker
782 .set_local_type(slot, VariableTypeInfo::unknown());
783 } else {
784 self.type_tracker
785 .set_binding_type(slot, VariableTypeInfo::unknown());
786 }
787 }
788
789 pub(super) fn propagate_assignment_type_to_identifier(&mut self, name: &str) {
793 if let Some(local_idx) = self.resolve_local(name) {
794 if self.ref_locals.contains(&local_idx) {
795 return;
796 }
797 self.propagate_assignment_type_to_slot(local_idx, true, true);
798 return;
799 }
800
801 let scoped_name = self
802 .resolve_scoped_module_binding_name(name)
803 .unwrap_or_else(|| name.to_string());
804 let binding_idx = self.get_or_create_module_binding(&scoped_name);
805 self.propagate_assignment_type_to_slot(binding_idx, false, true);
806 }
807
808 pub fn type_tracker(&self) -> &TypeTracker {
810 &self.type_tracker
811 }
812
813 pub fn type_tracker_mut(&mut self) -> &mut TypeTracker {
815 &mut self.type_tracker
816 }
817
818 pub(super) fn resolve_column_index(&self, field: &str) -> Result<u32> {
821 self.program
822 .data_schema
823 .as_ref()
824 .ok_or_else(|| ShapeError::RuntimeError {
825 message: format!(
826 "No data schema provided. Cannot resolve field '{}'. \
827 Hint: Use stdlib/finance to load market data with OHLCV schema.",
828 field
829 ),
830 location: None,
831 })?
832 .get_index(field)
833 .ok_or_else(|| ShapeError::RuntimeError {
834 message: format!(
835 "Unknown column '{}' in data schema. Available columns: {:?}",
836 field,
837 self.program
838 .data_schema
839 .as_ref()
840 .map(|s| &s.column_names)
841 .unwrap_or(&vec![])
842 ),
843 location: None,
844 })
845 }
846
847 pub(super) fn is_data_column(&self, field: &str) -> bool {
849 self.program
850 .data_schema
851 .as_ref()
852 .map(|s| s.get_index(field).is_some())
853 .unwrap_or(false)
854 }
855
856 pub(super) fn collect_outer_scope_vars(&self) -> Vec<String> {
858 let mut names = BTreeSet::new();
859 for scope in &self.locals {
860 for name in scope.keys() {
861 names.insert(name.clone());
862 }
863 }
864 for name in self.module_bindings.keys() {
865 names.insert(name.clone());
866 }
867 names.into_iter().collect()
868 }
869
870 pub(super) fn get_or_create_module_binding(&mut self, name: &str) -> u16 {
872 if let Some(&idx) = self.module_bindings.get(name) {
873 idx
874 } else {
875 let idx = self.next_global;
876 self.next_global += 1;
877 self.module_bindings.insert(name.to_string(), idx);
878 idx
879 }
880 }
881
882 pub(super) fn resolve_scoped_module_binding_name(&self, name: &str) -> Option<String> {
883 if self.module_bindings.contains_key(name) {
884 return Some(name.to_string());
885 }
886 for module_path in self.module_scope_stack.iter().rev() {
887 let candidate = format!("{}::{}", module_path, name);
888 if self.module_bindings.contains_key(&candidate) {
889 return Some(candidate);
890 }
891 }
892 None
893 }
894
895 pub(super) fn resolve_scoped_function_name(&self, name: &str) -> Option<String> {
896 if self.program.functions.iter().any(|f| f.name == name) {
897 return Some(name.to_string());
898 }
899 for module_path in self.module_scope_stack.iter().rev() {
900 let candidate = format!("{}::{}", module_path, name);
901 if self.program.functions.iter().any(|f| f.name == candidate) {
902 return Some(candidate);
903 }
904 }
905 None
906 }
907
908 pub(super) fn find_function(&self, name: &str) -> Option<usize> {
910 if let Some(actual_name) = self.function_aliases.get(name) {
912 if let Some(idx) = self
913 .program
914 .functions
915 .iter()
916 .position(|f| f.name == *actual_name)
917 {
918 return Some(idx);
919 }
920 }
921
922 if let Some(resolved) = self.resolve_scoped_function_name(name) {
924 if let Some(idx) = self
925 .program
926 .functions
927 .iter()
928 .position(|f| f.name == resolved)
929 {
930 return Some(idx);
931 }
932 }
933
934 if let Some(imported) = self.imported_names.get(name) {
939 let original = &imported.original_name;
940 if let Some(idx) = self
942 .program
943 .functions
944 .iter()
945 .position(|f| f.name == *original)
946 {
947 return Some(idx);
948 }
949 if let Some(resolved) = self.resolve_scoped_function_name(original) {
951 if let Some(idx) = self
952 .program
953 .functions
954 .iter()
955 .position(|f| f.name == resolved)
956 {
957 return Some(idx);
958 }
959 }
960 }
961
962 None
963 }
964
965 pub(super) fn resolve_receiver_extend_type(
975 &self,
976 receiver: &shape_ast::ast::Expr,
977 receiver_type_info: &Option<crate::type_tracking::VariableTypeInfo>,
978 _receiver_schema: Option<u32>,
979 ) -> Option<String> {
980 if let Some(numeric) = self.last_expr_numeric_type {
984 return Some(
985 match numeric {
986 crate::type_tracking::NumericType::Int
987 | crate::type_tracking::NumericType::IntWidth(_) => "Int",
988 crate::type_tracking::NumericType::Number => "Number",
989 crate::type_tracking::NumericType::Decimal => "Decimal",
990 }
991 .to_string(),
992 );
993 }
994
995 if let Some(info) = receiver_type_info {
997 if let Some(type_name) = &info.type_name {
998 let base = type_name.split('<').next().unwrap_or(type_name);
1000 return Some(base.to_string());
1001 }
1002 }
1003
1004 match receiver {
1006 shape_ast::ast::Expr::Literal(lit, _) => match lit {
1007 shape_ast::ast::Literal::String(_)
1008 | shape_ast::ast::Literal::FormattedString { .. }
1009 | shape_ast::ast::Literal::ContentString { .. } => Some("String".to_string()),
1010 shape_ast::ast::Literal::Bool(_) => Some("Bool".to_string()),
1011 _ => None,
1012 },
1013 shape_ast::ast::Expr::Array(..) => Some("Vec".to_string()),
1014 _ => None,
1015 }
1016 }
1017
1018 pub(super) fn emit_store_identifier(&mut self, name: &str) -> Result<()> {
1020 if let Some(&upvalue_idx) = self.mutable_closure_captures.get(name) {
1022 self.emit(Instruction::new(
1023 OpCode::StoreClosure,
1024 Some(Operand::Local(upvalue_idx)),
1025 ));
1026 return Ok(());
1027 }
1028 if let Some(local_idx) = self.resolve_local(name) {
1029 if self.ref_locals.contains(&local_idx) {
1030 self.emit(Instruction::new(
1031 OpCode::DerefStore,
1032 Some(Operand::Local(local_idx)),
1033 ));
1034 } else {
1035 self.emit(Instruction::new(
1036 OpCode::StoreLocal,
1037 Some(Operand::Local(local_idx)),
1038 ));
1039 if let Some(type_name) = self
1041 .type_tracker
1042 .get_local_type(local_idx)
1043 .and_then(|info| info.type_name.as_deref())
1044 {
1045 if let Some(w) = shape_ast::IntWidth::from_name(type_name) {
1046 if let Some(last) = self.program.instructions.last_mut() {
1047 if last.opcode == OpCode::StoreLocal {
1048 last.opcode = OpCode::StoreLocalTyped;
1049 last.operand = Some(Operand::TypedLocal(
1050 local_idx,
1051 crate::bytecode::NumericWidth::from_int_width(w),
1052 ));
1053 }
1054 }
1055 }
1056 }
1057 }
1058 } else {
1059 let scoped_name = self
1060 .resolve_scoped_module_binding_name(name)
1061 .unwrap_or_else(|| name.to_string());
1062 let binding_idx = self.get_or_create_module_binding(&scoped_name);
1063 self.emit(Instruction::new(
1064 OpCode::StoreModuleBinding,
1065 Some(Operand::ModuleBinding(binding_idx)),
1066 ));
1067 }
1068 Ok(())
1069 }
1070
1071 pub(super) fn get_builtin_function(&self, name: &str) -> Option<BuiltinFunction> {
1073 if !self.allow_internal_builtins
1078 && (name.starts_with("__native_")
1079 || name.starts_with("__intrinsic_")
1080 || name.starts_with("__json_"))
1081 {
1082 return None;
1083 }
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 "__intrinsic_map" => Some(BuiltinFunction::Map),
1137 "__intrinsic_filter" => Some(BuiltinFunction::Filter),
1138 "__intrinsic_reduce" => Some(BuiltinFunction::Reduce),
1139 "print" => Some(BuiltinFunction::Print),
1140 "format" => Some(BuiltinFunction::Format),
1141 "len" | "count" => Some(BuiltinFunction::Len),
1142 "__intrinsic_snapshot" | "snapshot" => Some(BuiltinFunction::Snapshot),
1144 "exit" => Some(BuiltinFunction::Exit),
1145 "range" => Some(BuiltinFunction::Range),
1146 "is_number" | "isNumber" => Some(BuiltinFunction::IsNumber),
1147 "is_string" | "isString" => Some(BuiltinFunction::IsString),
1148 "is_bool" | "isBool" => Some(BuiltinFunction::IsBool),
1149 "is_array" | "isArray" => Some(BuiltinFunction::IsArray),
1150 "is_object" | "isObject" => Some(BuiltinFunction::IsObject),
1151 "is_data_row" | "isDataRow" => Some(BuiltinFunction::IsDataRow),
1152 "to_string" | "toString" => Some(BuiltinFunction::ToString),
1153 "to_number" | "toNumber" => Some(BuiltinFunction::ToNumber),
1154 "to_bool" | "toBool" => Some(BuiltinFunction::ToBool),
1155 "__into_int" => Some(BuiltinFunction::IntoInt),
1156 "__into_number" => Some(BuiltinFunction::IntoNumber),
1157 "__into_decimal" => Some(BuiltinFunction::IntoDecimal),
1158 "__into_bool" => Some(BuiltinFunction::IntoBool),
1159 "__into_string" => Some(BuiltinFunction::IntoString),
1160 "__try_into_int" => Some(BuiltinFunction::TryIntoInt),
1161 "__try_into_number" => Some(BuiltinFunction::TryIntoNumber),
1162 "__try_into_decimal" => Some(BuiltinFunction::TryIntoDecimal),
1163 "__try_into_bool" => Some(BuiltinFunction::TryIntoBool),
1164 "__try_into_string" => Some(BuiltinFunction::TryIntoString),
1165 "__native_ptr_size" => Some(BuiltinFunction::NativePtrSize),
1166 "__native_ptr_new_cell" => Some(BuiltinFunction::NativePtrNewCell),
1167 "__native_ptr_free_cell" => Some(BuiltinFunction::NativePtrFreeCell),
1168 "__native_ptr_read_ptr" => Some(BuiltinFunction::NativePtrReadPtr),
1169 "__native_ptr_write_ptr" => Some(BuiltinFunction::NativePtrWritePtr),
1170 "__native_table_from_arrow_c" => Some(BuiltinFunction::NativeTableFromArrowC),
1171 "__native_table_from_arrow_c_typed" => {
1172 Some(BuiltinFunction::NativeTableFromArrowCTyped)
1173 }
1174 "__native_table_bind_type" => Some(BuiltinFunction::NativeTableBindType),
1175 "fold" => Some(BuiltinFunction::ControlFold),
1176
1177 "__intrinsic_sum" => Some(BuiltinFunction::IntrinsicSum),
1179 "__intrinsic_mean" => Some(BuiltinFunction::IntrinsicMean),
1180 "__intrinsic_min" => Some(BuiltinFunction::IntrinsicMin),
1181 "__intrinsic_max" => Some(BuiltinFunction::IntrinsicMax),
1182 "__intrinsic_std" => Some(BuiltinFunction::IntrinsicStd),
1183 "__intrinsic_variance" => Some(BuiltinFunction::IntrinsicVariance),
1184
1185 "__intrinsic_random" => Some(BuiltinFunction::IntrinsicRandom),
1187 "__intrinsic_random_int" => Some(BuiltinFunction::IntrinsicRandomInt),
1188 "__intrinsic_random_seed" => Some(BuiltinFunction::IntrinsicRandomSeed),
1189 "__intrinsic_random_normal" => Some(BuiltinFunction::IntrinsicRandomNormal),
1190 "__intrinsic_random_array" => Some(BuiltinFunction::IntrinsicRandomArray),
1191
1192 "__intrinsic_dist_uniform" => Some(BuiltinFunction::IntrinsicDistUniform),
1194 "__intrinsic_dist_lognormal" => Some(BuiltinFunction::IntrinsicDistLognormal),
1195 "__intrinsic_dist_exponential" => Some(BuiltinFunction::IntrinsicDistExponential),
1196 "__intrinsic_dist_poisson" => Some(BuiltinFunction::IntrinsicDistPoisson),
1197 "__intrinsic_dist_sample_n" => Some(BuiltinFunction::IntrinsicDistSampleN),
1198
1199 "__intrinsic_brownian_motion" => Some(BuiltinFunction::IntrinsicBrownianMotion),
1201 "__intrinsic_gbm" => Some(BuiltinFunction::IntrinsicGbm),
1202 "__intrinsic_ou_process" => Some(BuiltinFunction::IntrinsicOuProcess),
1203 "__intrinsic_random_walk" => Some(BuiltinFunction::IntrinsicRandomWalk),
1204
1205 "__intrinsic_rolling_sum" => Some(BuiltinFunction::IntrinsicRollingSum),
1207 "__intrinsic_rolling_mean" => Some(BuiltinFunction::IntrinsicRollingMean),
1208 "__intrinsic_rolling_std" => Some(BuiltinFunction::IntrinsicRollingStd),
1209 "__intrinsic_rolling_min" => Some(BuiltinFunction::IntrinsicRollingMin),
1210 "__intrinsic_rolling_max" => Some(BuiltinFunction::IntrinsicRollingMax),
1211 "__intrinsic_ema" => Some(BuiltinFunction::IntrinsicEma),
1212 "__intrinsic_linear_recurrence" => Some(BuiltinFunction::IntrinsicLinearRecurrence),
1213
1214 "__intrinsic_shift" => Some(BuiltinFunction::IntrinsicShift),
1216 "__intrinsic_diff" => Some(BuiltinFunction::IntrinsicDiff),
1217 "__intrinsic_pct_change" => Some(BuiltinFunction::IntrinsicPctChange),
1218 "__intrinsic_fillna" => Some(BuiltinFunction::IntrinsicFillna),
1219 "__intrinsic_cumsum" => Some(BuiltinFunction::IntrinsicCumsum),
1220 "__intrinsic_cumprod" => Some(BuiltinFunction::IntrinsicCumprod),
1221 "__intrinsic_clip" => Some(BuiltinFunction::IntrinsicClip),
1222
1223 "__intrinsic_correlation" => Some(BuiltinFunction::IntrinsicCorrelation),
1225 "__intrinsic_covariance" => Some(BuiltinFunction::IntrinsicCovariance),
1226 "__intrinsic_percentile" => Some(BuiltinFunction::IntrinsicPercentile),
1227 "__intrinsic_median" => Some(BuiltinFunction::IntrinsicMedian),
1228
1229 "__intrinsic_char_code" => Some(BuiltinFunction::IntrinsicCharCode),
1231 "__intrinsic_from_char_code" => Some(BuiltinFunction::IntrinsicFromCharCode),
1232
1233 "__intrinsic_series" => Some(BuiltinFunction::IntrinsicSeries),
1235
1236 "reflect" => Some(BuiltinFunction::Reflect),
1238
1239 "sign" => Some(BuiltinFunction::Sign),
1241 "gcd" => Some(BuiltinFunction::Gcd),
1242 "lcm" => Some(BuiltinFunction::Lcm),
1243 "hypot" => Some(BuiltinFunction::Hypot),
1244 "clamp" => Some(BuiltinFunction::Clamp),
1245 "isNaN" | "is_nan" => Some(BuiltinFunction::IsNaN),
1246 "isFinite" | "is_finite" => Some(BuiltinFunction::IsFinite),
1247
1248 _ => None,
1249 }
1250 }
1251
1252 pub(super) fn builtin_requires_arg_count(&self, builtin: BuiltinFunction) -> bool {
1254 matches!(
1255 builtin,
1256 BuiltinFunction::Abs
1257 | BuiltinFunction::Min
1258 | BuiltinFunction::Max
1259 | BuiltinFunction::Sqrt
1260 | BuiltinFunction::Ln
1261 | BuiltinFunction::Pow
1262 | BuiltinFunction::Exp
1263 | BuiltinFunction::Log
1264 | BuiltinFunction::Floor
1265 | BuiltinFunction::Ceil
1266 | BuiltinFunction::Round
1267 | BuiltinFunction::Sin
1268 | BuiltinFunction::Cos
1269 | BuiltinFunction::Tan
1270 | BuiltinFunction::Asin
1271 | BuiltinFunction::Acos
1272 | BuiltinFunction::Atan
1273 | BuiltinFunction::StdDev
1274 | BuiltinFunction::Range
1275 | BuiltinFunction::Slice
1276 | BuiltinFunction::Push
1277 | BuiltinFunction::Pop
1278 | BuiltinFunction::First
1279 | BuiltinFunction::Last
1280 | BuiltinFunction::Zip
1281 | BuiltinFunction::Map
1282 | BuiltinFunction::Filter
1283 | BuiltinFunction::Reduce
1284 | BuiltinFunction::ForEach
1285 | BuiltinFunction::Find
1286 | BuiltinFunction::FindIndex
1287 | BuiltinFunction::Some
1288 | BuiltinFunction::Every
1289 | BuiltinFunction::SomeCtor
1290 | BuiltinFunction::OkCtor
1291 | BuiltinFunction::ErrCtor
1292 | BuiltinFunction::HashMapCtor
1293 | BuiltinFunction::SetCtor
1294 | BuiltinFunction::DequeCtor
1295 | BuiltinFunction::PriorityQueueCtor
1296 | BuiltinFunction::MutexCtor
1297 | BuiltinFunction::AtomicCtor
1298 | BuiltinFunction::LazyCtor
1299 | BuiltinFunction::ChannelCtor
1300 | BuiltinFunction::Print
1301 | BuiltinFunction::Format
1302 | BuiltinFunction::Len
1303 | BuiltinFunction::Snapshot
1305 | BuiltinFunction::ObjectRest
1306 | BuiltinFunction::IsNumber
1307 | BuiltinFunction::IsString
1308 | BuiltinFunction::IsBool
1309 | BuiltinFunction::IsArray
1310 | BuiltinFunction::IsObject
1311 | BuiltinFunction::IsDataRow
1312 | BuiltinFunction::ToString
1313 | BuiltinFunction::ToNumber
1314 | BuiltinFunction::ToBool
1315 | BuiltinFunction::IntoInt
1316 | BuiltinFunction::IntoNumber
1317 | BuiltinFunction::IntoDecimal
1318 | BuiltinFunction::IntoBool
1319 | BuiltinFunction::IntoString
1320 | BuiltinFunction::TryIntoInt
1321 | BuiltinFunction::TryIntoNumber
1322 | BuiltinFunction::TryIntoDecimal
1323 | BuiltinFunction::TryIntoBool
1324 | BuiltinFunction::TryIntoString
1325 | BuiltinFunction::NativePtrSize
1326 | BuiltinFunction::NativePtrNewCell
1327 | BuiltinFunction::NativePtrFreeCell
1328 | BuiltinFunction::NativePtrReadPtr
1329 | BuiltinFunction::NativePtrWritePtr
1330 | BuiltinFunction::NativeTableFromArrowC
1331 | BuiltinFunction::NativeTableFromArrowCTyped
1332 | BuiltinFunction::NativeTableBindType
1333 | BuiltinFunction::ControlFold
1334 | BuiltinFunction::IntrinsicSum
1335 | BuiltinFunction::IntrinsicMean
1336 | BuiltinFunction::IntrinsicMin
1337 | BuiltinFunction::IntrinsicMax
1338 | BuiltinFunction::IntrinsicStd
1339 | BuiltinFunction::IntrinsicVariance
1340 | BuiltinFunction::IntrinsicRandom
1341 | BuiltinFunction::IntrinsicRandomInt
1342 | BuiltinFunction::IntrinsicRandomSeed
1343 | BuiltinFunction::IntrinsicRandomNormal
1344 | BuiltinFunction::IntrinsicRandomArray
1345 | BuiltinFunction::IntrinsicDistUniform
1346 | BuiltinFunction::IntrinsicDistLognormal
1347 | BuiltinFunction::IntrinsicDistExponential
1348 | BuiltinFunction::IntrinsicDistPoisson
1349 | BuiltinFunction::IntrinsicDistSampleN
1350 | BuiltinFunction::IntrinsicBrownianMotion
1351 | BuiltinFunction::IntrinsicGbm
1352 | BuiltinFunction::IntrinsicOuProcess
1353 | BuiltinFunction::IntrinsicRandomWalk
1354 | BuiltinFunction::IntrinsicRollingSum
1355 | BuiltinFunction::IntrinsicRollingMean
1356 | BuiltinFunction::IntrinsicRollingStd
1357 | BuiltinFunction::IntrinsicRollingMin
1358 | BuiltinFunction::IntrinsicRollingMax
1359 | BuiltinFunction::IntrinsicEma
1360 | BuiltinFunction::IntrinsicLinearRecurrence
1361 | BuiltinFunction::IntrinsicShift
1362 | BuiltinFunction::IntrinsicDiff
1363 | BuiltinFunction::IntrinsicPctChange
1364 | BuiltinFunction::IntrinsicFillna
1365 | BuiltinFunction::IntrinsicCumsum
1366 | BuiltinFunction::IntrinsicCumprod
1367 | BuiltinFunction::IntrinsicClip
1368 | BuiltinFunction::IntrinsicCorrelation
1369 | BuiltinFunction::IntrinsicCovariance
1370 | BuiltinFunction::IntrinsicPercentile
1371 | BuiltinFunction::IntrinsicMedian
1372 | BuiltinFunction::IntrinsicCharCode
1373 | BuiltinFunction::IntrinsicFromCharCode
1374 | BuiltinFunction::IntrinsicSeries
1375 | BuiltinFunction::IntrinsicVecAbs
1376 | BuiltinFunction::IntrinsicVecSqrt
1377 | BuiltinFunction::IntrinsicVecLn
1378 | BuiltinFunction::IntrinsicVecExp
1379 | BuiltinFunction::IntrinsicVecAdd
1380 | BuiltinFunction::IntrinsicVecSub
1381 | BuiltinFunction::IntrinsicVecMul
1382 | BuiltinFunction::IntrinsicVecDiv
1383 | BuiltinFunction::IntrinsicVecMax
1384 | BuiltinFunction::IntrinsicVecMin
1385 | BuiltinFunction::IntrinsicVecSelect
1386 | BuiltinFunction::IntrinsicMatMulVec
1387 | BuiltinFunction::IntrinsicMatMulMat
1388 | BuiltinFunction::Sign
1389 | BuiltinFunction::Gcd
1390 | BuiltinFunction::Lcm
1391 | BuiltinFunction::Hypot
1392 | BuiltinFunction::Clamp
1393 | BuiltinFunction::IsNaN
1394 | BuiltinFunction::IsFinite
1395 )
1396 }
1397
1398 pub(super) fn is_known_builtin_method(method: &str) -> bool {
1402 matches!(method,
1404 "map" | "filter" | "reduce" | "forEach" | "find" | "findIndex"
1405 | "some" | "every" | "sort" | "groupBy" | "flatMap"
1406 | "len" | "length" | "first" | "last" | "reverse" | "slice"
1407 | "concat" | "take" | "drop" | "skip"
1408 | "indexOf" | "includes"
1409 | "join" | "flatten" | "unique" | "distinct" | "distinctBy"
1410 | "sum" | "avg" | "min" | "max" | "count"
1411 | "where" | "select" | "orderBy" | "thenBy" | "takeWhile"
1412 | "skipWhile" | "single" | "any" | "all"
1413 | "innerJoin" | "leftJoin" | "crossJoin"
1414 | "union" | "intersect" | "except"
1415 )
1416 || matches!(method,
1418 "columns" | "column" | "head" | "tail" | "mean" | "std"
1419 | "describe" | "aggregate" | "group_by" | "index_by" | "indexBy"
1420 | "simulate" | "toMat" | "to_mat"
1421 )
1422 || matches!(method, "toArray")
1424 || matches!(method, "resample" | "between")
1426 || matches!(method,
1428 "toFixed" | "toInt" | "toNumber" | "to_number" | "floor" | "ceil" | "round"
1429 | "abs" | "sign" | "clamp"
1430 )
1431 || matches!(method,
1433 "toUpperCase" | "toLowerCase" | "trim" | "contains" | "startsWith"
1434 | "endsWith" | "split" | "replace" | "substring" | "charAt"
1435 | "padStart" | "padEnd" | "repeat" | "toString"
1436 )
1437 || matches!(method, "keys" | "values" | "has" | "get" | "set" | "len")
1439 || matches!(method, "type")
1441 }
1442
1443 pub(super) fn try_track_datatable_type(
1448 &mut self,
1449 type_ann: &shape_ast::ast::TypeAnnotation,
1450 slot: u16,
1451 is_local: bool,
1452 ) -> shape_ast::error::Result<()> {
1453 use shape_ast::ast::TypeAnnotation;
1454 if let TypeAnnotation::Generic { name, args } = type_ann {
1455 if name == "Table" && args.len() == 1 {
1456 let inner_name = match &args[0] {
1457 TypeAnnotation::Reference(t) => Some(t.as_str()),
1458 TypeAnnotation::Basic(t) => Some(t.as_str()),
1459 _ => None,
1460 };
1461 if let Some(type_name) = inner_name {
1462 let schema_id = self
1463 .type_tracker
1464 .schema_registry()
1465 .get(type_name)
1466 .map(|s| s.id);
1467 if let Some(sid) = schema_id {
1468 let info = crate::type_tracking::VariableTypeInfo::datatable(
1469 sid,
1470 type_name.to_string(),
1471 );
1472 if is_local {
1473 self.type_tracker.set_local_type(slot, info);
1474 } else {
1475 self.type_tracker.set_binding_type(slot, info);
1476 }
1477 } else {
1478 return Err(shape_ast::error::ShapeError::SemanticError {
1479 message: format!(
1480 "Unknown type '{}' in Table<{}> annotation",
1481 type_name, type_name
1482 ),
1483 location: None,
1484 });
1485 }
1486 }
1487 }
1488 }
1489 Ok(())
1490 }
1491
1492 pub(super) fn is_row_view_variable(&self, name: &str) -> bool {
1494 if let Some(local_idx) = self.resolve_local(name) {
1495 if let Some(info) = self.type_tracker.get_local_type(local_idx) {
1496 return info.is_row_view();
1497 }
1498 }
1499 if let Some(&binding_idx) = self.module_bindings.get(name) {
1500 if let Some(info) = self.type_tracker.get_binding_type(binding_idx) {
1501 return info.is_row_view();
1502 }
1503 }
1504 false
1505 }
1506
1507 pub(super) fn get_row_view_field_names(&self, name: &str) -> Option<Vec<String>> {
1509 let type_name = if let Some(local_idx) = self.resolve_local(name) {
1510 self.type_tracker
1511 .get_local_type(local_idx)
1512 .and_then(|info| {
1513 if info.is_row_view() {
1514 info.type_name.clone()
1515 } else {
1516 None
1517 }
1518 })
1519 } else if let Some(&binding_idx) = self.module_bindings.get(name) {
1520 self.type_tracker
1521 .get_binding_type(binding_idx)
1522 .and_then(|info| {
1523 if info.is_row_view() {
1524 info.type_name.clone()
1525 } else {
1526 None
1527 }
1528 })
1529 } else {
1530 None
1531 };
1532
1533 if let Some(tn) = type_name {
1534 if let Some(schema) = self.type_tracker.schema_registry().get(&tn) {
1535 return Some(schema.field_names().map(|n| n.to_string()).collect());
1536 }
1537 }
1538 None
1539 }
1540
1541 pub(super) fn try_resolve_row_view_column(
1547 &self,
1548 var_name: &str,
1549 field_name: &str,
1550 ) -> Option<u32> {
1551 if let Some(local_idx) = self.resolve_local(var_name) {
1553 return self
1554 .type_tracker
1555 .get_row_view_column_id(local_idx, true, field_name);
1556 }
1557 if let Some(&binding_idx) = self.module_bindings.get(var_name) {
1558 return self
1559 .type_tracker
1560 .get_row_view_column_id(binding_idx, false, field_name);
1561 }
1562 None
1563 }
1564
1565 pub(super) fn row_view_field_opcode(&self, var_name: &str, field_name: &str) -> OpCode {
1570 use shape_runtime::type_schema::FieldType;
1571
1572 let type_name = if let Some(local_idx) = self.resolve_local(var_name) {
1573 self.type_tracker
1574 .get_local_type(local_idx)
1575 .and_then(|info| info.type_name.clone())
1576 } else if let Some(&binding_idx) = self.module_bindings.get(var_name) {
1577 self.type_tracker
1578 .get_binding_type(binding_idx)
1579 .and_then(|info| info.type_name.clone())
1580 } else {
1581 None
1582 };
1583
1584 if let Some(type_name) = type_name {
1585 if let Some(schema) = self.type_tracker.schema_registry().get(&type_name) {
1586 if let Some(field) = schema.get_field(field_name) {
1587 return match field.field_type {
1588 FieldType::F64 => OpCode::LoadColF64,
1589 FieldType::I64 | FieldType::Timestamp => OpCode::LoadColI64,
1590 FieldType::Bool => OpCode::LoadColBool,
1591 FieldType::String => OpCode::LoadColStr,
1592 _ => OpCode::LoadColF64, };
1594 }
1595 }
1596 }
1597 OpCode::LoadColF64 }
1599
1600 pub(super) fn resolve_row_view_field_numeric_type(
1602 &self,
1603 var_name: &str,
1604 field_name: &str,
1605 ) -> Option<crate::type_tracking::NumericType> {
1606 use crate::type_tracking::NumericType;
1607 use shape_runtime::type_schema::FieldType;
1608
1609 let type_name = if let Some(local_idx) = self.resolve_local(var_name) {
1610 self.type_tracker
1611 .get_local_type(local_idx)
1612 .and_then(|info| info.type_name.clone())
1613 } else if let Some(&binding_idx) = self.module_bindings.get(var_name) {
1614 self.type_tracker
1615 .get_binding_type(binding_idx)
1616 .and_then(|info| info.type_name.clone())
1617 } else {
1618 None
1619 };
1620
1621 if let Some(type_name) = type_name {
1622 if let Some(schema) = self.type_tracker.schema_registry().get(&type_name) {
1623 if let Some(field) = schema.get_field(field_name) {
1624 return match field.field_type {
1625 FieldType::F64 => Some(NumericType::Number),
1626 FieldType::I64 | FieldType::Timestamp => Some(NumericType::Int),
1627 FieldType::Decimal => Some(NumericType::Decimal),
1628 _ => None,
1629 };
1630 }
1631 }
1632 }
1633 None
1634 }
1635
1636 pub(super) fn type_annotation_to_field_type(
1638 ann: &shape_ast::ast::TypeAnnotation,
1639 ) -> shape_runtime::type_schema::FieldType {
1640 use shape_ast::ast::TypeAnnotation;
1641 use shape_runtime::type_schema::FieldType;
1642 match ann {
1643 TypeAnnotation::Basic(s) => match s.as_str() {
1644 "number" | "float" | "f64" | "f32" => FieldType::F64,
1645 "i8" => FieldType::I8,
1646 "u8" => FieldType::U8,
1647 "i16" => FieldType::I16,
1648 "u16" => FieldType::U16,
1649 "i32" => FieldType::I32,
1650 "u32" => FieldType::U32,
1651 "u64" => FieldType::U64,
1652 "int" | "i64" | "integer" | "isize" | "usize" | "byte" | "char" => FieldType::I64,
1653 "string" | "str" => FieldType::String,
1654 "decimal" => FieldType::Decimal,
1655 "bool" | "boolean" => FieldType::Bool,
1656 "timestamp" => FieldType::Timestamp,
1657 other => FieldType::Object(other.to_string()),
1662 },
1663 TypeAnnotation::Reference(s) => FieldType::Object(s.clone()),
1664 TypeAnnotation::Array(inner) => {
1665 FieldType::Array(Box::new(Self::type_annotation_to_field_type(inner)))
1666 }
1667 TypeAnnotation::Generic { name, .. } => match name.as_str() {
1668 "HashMap" | "Map" | "Result" | "Option" | "Set" => FieldType::Any,
1670 other => FieldType::Object(other.to_string()),
1672 },
1673 _ => FieldType::Any,
1674 }
1675 }
1676
1677 pub(super) fn eval_annotation_arg(expr: &shape_ast::ast::Expr) -> Option<String> {
1680 use shape_ast::ast::{Expr, Literal};
1681 match expr {
1682 Expr::Literal(Literal::String(s), _) => Some(s.clone()),
1683 Expr::Literal(Literal::Number(n), _) => Some(n.to_string()),
1684 Expr::Literal(Literal::Int(i), _) => Some(i.to_string()),
1685 Expr::Literal(Literal::Bool(b), _) => Some(b.to_string()),
1686 _ => None,
1687 }
1688 }
1689
1690 pub(super) fn get_table_schema_id(
1695 &self,
1696 type_ann: &shape_ast::ast::TypeAnnotation,
1697 ) -> Option<u16> {
1698 use shape_ast::ast::TypeAnnotation;
1699 if let TypeAnnotation::Generic { name, args } = type_ann {
1700 if name == "Table" && args.len() == 1 {
1701 let inner_name = match &args[0] {
1702 TypeAnnotation::Reference(t) | TypeAnnotation::Basic(t) => Some(t.as_str()),
1703 _ => None,
1704 };
1705 if let Some(type_name) = inner_name {
1706 return self
1707 .type_tracker
1708 .schema_registry()
1709 .get(type_name)
1710 .map(|s| s.id as u16);
1711 }
1712 }
1713 }
1714 None
1715 }
1716
1717 pub(super) fn push_drop_scope(&mut self) {
1721 self.drop_locals.push(Vec::new());
1722 }
1723
1724 pub(super) fn pop_drop_scope(&mut self) -> Result<()> {
1727 if let Some(locals) = self.drop_locals.pop() {
1729 for (local_idx, is_async) in locals.into_iter().rev() {
1730 self.emit_drop_call_for_local(local_idx, is_async);
1731 }
1732 }
1733 Ok(())
1734 }
1735
1736 fn emit_drop_call_for_local(&mut self, local_idx: u16, is_async: bool) {
1740 let type_name_opt = self
1741 .type_tracker
1742 .get_local_type(local_idx)
1743 .and_then(|info| info.type_name.clone());
1744 self.emit(Instruction::new(
1745 OpCode::LoadLocal,
1746 Some(Operand::Local(local_idx)),
1747 ));
1748 let opcode = if is_async {
1749 OpCode::DropCallAsync
1750 } else {
1751 OpCode::DropCall
1752 };
1753 if let Some(type_name) = type_name_opt {
1754 let str_idx = self.program.add_string(type_name);
1755 self.emit(Instruction::new(opcode, Some(Operand::Property(str_idx))));
1756 } else {
1757 self.emit(Instruction::simple(opcode));
1758 }
1759 }
1760
1761 pub(super) fn emit_drop_call_for_module_binding(&mut self, binding_idx: u16, is_async: bool) {
1764 let type_name_opt = self
1765 .type_tracker
1766 .get_binding_type(binding_idx)
1767 .and_then(|info| info.type_name.clone());
1768 self.emit(Instruction::new(
1769 OpCode::LoadModuleBinding,
1770 Some(Operand::ModuleBinding(binding_idx)),
1771 ));
1772 let opcode = if is_async {
1773 OpCode::DropCallAsync
1774 } else {
1775 OpCode::DropCall
1776 };
1777 if let Some(type_name) = type_name_opt {
1778 let str_idx = self.program.add_string(type_name);
1779 self.emit(Instruction::new(opcode, Some(Operand::Property(str_idx))));
1780 } else {
1781 self.emit(Instruction::simple(opcode));
1782 }
1783 }
1784
1785 pub(super) fn track_drop_local(&mut self, local_idx: u16, is_async: bool) {
1787 if let Some(scope) = self.drop_locals.last_mut() {
1788 scope.push((local_idx, is_async));
1789 }
1790 }
1791
1792 pub(super) fn local_drop_kind(&self, local_idx: u16) -> Option<DropKind> {
1795 let type_name = self
1796 .type_tracker
1797 .get_local_type(local_idx)
1798 .and_then(|info| info.type_name.as_ref())?;
1799 self.drop_type_info.get(type_name).copied()
1800 }
1801
1802 pub(super) fn annotation_drop_kind(&self, type_ann: &TypeAnnotation) -> Option<DropKind> {
1804 let type_name = Self::tracked_type_name_from_annotation(type_ann)?;
1805 self.drop_type_info.get(&type_name).copied()
1806 }
1807
1808 pub(super) fn emit_drops_for_early_exit(&mut self, scopes_to_exit: usize) -> Result<()> {
1811 let total = self.drop_locals.len();
1812 if scopes_to_exit > total {
1813 return Ok(());
1814 }
1815 let mut scopes: Vec<Vec<(u16, bool)>> = Vec::new();
1817 for i in (total - scopes_to_exit..total).rev() {
1818 let locals = self.drop_locals.get(i).cloned().unwrap_or_default();
1819 scopes.push(locals);
1820 }
1821 for locals in scopes {
1823 for (local_idx, is_async) in locals.into_iter().rev() {
1824 self.emit_drop_call_for_local(local_idx, is_async);
1825 }
1826 }
1827 Ok(())
1828 }
1829
1830 pub(super) fn track_drop_module_binding(&mut self, binding_idx: u16, is_async: bool) {
1832 self.drop_module_bindings.push((binding_idx, is_async));
1833 }
1834}
1835
1836#[cfg(test)]
1837mod tests {
1838 use super::super::BytecodeCompiler;
1839 use shape_ast::ast::TypeAnnotation;
1840 use shape_runtime::type_schema::FieldType;
1841
1842 #[test]
1843 fn test_type_annotation_to_field_type_array_recursive() {
1844 let ann = TypeAnnotation::Array(Box::new(TypeAnnotation::Basic("int".to_string())));
1845 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
1846 assert_eq!(ft, FieldType::Array(Box::new(FieldType::I64)));
1847 }
1848
1849 #[test]
1850 fn test_type_annotation_to_field_type_optional() {
1851 let ann = TypeAnnotation::Generic {
1852 name: "Option".to_string(),
1853 args: vec![TypeAnnotation::Basic("int".to_string())],
1854 };
1855 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
1856 assert_eq!(ft, FieldType::Any);
1857 }
1858
1859 #[test]
1860 fn test_type_annotation_to_field_type_generic_hashmap() {
1861 let ann = TypeAnnotation::Generic {
1862 name: "HashMap".to_string(),
1863 args: vec![
1864 TypeAnnotation::Basic("string".to_string()),
1865 TypeAnnotation::Basic("int".to_string()),
1866 ],
1867 };
1868 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
1869 assert_eq!(ft, FieldType::Any);
1870 }
1871
1872 #[test]
1873 fn test_type_annotation_to_field_type_generic_user_struct() {
1874 let ann = TypeAnnotation::Generic {
1875 name: "MyContainer".to_string(),
1876 args: vec![TypeAnnotation::Basic("string".to_string())],
1877 };
1878 let ft = BytecodeCompiler::type_annotation_to_field_type(&ann);
1879 assert_eq!(ft, FieldType::Object("MyContainer".to_string()));
1880 }
1881}