1use super::*;
2
3impl BytecodeCompiler {
4 pub(super) fn collect_namespace_import_bindings(program: &Program) -> Vec<String> {
5 use shape_ast::ast::{ImportItems, Item};
6
7 let mut bindings = Vec::new();
8 for item in &program.items {
9 if let Item::Import(import_stmt, _) = item
10 && let ImportItems::Namespace { name, alias } = &import_stmt.items
11 {
12 bindings.push(alias.clone().unwrap_or_else(|| name.clone()));
13 }
14 }
15 bindings
16 }
17
18 pub fn new() -> Self {
19 Self {
20 program: BytecodeProgram::new(),
21 current_function: None,
22 locals: vec![HashMap::new()],
23 module_bindings: HashMap::new(),
24 next_local: 0,
25 next_global: 0,
26 loop_stack: Vec::new(),
27 closure_counter: 0,
28 closure_row_schema: None,
29 last_expr_type_info: None,
30 type_tracker: TypeTracker::with_stdlib(),
31 last_expr_schema: None,
32 last_expr_numeric_type: None,
33 type_inference: shape_runtime::type_system::inference::TypeInferenceEngine::new(),
34 type_aliases: HashMap::new(),
35 current_line: 1,
36 current_file_id: 0,
37 source_text: None,
38 source_lines: Vec::new(),
39 imported_names: HashMap::new(),
40 module_namespace_bindings: HashSet::new(),
41 module_scope_stack: Vec::new(),
42 known_exports: HashMap::new(),
43 function_arity_bounds: HashMap::new(),
44 function_const_params: HashMap::new(),
45 function_defs: HashMap::new(),
46 foreign_function_defs: HashMap::new(),
47 const_specializations: HashMap::new(),
48 next_const_specialization_id: 0,
49 specialization_const_bindings: HashMap::new(),
50 struct_types: HashMap::new(),
51 struct_generic_info: HashMap::new(),
52 native_layout_types: HashSet::new(),
53 generated_native_conversion_pairs: HashSet::new(),
54 current_function_is_async: false,
55 source_dir: None,
56 errors: Vec::new(),
57 hoisted_fields: HashMap::new(),
58 pending_variable_name: None,
59 known_traits: std::collections::HashSet::new(),
60 trait_defs: HashMap::new(),
61 extension_registry: None,
62 comptime_fields: HashMap::new(),
63 type_diagnostic_mode: TypeDiagnosticMode::ReliableOnly,
64 compile_diagnostic_mode: CompileDiagnosticMode::FailFast,
65 comptime_mode: false,
66 allow_internal_comptime_namespace: false,
67 method_table: MethodTable::new(),
68 borrow_checker: crate::borrow_checker::BorrowChecker::new(),
69 ref_locals: HashSet::new(),
70 exclusive_ref_locals: HashSet::new(),
71 const_locals: HashSet::new(),
72 const_module_bindings: HashSet::new(),
73 immutable_locals: HashSet::new(),
74 param_locals: HashSet::new(),
75 immutable_module_bindings: HashSet::new(),
76 in_call_args: false,
77 current_call_arg_borrow_mode: None,
78 call_arg_module_binding_ref_writebacks: Vec::new(),
79 inferred_ref_params: HashMap::new(),
80 inferred_ref_mutates: HashMap::new(),
81 inferred_param_pass_modes: HashMap::new(),
82 inferred_param_type_hints: HashMap::new(),
83 drop_locals: Vec::new(),
84 drop_type_info: HashMap::new(),
85 drop_module_bindings: Vec::new(),
86 mutable_closure_captures: HashMap::new(),
87 boxed_locals: HashSet::new(),
88 permission_set: None,
89 current_blob_builder: None,
90 completed_blobs: Vec::new(),
91 blob_name_to_hash: HashMap::new(),
92 content_addressed_program: None,
93 function_hashes_by_id: Vec::new(),
94 blob_cache: None,
95 function_aliases: HashMap::new(),
96 current_function_params: Vec::new(),
97 stdlib_function_names: HashSet::new(),
98 allow_internal_builtins: false,
99 native_resolution_context: None,
100 }
101 }
102
103 pub fn set_comptime_mode(&mut self, enabled: bool) {
105 self.comptime_mode = enabled;
106 }
107
108 pub fn set_blob_cache(&mut self, cache: BlobCache) {
114 self.blob_cache = Some(cache);
115 }
116
117 pub(crate) fn finalize_current_blob(&mut self, func_idx: usize) {
123 let entry = self.program.functions[func_idx].entry_point;
125 let end = self.program.instructions.len();
126 self.program.functions[func_idx].body_length = end - entry;
127
128 if let Some(builder) = self.current_blob_builder.take() {
129 let instr_end = self.program.instructions.len();
130 let func = &self.program.functions[func_idx];
131 let blob = builder.finalize(&self.program, func, &self.blob_name_to_hash, instr_end);
132 self.blob_name_to_hash
133 .insert(blob.name.clone(), blob.content_hash);
134
135 if let Some(ref mut cache) = self.blob_cache {
137 cache.put_blob(&blob);
138 }
139
140 if self.function_hashes_by_id.len() <= func_idx {
141 self.function_hashes_by_id.resize(func_idx + 1, None);
142 }
143 self.function_hashes_by_id[func_idx] = Some(blob.content_hash);
144
145 self.completed_blobs.push(blob);
146 }
147 }
148
149 pub(crate) fn record_blob_call(&mut self, func_idx: u16) {
152 if let Some(ref mut blob) = self.current_blob_builder {
153 let callee_name = self.program.functions[func_idx as usize].name.clone();
154 blob.record_call(&callee_name);
155 }
156 }
157
158 pub(crate) fn record_blob_permissions(&mut self, module: &str, function: &str) {
161 if let Some(ref mut blob) = self.current_blob_builder {
162 let perms =
163 shape_runtime::stdlib::capability_tags::required_permissions(module, function);
164 if !perms.is_empty() {
165 blob.record_permissions(&perms);
166 }
167 }
168 }
169
170 pub(super) fn build_content_addressed_program(&mut self) {
173 use crate::bytecode::Function;
174
175 if let Some(main_builder) = self.current_blob_builder.take() {
179 let instr_end = self.program.instructions.len();
180
181 let main_func = Function {
183 name: "__main__".to_string(),
184 arity: 0,
185 param_names: Vec::new(),
186 locals_count: self.next_local,
187 entry_point: main_builder.instr_start,
188 body_length: instr_end - main_builder.instr_start,
189 is_closure: false,
190 captures_count: 0,
191 is_async: false,
192 ref_params: Vec::new(),
193 ref_mutates: Vec::new(),
194 mutable_captures: Vec::new(),
195 frame_descriptor: self.program.top_level_frame.clone(),
196 osr_entry_points: Vec::new(),
197 };
198
199 let blob = main_builder.finalize(
200 &self.program,
201 &main_func,
202 &self.blob_name_to_hash,
203 instr_end,
204 );
205 self.blob_name_to_hash
206 .insert("__main__".to_string(), blob.content_hash);
207 let mut main_hash = blob.content_hash;
208
209 if let Some(ref mut cache) = self.blob_cache {
211 cache.put_blob(&blob);
212 }
213
214 self.completed_blobs.push(blob);
215
216 let mut function_store = HashMap::new();
218 for blob in &self.completed_blobs {
219 function_store.insert(blob.content_hash, blob.clone());
220 }
221
222 let mut call_edges: std::collections::HashSet<(String, String)> =
235 std::collections::HashSet::new();
236 for blob in function_store.values() {
237 for callee in &blob.callee_names {
238 if callee != &blob.name {
239 call_edges.insert((blob.name.clone(), callee.clone()));
240 }
241 }
242 }
243 let mut mutual_edges: std::collections::HashSet<(String, String)> =
244 std::collections::HashSet::new();
245 for (a, b) in &call_edges {
246 if call_edges.contains(&(b.clone(), a.clone())) {
247 mutual_edges.insert((a.clone(), b.clone()));
248 }
249 }
250
251 let max_iterations = 10;
252 for _iteration in 0..max_iterations {
253 let mut any_changed = false;
254 let mut recomputed: Vec<(FunctionHash, FunctionHash, FunctionBlob)> = Vec::new();
255
256 for blob in function_store.values() {
257 let mut updated = blob.clone();
258 let mut deps_changed = false;
259
260 for (i, dep) in updated.dependencies.iter_mut().enumerate() {
261 if let Some(name) = blob.callee_names.get(i) {
262 if name == &blob.name {
266 continue;
267 }
268 if mutual_edges.contains(&(blob.name.clone(), name.clone())) {
273 if *dep != FunctionHash::ZERO {
274 *dep = FunctionHash::ZERO;
275 deps_changed = true;
276 }
277 continue;
278 }
279 if let Some(¤t) = self.blob_name_to_hash.get(name) {
280 if *dep != current {
281 *dep = current;
282 deps_changed = true;
283 }
284 }
285 }
286 }
287
288 if deps_changed {
289 let old_hash = updated.content_hash;
290 updated.finalize();
291 if updated.content_hash != old_hash {
292 recomputed.push((old_hash, updated.content_hash, updated));
293 any_changed = true;
294 }
295 }
296 }
297
298 for (old_hash, new_hash, blob) in recomputed {
299 function_store.remove(&old_hash);
300 function_store.insert(new_hash, blob.clone());
301 self.blob_name_to_hash.insert(blob.name.clone(), new_hash);
302 for slot in &mut self.function_hashes_by_id {
303 if *slot == Some(old_hash) {
304 *slot = Some(new_hash);
305 }
306 }
307
308 if let Some(ref mut cache) = self.blob_cache {
310 cache.put_blob(&blob);
311 }
312 }
313
314 if !any_changed {
315 break;
316 }
317 }
318
319 for _perm_iter in 0..max_iterations {
324 let mut perm_changed = false;
325 let mut updates: Vec<(FunctionHash, shape_abi_v1::PermissionSet)> = Vec::new();
326
327 for (hash, blob) in function_store.iter() {
328 let mut accumulated = blob.required_permissions.clone();
329 for dep_hash in &blob.dependencies {
330 if let Some(dep_blob) = function_store.get(dep_hash) {
331 let unioned = accumulated.union(&dep_blob.required_permissions);
332 if unioned != accumulated {
333 accumulated = unioned;
334 }
335 }
336 }
337 if accumulated != blob.required_permissions {
338 updates.push((*hash, accumulated));
339 perm_changed = true;
340 }
341 }
342
343 let mut rehashed: Vec<(FunctionHash, FunctionBlob)> = Vec::new();
344 for (hash, perms) in updates {
345 if let Some(blob) = function_store.get_mut(&hash) {
346 blob.required_permissions = perms;
347 let old_hash = blob.content_hash;
348 blob.finalize();
349 if blob.content_hash != old_hash {
350 rehashed.push((old_hash, blob.clone()));
351 }
352 }
353 }
354
355 for (old_hash, blob) in rehashed {
356 function_store.remove(&old_hash);
357 self.blob_name_to_hash
358 .insert(blob.name.clone(), blob.content_hash);
359 for slot in &mut self.function_hashes_by_id {
360 if *slot == Some(old_hash) {
361 *slot = Some(blob.content_hash);
362 }
363 }
364 if let Some(ref mut cache) = self.blob_cache {
365 cache.put_blob(&blob);
366 }
367 function_store.insert(blob.content_hash, blob);
368 }
369
370 if !perm_changed {
371 break;
372 }
373 }
374
375 if let Some(&updated_main) = self.blob_name_to_hash.get("__main__") {
377 main_hash = updated_main;
378 }
379
380 let mut module_binding_names = vec![String::new(); self.module_bindings.len()];
382 for (name, &idx) in &self.module_bindings {
383 module_binding_names[idx as usize] = name.clone();
384 }
385
386 self.content_addressed_program = Some(ContentAddressedProgram {
387 entry: main_hash,
388 function_store,
389 top_level_locals_count: self.next_local,
390 top_level_local_storage_hints: self.program.top_level_local_storage_hints.clone(),
391 module_binding_names,
392 module_binding_storage_hints: self.program.module_binding_storage_hints.clone(),
393 function_local_storage_hints: self.program.function_local_storage_hints.clone(),
394 data_schema: self.program.data_schema.clone(),
395 type_schema_registry: self.type_tracker.schema_registry().clone(),
396 trait_method_symbols: self.program.trait_method_symbols.clone(),
397 foreign_functions: self.program.foreign_functions.clone(),
398 native_struct_layouts: self.program.native_struct_layouts.clone(),
399 debug_info: self.program.debug_info.clone(),
400 top_level_frame: None,
401 });
402 }
403 }
404
405 pub(crate) fn collect_comptime_helpers(&self) -> Vec<FunctionDef> {
407 let mut helpers: Vec<FunctionDef> = self
408 .function_defs
409 .values()
410 .filter(|def| def.is_comptime)
411 .cloned()
412 .collect();
413 helpers.sort_by(|a, b| a.name.cmp(&b.name));
414 helpers
415 }
416
417 pub fn register_known_export(&mut self, function_name: &str, module_path: &str) {
422 self.known_exports
423 .insert(function_name.to_string(), module_path.to_string());
424 }
425
426 pub fn register_known_exports(&mut self, exports: &HashMap<String, String>) {
428 for (name, path) in exports {
429 self.known_exports.insert(name.clone(), path.clone());
430 }
431 }
432
433 pub fn suggest_import(&self, function_name: &str) -> Option<&str> {
435 self.known_exports.get(function_name).map(|s| s.as_str())
436 }
437
438 pub fn set_source(&mut self, source: &str) {
440 self.source_text = Some(source.to_string());
441 self.source_lines = source.lines().map(|s| s.to_string()).collect();
442 }
443
444 pub fn set_source_with_file(&mut self, source: &str, file_name: &str) {
446 self.source_text = Some(source.to_string());
447 self.source_lines = source.lines().map(|s| s.to_string()).collect();
448 self.current_file_id = self
450 .program
451 .debug_info
452 .source_map
453 .add_file(file_name.to_string());
454 self.program
455 .debug_info
456 .source_map
457 .set_source_text(self.current_file_id, source.to_string());
458 }
459
460 pub fn set_line(&mut self, line: u32) {
462 self.current_line = line;
463 }
464
465 pub fn set_line_from_span(&mut self, span: shape_ast::ast::Span) {
467 if let Some(source) = &self.source_text {
468 let line = source[..span.start.min(source.len())]
470 .chars()
471 .filter(|c| *c == '\n')
472 .count() as u32
473 + 1;
474 self.current_line = line;
475 }
476 }
477
478 pub fn get_source_line(&self, line: usize) -> Option<&str> {
480 self.source_lines
481 .get(line.saturating_sub(1))
482 .map(|s| s.as_str())
483 }
484
485 pub(crate) fn span_to_source_location(
487 &self,
488 span: shape_ast::ast::Span,
489 ) -> shape_ast::error::SourceLocation {
490 let (line, column) = if let Some(source) = &self.source_text {
491 let clamped = span.start.min(source.len());
492 let line = source[..clamped].chars().filter(|c| *c == '\n').count() + 1;
493 let last_nl = source[..clamped].rfind('\n').map(|p| p + 1).unwrap_or(0);
494 let column = clamped - last_nl + 1;
495 (line, column)
496 } else {
497 (1, 1)
498 };
499 let source_line = self.source_lines.get(line.saturating_sub(1)).cloned();
500 let mut loc = shape_ast::error::SourceLocation::new(line, column);
501 if span.end > span.start {
502 loc = loc.with_length(span.end - span.start);
503 }
504 if let Some(sl) = source_line {
505 loc = loc.with_source_line(sl);
506 }
507 if let Some(file) = self
508 .program
509 .debug_info
510 .source_map
511 .get_file(self.current_file_id)
512 {
513 loc = loc.with_file(file.to_string());
514 }
515 loc
516 }
517
518 pub fn register_known_bindings(&mut self, names: &[String]) {
524 for name in names {
525 if !name.is_empty() && !self.module_bindings.contains_key(name) {
526 let idx = self.next_global;
527 self.module_bindings.insert(name.clone(), idx);
528 self.next_global += 1;
529 self.register_extension_module_schema(name);
530 let module_schema_name = format!("__mod_{}", name);
531 if self
532 .type_tracker
533 .schema_registry()
534 .get(&module_schema_name)
535 .is_some()
536 {
537 self.set_module_binding_type_info(idx, &module_schema_name);
538 self.module_namespace_bindings.insert(name.clone());
539 }
540 }
541 }
542 }
543
544 pub fn with_schema(schema: crate::bytecode::DataFrameSchema) -> Self {
547 let mut compiler = Self::new();
548 compiler.program.data_schema = Some(schema);
549 compiler
550 }
551
552 pub fn set_schema(&mut self, schema: crate::bytecode::DataFrameSchema) {
555 self.program.data_schema = Some(schema);
556 }
557
558 pub fn set_source_dir(&mut self, dir: std::path::PathBuf) {
560 self.source_dir = Some(dir);
561 }
562
563 pub fn with_extensions(
565 mut self,
566 extensions: Vec<shape_runtime::module_exports::ModuleExports>,
567 ) -> Self {
568 self.extension_registry = Some(Arc::new(extensions));
569 self
570 }
571
572 pub fn set_type_diagnostic_mode(&mut self, mode: TypeDiagnosticMode) {
574 self.type_diagnostic_mode = mode;
575 }
576
577 pub fn set_compile_diagnostic_mode(&mut self, mode: CompileDiagnosticMode) {
579 self.compile_diagnostic_mode = mode;
580 }
581
582 pub fn set_permission_set(&mut self, permissions: Option<shape_abi_v1::PermissionSet>) {
587 self.permission_set = permissions;
588 }
589
590 pub(crate) fn should_recover_compile_diagnostics(&self) -> bool {
591 matches!(
592 self.compile_diagnostic_mode,
593 CompileDiagnosticMode::RecoverAll
594 )
595 }
596
597 pub(super) fn type_error_with_location_to_shape(error: TypeErrorWithLocation) -> ShapeError {
598 let mut location = SourceLocation::new(error.line.max(1), error.column.max(1));
599 if let Some(file) = error.file {
600 location = location.with_file(file);
601 }
602 if let Some(source_line) = error.source_line {
603 location = location.with_source_line(source_line);
604 }
605
606 ShapeError::SemanticError {
607 message: error.error.to_string(),
608 location: Some(location),
609 }
610 }
611
612 pub(super) fn type_errors_to_shape(errors: Vec<TypeErrorWithLocation>) -> ShapeError {
613 let mut mapped: Vec<ShapeError> = errors
614 .into_iter()
615 .map(Self::type_error_with_location_to_shape)
616 .collect();
617 if mapped.len() == 1 {
618 return mapped.pop().unwrap_or_else(|| ShapeError::SemanticError {
619 message: "Type analysis failed".to_string(),
620 location: None,
621 });
622 }
623 ShapeError::MultiError(mapped)
624 }
625
626 pub(super) fn should_emit_type_diagnostic(error: &TypeError) -> bool {
627 matches!(error, TypeError::UnknownProperty(_, _))
628 }
629
630 pub(super) fn collect_program_functions(
631 program: &Program,
632 ) -> HashMap<String, shape_ast::ast::FunctionDef> {
633 let mut out = HashMap::new();
634 Self::collect_program_functions_recursive(&program.items, None, &mut out);
635 out
636 }
637
638 pub(super) fn collect_program_functions_recursive(
639 items: &[shape_ast::ast::Item],
640 module_prefix: Option<&str>,
641 out: &mut HashMap<String, shape_ast::ast::FunctionDef>,
642 ) {
643 for item in items {
644 match item {
645 shape_ast::ast::Item::Function(func, _) => {
646 let mut qualified = func.clone();
647 if let Some(prefix) = module_prefix {
648 qualified.name = format!("{}::{}", prefix, func.name);
649 }
650 out.insert(qualified.name.clone(), qualified);
651 }
652 shape_ast::ast::Item::Export(export, _) => {
653 if let shape_ast::ast::ExportItem::Function(func) = &export.item {
654 let mut qualified = func.clone();
655 if let Some(prefix) = module_prefix {
656 qualified.name = format!("{}::{}", prefix, func.name);
657 }
658 out.insert(qualified.name.clone(), qualified);
659 }
660 }
661 shape_ast::ast::Item::Module(module_def, _) => {
662 let prefix = if let Some(parent) = module_prefix {
663 format!("{}::{}", parent, module_def.name)
664 } else {
665 module_def.name.clone()
666 };
667 Self::collect_program_functions_recursive(
668 &module_def.items,
669 Some(prefix.as_str()),
670 out,
671 );
672 }
673 _ => {}
674 }
675 }
676 }
677
678 pub(super) fn is_primitive_value_type_name(name: &str) -> bool {
679 matches!(
680 name,
681 "int"
682 | "integer"
683 | "i64"
684 | "number"
685 | "float"
686 | "f64"
687 | "decimal"
688 | "bool"
689 | "boolean"
690 | "void"
691 | "unit"
692 | "none"
693 | "null"
694 | "undefined"
695 | "never"
696 )
697 }
698
699 pub(super) fn annotation_is_heap_like(ann: &TypeAnnotation) -> bool {
700 match ann {
701 TypeAnnotation::Basic(name) => !Self::is_primitive_value_type_name(name),
702 TypeAnnotation::Reference(name) => !Self::is_primitive_value_type_name(name),
703 TypeAnnotation::Array(_)
704 | TypeAnnotation::Tuple(_)
705 | TypeAnnotation::Object(_)
706 | TypeAnnotation::Function { .. }
707 | TypeAnnotation::Generic { .. }
708 | TypeAnnotation::Dyn(_) => true,
709 TypeAnnotation::Union(types) | TypeAnnotation::Intersection(types) => {
710 types.iter().any(Self::annotation_is_heap_like)
711 }
712 TypeAnnotation::Void
713 | TypeAnnotation::Never
714 | TypeAnnotation::Null
715 | TypeAnnotation::Undefined => false,
716 }
717 }
718
719 pub(super) fn type_is_heap_like(ty: &Type) -> bool {
720 match ty {
721 Type::Concrete(ann) => Self::annotation_is_heap_like(ann),
722 Type::Function { .. } => false,
723 Type::Generic { .. } => true,
724 Type::Variable(_) | Type::Constrained { .. } => false,
725 }
726 }
727
728 pub(crate) fn pass_mode_from_ref_flags(
729 ref_params: &[bool],
730 ref_mutates: &[bool],
731 idx: usize,
732 ) -> ParamPassMode {
733 if !ref_params.get(idx).copied().unwrap_or(false) {
734 ParamPassMode::ByValue
735 } else if ref_mutates.get(idx).copied().unwrap_or(false) {
736 ParamPassMode::ByRefExclusive
737 } else {
738 ParamPassMode::ByRefShared
739 }
740 }
741
742 pub(crate) fn pass_modes_from_ref_flags(
743 ref_params: &[bool],
744 ref_mutates: &[bool],
745 ) -> Vec<ParamPassMode> {
746 let len = ref_params.len().max(ref_mutates.len());
747 (0..len)
748 .map(|idx| Self::pass_mode_from_ref_flags(ref_params, ref_mutates, idx))
749 .collect()
750 }
751
752 pub(crate) fn build_param_pass_mode_map(
753 program: &Program,
754 inferred_ref_params: &HashMap<String, Vec<bool>>,
755 inferred_ref_mutates: &HashMap<String, Vec<bool>>,
756 ) -> HashMap<String, Vec<ParamPassMode>> {
757 let funcs = Self::collect_program_functions(program);
758 let mut by_function = HashMap::new();
759
760 for (name, func) in funcs {
761 let inferred_refs = inferred_ref_params.get(&name).cloned().unwrap_or_default();
762 let inferred_mutates = inferred_ref_mutates.get(&name).cloned().unwrap_or_default();
763 let mut modes = Vec::with_capacity(func.params.len());
764
765 for (idx, param) in func.params.iter().enumerate() {
766 let explicit_ref = param.is_reference;
767 let inferred_ref = inferred_refs.get(idx).copied().unwrap_or(false);
768 if !(explicit_ref || inferred_ref) {
769 modes.push(ParamPassMode::ByValue);
770 continue;
771 }
772
773 if inferred_mutates.get(idx).copied().unwrap_or(false) {
774 modes.push(ParamPassMode::ByRefExclusive);
775 } else {
776 modes.push(ParamPassMode::ByRefShared);
777 }
778 }
779
780 by_function.insert(name, modes);
781 }
782
783 by_function
784 }
785}