1use crate::value::VmDictExt;
2use std::collections::BTreeMap;
3use std::sync::Arc;
4
5use harn_parser::{substitute_type_expr, Node, SNode, ShapeField, TypeExpr, TypedParam};
6
7use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
8use crate::value::VmValue;
9
10use super::error::CompileError;
11use super::yield_scan::body_contains_yield;
12use super::{peel_node, Compiler, CompilerOptions, FinallyEntry};
13
14#[cfg(test)]
15thread_local! {
16 pub(super) static FORCE_DISCARDED_PRODUCES_VALUE: std::cell::Cell<Option<bool>> =
23 const { std::cell::Cell::new(None) };
24}
25
26impl Compiler {
27 pub fn new() -> Self {
28 Self::with_options(CompilerOptions::from_env())
29 }
30
31 pub fn with_imported_enum_candidates(
39 mut self,
40 candidates: impl IntoIterator<Item = String>,
41 ) -> Self {
42 self.add_imported_enum_candidates(candidates);
43 self
44 }
45
46 pub(crate) fn prepare_module_context(&mut self, program: &[SNode]) {
52 self.collect_module_enum_catalog(program);
53 if self.enum_names.insert("Result".to_string()) {
54 Self::seed_builtin_variant_owners(&mut self.enum_variant_owners);
55 }
56 Self::collect_struct_layouts(program, &mut self.struct_layouts);
57 Self::collect_interface_methods(program, &mut self.interface_methods);
58 self.collect_type_aliases(program);
59 self.collect_imported_enum_candidates(program);
60 self.seed_module_captured_idents(program);
64 }
65
66 pub(crate) fn add_imported_enum_candidates(
67 &mut self,
68 candidates: impl IntoIterator<Item = String>,
69 ) {
70 self.imported_enum_candidates_authoritative = true;
71 self.imported_enum_candidates.extend(candidates);
72 }
73
74 pub(crate) fn compile_module_init(
79 mut self,
80 context: &[SNode],
81 init_nodes: &[SNode],
82 imported_enum_candidates: &[String],
83 ) -> Result<Chunk, CompileError> {
84 self.add_imported_enum_candidates(imported_enum_candidates.iter().cloned());
85 self.prepare_module_context(context);
86 self.compile_top_level_declarations(init_nodes)?;
87 self.chunk.emit(Op::Nil, self.line);
88 self.chunk.emit(Op::Return, self.line);
89 super::ensure_chunk_addressable(&self.chunk, "the module initialization body", self.line)?;
90 Ok(self.chunk)
91 }
92
93 pub fn with_options(options: CompilerOptions) -> Self {
94 Self {
95 options,
96 chunk: Chunk::new(),
97 line: 1,
98 column: 1,
99 enum_names: std::collections::HashSet::new(),
100 enum_variant_owners: std::collections::HashMap::new(),
101 imported_enum_candidates: std::collections::HashSet::new(),
102 imported_enum_candidates_authoritative: false,
103 predeclared_enum_declarations: std::collections::HashSet::new(),
104 enum_catalog_scopes: Vec::new(),
105 struct_layouts: std::collections::HashMap::new(),
106 interface_methods: std::collections::HashMap::new(),
107 loop_stack: Vec::new(),
108 handler_depth: 0,
109 finally_bodies: Vec::new(),
110 temp_counter: 0,
111 scope_depth: 0,
112 type_aliases: std::collections::HashMap::new(),
113 type_scopes: vec![std::collections::HashMap::new()],
114 monomorphic_bindings: std::collections::HashSet::new(),
115 string_constants: std::collections::HashMap::new(),
116 local_scopes: vec![std::collections::HashMap::new()],
117 module_level: true,
118 captured_bindings: std::collections::HashSet::new(),
119 }
120 }
121
122 pub(super) fn for_nested_body(options: CompilerOptions) -> Self {
126 let mut c = Self::with_options(options);
127 c.module_level = false;
128 c
129 }
130
131 pub(super) fn nested_body(&self) -> Self {
132 Self::for_nested_body(self.options)
133 }
134
135 pub(super) fn nominal_type_names(&self) -> Vec<String> {
136 let mut names: Vec<String> = self
137 .struct_layouts
138 .keys()
139 .chain(self.enum_names.iter())
140 .cloned()
141 .collect();
142 names.sort();
143 names.dedup();
144 names
145 }
146
147 pub(super) fn string_constant(&mut self, value: &str) -> u16 {
148 if let Some(idx) = self.string_constants.get(value) {
149 return *idx;
150 }
151 let owned = value.to_string();
152 let idx = self.chunk.add_constant(Constant::String(owned.clone()));
153 self.string_constants.insert(owned, idx);
154 idx
155 }
156
157 pub(super) fn owned_string_constant(&mut self, value: String) -> u16 {
158 if let Some(idx) = self.string_constants.get(value.as_str()) {
159 return *idx;
160 }
161 let idx = self.chunk.add_constant(Constant::String(value.clone()));
162 self.string_constants.insert(value, idx);
163 idx
164 }
165
166 pub(crate) fn collect_type_aliases(&mut self, program: &[SNode]) {
170 for sn in program {
171 match peel_node(sn) {
172 Node::SelectiveImport { names, .. } => {
173 for name in names {
174 self.type_aliases.entry(name.clone()).or_insert_with(|| {
175 super::TypeAliasDefinition {
176 type_params: Vec::new(),
177 body: None,
178 }
179 });
180 }
181 }
182 Node::TypeDecl {
183 name,
184 type_expr,
185 type_params,
186 is_pub: _,
187 } => {
188 self.type_aliases.insert(
189 name.clone(),
190 super::TypeAliasDefinition {
191 type_params: type_params.clone(),
192 body: Some(type_expr.clone()),
193 },
194 );
195 }
196 _ => {}
197 }
198 }
199 }
200
201 pub(crate) fn expand_alias(&self, ty: &TypeExpr) -> TypeExpr {
211 let mut visiting = std::collections::HashSet::new();
212 self.expand_alias_inner(ty, &mut visiting)
213 }
214
215 fn expand_alias_inner(
216 &self,
217 ty: &TypeExpr,
218 visiting: &mut std::collections::HashSet<String>,
219 ) -> TypeExpr {
220 match ty {
221 TypeExpr::Named(name) => {
222 if let Some(target) = self
223 .type_aliases
224 .get(name)
225 .filter(|alias| alias.type_params.is_empty() && alias.body.is_some())
226 {
227 if !visiting.insert(name.clone()) {
228 return TypeExpr::Named(name.clone());
229 }
230 let resolved = self.expand_alias_inner(target.body.as_ref().unwrap(), visiting);
231 visiting.remove(name);
232 resolved
233 } else {
234 TypeExpr::Named(name.clone())
235 }
236 }
237 TypeExpr::Union(types) => TypeExpr::Union(
238 types
239 .iter()
240 .map(|t| self.expand_alias_inner(t, visiting))
241 .collect(),
242 ),
243 TypeExpr::Intersection(types) => TypeExpr::Intersection(
244 types
245 .iter()
246 .map(|t| self.expand_alias_inner(t, visiting))
247 .collect(),
248 ),
249 TypeExpr::Shape(fields) => TypeExpr::Shape(
250 fields
251 .iter()
252 .map(|field| ShapeField {
253 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
254 ..field.clone()
255 })
256 .collect(),
257 ),
258 TypeExpr::OpenShape { fields, rests } => TypeExpr::OpenShape {
259 fields: fields
260 .iter()
261 .map(|field| ShapeField {
262 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
263 ..field.clone()
264 })
265 .collect(),
266 rests: rests
267 .iter()
268 .map(|r| self.expand_alias_inner(r, visiting))
269 .collect(),
270 },
271 TypeExpr::List(inner) => {
272 TypeExpr::List(Box::new(self.expand_alias_inner(inner, visiting)))
273 }
274 TypeExpr::Tuple(elements) => TypeExpr::Tuple(
275 elements
276 .iter()
277 .map(|element| self.expand_alias_inner(element, visiting))
278 .collect(),
279 ),
280 TypeExpr::Iter(inner) => {
281 TypeExpr::Iter(Box::new(self.expand_alias_inner(inner, visiting)))
282 }
283 TypeExpr::Generator(inner) => {
284 TypeExpr::Generator(Box::new(self.expand_alias_inner(inner, visiting)))
285 }
286 TypeExpr::Stream(inner) => {
287 TypeExpr::Stream(Box::new(self.expand_alias_inner(inner, visiting)))
288 }
289 TypeExpr::DictType(k, v) => TypeExpr::DictType(
290 Box::new(self.expand_alias_inner(k, visiting)),
291 Box::new(self.expand_alias_inner(v, visiting)),
292 ),
293 TypeExpr::FnType {
294 params,
295 return_type,
296 } => TypeExpr::FnType {
297 params: params
298 .iter()
299 .map(|p| self.expand_alias_inner(p, visiting))
300 .collect(),
301 return_type: Box::new(self.expand_alias_inner(return_type, visiting)),
302 },
303 TypeExpr::Applied { name, args } => {
304 let args = args
305 .iter()
306 .map(|arg| self.expand_alias_inner(arg, visiting))
307 .collect::<Vec<_>>();
308 let Some(alias) = self.type_aliases.get(name) else {
309 return TypeExpr::Applied {
310 name: name.clone(),
311 args,
312 };
313 };
314 let Some(body) = alias.body.as_ref() else {
315 return TypeExpr::Applied {
316 name: name.clone(),
317 args,
318 };
319 };
320 if alias.type_params.len() != args.len() || !visiting.insert(name.clone()) {
321 return TypeExpr::Applied {
322 name: name.clone(),
323 args,
324 };
325 }
326 let bindings = alias
327 .type_params
328 .iter()
329 .zip(args.iter().cloned())
330 .map(|(param, arg)| (param.name.clone(), arg))
331 .collect();
332 let instantiated = substitute_type_expr(body, &bindings);
333 let resolved = self.expand_alias_inner(&instantiated, visiting);
334 visiting.remove(name);
335 resolved
336 }
337 TypeExpr::Never => TypeExpr::Never,
338 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
339 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
340 TypeExpr::Owned(inner) => {
341 TypeExpr::Owned(Box::new(self.expand_alias_inner(inner, visiting)))
342 }
343 }
344 }
345
346 pub fn compile_public_type_schema_initializers(
350 program: &[SNode],
351 source_file: Option<String>,
352 ) -> Result<Option<Chunk>, CompileError> {
353 let mut compiler = Compiler::new();
354 compiler.collect_type_aliases(program);
355 compiler.chunk.source_file = source_file;
356 let mut emitted = false;
357 for sn in program {
358 let Node::TypeDecl {
359 name, is_pub: true, ..
360 } = peel_node(sn)
361 else {
362 continue;
363 };
364 if compiler.emit_schema_for_alias(name) {
365 compiler.emit_define_binding(name, false);
366 emitted = true;
367 }
368 }
369 if !emitted {
370 return Ok(None);
371 }
372 compiler.chunk.emit(Op::Nil, compiler.line);
373 compiler.chunk.emit(Op::Return, compiler.line);
374 super::ensure_chunk_addressable(
375 &compiler.chunk,
376 "the module type-schema initializer",
377 compiler.line,
378 )?;
379 Ok(Some(compiler.chunk))
380 }
381
382 pub(super) fn is_schema_guard(name: &str) -> bool {
386 matches!(
387 name,
388 "schema_is"
389 | "schema_expect"
390 | "schema_parse"
391 | "schema_check"
392 | "schema_report"
393 | "is_type"
394 | "json_validate"
395 )
396 }
397
398 pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
401 matches!(
402 &key.node,
403 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
404 if name == keyword
405 )
406 }
407
408 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
411 self.prepare_module_context(program);
414
415 for sn in program {
416 match &sn.node {
417 Node::ImportDecl { .. }
418 | Node::SelectiveImport { .. }
419 | Node::NamespaceImport { .. } => {
420 self.compile_node(sn)?;
421 }
422 _ => {}
423 }
424 }
425 let main = program
426 .iter()
427 .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
428 .or_else(|| {
429 program
430 .iter()
431 .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
432 });
433
434 let mut pipeline_emits_value = false;
438 if let Some(sn) = main {
439 self.compile_top_level_declarations(program)?;
440 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
441 self.compile_with_pipeline_captures(
442 program,
443 body,
444 extends.as_deref(),
445 |compiler| {
446 if let Some(parent_name) = extends {
447 compiler.compile_parent_pipeline(program, parent_name)?;
448 }
449 let saved = std::mem::replace(&mut compiler.module_level, false);
450 let result = compiler.compile_block(body);
451 compiler.module_level = saved;
452 result
453 },
454 )?;
455 pipeline_emits_value = true;
456 }
457 } else {
458 let top_level: Vec<&SNode> = program
460 .iter()
461 .filter(|sn| {
462 !matches!(
463 &sn.node,
464 Node::ImportDecl { .. }
465 | Node::SelectiveImport { .. }
466 | Node::NamespaceImport { .. }
467 )
468 })
469 .collect();
470 for sn in &top_level {
471 self.compile_discarded_stmt(sn)?;
472 }
473 if Self::has_top_level_fn_main(program) {
478 let harness_name = self.string_constant("harness");
479 self.chunk.emit_u16(Op::GetVar, harness_name, self.line);
480 self.emit_named_call("main", 1);
481 pipeline_emits_value = true;
482 }
483 }
484
485 self.drain_finallys_to_floor(0)?;
486 if !pipeline_emits_value {
487 self.chunk.emit(Op::Nil, self.line);
488 }
489 self.chunk.emit(Op::Return, self.line);
490 super::ensure_chunk_addressable(&self.chunk, "the program body", self.line)?;
491 Ok(self.chunk)
492 }
493
494 fn has_top_level_fn_main(program: &[SNode]) -> bool {
498 program
499 .iter()
500 .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
501 }
502
503 pub fn compile_named(
505 self,
506 program: &[SNode],
507 pipeline_name: &str,
508 ) -> Result<Chunk, CompileError> {
509 self.compile_named_inner(program, pipeline_name)
510 }
511
512 fn compile_named_inner(
513 mut self,
514 program: &[SNode],
515 pipeline_name: &str,
516 ) -> Result<Chunk, CompileError> {
517 self.prepare_module_context(program);
518
519 for sn in program {
520 if matches!(
521 &sn.node,
522 Node::ImportDecl { .. }
523 | Node::SelectiveImport { .. }
524 | Node::NamespaceImport { .. }
525 ) {
526 self.compile_node(sn)?;
527 }
528 }
529 let target = program.iter().find(
530 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
531 );
532
533 if let Some(sn) = target {
534 self.compile_top_level_declarations(program)?;
535 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
536 self.compile_with_pipeline_captures(
537 program,
538 body,
539 extends.as_deref(),
540 |compiler| {
541 if let Some(parent_name) = extends {
542 compiler.compile_parent_pipeline(program, parent_name)?;
543 }
544 let saved = std::mem::replace(&mut compiler.module_level, false);
545 let result = compiler.compile_block(body);
546 compiler.module_level = saved;
547 result
548 },
549 )?;
550 }
551 }
552
553 self.drain_finallys_to_floor(0)?;
554 self.chunk.emit(Op::Nil, self.line);
555 self.chunk.emit(Op::Return, self.line);
556 super::ensure_chunk_addressable(&self.chunk, "the pipeline body", self.line)?;
557 Ok(self.chunk)
558 }
559
560 pub(super) fn emit_default_preamble(
565 &mut self,
566 params: &[TypedParam],
567 ) -> Result<(), CompileError> {
568 for (i, param) in params.iter().enumerate() {
569 if let Some(default_expr) = ¶m.default_value {
570 self.chunk.emit(Op::GetArgc, self.line);
571 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
572 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
573 self.chunk.emit(Op::GreaterEqual, self.line);
574 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
575 self.chunk.emit(Op::Pop, self.line);
577 let masked = self.mask_param_names(¶ms[i..]);
586 let result = self.compile_node(default_expr);
587 self.restore_param_names(masked);
588 result?;
589 self.emit_init_or_define_binding(¶m.name, false);
590 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
591 self.chunk.patch_jump(skip_jump);
592 self.chunk.emit(Op::Pop, self.line);
593 self.chunk.patch_jump(end_jump);
594 }
595 }
596 Ok(())
597 }
598
599 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
606 for (param_index, param) in params.iter().enumerate() {
607 if let Some(type_expr) = ¶m.type_expr {
608 let check_type = if param.rest {
609 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
610 } else {
611 type_expr.clone()
612 };
613
614 if let harn_parser::TypeExpr::Named(name) = &check_type {
615 if let Some(methods) = self.interface_methods.get(name).cloned() {
616 let fn_idx = self.string_constant("__assert_interface");
617 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
618 self.emit_get_binding(¶m.name);
619 let name_idx = self.string_constant(¶m.name);
620 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
621 let iface_idx = self.string_constant(name);
622 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
623 let methods_str = methods.join(",");
624 let methods_idx = self.owned_string_constant(methods_str);
625 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
626 self.chunk.emit_u8(Op::Call, 4, self.line);
627 self.chunk.emit(Op::Pop, self.line);
628 continue;
629 }
630 }
631
632 if param.default_value.is_some() {
633 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
634 self.emit_default_param_schema_check(param_index, param, &schema);
635 }
636 }
637 }
638 }
639 }
640
641 fn emit_default_param_schema_check(
642 &mut self,
643 param_index: usize,
644 param: &TypedParam,
645 schema: &VmValue,
646 ) {
647 self.chunk.emit(Op::GetArgc, self.line);
648 let threshold_idx = self
649 .chunk
650 .add_constant(Constant::Int((param_index + 1) as i64));
651 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
652 self.chunk.emit(Op::GreaterEqual, self.line);
653 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
654 self.chunk.emit(Op::Pop, self.line);
655 self.emit_schema_assert_call(param, schema);
656 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
657 self.chunk.patch_jump(supplied_jump);
658 self.chunk.emit(Op::Pop, self.line);
659 self.chunk.patch_jump(end_jump);
660 }
661
662 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
663 let fn_idx = self.string_constant("__assert_schema");
664 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
665 self.emit_get_binding(¶m.name);
666 let name_idx = self.string_constant(¶m.name);
667 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
668 self.emit_vm_value_literal(schema);
669 self.chunk.emit_u8(Op::Call, 3, self.line);
670 self.chunk.emit(Op::Pop, self.line);
671 }
672
673 pub(crate) fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
674 match type_expr {
675 harn_parser::TypeExpr::Named(name) => match name.as_str() {
676 "any" | "unknown" => Some(VmValue::dict(BTreeMap::<String, VmValue>::new())),
677 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
678 | "closure" | "bytes" => Some(VmValue::dict(BTreeMap::from([(
679 "type".to_string(),
680 VmValue::String(arcstr::ArcStr::from(name.as_str())),
681 )]))),
682 _ => None,
683 },
684 harn_parser::TypeExpr::Shape(fields) => {
685 let mut properties = BTreeMap::new();
686 let mut required = Vec::new();
687 for field in fields {
688 let mut field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
689 if field.optional {
690 field_schema = VmValue::dict(BTreeMap::from([(
691 "union".to_string(),
692 VmValue::List(std::sync::Arc::new(vec![
693 field_schema,
694 VmValue::dict(BTreeMap::from([(
695 "type".to_string(),
696 VmValue::String(arcstr::ArcStr::from("nil")),
697 )])),
698 ])),
699 )]));
700 }
701 properties.insert(field.name.clone(), field_schema);
702 if !field.optional {
703 required.push(VmValue::String(arcstr::ArcStr::from(field.name.as_str())));
704 }
705 }
706 let mut out = BTreeMap::new();
707 out.put_str("type", "dict");
708 out.insert("properties".to_string(), VmValue::dict(properties));
709 if !required.is_empty() {
710 out.insert(
711 "required".to_string(),
712 VmValue::List(std::sync::Arc::new(required)),
713 );
714 }
715 Some(VmValue::dict(out))
716 }
717 harn_parser::TypeExpr::OpenShape { .. } => None,
718 harn_parser::TypeExpr::List(inner) => {
719 let mut out = BTreeMap::new();
720 out.put_str("type", "list");
721 let item_schema = Self::type_expr_to_schema_value(inner)?;
722 out.insert("items".to_string(), item_schema);
723 Some(VmValue::dict(out))
724 }
725 harn_parser::TypeExpr::Tuple(_) => None,
731 harn_parser::TypeExpr::DictType(key, value) => {
732 let mut out = BTreeMap::new();
733 out.put_str("type", "dict");
734 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
735 let value_schema = Self::type_expr_to_schema_value(value)?;
736 out.insert("additional_properties".to_string(), value_schema);
737 }
738 Some(VmValue::dict(out))
739 }
740 harn_parser::TypeExpr::Union(members) => {
741 if !members.is_empty()
746 && members
747 .iter()
748 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
749 {
750 let values = members
751 .iter()
752 .map(|m| match m {
753 harn_parser::TypeExpr::LitString(s) => {
754 VmValue::String(arcstr::ArcStr::from(s.as_str()))
755 }
756 _ => unreachable!(),
757 })
758 .collect::<Vec<_>>();
759 return Some(VmValue::dict(BTreeMap::from([
760 (
761 "type".to_string(),
762 VmValue::String(arcstr::ArcStr::from("string")),
763 ),
764 (
765 "enum".to_string(),
766 VmValue::List(std::sync::Arc::new(values)),
767 ),
768 ])));
769 }
770 if !members.is_empty()
771 && members
772 .iter()
773 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
774 {
775 let values = members
776 .iter()
777 .map(|m| match m {
778 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
779 _ => unreachable!(),
780 })
781 .collect::<Vec<_>>();
782 return Some(VmValue::dict(BTreeMap::from([
783 (
784 "type".to_string(),
785 VmValue::String(arcstr::ArcStr::from("int")),
786 ),
787 (
788 "enum".to_string(),
789 VmValue::List(std::sync::Arc::new(values)),
790 ),
791 ])));
792 }
793 let branches = members
794 .iter()
795 .map(Self::type_expr_to_schema_value)
796 .collect::<Option<Vec<_>>>()?;
797 if branches.is_empty() {
798 None
799 } else {
800 Some(VmValue::dict(BTreeMap::from([(
801 "union".to_string(),
802 VmValue::List(std::sync::Arc::new(branches)),
803 )])))
804 }
805 }
806 harn_parser::TypeExpr::Intersection(members) => {
807 let branches = members
811 .iter()
812 .map(Self::type_expr_to_schema_value)
813 .collect::<Option<Vec<_>>>()?;
814 if branches.is_empty() {
815 None
816 } else {
817 Some(VmValue::dict(BTreeMap::from([(
818 "all_of".to_string(),
819 VmValue::List(std::sync::Arc::new(branches)),
820 )])))
821 }
822 }
823 harn_parser::TypeExpr::FnType { .. } => Some(VmValue::dict(BTreeMap::from([(
824 "type".to_string(),
825 VmValue::String(arcstr::ArcStr::from("closure")),
826 )]))),
827 harn_parser::TypeExpr::Applied { .. } => None,
828 harn_parser::TypeExpr::Iter(_)
829 | harn_parser::TypeExpr::Generator(_)
830 | harn_parser::TypeExpr::Stream(_) => None,
831 harn_parser::TypeExpr::Never => None,
832 harn_parser::TypeExpr::LitString(s) => Some(VmValue::dict(BTreeMap::from([
833 (
834 "type".to_string(),
835 VmValue::String(arcstr::ArcStr::from("string")),
836 ),
837 (
838 "const".to_string(),
839 VmValue::String(arcstr::ArcStr::from(s.as_str())),
840 ),
841 ]))),
842 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::dict(BTreeMap::from([
843 (
844 "type".to_string(),
845 VmValue::String(arcstr::ArcStr::from("int")),
846 ),
847 ("const".to_string(), VmValue::Int(*v)),
848 ]))),
849 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
850 }
851 }
852
853 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
854 match value {
855 VmValue::String(text) => {
856 let idx = self.string_constant(text);
857 self.chunk.emit_u16(Op::Constant, idx, self.line);
858 }
859 VmValue::Int(number) => {
860 let idx = self.chunk.add_constant(Constant::Int(*number));
861 self.chunk.emit_u16(Op::Constant, idx, self.line);
862 }
863 VmValue::Float(number) => {
864 let idx = self.chunk.add_constant(Constant::Float(*number));
865 self.chunk.emit_u16(Op::Constant, idx, self.line);
866 }
867 VmValue::Bool(value) => {
868 let idx = self.chunk.add_constant(Constant::Bool(*value));
869 self.chunk.emit_u16(Op::Constant, idx, self.line);
870 }
871 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
872 VmValue::List(items) => {
873 for item in items.iter() {
874 self.emit_vm_value_literal(item);
875 }
876 self.chunk
877 .emit_u16(Op::BuildList, items.len() as u16, self.line);
878 }
879 VmValue::Dict(entries) => {
880 for (key, item) in entries.iter() {
881 let key_idx = self.string_constant(key);
882 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
883 self.emit_vm_value_literal(item);
884 }
885 self.chunk
886 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
887 }
888 _ => {}
889 }
890 }
891
892 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
894 let hi = (type_name_idx >> 8) as u8;
895 let lo = type_name_idx as u8;
896 self.chunk.code.push(hi);
897 self.chunk.code.push(lo);
898 self.chunk.lines.push(self.line);
899 self.chunk.columns.push(self.column);
900 self.chunk.lines.push(self.line);
901 self.chunk.columns.push(self.column);
902 }
903
904 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
906 if body.is_empty() {
907 self.chunk.emit(Op::Nil, self.line);
908 } else {
909 self.compile_scoped_block(body)?;
910 }
911 Ok(())
912 }
913
914 pub(super) fn compile_catch_binding(
916 &mut self,
917 error_var: &Option<String>,
918 ) -> Result<(), CompileError> {
919 if let Some(var_name) = error_var {
920 self.emit_define_binding(var_name, false);
921 } else {
922 self.chunk.emit(Op::Pop, self.line);
923 }
924 Ok(())
925 }
926
927 pub(super) fn compile_finally_inline(
934 &mut self,
935 finally_body: &[SNode],
936 ) -> Result<(), CompileError> {
937 if !finally_body.is_empty() {
938 self.compile_scoped_block(finally_body)?;
939 self.chunk.emit(Op::Pop, self.line);
940 }
941 Ok(())
942 }
943
944 pub(super) fn has_pending_finally_until_barrier(&self) -> bool {
948 self.finally_bodies
949 .iter()
950 .rev()
951 .take_while(|entry| !matches!(entry, FinallyEntry::CatchBarrier))
952 .any(|entry| matches!(entry, FinallyEntry::Finally(_)))
953 }
954
955 pub(super) fn has_pending_finally(&self) -> bool {
957 self.finally_bodies
958 .iter()
959 .any(|e| matches!(e, FinallyEntry::Finally(_)))
960 }
961
962 pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
971 self.temp_counter += 1;
972 let temp_name = format!("__finally_err_{}__", self.temp_counter);
973 self.emit_define_binding(&temp_name, true);
974 self.emit_get_binding(&temp_name);
975 self.chunk.emit(Op::Throw, self.line);
976 Ok(())
977 }
978
979 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
980 for param in params {
981 self.define_local_slot(¶m.name, false);
982 }
983 }
984
985 fn mask_param_names(&mut self, params: &[TypedParam]) -> Vec<(String, super::LocalBinding)> {
991 let mut removed = Vec::new();
992 if let Some(scope) = self.local_scopes.last_mut() {
993 for param in params {
994 if let Some(binding) = scope.remove(¶m.name) {
995 removed.push((param.name.clone(), binding));
996 }
997 }
998 }
999 removed
1000 }
1001
1002 fn restore_param_names(&mut self, removed: Vec<(String, super::LocalBinding)>) {
1004 if let Some(scope) = self.local_scopes.last_mut() {
1005 for (name, binding) in removed {
1006 scope.insert(name, binding);
1007 }
1008 }
1009 }
1010
1011 pub(super) fn seed_captured_idents(&mut self, body: &[SNode]) {
1016 let match_patterns = self.lexical_match_pattern_catalog();
1017 self.captured_bindings =
1018 harn_parser::lexical::captured_bindings_in_nested_callables(body, &match_patterns);
1019 }
1020
1021 fn seed_module_captured_idents(&mut self, body: &[SNode]) {
1022 let match_patterns = self.lexical_match_pattern_catalog();
1023 self.captured_bindings =
1024 harn_parser::lexical::captured_bindings_in_compiled_module(body, &match_patterns);
1025 }
1026
1027 pub(super) fn lexical_match_pattern_catalog(
1028 &self,
1029 ) -> harn_parser::lexical::MatchPatternCatalog {
1030 if self.imported_enum_candidates.is_empty() {
1031 return harn_parser::lexical::MatchPatternCatalog::new(
1032 &self.enum_names,
1033 &self.enum_variant_owners,
1034 );
1035 }
1036 let mut enum_names = self.enum_names.clone();
1037 enum_names.extend(self.imported_enum_candidates.iter().cloned());
1038 harn_parser::lexical::MatchPatternCatalog::new(&enum_names, &self.enum_variant_owners)
1039 }
1040
1041 pub(super) fn begin_scope(&mut self) {
1042 self.chunk.emit(Op::PushScope, self.line);
1043 self.scope_depth += 1;
1044 let enum_catalog = self.enum_catalog_snapshot();
1045 self.enum_catalog_scopes.push(enum_catalog);
1046 self.type_scopes.push(std::collections::HashMap::new());
1047 self.local_scopes.push(std::collections::HashMap::new());
1048 }
1049
1050 pub(super) fn end_scope(&mut self) {
1051 if self.scope_depth > 0 {
1052 self.chunk.emit(Op::PopScope, self.line);
1053 self.scope_depth -= 1;
1054 if let Some(snapshot) = self.enum_catalog_scopes.pop() {
1055 self.restore_enum_catalog(snapshot);
1056 }
1057 self.type_scopes.pop();
1058 self.local_scopes.pop();
1059 }
1060 }
1061
1062 pub(super) fn emit_scope_unwind_to(&mut self, target_depth: usize) {
1065 for _ in target_depth..self.scope_depth {
1066 self.chunk.emit(Op::PopScope, self.line);
1067 }
1068 }
1069
1070 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1071 self.begin_scope();
1072 let finally_floor = self.finally_bodies.len();
1073 if stmts.is_empty() {
1074 self.chunk.emit(Op::Nil, self.line);
1075 } else {
1076 self.compile_block(stmts)?;
1077 }
1078 self.drain_finallys_to_floor(finally_floor)?;
1079 self.end_scope();
1080 Ok(())
1081 }
1082
1083 pub(super) fn compile_scoped_statements(
1084 &mut self,
1085 stmts: &[SNode],
1086 ) -> Result<(), CompileError> {
1087 self.begin_scope();
1088 self.record_monomorphic_var_bindings(stmts);
1089 let finally_floor = self.finally_bodies.len();
1090 for sn in stmts {
1091 self.compile_discarded_stmt(sn)?;
1092 }
1093 self.drain_finallys_to_floor(finally_floor)?;
1094 self.end_scope();
1095 Ok(())
1096 }
1097
1098 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
1103 while self.finally_bodies.len() > floor {
1104 let entry = self.finally_bodies.pop().expect("non-empty by guard");
1105 if let FinallyEntry::Finally(body) = entry {
1106 self.compile_finally_inline(&body)?;
1107 }
1108 }
1109 Ok(())
1110 }
1111
1112 pub(super) fn run_pending_finallys_for_transfer(
1125 &mut self,
1126 floor: usize,
1127 ) -> Result<(), CompileError> {
1128 if self.finally_bodies.len() <= floor {
1129 return Ok(());
1130 }
1131 let saved = self.finally_bodies[floor..].to_vec();
1132 let result = self.drain_finallys_to_floor(floor);
1133 self.finally_bodies.extend(saved);
1134 result
1135 }
1136
1137 pub(super) fn run_pending_finallys_until_barrier(&mut self) -> Result<(), CompileError> {
1142 let floor = self
1143 .finally_bodies
1144 .iter()
1145 .rposition(|e| matches!(e, FinallyEntry::CatchBarrier))
1146 .map(|i| i + 1)
1147 .unwrap_or(0);
1148 self.run_pending_finallys_for_transfer(floor)
1149 }
1150
1151 pub(super) fn maybe_register_owned_drop(
1156 &mut self,
1157 pattern: &harn_parser::BindingPattern,
1158 type_ann: Option<&TypeExpr>,
1159 span: harn_lexer::Span,
1160 ) {
1161 let Some(ty) = type_ann else {
1167 return;
1168 };
1169 if !matches!(ty, TypeExpr::Owned(_)) {
1170 return;
1171 }
1172 let harn_parser::BindingPattern::Identifier(name) = pattern else {
1173 return;
1174 };
1175 if harn_parser::is_discard_name(name) {
1176 return;
1177 }
1178 let call = harn_parser::spanned(
1179 Node::FunctionCall {
1180 name: "drop".to_string(),
1181 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
1182 type_args: Vec::new(),
1183 },
1184 span,
1185 );
1186 self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
1187 }
1188
1189 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
1205 #[cfg(debug_assertions)]
1206 let probe = self.chunk.balance_probe();
1207 self.compile_node(sn)?;
1208 #[allow(unused_mut)]
1209 let mut produces = Self::produces_value(&sn.node);
1210 #[cfg(test)]
1214 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
1215 produces = forced;
1216 }
1217 #[cfg(debug_assertions)]
1218 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1219 let expected = i32::from(produces);
1220 debug_assert_eq!(
1221 delta, expected,
1222 "operand-stack imbalance at line {}: produces_value={produces} but the \
1223 node's emitted bytecode netted {delta} (expected {expected}). A \
1224 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1225 node: {:?}",
1226 self.line, sn.node,
1227 );
1228 }
1229 if produces {
1230 self.chunk.emit(Op::Pop, self.line);
1231 }
1232 Ok(())
1233 }
1234
1235 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1236 self.record_monomorphic_var_bindings(stmts);
1237 for (i, snode) in stmts.iter().enumerate() {
1238 if i == stmts.len() - 1 {
1239 self.compile_node(snode)?;
1243 if !Self::produces_value(&snode.node) {
1244 self.chunk.emit(Op::Nil, self.line);
1245 }
1246 } else {
1247 self.compile_discarded_stmt(snode)?;
1248 }
1249 }
1250 Ok(())
1251 }
1252
1253 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1255 self.begin_scope();
1256 let finally_floor = self.finally_bodies.len();
1257 if body.is_empty() {
1258 self.chunk.emit(Op::Nil, self.line);
1259 } else {
1260 self.compile_block(body)?;
1261 if !Self::produces_value(&body.last().unwrap().node) {
1262 self.chunk.emit(Op::Nil, self.line);
1263 }
1264 }
1265 self.drain_finallys_to_floor(finally_floor)?;
1266 self.end_scope();
1267 Ok(())
1268 }
1269
1270 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1272 match op {
1273 "+" => self.chunk.emit(Op::Add, self.line),
1274 "-" => self.chunk.emit(Op::Sub, self.line),
1275 "*" => self.chunk.emit(Op::Mul, self.line),
1276 "/" => self.chunk.emit(Op::Div, self.line),
1277 "%" => self.chunk.emit(Op::Mod, self.line),
1278 _ => {
1279 return Err(CompileError {
1280 message: format!("Unknown compound operator: {op}"),
1281 line: self.line,
1282 })
1283 }
1284 }
1285 Ok(())
1286 }
1287
1288 pub(super) fn compile_top_level_declarations(
1289 &mut self,
1290 program: &[SNode],
1291 ) -> Result<(), CompileError> {
1292 for sn in program {
1304 if !harn_parser::lexical::is_deferred_module_declaration(sn) {
1305 self.compile_discarded_stmt(sn)?;
1306 }
1307 }
1308 for sn in program {
1314 let inner_kind = match &sn.node {
1315 Node::AttributedDecl { inner, .. } => &inner.node,
1316 other => other,
1317 };
1318 match inner_kind {
1319 Node::EvalPackDecl {
1320 binding_name,
1321 pack_id,
1322 fields,
1323 body,
1324 summarize,
1325 ..
1326 } => {
1327 self.compile_eval_pack_decl(
1328 binding_name,
1329 pack_id,
1330 fields,
1331 body,
1332 summarize,
1333 false,
1334 )?;
1335 }
1336 Node::FnDecl { .. }
1337 | Node::ToolDecl { .. }
1338 | Node::SkillDecl { .. }
1339 | Node::ImplBlock { .. }
1340 | Node::StructDecl { .. }
1341 | Node::EnumDecl { .. }
1342 | Node::InterfaceDecl { .. } => {
1343 self.compile_node(sn)?;
1344 }
1345 Node::TypeDecl { .. } => {}
1346 _ => {}
1347 }
1348 }
1349 Ok(())
1350 }
1351
1352 pub fn compile_fn_body(
1365 &mut self,
1366 type_params: &[harn_parser::TypeParam],
1367 params: &[TypedParam],
1368 body: &[SNode],
1369 source_file: Option<String>,
1370 ) -> Result<CompiledFunction, CompileError> {
1371 let mut fn_compiler = self.nested_body();
1372 fn_compiler.enum_names = self.enum_names.clone();
1373 fn_compiler.enum_variant_owners = self.enum_variant_owners.clone();
1374 fn_compiler.imported_enum_candidates = self.imported_enum_candidates.clone();
1375 fn_compiler.imported_enum_candidates_authoritative =
1376 self.imported_enum_candidates_authoritative;
1377 fn_compiler.interface_methods = self.interface_methods.clone();
1378 fn_compiler.type_aliases = self.type_aliases.clone();
1379 fn_compiler.struct_layouts = self.struct_layouts.clone();
1380 fn_compiler.declare_param_slots(params);
1381 fn_compiler.record_param_types(params);
1382 fn_compiler.emit_default_preamble(params)?;
1383 fn_compiler.emit_type_checks(params);
1384 let is_gen = body_contains_yield(body);
1385 fn_compiler.seed_captured_idents(body);
1386 fn_compiler.compile_block(body)?;
1387 fn_compiler.chunk.emit(Op::Nil, 0);
1388 fn_compiler.chunk.emit(Op::Return, 0);
1389 fn_compiler.chunk.source_file = source_file;
1390 let param_slots = fn_compiler.compile_param_slots(params);
1391 let has_runtime_type_checks =
1392 CompiledFunction::has_runtime_type_checks_for_params(¶m_slots);
1393 super::ensure_chunk_addressable(&fn_compiler.chunk, "function body", self.line)?;
1394 Ok(CompiledFunction {
1395 name: String::new(),
1396 type_params: type_params.iter().map(|param| param.name.clone()).collect(),
1397 nominal_type_names: fn_compiler.nominal_type_names(),
1398 params: param_slots,
1399 default_start: TypedParam::default_start(params),
1400 chunk: Arc::new(fn_compiler.chunk),
1401 is_generator: is_gen,
1402 is_stream: false,
1403 has_rest_param: false,
1404 has_runtime_type_checks,
1405 })
1406 }
1407
1408 pub(super) fn produces_value(node: &Node) -> bool {
1410 harn_parser::node_produces_value(node)
1411 }
1412}
1413
1414impl Default for Compiler {
1415 fn default() -> Self {
1416 Self::new()
1417 }
1418}