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::Iter(inner) => {
275 TypeExpr::Iter(Box::new(self.expand_alias_inner(inner, visiting)))
276 }
277 TypeExpr::Generator(inner) => {
278 TypeExpr::Generator(Box::new(self.expand_alias_inner(inner, visiting)))
279 }
280 TypeExpr::Stream(inner) => {
281 TypeExpr::Stream(Box::new(self.expand_alias_inner(inner, visiting)))
282 }
283 TypeExpr::DictType(k, v) => TypeExpr::DictType(
284 Box::new(self.expand_alias_inner(k, visiting)),
285 Box::new(self.expand_alias_inner(v, visiting)),
286 ),
287 TypeExpr::FnType {
288 params,
289 return_type,
290 } => TypeExpr::FnType {
291 params: params
292 .iter()
293 .map(|p| self.expand_alias_inner(p, visiting))
294 .collect(),
295 return_type: Box::new(self.expand_alias_inner(return_type, visiting)),
296 },
297 TypeExpr::Applied { name, args } => {
298 let args = args
299 .iter()
300 .map(|arg| self.expand_alias_inner(arg, visiting))
301 .collect::<Vec<_>>();
302 let Some(alias) = self.type_aliases.get(name) else {
303 return TypeExpr::Applied {
304 name: name.clone(),
305 args,
306 };
307 };
308 let Some(body) = alias.body.as_ref() else {
309 return TypeExpr::Applied {
310 name: name.clone(),
311 args,
312 };
313 };
314 if alias.type_params.len() != args.len() || !visiting.insert(name.clone()) {
315 return TypeExpr::Applied {
316 name: name.clone(),
317 args,
318 };
319 }
320 let bindings = alias
321 .type_params
322 .iter()
323 .zip(args.iter().cloned())
324 .map(|(param, arg)| (param.name.clone(), arg))
325 .collect();
326 let instantiated = substitute_type_expr(body, &bindings);
327 let resolved = self.expand_alias_inner(&instantiated, visiting);
328 visiting.remove(name);
329 resolved
330 }
331 TypeExpr::Never => TypeExpr::Never,
332 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
333 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
334 TypeExpr::Owned(inner) => {
335 TypeExpr::Owned(Box::new(self.expand_alias_inner(inner, visiting)))
336 }
337 }
338 }
339
340 pub fn compile_public_type_schema_initializers(
344 program: &[SNode],
345 source_file: Option<String>,
346 ) -> Result<Option<Chunk>, CompileError> {
347 let mut compiler = Compiler::new();
348 compiler.collect_type_aliases(program);
349 compiler.chunk.source_file = source_file;
350 let mut emitted = false;
351 for sn in program {
352 let Node::TypeDecl {
353 name, is_pub: true, ..
354 } = peel_node(sn)
355 else {
356 continue;
357 };
358 if compiler.emit_schema_for_alias(name) {
359 compiler.emit_define_binding(name, false);
360 emitted = true;
361 }
362 }
363 if !emitted {
364 return Ok(None);
365 }
366 compiler.chunk.emit(Op::Nil, compiler.line);
367 compiler.chunk.emit(Op::Return, compiler.line);
368 super::ensure_chunk_addressable(
369 &compiler.chunk,
370 "the module type-schema initializer",
371 compiler.line,
372 )?;
373 Ok(Some(compiler.chunk))
374 }
375
376 pub(super) fn is_schema_guard(name: &str) -> bool {
380 matches!(
381 name,
382 "schema_is"
383 | "schema_expect"
384 | "schema_parse"
385 | "schema_check"
386 | "schema_report"
387 | "is_type"
388 | "json_validate"
389 )
390 }
391
392 pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
395 matches!(
396 &key.node,
397 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
398 if name == keyword
399 )
400 }
401
402 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
405 self.prepare_module_context(program);
408
409 for sn in program {
410 match &sn.node {
411 Node::ImportDecl { .. }
412 | Node::SelectiveImport { .. }
413 | Node::NamespaceImport { .. } => {
414 self.compile_node(sn)?;
415 }
416 _ => {}
417 }
418 }
419 let main = program
420 .iter()
421 .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
422 .or_else(|| {
423 program
424 .iter()
425 .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
426 });
427
428 let mut pipeline_emits_value = false;
432 if let Some(sn) = main {
433 self.compile_top_level_declarations(program)?;
434 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
435 self.compile_with_pipeline_captures(
436 program,
437 body,
438 extends.as_deref(),
439 |compiler| {
440 if let Some(parent_name) = extends {
441 compiler.compile_parent_pipeline(program, parent_name)?;
442 }
443 let saved = std::mem::replace(&mut compiler.module_level, false);
444 let result = compiler.compile_block(body);
445 compiler.module_level = saved;
446 result
447 },
448 )?;
449 pipeline_emits_value = true;
450 }
451 } else {
452 let top_level: Vec<&SNode> = program
454 .iter()
455 .filter(|sn| {
456 !matches!(
457 &sn.node,
458 Node::ImportDecl { .. }
459 | Node::SelectiveImport { .. }
460 | Node::NamespaceImport { .. }
461 )
462 })
463 .collect();
464 for sn in &top_level {
465 self.compile_discarded_stmt(sn)?;
466 }
467 if Self::has_top_level_fn_main(program) {
472 let harness_name = self.string_constant("harness");
473 self.chunk.emit_u16(Op::GetVar, harness_name, self.line);
474 self.emit_named_call("main", 1);
475 pipeline_emits_value = true;
476 }
477 }
478
479 self.drain_finallys_to_floor(0)?;
480 if !pipeline_emits_value {
481 self.chunk.emit(Op::Nil, self.line);
482 }
483 self.chunk.emit(Op::Return, self.line);
484 super::ensure_chunk_addressable(&self.chunk, "the program body", self.line)?;
485 Ok(self.chunk)
486 }
487
488 fn has_top_level_fn_main(program: &[SNode]) -> bool {
492 program
493 .iter()
494 .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
495 }
496
497 pub fn compile_named(
499 self,
500 program: &[SNode],
501 pipeline_name: &str,
502 ) -> Result<Chunk, CompileError> {
503 self.compile_named_inner(program, pipeline_name, false)
504 }
505
506 pub fn compile_named_with_param_globals(
513 self,
514 program: &[SNode],
515 pipeline_name: &str,
516 ) -> Result<Chunk, CompileError> {
517 self.compile_named_inner(program, pipeline_name, true)
518 }
519
520 fn compile_named_inner(
521 mut self,
522 program: &[SNode],
523 pipeline_name: &str,
524 bind_params_from_globals: bool,
525 ) -> Result<Chunk, CompileError> {
526 self.prepare_module_context(program);
527
528 for sn in program {
529 if matches!(
530 &sn.node,
531 Node::ImportDecl { .. }
532 | Node::SelectiveImport { .. }
533 | Node::NamespaceImport { .. }
534 ) {
535 self.compile_node(sn)?;
536 }
537 }
538 let target = program.iter().find(
539 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
540 );
541
542 let mut pipeline_emits_value = false;
543 if let Some(sn) = target {
544 self.compile_top_level_declarations(program)?;
545 if let Node::Pipeline {
546 name,
547 body,
548 extends,
549 params,
550 ..
551 } = peel_node(sn)
552 {
553 if bind_params_from_globals {
554 let callable = self.compile_pipeline_callable(
555 program,
556 name,
557 params,
558 body,
559 extends.as_deref(),
560 )?;
561 let function_index = self.chunk.functions.len();
562 self.chunk.functions.push(Arc::new(callable));
563 self.chunk
564 .emit_u16(Op::Closure, function_index as u16, self.line);
565 for param in params {
566 let index = self.string_constant(¶m.name);
567 self.chunk.emit_u16(Op::GetVar, index, self.line);
568 }
569 self.chunk.emit_u8(Op::Call, params.len() as u8, self.line);
570 pipeline_emits_value = true;
571 } else {
572 self.compile_with_pipeline_captures(
573 program,
574 body,
575 extends.as_deref(),
576 |compiler| {
577 if let Some(parent_name) = extends {
578 compiler.compile_parent_pipeline(program, parent_name)?;
579 }
580 let saved = std::mem::replace(&mut compiler.module_level, false);
581 let result = compiler.compile_block(body);
582 compiler.module_level = saved;
583 result
584 },
585 )?;
586 }
587 }
588 }
589
590 self.drain_finallys_to_floor(0)?;
591 if !pipeline_emits_value {
592 self.chunk.emit(Op::Nil, self.line);
593 }
594 self.chunk.emit(Op::Return, self.line);
595 super::ensure_chunk_addressable(&self.chunk, "the pipeline body", self.line)?;
596 Ok(self.chunk)
597 }
598
599 pub(super) fn emit_default_preamble(
604 &mut self,
605 params: &[TypedParam],
606 ) -> Result<(), CompileError> {
607 for (i, param) in params.iter().enumerate() {
608 if let Some(default_expr) = ¶m.default_value {
609 self.chunk.emit(Op::GetArgc, self.line);
610 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
611 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
612 self.chunk.emit(Op::GreaterEqual, self.line);
613 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
614 self.chunk.emit(Op::Pop, self.line);
616 let masked = self.mask_param_names(¶ms[i..]);
625 let result = self.compile_node(default_expr);
626 self.restore_param_names(masked);
627 result?;
628 self.emit_init_or_define_binding(¶m.name, false);
629 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
630 self.chunk.patch_jump(skip_jump);
631 self.chunk.emit(Op::Pop, self.line);
632 self.chunk.patch_jump(end_jump);
633 }
634 }
635 Ok(())
636 }
637
638 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
645 for (param_index, param) in params.iter().enumerate() {
646 if let Some(type_expr) = ¶m.type_expr {
647 let check_type = if param.rest {
648 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
649 } else {
650 type_expr.clone()
651 };
652
653 if let harn_parser::TypeExpr::Named(name) = &check_type {
654 if let Some(methods) = self.interface_methods.get(name).cloned() {
655 let fn_idx = self.string_constant("__assert_interface");
656 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
657 self.emit_get_binding(¶m.name);
658 let name_idx = self.string_constant(¶m.name);
659 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
660 let iface_idx = self.string_constant(name);
661 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
662 let methods_str = methods.join(",");
663 let methods_idx = self.owned_string_constant(methods_str);
664 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
665 self.chunk.emit_u8(Op::Call, 4, self.line);
666 self.chunk.emit(Op::Pop, self.line);
667 continue;
668 }
669 }
670
671 if param.default_value.is_some() {
672 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
673 self.emit_default_param_schema_check(param_index, param, &schema);
674 }
675 }
676 }
677 }
678 }
679
680 fn emit_default_param_schema_check(
681 &mut self,
682 param_index: usize,
683 param: &TypedParam,
684 schema: &VmValue,
685 ) {
686 self.chunk.emit(Op::GetArgc, self.line);
687 let threshold_idx = self
688 .chunk
689 .add_constant(Constant::Int((param_index + 1) as i64));
690 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
691 self.chunk.emit(Op::GreaterEqual, self.line);
692 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
693 self.chunk.emit(Op::Pop, self.line);
694 self.emit_schema_assert_call(param, schema);
695 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
696 self.chunk.patch_jump(supplied_jump);
697 self.chunk.emit(Op::Pop, self.line);
698 self.chunk.patch_jump(end_jump);
699 }
700
701 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
702 let fn_idx = self.string_constant("__assert_schema");
703 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
704 self.emit_get_binding(¶m.name);
705 let name_idx = self.string_constant(¶m.name);
706 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
707 self.emit_vm_value_literal(schema);
708 self.chunk.emit_u8(Op::Call, 3, self.line);
709 self.chunk.emit(Op::Pop, self.line);
710 }
711
712 pub(crate) fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
713 match type_expr {
714 harn_parser::TypeExpr::Named(name) => match name.as_str() {
715 "any" | "unknown" => Some(VmValue::dict(BTreeMap::<String, VmValue>::new())),
716 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
717 | "closure" | "bytes" => Some(VmValue::dict(BTreeMap::from([(
718 "type".to_string(),
719 VmValue::String(arcstr::ArcStr::from(name.as_str())),
720 )]))),
721 _ => None,
722 },
723 harn_parser::TypeExpr::Shape(fields) => {
724 let mut properties = BTreeMap::new();
725 let mut required = Vec::new();
726 for field in fields {
727 let mut field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
728 if field.optional {
729 field_schema = VmValue::dict(BTreeMap::from([(
730 "union".to_string(),
731 VmValue::List(std::sync::Arc::new(vec![
732 field_schema,
733 VmValue::dict(BTreeMap::from([(
734 "type".to_string(),
735 VmValue::String(arcstr::ArcStr::from("nil")),
736 )])),
737 ])),
738 )]));
739 }
740 properties.insert(field.name.clone(), field_schema);
741 if !field.optional {
742 required.push(VmValue::String(arcstr::ArcStr::from(field.name.as_str())));
743 }
744 }
745 let mut out = BTreeMap::new();
746 out.put_str("type", "dict");
747 out.insert("properties".to_string(), VmValue::dict(properties));
748 if !required.is_empty() {
749 out.insert(
750 "required".to_string(),
751 VmValue::List(std::sync::Arc::new(required)),
752 );
753 }
754 Some(VmValue::dict(out))
755 }
756 harn_parser::TypeExpr::OpenShape { .. } => None,
757 harn_parser::TypeExpr::List(inner) => {
758 let mut out = BTreeMap::new();
759 out.put_str("type", "list");
760 let item_schema = Self::type_expr_to_schema_value(inner)?;
761 out.insert("items".to_string(), item_schema);
762 Some(VmValue::dict(out))
763 }
764 harn_parser::TypeExpr::DictType(key, value) => {
765 let mut out = BTreeMap::new();
766 out.put_str("type", "dict");
767 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
768 let value_schema = Self::type_expr_to_schema_value(value)?;
769 out.insert("additional_properties".to_string(), value_schema);
770 }
771 Some(VmValue::dict(out))
772 }
773 harn_parser::TypeExpr::Union(members) => {
774 if !members.is_empty()
779 && members
780 .iter()
781 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
782 {
783 let values = members
784 .iter()
785 .map(|m| match m {
786 harn_parser::TypeExpr::LitString(s) => {
787 VmValue::String(arcstr::ArcStr::from(s.as_str()))
788 }
789 _ => unreachable!(),
790 })
791 .collect::<Vec<_>>();
792 return Some(VmValue::dict(BTreeMap::from([
793 (
794 "type".to_string(),
795 VmValue::String(arcstr::ArcStr::from("string")),
796 ),
797 (
798 "enum".to_string(),
799 VmValue::List(std::sync::Arc::new(values)),
800 ),
801 ])));
802 }
803 if !members.is_empty()
804 && members
805 .iter()
806 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
807 {
808 let values = members
809 .iter()
810 .map(|m| match m {
811 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
812 _ => unreachable!(),
813 })
814 .collect::<Vec<_>>();
815 return Some(VmValue::dict(BTreeMap::from([
816 (
817 "type".to_string(),
818 VmValue::String(arcstr::ArcStr::from("int")),
819 ),
820 (
821 "enum".to_string(),
822 VmValue::List(std::sync::Arc::new(values)),
823 ),
824 ])));
825 }
826 let branches = members
827 .iter()
828 .map(Self::type_expr_to_schema_value)
829 .collect::<Option<Vec<_>>>()?;
830 if branches.is_empty() {
831 None
832 } else {
833 Some(VmValue::dict(BTreeMap::from([(
834 "union".to_string(),
835 VmValue::List(std::sync::Arc::new(branches)),
836 )])))
837 }
838 }
839 harn_parser::TypeExpr::Intersection(members) => {
840 let branches = members
844 .iter()
845 .map(Self::type_expr_to_schema_value)
846 .collect::<Option<Vec<_>>>()?;
847 if branches.is_empty() {
848 None
849 } else {
850 Some(VmValue::dict(BTreeMap::from([(
851 "all_of".to_string(),
852 VmValue::List(std::sync::Arc::new(branches)),
853 )])))
854 }
855 }
856 harn_parser::TypeExpr::FnType { .. } => Some(VmValue::dict(BTreeMap::from([(
857 "type".to_string(),
858 VmValue::String(arcstr::ArcStr::from("closure")),
859 )]))),
860 harn_parser::TypeExpr::Applied { .. } => None,
861 harn_parser::TypeExpr::Iter(_)
862 | harn_parser::TypeExpr::Generator(_)
863 | harn_parser::TypeExpr::Stream(_) => None,
864 harn_parser::TypeExpr::Never => None,
865 harn_parser::TypeExpr::LitString(s) => Some(VmValue::dict(BTreeMap::from([
866 (
867 "type".to_string(),
868 VmValue::String(arcstr::ArcStr::from("string")),
869 ),
870 (
871 "const".to_string(),
872 VmValue::String(arcstr::ArcStr::from(s.as_str())),
873 ),
874 ]))),
875 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::dict(BTreeMap::from([
876 (
877 "type".to_string(),
878 VmValue::String(arcstr::ArcStr::from("int")),
879 ),
880 ("const".to_string(), VmValue::Int(*v)),
881 ]))),
882 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
883 }
884 }
885
886 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
887 match value {
888 VmValue::String(text) => {
889 let idx = self.string_constant(text);
890 self.chunk.emit_u16(Op::Constant, idx, self.line);
891 }
892 VmValue::Int(number) => {
893 let idx = self.chunk.add_constant(Constant::Int(*number));
894 self.chunk.emit_u16(Op::Constant, idx, self.line);
895 }
896 VmValue::Float(number) => {
897 let idx = self.chunk.add_constant(Constant::Float(*number));
898 self.chunk.emit_u16(Op::Constant, idx, self.line);
899 }
900 VmValue::Bool(value) => {
901 let idx = self.chunk.add_constant(Constant::Bool(*value));
902 self.chunk.emit_u16(Op::Constant, idx, self.line);
903 }
904 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
905 VmValue::List(items) => {
906 for item in items.iter() {
907 self.emit_vm_value_literal(item);
908 }
909 self.chunk
910 .emit_u16(Op::BuildList, items.len() as u16, self.line);
911 }
912 VmValue::Dict(entries) => {
913 for (key, item) in entries.iter() {
914 let key_idx = self.string_constant(key);
915 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
916 self.emit_vm_value_literal(item);
917 }
918 self.chunk
919 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
920 }
921 _ => {}
922 }
923 }
924
925 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
927 let hi = (type_name_idx >> 8) as u8;
928 let lo = type_name_idx as u8;
929 self.chunk.code.push(hi);
930 self.chunk.code.push(lo);
931 self.chunk.lines.push(self.line);
932 self.chunk.columns.push(self.column);
933 self.chunk.lines.push(self.line);
934 self.chunk.columns.push(self.column);
935 }
936
937 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
939 if body.is_empty() {
940 self.chunk.emit(Op::Nil, self.line);
941 } else {
942 self.compile_scoped_block(body)?;
943 }
944 Ok(())
945 }
946
947 pub(super) fn compile_catch_binding(
949 &mut self,
950 error_var: &Option<String>,
951 ) -> Result<(), CompileError> {
952 if let Some(var_name) = error_var {
953 self.emit_define_binding(var_name, false);
954 } else {
955 self.chunk.emit(Op::Pop, self.line);
956 }
957 Ok(())
958 }
959
960 pub(super) fn compile_finally_inline(
967 &mut self,
968 finally_body: &[SNode],
969 ) -> Result<(), CompileError> {
970 if !finally_body.is_empty() {
971 self.compile_scoped_block(finally_body)?;
972 self.chunk.emit(Op::Pop, self.line);
973 }
974 Ok(())
975 }
976
977 pub(super) fn has_pending_finally_until_barrier(&self) -> bool {
981 self.finally_bodies
982 .iter()
983 .rev()
984 .take_while(|entry| !matches!(entry, FinallyEntry::CatchBarrier))
985 .any(|entry| matches!(entry, FinallyEntry::Finally(_)))
986 }
987
988 pub(super) fn has_pending_finally(&self) -> bool {
990 self.finally_bodies
991 .iter()
992 .any(|e| matches!(e, FinallyEntry::Finally(_)))
993 }
994
995 pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
1004 self.temp_counter += 1;
1005 let temp_name = format!("__finally_err_{}__", self.temp_counter);
1006 self.emit_define_binding(&temp_name, true);
1007 self.emit_get_binding(&temp_name);
1008 self.chunk.emit(Op::Throw, self.line);
1009 Ok(())
1010 }
1011
1012 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
1013 for param in params {
1014 self.define_local_slot(¶m.name, false);
1015 }
1016 }
1017
1018 fn mask_param_names(&mut self, params: &[TypedParam]) -> Vec<(String, super::LocalBinding)> {
1024 let mut removed = Vec::new();
1025 if let Some(scope) = self.local_scopes.last_mut() {
1026 for param in params {
1027 if let Some(binding) = scope.remove(¶m.name) {
1028 removed.push((param.name.clone(), binding));
1029 }
1030 }
1031 }
1032 removed
1033 }
1034
1035 fn restore_param_names(&mut self, removed: Vec<(String, super::LocalBinding)>) {
1037 if let Some(scope) = self.local_scopes.last_mut() {
1038 for (name, binding) in removed {
1039 scope.insert(name, binding);
1040 }
1041 }
1042 }
1043
1044 pub(super) fn seed_captured_idents(&mut self, body: &[SNode]) {
1049 let match_patterns = self.lexical_match_pattern_catalog();
1050 self.captured_bindings =
1051 harn_parser::lexical::captured_bindings_in_nested_callables(body, &match_patterns);
1052 }
1053
1054 fn seed_module_captured_idents(&mut self, body: &[SNode]) {
1055 let match_patterns = self.lexical_match_pattern_catalog();
1056 self.captured_bindings =
1057 harn_parser::lexical::captured_bindings_in_compiled_module(body, &match_patterns);
1058 }
1059
1060 pub(super) fn lexical_match_pattern_catalog(
1061 &self,
1062 ) -> harn_parser::lexical::MatchPatternCatalog {
1063 if self.imported_enum_candidates.is_empty() {
1064 return harn_parser::lexical::MatchPatternCatalog::new(
1065 &self.enum_names,
1066 &self.enum_variant_owners,
1067 );
1068 }
1069 let mut enum_names = self.enum_names.clone();
1070 enum_names.extend(self.imported_enum_candidates.iter().cloned());
1071 harn_parser::lexical::MatchPatternCatalog::new(&enum_names, &self.enum_variant_owners)
1072 }
1073
1074 pub(super) fn begin_scope(&mut self) {
1075 self.chunk.emit(Op::PushScope, self.line);
1076 self.scope_depth += 1;
1077 let enum_catalog = self.enum_catalog_snapshot();
1078 self.enum_catalog_scopes.push(enum_catalog);
1079 self.type_scopes.push(std::collections::HashMap::new());
1080 self.local_scopes.push(std::collections::HashMap::new());
1081 }
1082
1083 pub(super) fn end_scope(&mut self) {
1084 if self.scope_depth > 0 {
1085 self.chunk.emit(Op::PopScope, self.line);
1086 self.scope_depth -= 1;
1087 if let Some(snapshot) = self.enum_catalog_scopes.pop() {
1088 self.restore_enum_catalog(snapshot);
1089 }
1090 self.type_scopes.pop();
1091 self.local_scopes.pop();
1092 }
1093 }
1094
1095 pub(super) fn emit_scope_unwind_to(&mut self, target_depth: usize) {
1098 for _ in target_depth..self.scope_depth {
1099 self.chunk.emit(Op::PopScope, self.line);
1100 }
1101 }
1102
1103 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1104 self.begin_scope();
1105 let finally_floor = self.finally_bodies.len();
1106 if stmts.is_empty() {
1107 self.chunk.emit(Op::Nil, self.line);
1108 } else {
1109 self.compile_block(stmts)?;
1110 }
1111 self.drain_finallys_to_floor(finally_floor)?;
1112 self.end_scope();
1113 Ok(())
1114 }
1115
1116 pub(super) fn compile_scoped_statements(
1117 &mut self,
1118 stmts: &[SNode],
1119 ) -> Result<(), CompileError> {
1120 self.begin_scope();
1121 self.record_monomorphic_var_bindings(stmts);
1122 let finally_floor = self.finally_bodies.len();
1123 for sn in stmts {
1124 self.compile_discarded_stmt(sn)?;
1125 }
1126 self.drain_finallys_to_floor(finally_floor)?;
1127 self.end_scope();
1128 Ok(())
1129 }
1130
1131 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
1136 while self.finally_bodies.len() > floor {
1137 let entry = self.finally_bodies.pop().expect("non-empty by guard");
1138 if let FinallyEntry::Finally(body) = entry {
1139 self.compile_finally_inline(&body)?;
1140 }
1141 }
1142 Ok(())
1143 }
1144
1145 pub(super) fn run_pending_finallys_for_transfer(
1158 &mut self,
1159 floor: usize,
1160 ) -> Result<(), CompileError> {
1161 if self.finally_bodies.len() <= floor {
1162 return Ok(());
1163 }
1164 let saved = self.finally_bodies[floor..].to_vec();
1165 let result = self.drain_finallys_to_floor(floor);
1166 self.finally_bodies.extend(saved);
1167 result
1168 }
1169
1170 pub(super) fn run_pending_finallys_until_barrier(&mut self) -> Result<(), CompileError> {
1175 let floor = self
1176 .finally_bodies
1177 .iter()
1178 .rposition(|e| matches!(e, FinallyEntry::CatchBarrier))
1179 .map(|i| i + 1)
1180 .unwrap_or(0);
1181 self.run_pending_finallys_for_transfer(floor)
1182 }
1183
1184 pub(super) fn maybe_register_owned_drop(
1189 &mut self,
1190 pattern: &harn_parser::BindingPattern,
1191 type_ann: Option<&TypeExpr>,
1192 span: harn_lexer::Span,
1193 ) {
1194 let Some(ty) = type_ann else {
1200 return;
1201 };
1202 if !matches!(ty, TypeExpr::Owned(_)) {
1203 return;
1204 }
1205 let harn_parser::BindingPattern::Identifier(name) = pattern else {
1206 return;
1207 };
1208 if harn_parser::is_discard_name(name) {
1209 return;
1210 }
1211 let call = harn_parser::spanned(
1212 Node::FunctionCall {
1213 name: "drop".to_string(),
1214 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
1215 type_args: Vec::new(),
1216 },
1217 span,
1218 );
1219 self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
1220 }
1221
1222 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
1238 #[cfg(debug_assertions)]
1239 let probe = self.chunk.balance_probe();
1240 self.compile_node(sn)?;
1241 #[allow(unused_mut)]
1242 let mut produces = Self::produces_value(&sn.node);
1243 #[cfg(test)]
1247 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
1248 produces = forced;
1249 }
1250 #[cfg(debug_assertions)]
1251 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1252 let expected = i32::from(produces);
1253 debug_assert_eq!(
1254 delta, expected,
1255 "operand-stack imbalance at line {}: produces_value={produces} but the \
1256 node's emitted bytecode netted {delta} (expected {expected}). A \
1257 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1258 node: {:?}",
1259 self.line, sn.node,
1260 );
1261 }
1262 if produces {
1263 self.chunk.emit(Op::Pop, self.line);
1264 }
1265 Ok(())
1266 }
1267
1268 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1269 self.record_monomorphic_var_bindings(stmts);
1270 for (i, snode) in stmts.iter().enumerate() {
1271 if i == stmts.len() - 1 {
1272 self.compile_node(snode)?;
1276 if !Self::produces_value(&snode.node) {
1277 self.chunk.emit(Op::Nil, self.line);
1278 }
1279 } else {
1280 self.compile_discarded_stmt(snode)?;
1281 }
1282 }
1283 Ok(())
1284 }
1285
1286 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1288 self.begin_scope();
1289 let finally_floor = self.finally_bodies.len();
1290 if body.is_empty() {
1291 self.chunk.emit(Op::Nil, self.line);
1292 } else {
1293 self.compile_block(body)?;
1294 if !Self::produces_value(&body.last().unwrap().node) {
1295 self.chunk.emit(Op::Nil, self.line);
1296 }
1297 }
1298 self.drain_finallys_to_floor(finally_floor)?;
1299 self.end_scope();
1300 Ok(())
1301 }
1302
1303 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1305 match op {
1306 "+" => self.chunk.emit(Op::Add, self.line),
1307 "-" => self.chunk.emit(Op::Sub, self.line),
1308 "*" => self.chunk.emit(Op::Mul, self.line),
1309 "/" => self.chunk.emit(Op::Div, self.line),
1310 "%" => self.chunk.emit(Op::Mod, self.line),
1311 _ => {
1312 return Err(CompileError {
1313 message: format!("Unknown compound operator: {op}"),
1314 line: self.line,
1315 })
1316 }
1317 }
1318 Ok(())
1319 }
1320
1321 pub(super) fn compile_top_level_declarations(
1322 &mut self,
1323 program: &[SNode],
1324 ) -> Result<(), CompileError> {
1325 for sn in program {
1337 if !harn_parser::lexical::is_deferred_module_declaration(sn) {
1338 self.compile_discarded_stmt(sn)?;
1339 }
1340 }
1341 for sn in program {
1347 let inner_kind = match &sn.node {
1348 Node::AttributedDecl { inner, .. } => &inner.node,
1349 other => other,
1350 };
1351 match inner_kind {
1352 Node::EvalPackDecl {
1353 binding_name,
1354 pack_id,
1355 fields,
1356 body,
1357 summarize,
1358 ..
1359 } => {
1360 self.compile_eval_pack_decl(
1361 binding_name,
1362 pack_id,
1363 fields,
1364 body,
1365 summarize,
1366 false,
1367 )?;
1368 }
1369 Node::FnDecl { .. }
1370 | Node::ToolDecl { .. }
1371 | Node::SkillDecl { .. }
1372 | Node::ImplBlock { .. }
1373 | Node::StructDecl { .. }
1374 | Node::EnumDecl { .. }
1375 | Node::InterfaceDecl { .. } => {
1376 self.compile_node(sn)?;
1377 }
1378 Node::TypeDecl { .. } => {}
1379 _ => {}
1380 }
1381 }
1382 Ok(())
1383 }
1384
1385 pub fn compile_fn_body(
1398 &mut self,
1399 type_params: &[harn_parser::TypeParam],
1400 params: &[TypedParam],
1401 body: &[SNode],
1402 source_file: Option<String>,
1403 ) -> Result<CompiledFunction, CompileError> {
1404 let mut fn_compiler = self.nested_body();
1405 fn_compiler.enum_names = self.enum_names.clone();
1406 fn_compiler.enum_variant_owners = self.enum_variant_owners.clone();
1407 fn_compiler.imported_enum_candidates = self.imported_enum_candidates.clone();
1408 fn_compiler.imported_enum_candidates_authoritative =
1409 self.imported_enum_candidates_authoritative;
1410 fn_compiler.interface_methods = self.interface_methods.clone();
1411 fn_compiler.type_aliases = self.type_aliases.clone();
1412 fn_compiler.struct_layouts = self.struct_layouts.clone();
1413 fn_compiler.declare_param_slots(params);
1414 fn_compiler.record_param_types(params);
1415 fn_compiler.emit_default_preamble(params)?;
1416 fn_compiler.emit_type_checks(params);
1417 let is_gen = body_contains_yield(body);
1418 fn_compiler.seed_captured_idents(body);
1419 fn_compiler.compile_block(body)?;
1420 fn_compiler.chunk.emit(Op::Nil, 0);
1421 fn_compiler.chunk.emit(Op::Return, 0);
1422 fn_compiler.chunk.source_file = source_file;
1423 let param_slots = fn_compiler.compile_param_slots(params);
1424 let has_runtime_type_checks =
1425 CompiledFunction::has_runtime_type_checks_for_params(¶m_slots);
1426 super::ensure_chunk_addressable(&fn_compiler.chunk, "function body", self.line)?;
1427 Ok(CompiledFunction {
1428 name: String::new(),
1429 type_params: type_params.iter().map(|param| param.name.clone()).collect(),
1430 nominal_type_names: fn_compiler.nominal_type_names(),
1431 params: param_slots,
1432 default_start: TypedParam::default_start(params),
1433 chunk: Arc::new(fn_compiler.chunk),
1434 is_generator: is_gen,
1435 is_stream: false,
1436 has_rest_param: false,
1437 has_runtime_type_checks,
1438 })
1439 }
1440
1441 pub(super) fn produces_value(node: &Node) -> bool {
1443 harn_parser::node_produces_value(node)
1444 }
1445}
1446
1447impl Default for Compiler {
1448 fn default() -> Self {
1449 Self::new()
1450 }
1451}