Skip to main content

shape_vm/compiler/
compiler_impl_part1.rs

1use super::*;
2
3impl BytecodeCompiler {
4    pub(super) fn collect_namespace_import_bindings(program: &Program) -> Vec<String> {
5        use shape_ast::ast::{ImportItems, Item};
6
7        let mut bindings = Vec::new();
8        for item in &program.items {
9            if let Item::Import(import_stmt, _) = item
10                && let ImportItems::Namespace { name, alias } = &import_stmt.items
11            {
12                bindings.push(alias.clone().unwrap_or_else(|| name.clone()));
13            }
14        }
15        bindings
16    }
17
18    pub fn new() -> Self {
19        Self {
20            program: BytecodeProgram::new(),
21            current_function: None,
22            locals: vec![HashMap::new()],
23            module_bindings: HashMap::new(),
24            next_local: 0,
25            next_global: 0,
26            loop_stack: Vec::new(),
27            closure_counter: 0,
28            closure_row_schema: None,
29            last_expr_type_info: None,
30            type_tracker: TypeTracker::with_stdlib(),
31            last_expr_schema: None,
32            last_expr_numeric_type: None,
33            type_inference: shape_runtime::type_system::inference::TypeInferenceEngine::new(),
34            type_aliases: HashMap::new(),
35            current_line: 1,
36            current_file_id: 0,
37            source_text: None,
38            source_lines: Vec::new(),
39            imported_names: HashMap::new(),
40            module_namespace_bindings: HashSet::new(),
41            module_scope_stack: Vec::new(),
42            known_exports: HashMap::new(),
43            function_arity_bounds: HashMap::new(),
44            function_const_params: HashMap::new(),
45            function_defs: HashMap::new(),
46            foreign_function_defs: HashMap::new(),
47            const_specializations: HashMap::new(),
48            next_const_specialization_id: 0,
49            specialization_const_bindings: HashMap::new(),
50            struct_types: HashMap::new(),
51            struct_generic_info: HashMap::new(),
52            native_layout_types: HashSet::new(),
53            generated_native_conversion_pairs: HashSet::new(),
54            current_function_is_async: false,
55            source_dir: None,
56            errors: Vec::new(),
57            hoisted_fields: HashMap::new(),
58            pending_variable_name: None,
59            known_traits: std::collections::HashSet::new(),
60            trait_defs: HashMap::new(),
61            extension_registry: None,
62            comptime_fields: HashMap::new(),
63            type_diagnostic_mode: TypeDiagnosticMode::ReliableOnly,
64            compile_diagnostic_mode: CompileDiagnosticMode::FailFast,
65            comptime_mode: false,
66            allow_internal_comptime_namespace: false,
67            method_table: MethodTable::new(),
68            borrow_checker: crate::borrow_checker::BorrowChecker::new(),
69            ref_locals: HashSet::new(),
70            exclusive_ref_locals: HashSet::new(),
71            const_locals: HashSet::new(),
72            const_module_bindings: HashSet::new(),
73            immutable_locals: HashSet::new(),
74            param_locals: HashSet::new(),
75            immutable_module_bindings: HashSet::new(),
76            in_call_args: false,
77            current_call_arg_borrow_mode: None,
78            call_arg_module_binding_ref_writebacks: Vec::new(),
79            inferred_ref_params: HashMap::new(),
80            inferred_ref_mutates: HashMap::new(),
81            inferred_param_pass_modes: HashMap::new(),
82            inferred_param_type_hints: HashMap::new(),
83            drop_locals: Vec::new(),
84            drop_type_info: HashMap::new(),
85            mutable_closure_captures: HashMap::new(),
86            boxed_locals: HashSet::new(),
87            permission_set: None,
88            current_blob_builder: None,
89            completed_blobs: Vec::new(),
90            blob_name_to_hash: HashMap::new(),
91            content_addressed_program: None,
92            function_hashes_by_id: Vec::new(),
93            blob_cache: None,
94            function_aliases: HashMap::new(),
95            current_function_params: Vec::new(),
96        }
97    }
98
99    /// Enable comptime compilation mode for this compiler instance.
100    pub fn set_comptime_mode(&mut self, enabled: bool) {
101        self.comptime_mode = enabled;
102    }
103
104    /// Attach a blob-level cache for incremental compilation.
105    ///
106    /// When set, `finalize_current_blob` stores each compiled blob in the cache,
107    /// and `build_content_addressed_program` populates the function store from
108    /// cached blobs when possible.
109    pub fn set_blob_cache(&mut self, cache: BlobCache) {
110        self.blob_cache = Some(cache);
111    }
112
113    /// Finalize the current blob builder for the function at `func_idx` and
114    /// move it to `completed_blobs`. Called at the end of function body compilation.
115    ///
116    /// If a `blob_cache` is attached, the finalized blob is stored in the cache
117    /// for reuse in subsequent compilations.
118    pub(crate) fn finalize_current_blob(&mut self, func_idx: usize) {
119        // Set body_length before finalizing blob
120        let entry = self.program.functions[func_idx].entry_point;
121        let end = self.program.instructions.len();
122        self.program.functions[func_idx].body_length = end - entry;
123
124        if let Some(builder) = self.current_blob_builder.take() {
125            let instr_end = self.program.instructions.len();
126            let func = &self.program.functions[func_idx];
127            let blob = builder.finalize(&self.program, func, &self.blob_name_to_hash, instr_end);
128            self.blob_name_to_hash
129                .insert(blob.name.clone(), blob.content_hash);
130
131            // Store in cache for future incremental compilation.
132            if let Some(ref mut cache) = self.blob_cache {
133                cache.put_blob(&blob);
134            }
135
136            if self.function_hashes_by_id.len() <= func_idx {
137                self.function_hashes_by_id.resize(func_idx + 1, None);
138            }
139            self.function_hashes_by_id[func_idx] = Some(blob.content_hash);
140
141            self.completed_blobs.push(blob);
142        }
143    }
144
145    /// Record a call dependency in the current blob builder.
146    /// `func_idx` is the global function index used in the `Opcode::Call` operand.
147    pub(crate) fn record_blob_call(&mut self, func_idx: u16) {
148        if let Some(ref mut blob) = self.current_blob_builder {
149            let callee_name = self.program.functions[func_idx as usize].name.clone();
150            blob.record_call(&callee_name);
151        }
152    }
153
154    /// Record permissions required by a stdlib function in the current blob builder.
155    /// Called during import processing so the blob captures its permission requirements.
156    pub(crate) fn record_blob_permissions(&mut self, module: &str, function: &str) {
157        if let Some(ref mut blob) = self.current_blob_builder {
158            let perms =
159                shape_runtime::stdlib::capability_tags::required_permissions(module, function);
160            if !perms.is_empty() {
161                blob.record_permissions(&perms);
162            }
163        }
164    }
165
166    /// Finalize the `__main__` blob for top-level code and assemble
167    /// the content-addressed `Program` from all completed blobs.
168    pub(super) fn build_content_addressed_program(&mut self) {
169        use crate::bytecode::Function;
170
171        // Finalize the __main__ blob.
172        // __main__ is special: it uses the full instruction range and is not a registered Function.
173        // We create a synthetic Function entry for it.
174        if let Some(main_builder) = self.current_blob_builder.take() {
175            let instr_end = self.program.instructions.len();
176
177            // Create a synthetic Function for __main__
178            let main_func = Function {
179                name: "__main__".to_string(),
180                arity: 0,
181                param_names: Vec::new(),
182                locals_count: self.next_local,
183                entry_point: main_builder.instr_start,
184                body_length: instr_end - main_builder.instr_start,
185                is_closure: false,
186                captures_count: 0,
187                is_async: false,
188                ref_params: Vec::new(),
189                ref_mutates: Vec::new(),
190                mutable_captures: Vec::new(),
191                frame_descriptor: None,
192                osr_entry_points: Vec::new(),
193            };
194
195            let blob = main_builder.finalize(
196                &self.program,
197                &main_func,
198                &self.blob_name_to_hash,
199                instr_end,
200            );
201            self.blob_name_to_hash
202                .insert("__main__".to_string(), blob.content_hash);
203            let mut main_hash = blob.content_hash;
204
205            // Store __main__ blob in cache.
206            if let Some(ref mut cache) = self.blob_cache {
207                cache.put_blob(&blob);
208            }
209
210            self.completed_blobs.push(blob);
211
212            // Build the function_store from all completed blobs.
213            let mut function_store = HashMap::new();
214            for blob in &self.completed_blobs {
215                function_store.insert(blob.content_hash, blob.clone());
216            }
217
218            // Fixed-point resolution of forward references.
219            //
220            // A single pass is insufficient: resolving B's forward deps changes B's
221            // hash, which makes A's reference to B stale. We iterate until no hashes
222            // change (i.e., the dependency graph reaches a fixed point).
223            //
224            // Mutual recursion (A calls B, B calls A) can never converge because
225            // each function's hash depends on the other. We detect mutual-recursion
226            // edges and treat them the same as self-recursion: use ZERO sentinel.
227            // The linker resolves ZERO+callee_name to the correct function ID.
228
229            // Build mutual-recursion edge set from callee_names.
230            let mut call_edges: std::collections::HashSet<(String, String)> =
231                std::collections::HashSet::new();
232            for blob in function_store.values() {
233                for callee in &blob.callee_names {
234                    if callee != &blob.name {
235                        call_edges.insert((blob.name.clone(), callee.clone()));
236                    }
237                }
238            }
239            let mut mutual_edges: std::collections::HashSet<(String, String)> =
240                std::collections::HashSet::new();
241            for (a, b) in &call_edges {
242                if call_edges.contains(&(b.clone(), a.clone())) {
243                    mutual_edges.insert((a.clone(), b.clone()));
244                }
245            }
246
247            let max_iterations = 10;
248            for _iteration in 0..max_iterations {
249                let mut any_changed = false;
250                let mut recomputed: Vec<(FunctionHash, FunctionHash, FunctionBlob)> = Vec::new();
251
252                for blob in function_store.values() {
253                    let mut updated = blob.clone();
254                    let mut deps_changed = false;
255
256                    for (i, dep) in updated.dependencies.iter_mut().enumerate() {
257                        if let Some(name) = blob.callee_names.get(i) {
258                            // Keep self-recursive edges as ZERO sentinel.
259                            // Resolving self to a concrete hash makes the hash equation
260                            // non-convergent for recursive functions.
261                            if name == &blob.name {
262                                continue;
263                            }
264                            // Keep mutual-recursion edges as ZERO sentinel.
265                            // Like self-recursion, mutual recursion creates a hash
266                            // equation with no fixed point. The linker resolves these
267                            // using callee_names instead.
268                            if mutual_edges.contains(&(blob.name.clone(), name.clone())) {
269                                if *dep != FunctionHash::ZERO {
270                                    *dep = FunctionHash::ZERO;
271                                    deps_changed = true;
272                                }
273                                continue;
274                            }
275                            if let Some(&current) = self.blob_name_to_hash.get(name) {
276                                if *dep != current {
277                                    *dep = current;
278                                    deps_changed = true;
279                                }
280                            }
281                        }
282                    }
283
284                    if deps_changed {
285                        let old_hash = updated.content_hash;
286                        updated.finalize();
287                        if updated.content_hash != old_hash {
288                            recomputed.push((old_hash, updated.content_hash, updated));
289                            any_changed = true;
290                        }
291                    }
292                }
293
294                for (old_hash, new_hash, blob) in recomputed {
295                    function_store.remove(&old_hash);
296                    function_store.insert(new_hash, blob.clone());
297                    self.blob_name_to_hash.insert(blob.name.clone(), new_hash);
298                    for slot in &mut self.function_hashes_by_id {
299                        if *slot == Some(old_hash) {
300                            *slot = Some(new_hash);
301                        }
302                    }
303
304                    // Update cache with the re-hashed blob.
305                    if let Some(ref mut cache) = self.blob_cache {
306                        cache.put_blob(&blob);
307                    }
308                }
309
310                if !any_changed {
311                    break;
312                }
313            }
314
315            // Transitive permission propagation:
316            // If function A calls function B, A inherits B's required_permissions.
317            // This must happen after dependency hash resolution, and forms its own
318            // fixpoint because permission changes alter content hashes.
319            for _perm_iter in 0..max_iterations {
320                let mut perm_changed = false;
321                let mut updates: Vec<(FunctionHash, shape_abi_v1::PermissionSet)> = Vec::new();
322
323                for (hash, blob) in function_store.iter() {
324                    let mut accumulated = blob.required_permissions.clone();
325                    for dep_hash in &blob.dependencies {
326                        if let Some(dep_blob) = function_store.get(dep_hash) {
327                            let unioned = accumulated.union(&dep_blob.required_permissions);
328                            if unioned != accumulated {
329                                accumulated = unioned;
330                            }
331                        }
332                    }
333                    if accumulated != blob.required_permissions {
334                        updates.push((*hash, accumulated));
335                        perm_changed = true;
336                    }
337                }
338
339                let mut rehashed: Vec<(FunctionHash, FunctionBlob)> = Vec::new();
340                for (hash, perms) in updates {
341                    if let Some(blob) = function_store.get_mut(&hash) {
342                        blob.required_permissions = perms;
343                        let old_hash = blob.content_hash;
344                        blob.finalize();
345                        if blob.content_hash != old_hash {
346                            rehashed.push((old_hash, blob.clone()));
347                        }
348                    }
349                }
350
351                for (old_hash, blob) in rehashed {
352                    function_store.remove(&old_hash);
353                    self.blob_name_to_hash
354                        .insert(blob.name.clone(), blob.content_hash);
355                    for slot in &mut self.function_hashes_by_id {
356                        if *slot == Some(old_hash) {
357                            *slot = Some(blob.content_hash);
358                        }
359                    }
360                    if let Some(ref mut cache) = self.blob_cache {
361                        cache.put_blob(&blob);
362                    }
363                    function_store.insert(blob.content_hash, blob);
364                }
365
366                if !perm_changed {
367                    break;
368                }
369            }
370
371            // Update main_hash if it changed
372            if let Some(&updated_main) = self.blob_name_to_hash.get("__main__") {
373                main_hash = updated_main;
374            }
375
376            // Build module_binding_names
377            let mut module_binding_names = vec![String::new(); self.module_bindings.len()];
378            for (name, &idx) in &self.module_bindings {
379                module_binding_names[idx as usize] = name.clone();
380            }
381
382            self.content_addressed_program = Some(ContentAddressedProgram {
383                entry: main_hash,
384                function_store,
385                top_level_locals_count: self.next_local,
386                top_level_local_storage_hints: self.program.top_level_local_storage_hints.clone(),
387                module_binding_names,
388                module_binding_storage_hints: self.program.module_binding_storage_hints.clone(),
389                function_local_storage_hints: self.program.function_local_storage_hints.clone(),
390                data_schema: self.program.data_schema.clone(),
391                type_schema_registry: self.type_tracker.schema_registry().clone(),
392                trait_method_symbols: self.program.trait_method_symbols.clone(),
393                foreign_functions: self.program.foreign_functions.clone(),
394                native_struct_layouts: self.program.native_struct_layouts.clone(),
395                debug_info: self.program.debug_info.clone(),
396                top_level_frame: None,
397            });
398        }
399    }
400
401    /// Collect top-level `comptime fn` helpers visible to nested comptime execution.
402    pub(crate) fn collect_comptime_helpers(&self) -> Vec<FunctionDef> {
403        let mut helpers: Vec<FunctionDef> = self
404            .function_defs
405            .values()
406            .filter(|def| def.is_comptime)
407            .cloned()
408            .collect();
409        helpers.sort_by(|a, b| a.name.cmp(&b.name));
410        helpers
411    }
412
413    /// Register a known export for import suggestions
414    ///
415    /// This enables helpful error messages like:
416    /// "Unknown function 'sma'. Did you mean to import from '@stdlib/finance/indicators/moving_averages'?"
417    pub fn register_known_export(&mut self, function_name: &str, module_path: &str) {
418        self.known_exports
419            .insert(function_name.to_string(), module_path.to_string());
420    }
421
422    /// Register multiple known exports at once
423    pub fn register_known_exports(&mut self, exports: &HashMap<String, String>) {
424        for (name, path) in exports {
425            self.known_exports.insert(name.clone(), path.clone());
426        }
427    }
428
429    /// Suggest an import for an unknown function
430    pub fn suggest_import(&self, function_name: &str) -> Option<&str> {
431        self.known_exports.get(function_name).map(|s| s.as_str())
432    }
433
434    /// Set the source text for error messages
435    pub fn set_source(&mut self, source: &str) {
436        self.source_text = Some(source.to_string());
437        self.source_lines = source.lines().map(|s| s.to_string()).collect();
438    }
439
440    /// Set the source text and file name for error messages
441    pub fn set_source_with_file(&mut self, source: &str, file_name: &str) {
442        self.source_text = Some(source.to_string());
443        self.source_lines = source.lines().map(|s| s.to_string()).collect();
444        // Set up the source map with this file
445        self.current_file_id = self
446            .program
447            .debug_info
448            .source_map
449            .add_file(file_name.to_string());
450        self.program
451            .debug_info
452            .source_map
453            .set_source_text(self.current_file_id, source.to_string());
454    }
455
456    /// Set the current source line (from AST span)
457    pub fn set_line(&mut self, line: u32) {
458        self.current_line = line;
459    }
460
461    /// Set line from a Span (converts byte offset to line number)
462    pub fn set_line_from_span(&mut self, span: shape_ast::ast::Span) {
463        if let Some(source) = &self.source_text {
464            // Count newlines up to span.start to get line number
465            let line = source[..span.start.min(source.len())]
466                .chars()
467                .filter(|c| *c == '\n')
468                .count() as u32
469                + 1;
470            self.current_line = line;
471        }
472    }
473
474    /// Get a source line by line number (1-indexed)
475    pub fn get_source_line(&self, line: usize) -> Option<&str> {
476        self.source_lines
477            .get(line.saturating_sub(1))
478            .map(|s| s.as_str())
479    }
480
481    /// Convert a Span to a SourceLocation for error reporting
482    pub(crate) fn span_to_source_location(
483        &self,
484        span: shape_ast::ast::Span,
485    ) -> shape_ast::error::SourceLocation {
486        let (line, column) = if let Some(source) = &self.source_text {
487            let clamped = span.start.min(source.len());
488            let line = source[..clamped].chars().filter(|c| *c == '\n').count() + 1;
489            let last_nl = source[..clamped].rfind('\n').map(|p| p + 1).unwrap_or(0);
490            let column = clamped - last_nl + 1;
491            (line, column)
492        } else {
493            (1, 1)
494        };
495        let source_line = self.source_lines.get(line.saturating_sub(1)).cloned();
496        let mut loc = shape_ast::error::SourceLocation::new(line, column);
497        if span.end > span.start {
498            loc = loc.with_length(span.end - span.start);
499        }
500        if let Some(sl) = source_line {
501            loc = loc.with_source_line(sl);
502        }
503        if let Some(file) = self
504            .program
505            .debug_info
506            .source_map
507            .get_file(self.current_file_id)
508        {
509            loc = loc.with_file(file.to_string());
510        }
511        loc
512    }
513
514    /// Pre-register known root-scope bindings (for REPL persistence)
515    ///
516    /// Call this before compilation to register bindings from previous REPL sessions.
517    /// This ensures that references to these bindings compile to LoadModuleBinding/StoreModuleBinding
518    /// instructions rather than causing "Undefined variable" errors.
519    pub fn register_known_bindings(&mut self, names: &[String]) {
520        for name in names {
521            if !name.is_empty() && !self.module_bindings.contains_key(name) {
522                let idx = self.next_global;
523                self.module_bindings.insert(name.clone(), idx);
524                self.next_global += 1;
525                self.register_extension_module_schema(name);
526                let module_schema_name = format!("__mod_{}", name);
527                if self
528                    .type_tracker
529                    .schema_registry()
530                    .get(&module_schema_name)
531                    .is_some()
532                {
533                    self.set_module_binding_type_info(idx, &module_schema_name);
534                    self.module_namespace_bindings.insert(name.clone());
535                }
536            }
537        }
538    }
539
540    /// Create a new compiler with a data schema for column resolution.
541    /// This enables optimized GetDataField/GetDataRow opcodes.
542    pub fn with_schema(schema: crate::bytecode::DataFrameSchema) -> Self {
543        let mut compiler = Self::new();
544        compiler.program.data_schema = Some(schema);
545        compiler
546    }
547
548    /// Set the data schema for column resolution.
549    /// Must be called before compiling data access expressions.
550    pub fn set_schema(&mut self, schema: crate::bytecode::DataFrameSchema) {
551        self.program.data_schema = Some(schema);
552    }
553
554    /// Set the source directory for resolving relative source file paths.
555    pub fn set_source_dir(&mut self, dir: std::path::PathBuf) {
556        self.source_dir = Some(dir);
557    }
558
559    /// Set extension modules for comptime execution.
560    pub fn with_extensions(
561        mut self,
562        extensions: Vec<shape_runtime::module_exports::ModuleExports>,
563    ) -> Self {
564        self.extension_registry = Some(Arc::new(extensions));
565        self
566    }
567
568    /// Configure how shared analyzer diagnostics are emitted.
569    pub fn set_type_diagnostic_mode(&mut self, mode: TypeDiagnosticMode) {
570        self.type_diagnostic_mode = mode;
571    }
572
573    /// Configure expression-compilation error recovery behavior.
574    pub fn set_compile_diagnostic_mode(&mut self, mode: CompileDiagnosticMode) {
575        self.compile_diagnostic_mode = mode;
576    }
577
578    /// Set the active permission set for compile-time capability checking.
579    ///
580    /// When set, imports that require permissions not in this set will produce
581    /// compile errors. Pass `None` to disable checking (default).
582    pub fn set_permission_set(&mut self, permissions: Option<shape_abi_v1::PermissionSet>) {
583        self.permission_set = permissions;
584    }
585
586    pub(crate) fn should_recover_compile_diagnostics(&self) -> bool {
587        matches!(
588            self.compile_diagnostic_mode,
589            CompileDiagnosticMode::RecoverAll
590        )
591    }
592
593    pub(super) fn type_error_with_location_to_shape(error: TypeErrorWithLocation) -> ShapeError {
594        let mut location = SourceLocation::new(error.line.max(1), error.column.max(1));
595        if let Some(file) = error.file {
596            location = location.with_file(file);
597        }
598        if let Some(source_line) = error.source_line {
599            location = location.with_source_line(source_line);
600        }
601
602        ShapeError::SemanticError {
603            message: error.error.to_string(),
604            location: Some(location),
605        }
606    }
607
608    pub(super) fn type_errors_to_shape(errors: Vec<TypeErrorWithLocation>) -> ShapeError {
609        let mut mapped: Vec<ShapeError> = errors
610            .into_iter()
611            .map(Self::type_error_with_location_to_shape)
612            .collect();
613        if mapped.len() == 1 {
614            return mapped.pop().unwrap_or_else(|| ShapeError::SemanticError {
615                message: "Type analysis failed".to_string(),
616                location: None,
617            });
618        }
619        ShapeError::MultiError(mapped)
620    }
621
622    pub(super) fn should_emit_type_diagnostic(error: &TypeError) -> bool {
623        matches!(error, TypeError::UnknownProperty(_, _))
624    }
625
626    pub(super) fn collect_program_functions(
627        program: &Program,
628    ) -> HashMap<String, shape_ast::ast::FunctionDef> {
629        let mut out = HashMap::new();
630        Self::collect_program_functions_recursive(&program.items, None, &mut out);
631        out
632    }
633
634    pub(super) fn collect_program_functions_recursive(
635        items: &[shape_ast::ast::Item],
636        module_prefix: Option<&str>,
637        out: &mut HashMap<String, shape_ast::ast::FunctionDef>,
638    ) {
639        for item in items {
640            match item {
641                shape_ast::ast::Item::Function(func, _) => {
642                    let mut qualified = func.clone();
643                    if let Some(prefix) = module_prefix {
644                        qualified.name = format!("{}::{}", prefix, func.name);
645                    }
646                    out.insert(qualified.name.clone(), qualified);
647                }
648                shape_ast::ast::Item::Export(export, _) => {
649                    if let shape_ast::ast::ExportItem::Function(func) = &export.item {
650                        let mut qualified = func.clone();
651                        if let Some(prefix) = module_prefix {
652                            qualified.name = format!("{}::{}", prefix, func.name);
653                        }
654                        out.insert(qualified.name.clone(), qualified);
655                    }
656                }
657                shape_ast::ast::Item::Module(module_def, _) => {
658                    let prefix = if let Some(parent) = module_prefix {
659                        format!("{}::{}", parent, module_def.name)
660                    } else {
661                        module_def.name.clone()
662                    };
663                    Self::collect_program_functions_recursive(
664                        &module_def.items,
665                        Some(prefix.as_str()),
666                        out,
667                    );
668                }
669                _ => {}
670            }
671        }
672    }
673
674    pub(super) fn is_primitive_value_type_name(name: &str) -> bool {
675        matches!(
676            name,
677            "int"
678                | "integer"
679                | "i64"
680                | "number"
681                | "float"
682                | "f64"
683                | "decimal"
684                | "bool"
685                | "boolean"
686                | "void"
687                | "unit"
688                | "none"
689                | "null"
690                | "undefined"
691                | "never"
692        )
693    }
694
695    pub(super) fn annotation_is_heap_like(ann: &TypeAnnotation) -> bool {
696        match ann {
697            TypeAnnotation::Basic(name) => !Self::is_primitive_value_type_name(name),
698            TypeAnnotation::Reference(name) => !Self::is_primitive_value_type_name(name),
699            TypeAnnotation::Array(_)
700            | TypeAnnotation::Tuple(_)
701            | TypeAnnotation::Object(_)
702            | TypeAnnotation::Function { .. }
703            | TypeAnnotation::Generic { .. }
704            | TypeAnnotation::Dyn(_) => true,
705            TypeAnnotation::Union(types) | TypeAnnotation::Intersection(types) => {
706                types.iter().any(Self::annotation_is_heap_like)
707            }
708            TypeAnnotation::Optional(inner) => Self::annotation_is_heap_like(inner),
709            TypeAnnotation::Void
710            | TypeAnnotation::Any
711            | TypeAnnotation::Never
712            | TypeAnnotation::Null
713            | TypeAnnotation::Undefined => false,
714        }
715    }
716
717    pub(super) fn type_is_heap_like(ty: &Type) -> bool {
718        match ty {
719            Type::Concrete(ann) => Self::annotation_is_heap_like(ann),
720            Type::Function { .. } => false,
721            Type::Generic { .. } => true,
722            Type::Variable(_) | Type::Constrained { .. } => false,
723        }
724    }
725
726    pub(crate) fn pass_mode_from_ref_flags(
727        ref_params: &[bool],
728        ref_mutates: &[bool],
729        idx: usize,
730    ) -> ParamPassMode {
731        if !ref_params.get(idx).copied().unwrap_or(false) {
732            ParamPassMode::ByValue
733        } else if ref_mutates.get(idx).copied().unwrap_or(false) {
734            ParamPassMode::ByRefExclusive
735        } else {
736            ParamPassMode::ByRefShared
737        }
738    }
739
740    pub(crate) fn pass_modes_from_ref_flags(
741        ref_params: &[bool],
742        ref_mutates: &[bool],
743    ) -> Vec<ParamPassMode> {
744        let len = ref_params.len().max(ref_mutates.len());
745        (0..len)
746            .map(|idx| Self::pass_mode_from_ref_flags(ref_params, ref_mutates, idx))
747            .collect()
748    }
749
750    pub(crate) fn build_param_pass_mode_map(
751        program: &Program,
752        inferred_ref_params: &HashMap<String, Vec<bool>>,
753        inferred_ref_mutates: &HashMap<String, Vec<bool>>,
754    ) -> HashMap<String, Vec<ParamPassMode>> {
755        let funcs = Self::collect_program_functions(program);
756        let mut by_function = HashMap::new();
757
758        for (name, func) in funcs {
759            let inferred_refs = inferred_ref_params.get(&name).cloned().unwrap_or_default();
760            let inferred_mutates = inferred_ref_mutates.get(&name).cloned().unwrap_or_default();
761            let mut modes = Vec::with_capacity(func.params.len());
762
763            for (idx, param) in func.params.iter().enumerate() {
764                let explicit_ref = param.is_reference;
765                let inferred_ref = inferred_refs.get(idx).copied().unwrap_or(false);
766                if !(explicit_ref || inferred_ref) {
767                    modes.push(ParamPassMode::ByValue);
768                    continue;
769                }
770
771                if inferred_mutates.get(idx).copied().unwrap_or(false) {
772                    modes.push(ParamPassMode::ByRefExclusive);
773                } else {
774                    modes.push(ParamPassMode::ByRefShared);
775                }
776            }
777
778            by_function.insert(name, modes);
779        }
780
781        by_function
782    }
783}