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_function_ids: Vec::new(),
29            closure_registry: shape_value::v2::closure_layout::ClosureRegistry::new(),
30            closure_type_ids: Vec::new(),
31            closure_capture_kinds: Vec::new(),
32            function_type_registry:
33                shape_value::v2::function_type_registry::FunctionTypeRegistry::new(),
34            function_type_ids: Vec::new(),
35            emit_make_closure_heap_next: false,
36            get_prop_native_kinds: HashMap::new(),
37            closure_row_schema: None,
38            pending_closure_param_types: None,
39            last_expr_type_info: None,
40            type_tracker: TypeTracker::with_stdlib(),
41            last_expr_schema: None,
42            last_expr_numeric_type: None,
43            top_level_program_return_kind: None,
44            current_expr_result_mode: ExprResultMode::Value,
45            last_expr_reference_result: ExprReferenceResult::default(),
46            local_callable_pass_modes: HashMap::new(),
47            local_callable_return_reference_summaries: HashMap::new(),
48            module_binding_callable_pass_modes: HashMap::new(),
49            module_binding_callable_return_reference_summaries: HashMap::new(),
50            local_callable_return_types: HashMap::new(),
51            module_binding_callable_return_types: HashMap::new(),
52            local_array_callable_return_types: HashMap::new(),
53            module_binding_array_callable_return_types: HashMap::new(),
54            // cluster-2-cw-IB-class-b: retained closure-literal AST for
55            // local `let f = |..| ..` bindings; consumed by value-call
56            // return-kind inference at `compile_expr_function_call`.
57            local_callable_closure_bodies: HashMap::new(),
58            module_binding_callable_closure_bodies: HashMap::new(),
59            dyn_locals: HashMap::new(),
60            dyn_module_bindings: HashMap::new(),
61            function_return_reference_summaries: HashMap::new(),
62            current_function_return_reference_summary: None,
63            type_inference: shape_runtime::type_system::inference::TypeInferenceEngine::new(),
64            type_aliases: HashMap::new(),
65            current_line: 1,
66            current_file_id: 0,
67            source_text: None,
68            source_lines: Vec::new(),
69            imported_names: HashMap::new(),
70            imported_annotations: HashMap::new(),
71            imported_consts: HashMap::new(),
72            module_builtin_functions: HashMap::new(),
73            module_namespace_bindings: HashSet::new(),
74            module_scope_sources: HashMap::new(),
75            module_scope_stack: Vec::new(),
76            known_exports: HashMap::new(),
77            function_arity_bounds: HashMap::new(),
78            function_const_params: HashMap::new(),
79            function_defs: HashMap::new(),
80            foreign_function_defs: HashMap::new(),
81            enum_struct_variant_fields: HashMap::new(),
82            enum_tuple_variant_fields: HashMap::new(),
83            const_specializations: HashMap::new(),
84            next_const_specialization_id: 0,
85            specialization_const_bindings: HashMap::new(),
86            struct_types: HashMap::new(),
87            struct_generic_info: HashMap::new(),
88            native_layout_types: HashSet::new(),
89            generated_native_conversion_pairs: HashSet::new(),
90            current_function_is_async: false,
91            source_dir: None,
92            errors: Vec::new(),
93            hoisted_fields: HashMap::new(),
94            hoisted_field_types: HashMap::new(),
95            pending_variable_name: None,
96            pending_variable_typed_array_kind: None,
97            nested_array_literal_depth: 0,
98            v2_typed_array_locals: HashMap::new(),
99            v2_typed_array_module_bindings: HashMap::new(),
100            comprehension_element_kind: None,
101            comprehension_push_sites: Vec::new(),
102            empty_array_accumulators: HashMap::new(),
103            pending_empty_array_alloc_idx: None,
104            pending_variable_typed_map_kind: None,
105            v2_typed_map_locals: HashMap::new(),
106            v2_typed_map_module_bindings: HashMap::new(),
107            // ADR-006 §2.7.27 / Item 4 ruling: container-kind tracking for
108            // `&mut self` write-back emission.
109            mut_self_container_locals: HashMap::new(),
110            mut_self_container_bindings: HashMap::new(),
111            pending_variable_container_kind: None,
112            map_key_value_types: HashMap::new(),
113            local_map_key_value_types: HashMap::new(),
114            module_binding_map_key_value_types: HashMap::new(),
115            array_element_types: HashMap::new(),
116            local_array_element_types: HashMap::new(),
117            module_binding_array_element_types: HashMap::new(),
118            future_reference_use_name_scopes: Vec::new(),
119            known_traits: std::collections::HashSet::new(),
120            trait_defs: HashMap::new(),
121            comptime_impl_blocks: Vec::new(),
122            comptime_context_struct_defs: HashMap::new(),
123            extension_registry: None,
124            comptime_fields: HashMap::new(),
125            type_diagnostic_mode: TypeDiagnosticMode::ReliableOnly,
126            compile_diagnostic_mode: CompileDiagnosticMode::FailFast,
127            comptime_mode: false,
128            removed_functions: HashSet::new(),
129            allow_internal_comptime_namespace: false,
130            method_table: MethodTable::new(),
131            ref_locals: HashSet::new(),
132            exclusive_ref_locals: HashSet::new(),
133            inferred_ref_locals: HashSet::new(),
134            reference_value_locals: HashSet::new(),
135            exclusive_reference_value_locals: HashSet::new(),
136            const_locals: HashSet::new(),
137            const_module_bindings: HashSet::new(),
138            immutable_locals: HashSet::new(),
139            param_locals: HashSet::new(),
140            immutable_module_bindings: HashSet::new(),
141            reference_value_module_bindings: HashSet::new(),
142            exclusive_reference_value_module_bindings: HashSet::new(),
143            call_arg_module_binding_ref_writebacks: Vec::new(),
144            inferred_ref_params: HashMap::new(),
145            inferred_ref_mutates: HashMap::new(),
146            inferred_param_pass_modes: HashMap::new(),
147            inferred_param_type_hints: HashMap::new(),
148            inferred_param_concrete_types: HashMap::new(),
149            inferred_param_object_fields: HashMap::new(),
150            inferred_return_object_fields: HashMap::new(),
151            function_return_schema_ids: HashMap::new(),
152            drop_locals: Vec::new(),
153            ownership_drop_locals: Vec::new(),
154            drop_type_info: HashMap::new(),
155            drop_module_bindings: Vec::new(),
156            mutable_closure_captures: HashMap::new(),
157            shared_closure_captures: HashMap::new(),
158            owned_mutable_closure_captures: HashMap::new(),
159            owned_mutable_capture_inner_kinds: HashMap::new(),
160            shared_capture_inner_kinds: HashMap::new(),
161            boxed_locals: HashSet::new(),
162            shared_locals: HashSet::new(),
163            owned_mutable_locals: HashSet::new(),
164            captured_let_mut_moved: HashMap::new(),
165            shared_module_bindings: HashSet::new(),
166            shared_drop_locals: Vec::new(),
167            permission_set: None,
168            current_blob_builder: None,
169            completed_blobs: Vec::new(),
170            blob_name_to_hash: HashMap::new(),
171            content_addressed_program: None,
172            function_hashes_by_id: Vec::new(),
173            blob_cache: None,
174            function_aliases: HashMap::new(),
175            current_function_params: Vec::new(),
176            stdlib_function_names: HashSet::new(),
177            allow_internal_builtins: false,
178            native_resolution_context: None,
179            non_function_mir_context_stack: Vec::new(),
180            mir_functions: HashMap::new(),
181            mir_borrow_analyses: HashMap::new(),
182            mir_storage_plans: HashMap::new(),
183            function_borrow_summaries: HashMap::new(),
184            mir_span_to_point: HashMap::new(),
185            mir_field_analyses: HashMap::new(),
186            graph_namespace_map: HashMap::new(),
187            module_graph: None,
188            current_function_local_concrete_types: HashMap::new(),
189            module_binding_concrete_types: HashMap::new(),
190            monomorphization_cache:
191                crate::compiler::monomorphization::cache::MonomorphizationCache::new(),
192            monomorphization_in_progress: std::collections::HashSet::new(),
193            next_monomorphization_id: 0,
194            closure_specialization_count: 0,
195        }
196    }
197
198    /// Enable comptime compilation mode for this compiler instance.
199    pub fn set_comptime_mode(&mut self, enabled: bool) {
200        self.comptime_mode = enabled;
201    }
202
203    /// Attach a blob-level cache for incremental compilation.
204    ///
205    /// When set, `finalize_current_blob` stores each compiled blob in the cache,
206    /// and `build_content_addressed_program` populates the function store from
207    /// cached blobs when possible.
208    pub fn set_blob_cache(&mut self, cache: BlobCache) {
209        self.blob_cache = Some(cache);
210    }
211
212    /// Finalize the current blob builder for the function at `func_idx` and
213    /// move it to `completed_blobs`. Called at the end of function body compilation.
214    ///
215    /// If a `blob_cache` is attached, the finalized blob is stored in the cache
216    /// for reuse in subsequent compilations.
217    pub(crate) fn finalize_current_blob(&mut self, func_idx: usize) {
218        // Set body_length before finalizing blob
219        let entry = self.program.functions[func_idx].entry_point;
220        let end = self.program.instructions.len();
221        self.program.functions[func_idx].body_length = end - entry;
222
223        if let Some(builder) = self.current_blob_builder.take() {
224            let instr_end = self.program.instructions.len();
225            let func = &self.program.functions[func_idx];
226            let blob = builder.finalize(&self.program, func, &self.blob_name_to_hash, instr_end);
227            self.blob_name_to_hash
228                .insert(blob.name.clone(), blob.content_hash);
229
230            // Store in cache for future incremental compilation.
231            if let Some(ref mut cache) = self.blob_cache {
232                cache.put_blob(&blob);
233            }
234
235            if self.function_hashes_by_id.len() <= func_idx {
236                self.function_hashes_by_id.resize(func_idx + 1, None);
237            }
238            self.function_hashes_by_id[func_idx] = Some(blob.content_hash);
239
240            self.completed_blobs.push(blob);
241        }
242    }
243
244    /// Record a call dependency in the current blob builder.
245    /// `func_idx` is the global function index used in the `Opcode::Call` operand.
246    pub(crate) fn record_blob_call(&mut self, func_idx: u16) {
247        if let Some(ref mut blob) = self.current_blob_builder {
248            let callee_name = self.program.functions[func_idx as usize].name.clone();
249            blob.record_call(&callee_name);
250        }
251    }
252
253    /// Record permissions required by a stdlib function in the current blob builder.
254    /// Called during import processing so the blob captures its permission requirements.
255    pub(crate) fn record_blob_permissions(&mut self, module: &str, function: &str) {
256        if let Some(ref mut blob) = self.current_blob_builder {
257            let perms =
258                shape_runtime::stdlib::capability_tags::required_permissions(module, function);
259            if !perms.is_empty() {
260                blob.record_permissions(&perms);
261            }
262        }
263    }
264
265    /// Finalize the `__main__` blob for top-level code and assemble
266    /// the content-addressed `Program` from all completed blobs.
267    pub(super) fn build_content_addressed_program(&mut self) {
268        use crate::bytecode::Function;
269
270        // Finalize the __main__ blob.
271        // __main__ is special: it uses the full instruction range and is not a registered Function.
272        // We create a synthetic Function entry for it.
273        if let Some(main_builder) = self.current_blob_builder.take() {
274            let instr_end = self.program.instructions.len();
275
276            // Create a synthetic Function for __main__
277            let main_func = Function {
278                name: "__main__".to_string(),
279                arity: 0,
280                param_names: Vec::new(),
281                locals_count: self.next_local,
282                entry_point: main_builder.instr_start,
283                body_length: instr_end - main_builder.instr_start,
284                is_closure: false,
285                captures_count: 0,
286                is_async: false,
287                ref_params: Vec::new(),
288                ref_mutates: Vec::new(),
289                mutable_captures: Vec::new(),
290                frame_descriptor: self.program.top_level_frame.clone(),
291                osr_entry_points: Vec::new(),
292                mir_data: None,
293            };
294
295            let blob = main_builder.finalize(
296                &self.program,
297                &main_func,
298                &self.blob_name_to_hash,
299                instr_end,
300            );
301            self.blob_name_to_hash
302                .insert("__main__".to_string(), blob.content_hash);
303            let mut main_hash = blob.content_hash;
304
305            // Store __main__ blob in cache.
306            if let Some(ref mut cache) = self.blob_cache {
307                cache.put_blob(&blob);
308            }
309
310            self.completed_blobs.push(blob);
311
312            // Build the function_store from all completed blobs.
313            let mut function_store = HashMap::new();
314            for blob in &self.completed_blobs {
315                function_store.insert(blob.content_hash, blob.clone());
316            }
317
318            // Fixed-point resolution of forward references.
319            //
320            // A single pass is insufficient: resolving B's forward deps changes B's
321            // hash, which makes A's reference to B stale. We iterate until no hashes
322            // change (i.e., the dependency graph reaches a fixed point).
323            //
324            // Mutual recursion (A calls B, B calls A) can never converge because
325            // each function's hash depends on the other. We detect mutual-recursion
326            // edges and treat them the same as self-recursion: use ZERO sentinel.
327            // The linker resolves ZERO+callee_name to the correct function ID.
328
329            // Build mutual-recursion edge set from callee_names.
330            let mut call_edges: std::collections::HashSet<(String, String)> =
331                std::collections::HashSet::new();
332            for blob in function_store.values() {
333                for callee in &blob.callee_names {
334                    if callee != &blob.name {
335                        call_edges.insert((blob.name.clone(), callee.clone()));
336                    }
337                }
338            }
339            let mut mutual_edges: std::collections::HashSet<(String, String)> =
340                std::collections::HashSet::new();
341            for (a, b) in &call_edges {
342                if call_edges.contains(&(b.clone(), a.clone())) {
343                    mutual_edges.insert((a.clone(), b.clone()));
344                }
345            }
346
347            let max_iterations = 10;
348            for _iteration in 0..max_iterations {
349                let mut any_changed = false;
350                let mut recomputed: Vec<(FunctionHash, FunctionHash, FunctionBlob)> = Vec::new();
351
352                for blob in function_store.values() {
353                    let mut updated = blob.clone();
354                    let mut deps_changed = false;
355
356                    for (i, dep) in updated.dependencies.iter_mut().enumerate() {
357                        if let Some(name) = blob.callee_names.get(i) {
358                            // Keep self-recursive edges as ZERO sentinel.
359                            // Resolving self to a concrete hash makes the hash equation
360                            // non-convergent for recursive functions.
361                            if name == &blob.name {
362                                continue;
363                            }
364                            // Keep mutual-recursion edges as ZERO sentinel.
365                            // Like self-recursion, mutual recursion creates a hash
366                            // equation with no fixed point. The linker resolves these
367                            // using callee_names instead.
368                            if mutual_edges.contains(&(blob.name.clone(), name.clone())) {
369                                if *dep != FunctionHash::ZERO {
370                                    *dep = FunctionHash::ZERO;
371                                    deps_changed = true;
372                                }
373                                continue;
374                            }
375                            if let Some(&current) = self.blob_name_to_hash.get(name) {
376                                if *dep != current {
377                                    *dep = current;
378                                    deps_changed = true;
379                                }
380                            }
381                        }
382                    }
383
384                    if deps_changed {
385                        let old_hash = updated.content_hash;
386                        updated.finalize();
387                        if updated.content_hash != old_hash {
388                            recomputed.push((old_hash, updated.content_hash, updated));
389                            any_changed = true;
390                        }
391                    }
392                }
393
394                for (old_hash, new_hash, blob) in recomputed {
395                    function_store.remove(&old_hash);
396                    function_store.insert(new_hash, blob.clone());
397                    self.blob_name_to_hash.insert(blob.name.clone(), new_hash);
398                    for slot in &mut self.function_hashes_by_id {
399                        if *slot == Some(old_hash) {
400                            *slot = Some(new_hash);
401                        }
402                    }
403
404                    // Update cache with the re-hashed blob.
405                    if let Some(ref mut cache) = self.blob_cache {
406                        cache.put_blob(&blob);
407                    }
408                }
409
410                if !any_changed {
411                    break;
412                }
413            }
414
415            // Transitive permission propagation:
416            // If function A calls function B, A inherits B's required_permissions.
417            // This must happen after dependency hash resolution, and forms its own
418            // fixpoint because permission changes alter content hashes.
419            for _perm_iter in 0..max_iterations {
420                let mut perm_changed = false;
421                let mut updates: Vec<(FunctionHash, shape_abi_v1::PermissionSet)> = Vec::new();
422
423                for (hash, blob) in function_store.iter() {
424                    let mut accumulated = blob.required_permissions.clone();
425                    for dep_hash in &blob.dependencies {
426                        if let Some(dep_blob) = function_store.get(dep_hash) {
427                            let unioned = accumulated.union(&dep_blob.required_permissions);
428                            if unioned != accumulated {
429                                accumulated = unioned;
430                            }
431                        }
432                    }
433                    if accumulated != blob.required_permissions {
434                        updates.push((*hash, accumulated));
435                        perm_changed = true;
436                    }
437                }
438
439                let mut rehashed: Vec<(FunctionHash, FunctionBlob)> = Vec::new();
440                for (hash, perms) in updates {
441                    if let Some(blob) = function_store.get_mut(&hash) {
442                        blob.required_permissions = perms;
443                        let old_hash = blob.content_hash;
444                        blob.finalize();
445                        if blob.content_hash != old_hash {
446                            rehashed.push((old_hash, blob.clone()));
447                        }
448                    }
449                }
450
451                for (old_hash, blob) in rehashed {
452                    function_store.remove(&old_hash);
453                    self.blob_name_to_hash
454                        .insert(blob.name.clone(), blob.content_hash);
455                    for slot in &mut self.function_hashes_by_id {
456                        if *slot == Some(old_hash) {
457                            *slot = Some(blob.content_hash);
458                        }
459                    }
460                    if let Some(ref mut cache) = self.blob_cache {
461                        cache.put_blob(&blob);
462                    }
463                    function_store.insert(blob.content_hash, blob);
464                }
465
466                if !perm_changed {
467                    break;
468                }
469            }
470
471            // Update main_hash if it changed
472            if let Some(&updated_main) = self.blob_name_to_hash.get("__main__") {
473                main_hash = updated_main;
474            }
475
476            // Build module_binding_names
477            let mut module_binding_names = vec![String::new(); self.module_bindings.len()];
478            for (name, &idx) in &self.module_bindings {
479                module_binding_names[idx as usize] = name.clone();
480            }
481
482            self.content_addressed_program = Some(ContentAddressedProgram {
483                entry: main_hash,
484                function_store,
485                top_level_locals_count: self.next_local,
486                top_level_local_storage_hints: self.program.top_level_local_storage_hints.clone(),
487                module_binding_names,
488                module_binding_storage_hints: self.program.module_binding_storage_hints.clone(),
489                function_local_storage_hints: self.program.function_local_storage_hints.clone(),
490                data_schema: self.program.data_schema.clone(),
491                type_schema_registry: self.type_tracker.schema_registry().clone(),
492                trait_method_symbols: self.program.trait_method_symbols.clone(),
493                foreign_functions: self.program.foreign_functions.clone(),
494                native_struct_layouts: self.program.native_struct_layouts.clone(),
495                debug_info: self.program.debug_info.clone(),
496                // E+5.5 Unit C step 2: propagate the typed top-level frame
497                // through the content-addressed path so the linker can
498                // forward it to `BytecodeProgram` and the VM host boundary
499                // synthesises a tagged ValueWord on `vm.execute()`.
500                top_level_frame: self.program.top_level_frame.clone(),
501                // ADR-006 §2.7.5 conduit: top-level concrete-types side-
502                // table propagated through the content-addressed path so
503                // the linker forwards it to `BytecodeProgram` for the JIT
504                // typed-array / TypedObject fast paths.
505                top_level_local_concrete_types: self
506                    .program
507                    .top_level_local_concrete_types
508                    .clone(),
509                // ADR-006 §2.7.5 conduit (W12-jit-aggregate-non-array,
510                // 2026-05-12): per-user-function concrete-types side-table
511                // propagated through the content-addressed path for the
512                // JIT's TypedObject Aggregate short-circuit inside user
513                // function bodies.
514                function_local_concrete_types: self
515                    .program
516                    .function_local_concrete_types
517                    .clone(),
518                // ADR-006 §2.7.5 conduit (W12-jit-call-return-kind,
519                // 2026-05-12): per-user-function declared return
520                // ConcreteType propagated through the content-addressed
521                // path so the conduit can stamp `TerminatorKind::Call`
522                // destination slots from the callee's return type.
523                function_return_concrete_types: self
524                    .program
525                    .function_return_concrete_types
526                    .clone(),
527                // ADR-006 §2.7.5 conduit (V3-S6b-jit-method-monomorph-
528                // conduit close, 2026-05-15): per-call-site monomorphized
529                // method-call FunctionId side-table propagated through
530                // the content-addressed path so the conduit producer can
531                // lift `function_return_concrete_types[specialized_idx]`
532                // at `MirConstant::Method` Call-terminator sites.
533                monomorphized_method_call_sites: self
534                    .program
535                    .monomorphized_method_call_sites
536                    .clone(),
537                // cluster-2-cw-IB-class-b: per-call-site value-call return
538                // ConcreteType side-table propagated through the content-
539                // addressed path so the conduit producer can stamp value-
540                // call `TerminatorKind::Call` destinations from the
541                // closure-bound callee's inferred return type. Same path
542                // shape as `monomorphized_method_call_sites`.
543                value_call_return_concrete_types: self
544                    .program
545                    .value_call_return_concrete_types
546                    .clone(),
547                // ADR-006 §2.7.5 conduit (W10 jit-call-method-user-trait-
548                // fix close, 2026-05-17): per-binop/unop-site operator
549                // trait dispatch side-table propagated through the
550                // content-addressed path so the JIT consumer (rvalues.rs
551                // BinaryOp / UnaryOp arms) can lift the bytecode-time
552                // trait-dispatch decision to method-call IR.
553                operator_trait_dispatch_sites: self
554                    .program
555                    .operator_trait_dispatch_sites
556                    .clone(),
557                // Closure spec §14.6 (H6.5): propagate layouts through the
558                // content-addressed path so `load_linked_program` → VM
559                // preserves enough metadata for the raw producer path.
560                // Keyed by function name because the linker's topo_sort
561                // reorders blobs.
562                closure_function_layouts_by_name: {
563                    let mut map: std::collections::HashMap<
564                        String,
565                        std::sync::Arc<shape_value::v2::closure_layout::ClosureLayout>,
566                    > = std::collections::HashMap::new();
567                    for (idx, layout_opt) in
568                        self.program.closure_function_layouts.iter().enumerate()
569                    {
570                        if let Some(layout) = layout_opt {
571                            if let Some(func) = self.program.functions.get(idx) {
572                                map.insert(func.name.clone(), layout.clone());
573                            }
574                        }
575                    }
576                    map
577                },
578                // ADR-006 §2.7.24 Q25.C: propagate trait-object vtables
579                // through the content-addressed path so the linker
580                // forwards them to `LinkedProgram.trait_vtables` and the
581                // VM `op_box_trait_object` handler can look them up at
582                // runtime.
583                trait_vtables: self.program.trait_vtables.clone(),
584                has_imported_const_inline: self.program.has_imported_const_inline,
585                has_w17_marshal_residual: self.program.has_w17_marshal_residual,
586            });
587        }
588    }
589
590    /// Collect top-level `comptime fn` helpers visible to nested comptime execution.
591    pub(crate) fn collect_comptime_helpers(&self) -> Vec<FunctionDef> {
592        let mut helpers: Vec<FunctionDef> = self
593            .function_defs
594            .values()
595            .filter(|def| def.is_comptime)
596            .cloned()
597            .collect();
598        helpers.sort_by(|a, b| a.name.cmp(&b.name));
599        helpers
600    }
601
602    /// Register a known export for import suggestions
603    ///
604    /// This enables helpful error messages like:
605    /// "Unknown function 'sma'. Did you mean to import from '@stdlib/finance/indicators/moving_averages'?"
606    pub fn register_known_export(&mut self, function_name: &str, module_path: &str) {
607        self.known_exports
608            .insert(function_name.to_string(), module_path.to_string());
609    }
610
611    /// Register multiple known exports at once
612    pub fn register_known_exports(&mut self, exports: &HashMap<String, String>) {
613        for (name, path) in exports {
614            self.known_exports.insert(name.clone(), path.clone());
615        }
616    }
617
618    /// Suggest an import for an unknown function
619    pub fn suggest_import(&self, function_name: &str) -> Option<&str> {
620        self.known_exports.get(function_name).map(|s| s.as_str())
621    }
622
623    /// Set the source text for error messages
624    pub fn set_source(&mut self, source: &str) {
625        self.source_text = Some(source.to_string());
626        self.source_lines = source.lines().map(|s| s.to_string()).collect();
627    }
628
629    /// Set the source text and file name for error messages
630    pub fn set_source_with_file(&mut self, source: &str, file_name: &str) {
631        self.source_text = Some(source.to_string());
632        self.source_lines = source.lines().map(|s| s.to_string()).collect();
633        // Set up the source map with this file
634        self.current_file_id = self
635            .program
636            .debug_info
637            .source_map
638            .add_file(file_name.to_string());
639        self.program
640            .debug_info
641            .source_map
642            .set_source_text(self.current_file_id, source.to_string());
643    }
644
645    /// Set the current source line (from AST span)
646    pub fn set_line(&mut self, line: u32) {
647        self.current_line = line;
648    }
649
650    /// Set line from a Span (converts byte offset to line number)
651    pub fn set_line_from_span(&mut self, span: shape_ast::ast::Span) {
652        if let Some(source) = &self.source_text {
653            // Count newlines up to span.start to get line number
654            let line = source[..span.start.min(source.len())]
655                .chars()
656                .filter(|c| *c == '\n')
657                .count() as u32
658                + 1;
659            self.current_line = line;
660        }
661    }
662
663    /// Get a source line by line number (1-indexed)
664    pub fn get_source_line(&self, line: usize) -> Option<&str> {
665        self.source_lines
666            .get(line.saturating_sub(1))
667            .map(|s| s.as_str())
668    }
669
670    /// Convert a Span to a SourceLocation for error reporting
671    pub(crate) fn span_to_source_location(
672        &self,
673        span: shape_ast::ast::Span,
674    ) -> shape_ast::error::SourceLocation {
675        let (line, column) = if let Some(source) = &self.source_text {
676            let clamped = span.start.min(source.len());
677            let line = source[..clamped].chars().filter(|c| *c == '\n').count() + 1;
678            let last_nl = source[..clamped].rfind('\n').map(|p| p + 1).unwrap_or(0);
679            let column = clamped - last_nl + 1;
680            (line, column)
681        } else {
682            (1, 1)
683        };
684        let source_line = self.source_lines.get(line.saturating_sub(1)).cloned();
685        let mut loc = shape_ast::error::SourceLocation::new(line, column);
686        if span.end > span.start {
687            loc = loc.with_length(span.end - span.start);
688        }
689        if let Some(sl) = source_line {
690            loc = loc.with_source_line(sl);
691        }
692        if let Some(file) = self
693            .program
694            .debug_info
695            .source_map
696            .get_file(self.current_file_id)
697        {
698            loc = loc.with_file(file.to_string());
699        }
700        loc
701    }
702
703    /// Pre-register known root-scope bindings (for REPL persistence)
704    ///
705    /// Call this before compilation to register bindings from previous REPL sessions.
706    /// This ensures that references to these bindings compile to LoadModuleBinding/StoreModuleBinding
707    /// instructions rather than causing "Undefined variable" errors.
708    pub fn register_known_bindings(&mut self, names: &[String]) {
709        for name in names {
710            if !name.is_empty() && !self.module_bindings.contains_key(name) {
711                let idx = self.next_global;
712                self.module_bindings.insert(name.clone(), idx);
713                self.next_global += 1;
714                self.register_extension_module_schema(name);
715                let module_schema_name = format!("__mod_{}", name);
716                if self
717                    .type_tracker
718                    .schema_registry()
719                    .get(&module_schema_name)
720                    .is_some()
721                {
722                    self.set_module_binding_type_info(idx, &module_schema_name);
723                    self.module_namespace_bindings.insert(name.clone());
724                }
725            }
726        }
727    }
728
729    /// Register the type for a previously-known module binding (e.g. from
730    /// a persisted REPL ExecutionContext). Wave E+5.5 host-boundary
731    /// extension to `register_known_bindings`: pairs the binding name
732    /// with the inferred type so subsequent expressions referencing the
733    /// binding (e.g. `a + b`) resolve through the strict-typing arithmetic
734    /// path. Without this, the next REPL command's `a + b` falls into
735    /// `unknown + unknown` and errors out under strict typing.
736    ///
737    /// Type names use the same canonical strings as `set_module_binding_type_info`
738    /// (`"int"`, `"number"`, `"bool"`, `"string"`, etc.) — typically
739    /// derived from the persisted `ValueWord`'s tag/heap kind via
740    /// `valueword_type_name_for_persistence`.
741    pub fn register_known_binding_type(&mut self, name: &str, type_name: &str) {
742        if let Some(&idx) = self.module_bindings.get(name) {
743            self.set_module_binding_type_info(idx, type_name);
744        }
745    }
746
747    /// Seed the compiler's type-schema registry with user type schemas
748    /// from prior REPL cells (WS-11 cross-cell schema-id stabilization).
749    ///
750    /// Each `TypeSchema` is registered under its **original** id (the id
751    /// it received the first time its `type` was compiled this REPL
752    /// session). Because `predeclare_struct_schema` / `register_struct_type`
753    /// both short-circuit on `schema_registry().get(name).is_some()`,
754    /// pre-seeding a schema makes the cell's re-injected `type` resolve
755    /// to that same id instead of allocating a fresh one — so a
756    /// `TypedObject` persisted from an earlier cell (which carries the
757    /// original `schema_id`) still resolves under this cell's program
758    /// registry.
759    ///
760    /// `ensure_next_id_above` advances the allocator past every seeded
761    /// id so a *new* type declared in this cell cannot collide with a
762    /// seeded one.
763    pub fn seed_persistent_schemas(
764        &mut self,
765        schemas: &[shape_runtime::type_schema::TypeSchema],
766    ) {
767        let registry = self.type_tracker.schema_registry_mut();
768        let mut max_id = 0u32;
769        for schema in schemas {
770            max_id = max_id.max(schema.id);
771            if registry.get(&schema.name).is_none() {
772                registry.register(schema.clone());
773            }
774        }
775        if max_id > 0 {
776            registry.ensure_next_id_above(max_id);
777        }
778    }
779
780    /// Create a new compiler with a data schema for column resolution.
781    /// This enables optimized GetDataField/GetDataRow opcodes.
782    pub fn with_schema(schema: crate::bytecode::DataFrameSchema) -> Self {
783        let mut compiler = Self::new();
784        compiler.program.data_schema = Some(schema);
785        compiler
786    }
787
788    /// Set the data schema for column resolution.
789    /// Must be called before compiling data access expressions.
790    pub fn set_schema(&mut self, schema: crate::bytecode::DataFrameSchema) {
791        self.program.data_schema = Some(schema);
792    }
793
794    /// Set the source directory for resolving relative source file paths.
795    pub fn set_source_dir(&mut self, dir: std::path::PathBuf) {
796        self.source_dir = Some(dir);
797    }
798
799    /// Set extension modules for comptime execution.
800    pub fn with_extensions(
801        mut self,
802        extensions: Vec<shape_runtime::module_exports::ModuleExports>,
803    ) -> Self {
804        self.extension_registry = Some(Arc::new(extensions));
805        self
806    }
807
808    /// Configure how shared analyzer diagnostics are emitted.
809    pub fn set_type_diagnostic_mode(&mut self, mode: TypeDiagnosticMode) {
810        self.type_diagnostic_mode = mode;
811    }
812
813    /// Configure expression-compilation error recovery behavior.
814    pub fn set_compile_diagnostic_mode(&mut self, mode: CompileDiagnosticMode) {
815        self.compile_diagnostic_mode = mode;
816    }
817
818    /// Set the active permission set for compile-time capability checking.
819    ///
820    /// When set, imports that require permissions not in this set will produce
821    /// compile errors. Pass `None` to disable checking (default).
822    pub fn set_permission_set(&mut self, permissions: Option<shape_abi_v1::PermissionSet>) {
823        self.permission_set = permissions;
824    }
825
826    pub(crate) fn should_recover_compile_diagnostics(&self) -> bool {
827        matches!(
828            self.compile_diagnostic_mode,
829            CompileDiagnosticMode::RecoverAll
830        )
831    }
832
833    pub(super) fn type_error_with_location_to_shape(error: TypeErrorWithLocation) -> ShapeError {
834        let mut location = SourceLocation::new(error.line.max(1), error.column.max(1));
835        if let Some(file) = error.file {
836            location = location.with_file(file);
837        }
838        if let Some(source_line) = error.source_line {
839            location = location.with_source_line(source_line);
840        }
841
842        ShapeError::SemanticError {
843            message: error.error.to_string(),
844            location: Some(location),
845        }
846    }
847
848    pub(super) fn type_errors_to_shape(errors: Vec<TypeErrorWithLocation>) -> ShapeError {
849        let mut mapped: Vec<ShapeError> = errors
850            .into_iter()
851            .map(Self::type_error_with_location_to_shape)
852            .collect();
853        if mapped.len() == 1 {
854            return mapped.pop().unwrap_or_else(|| ShapeError::SemanticError {
855                message: "Type analysis failed".to_string(),
856                location: None,
857            });
858        }
859        ShapeError::MultiError(mapped)
860    }
861
862    pub(super) fn should_emit_type_diagnostic(error: &TypeError) -> bool {
863        matches!(
864            error,
865            TypeError::UnknownProperty(_, _)
866            // J-CT.1: comptime-trait diagnostics are emitted in the default
867            // `ReliableOnly` mode. Both are pure rules with no false-positive
868            // surface (the gate fires only on `comptime impl`-marked methods
869            // and the alignment check only on declared trait/impl pairs), so
870            // failing the compile is the right behavior — matches the audit's
871            // §5(d) "compile-time semantic error" expectation.
872            | TypeError::ComptimeMethodCallOutsideComptime { .. }
873            | TypeError::ComptimeImplTraitMismatch { .. }
874        )
875    }
876
877    pub(super) fn collect_program_functions(
878        program: &Program,
879    ) -> HashMap<String, shape_ast::ast::FunctionDef> {
880        let mut out = HashMap::new();
881        Self::collect_program_functions_recursive(&program.items, None, &mut out);
882        out
883    }
884
885    pub(super) fn collect_program_functions_recursive(
886        items: &[shape_ast::ast::Item],
887        module_prefix: Option<&str>,
888        out: &mut HashMap<String, shape_ast::ast::FunctionDef>,
889    ) {
890        for item in items {
891            match item {
892                shape_ast::ast::Item::Function(func, _) => {
893                    let mut qualified = func.clone();
894                    if let Some(prefix) = module_prefix {
895                        qualified.name = format!("{}::{}", prefix, func.name);
896                    }
897                    out.insert(qualified.name.clone(), qualified);
898                }
899                shape_ast::ast::Item::Export(export, _) => {
900                    if let shape_ast::ast::ExportItem::Function(func) = &export.item {
901                        let mut qualified = func.clone();
902                        if let Some(prefix) = module_prefix {
903                            qualified.name = format!("{}::{}", prefix, func.name);
904                        }
905                        out.insert(qualified.name.clone(), qualified);
906                    }
907                }
908                shape_ast::ast::Item::Module(module_def, _) => {
909                    let prefix = if let Some(parent) = module_prefix {
910                        format!("{}::{}", parent, module_def.name)
911                    } else {
912                        module_def.name.clone()
913                    };
914                    Self::collect_program_functions_recursive(
915                        &module_def.items,
916                        Some(prefix.as_str()),
917                        out,
918                    );
919                }
920                _ => {}
921            }
922        }
923    }
924
925    pub(super) fn is_primitive_value_type_name(name: &str) -> bool {
926        matches!(
927            name,
928            "int"
929                | "integer"
930                | "i64"
931                | "number"
932                | "float"
933                | "f64"
934                | "decimal"
935                | "bool"
936                | "boolean"
937                | "void"
938                | "unit"
939                | "none"
940                | "null"
941                | "undefined"
942                | "never"
943        )
944    }
945
946    pub(super) fn annotation_is_heap_like(ann: &TypeAnnotation) -> bool {
947        match ann {
948            TypeAnnotation::Basic(name) => !Self::is_primitive_value_type_name(name),
949            TypeAnnotation::Reference(name) => !Self::is_primitive_value_type_name(name),
950            TypeAnnotation::Array(_)
951            | TypeAnnotation::Tuple(_)
952            | TypeAnnotation::Object(_)
953            | TypeAnnotation::Function { .. }
954            | TypeAnnotation::Generic { .. }
955            | TypeAnnotation::Dyn(_) => true,
956            TypeAnnotation::Union(types) | TypeAnnotation::Intersection(types) => {
957                types.iter().any(Self::annotation_is_heap_like)
958            }
959            TypeAnnotation::Void
960            | TypeAnnotation::Never
961            | TypeAnnotation::Null
962            | TypeAnnotation::Undefined => false,
963        }
964    }
965
966    pub(super) fn type_is_heap_like(ty: &Type) -> bool {
967        match ty {
968            Type::Concrete(ann) => Self::annotation_is_heap_like(ann),
969            Type::Function { .. } => false,
970            Type::Generic { .. } => true,
971            Type::Variable(_) | Type::Constrained { .. } => false,
972        }
973    }
974
975    pub(crate) fn pass_mode_from_ref_flags(
976        ref_params: &[bool],
977        ref_mutates: &[bool],
978        idx: usize,
979    ) -> ParamPassMode {
980        if !ref_params.get(idx).copied().unwrap_or(false) {
981            ParamPassMode::ByValue
982        } else if ref_mutates.get(idx).copied().unwrap_or(false) {
983            ParamPassMode::ByRefExclusive
984        } else {
985            ParamPassMode::ByRefShared
986        }
987    }
988
989    pub(crate) fn pass_modes_from_ref_flags(
990        ref_params: &[bool],
991        ref_mutates: &[bool],
992    ) -> Vec<ParamPassMode> {
993        let len = ref_params.len().max(ref_mutates.len());
994        (0..len)
995            .map(|idx| Self::pass_mode_from_ref_flags(ref_params, ref_mutates, idx))
996            .collect()
997    }
998
999    pub(crate) fn build_param_pass_mode_map(
1000        program: &Program,
1001        inferred_ref_params: &HashMap<String, Vec<bool>>,
1002        inferred_ref_mutates: &HashMap<String, Vec<bool>>,
1003    ) -> HashMap<String, Vec<ParamPassMode>> {
1004        let funcs = Self::collect_program_functions(program);
1005        let mut by_function = HashMap::new();
1006
1007        for (name, func) in funcs {
1008            let inferred_refs = inferred_ref_params.get(&name).cloned().unwrap_or_default();
1009            let inferred_mutates = inferred_ref_mutates.get(&name).cloned().unwrap_or_default();
1010            let mut modes = Vec::with_capacity(func.params.len());
1011
1012            for (idx, param) in func.params.iter().enumerate() {
1013                let explicit_ref = param.is_reference;
1014                let inferred_ref = inferred_refs.get(idx).copied().unwrap_or(false);
1015                if !(explicit_ref || inferred_ref) {
1016                    modes.push(ParamPassMode::ByValue);
1017                    continue;
1018                }
1019
1020                if inferred_mutates.get(idx).copied().unwrap_or(false) {
1021                    modes.push(ParamPassMode::ByRefExclusive);
1022                } else {
1023                    modes.push(ParamPassMode::ByRefShared);
1024                }
1025            }
1026
1027            by_function.insert(name, modes);
1028        }
1029
1030        by_function
1031    }
1032}