Skip to main content

shape_vm/compiler/
helpers.rs

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