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