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