Skip to main content

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