1use harn_parser::{substitute_type_expr, Node, SNode, ShapeField, TypeExpr, TypedParam};
2use std::collections::BTreeMap;
3
4use crate::chunk::{Chunk, Constant, Op};
5use crate::value::VmDictExt;
6use crate::value::VmValue;
7
8use super::error::CompileError;
9use super::{peel_node, Compiler, CompilerOptions, FinallyEntry};
10
11#[cfg(test)]
12thread_local! {
13 pub(super) static FORCE_DISCARDED_PRODUCES_VALUE: std::cell::Cell<Option<bool>> =
20 const { std::cell::Cell::new(None) };
21}
22
23impl Compiler {
24 pub fn new() -> Self {
25 Self::with_options(CompilerOptions::from_env())
26 }
27
28 pub fn new_trusted_host_dispatch() -> Self {
33 Self::with_options(CompilerOptions::privileged_wire())
34 }
35
36 pub fn with_imported_enum_candidates(
44 mut self,
45 candidates: impl IntoIterator<Item = String>,
46 ) -> Self {
47 self.add_imported_enum_candidates(candidates);
48 self
49 }
50
51 #[doc(hidden)]
57 pub fn prepare_module_context(&mut self, program: &[SNode]) {
58 self.collect_module_enum_catalog(program);
59 if self.enum_names.insert("Result".to_string()) {
60 Self::seed_builtin_variant_owners(&mut self.enum_variant_owners);
61 }
62 Self::collect_struct_layouts(program, &mut self.struct_layouts);
63 Self::collect_interface_methods(program, &mut self.interface_methods);
64 self.collect_type_aliases(program);
65 self.collect_imported_enum_candidates(program);
66 self.collect_source_callable_names(program);
67 self.namespace_import_demands = harn_parser::namespace_import_demands(program);
68 self.seed_module_captured_idents(program);
72 }
73
74 #[doc(hidden)]
75 pub fn add_imported_enum_candidates(&mut self, candidates: impl IntoIterator<Item = String>) {
76 self.imported_enum_candidates_authoritative = true;
77 self.imported_enum_candidates.extend(candidates);
78 }
79
80 #[doc(hidden)]
85 pub fn compile_module_init(
86 mut self,
87 context: &[SNode],
88 init_nodes: &[SNode],
89 imported_enum_candidates: &[String],
90 ) -> Result<Chunk, CompileError> {
91 self.add_imported_enum_candidates(imported_enum_candidates.iter().cloned());
92 self.prepare_module_context(context);
93 self.compile_top_level_declarations(init_nodes)?;
94 self.chunk.emit(Op::Nil, self.line);
95 self.chunk.emit(Op::Return, self.line);
96 super::ensure_chunk_addressable(&self.chunk, "the module initialization body", self.line)?;
97 Ok(self.chunk)
98 }
99
100 pub fn with_options(options: CompilerOptions) -> Self {
101 Self {
106 options,
107 chunk: Chunk::new(),
108 line: 1,
109 column: 1,
110 enum_names: std::collections::HashSet::new(),
111 enum_variant_owners: std::collections::HashMap::new(),
112 imported_enum_candidates: std::collections::HashSet::new(),
113 imported_enum_candidates_authoritative: false,
114 source_callable_names: std::collections::HashSet::new(),
115 predeclared_enum_declarations: std::collections::HashSet::new(),
116 enum_catalog_scopes: Vec::new(),
117 struct_layouts: std::collections::HashMap::new(),
118 interface_methods: std::collections::HashMap::new(),
119 loop_stack: Vec::new(),
120 handler_depth: 0,
121 finally_bodies: Vec::new(),
122 temp_counter: 0,
123 scope_depth: 0,
124 type_aliases: std::collections::HashMap::new(),
125 type_scopes: vec![std::collections::HashMap::new()],
126 monomorphic_bindings: std::collections::HashSet::new(),
127 string_constants: std::collections::HashMap::new(),
128 local_scopes: vec![std::collections::HashMap::new()],
129 module_level: true,
130 captured_bindings: std::collections::HashSet::new(),
131 namespace_import_demands: std::collections::BTreeMap::new(),
132 }
133 }
134
135 pub(super) fn for_nested_body(options: CompilerOptions) -> Self {
139 let mut c = Self::with_options(options);
140 c.module_level = false;
141 c
142 }
143
144 pub(super) fn nested_body(&self) -> Self {
145 let mut nested = Self::for_nested_body(self.options);
146 nested.source_callable_names = self.source_callable_names.clone();
147 nested
148 }
149
150 pub(super) fn nominal_type_names(&self) -> Vec<String> {
151 let mut names: Vec<String> = self
152 .struct_layouts
153 .keys()
154 .chain(self.enum_names.iter())
155 .cloned()
156 .collect();
157 names.sort();
158 names.dedup();
159 names
160 }
161
162 pub(super) fn string_constant(&mut self, value: &str) -> u16 {
163 if let Some(idx) = self.string_constants.get(value) {
164 return *idx;
165 }
166 let owned = value.to_string();
167 let idx = self.chunk.add_constant(Constant::String(owned.clone()));
168 self.string_constants.insert(owned, idx);
169 idx
170 }
171
172 pub(super) fn owned_string_constant(&mut self, value: String) -> u16 {
173 if let Some(idx) = self.string_constants.get(value.as_str()) {
174 return *idx;
175 }
176 let idx = self.chunk.add_constant(Constant::String(value.clone()));
177 self.string_constants.insert(value, idx);
178 idx
179 }
180
181 #[doc(hidden)]
185 pub fn collect_type_aliases(&mut self, program: &[SNode]) {
186 for sn in program {
187 match peel_node(sn) {
188 Node::SelectiveImport { names, .. } => {
189 for name in names {
190 self.type_aliases.entry(name.clone()).or_insert_with(|| {
191 super::TypeAliasDefinition {
192 type_params: Vec::new(),
193 body: None,
194 }
195 });
196 }
197 }
198 Node::TypeDecl {
199 name,
200 type_expr,
201 type_params,
202 is_pub: _,
203 } => {
204 self.type_aliases.insert(
205 name.clone(),
206 super::TypeAliasDefinition {
207 type_params: type_params.clone(),
208 body: Some(type_expr.clone()),
209 },
210 );
211 }
212 _ => {}
213 }
214 }
215 }
216
217 #[doc(hidden)]
227 pub fn expand_alias(&self, ty: &TypeExpr) -> TypeExpr {
228 let mut visiting = std::collections::HashSet::new();
229 self.expand_alias_inner(ty, &mut visiting)
230 }
231
232 fn expand_alias_inner(
233 &self,
234 ty: &TypeExpr,
235 visiting: &mut std::collections::HashSet<String>,
236 ) -> TypeExpr {
237 match ty {
238 TypeExpr::Named(name) => {
239 if let Some(target) = self
240 .type_aliases
241 .get(name)
242 .filter(|alias| alias.type_params.is_empty() && alias.body.is_some())
243 {
244 if !visiting.insert(name.clone()) {
245 return TypeExpr::Named(name.clone());
246 }
247 let resolved = self.expand_alias_inner(target.body.as_ref().unwrap(), visiting);
248 visiting.remove(name);
249 resolved
250 } else {
251 TypeExpr::Named(name.clone())
252 }
253 }
254 TypeExpr::Union(types) => TypeExpr::Union(
255 types
256 .iter()
257 .map(|t| self.expand_alias_inner(t, visiting))
258 .collect(),
259 ),
260 TypeExpr::Intersection(types) => TypeExpr::Intersection(
261 types
262 .iter()
263 .map(|t| self.expand_alias_inner(t, visiting))
264 .collect(),
265 ),
266 TypeExpr::Shape(fields) => TypeExpr::Shape(
267 fields
268 .iter()
269 .map(|field| ShapeField {
270 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
271 ..field.clone()
272 })
273 .collect(),
274 ),
275 TypeExpr::OpenShape { fields, rests } => TypeExpr::OpenShape {
276 fields: fields
277 .iter()
278 .map(|field| ShapeField {
279 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
280 ..field.clone()
281 })
282 .collect(),
283 rests: rests
284 .iter()
285 .map(|r| self.expand_alias_inner(r, visiting))
286 .collect(),
287 },
288 TypeExpr::List(inner) => {
289 TypeExpr::List(Box::new(self.expand_alias_inner(inner, visiting)))
290 }
291 TypeExpr::Tuple(elements) => TypeExpr::Tuple(
292 elements
293 .iter()
294 .map(|element| self.expand_alias_inner(element, visiting))
295 .collect(),
296 ),
297 TypeExpr::Iter(inner) => {
298 TypeExpr::Iter(Box::new(self.expand_alias_inner(inner, visiting)))
299 }
300 TypeExpr::Generator(inner) => {
301 TypeExpr::Generator(Box::new(self.expand_alias_inner(inner, visiting)))
302 }
303 TypeExpr::Stream(inner) => {
304 TypeExpr::Stream(Box::new(self.expand_alias_inner(inner, visiting)))
305 }
306 TypeExpr::DictType(k, v) => TypeExpr::DictType(
307 Box::new(self.expand_alias_inner(k, visiting)),
308 Box::new(self.expand_alias_inner(v, visiting)),
309 ),
310 TypeExpr::FnType {
311 params,
312 return_type,
313 } => TypeExpr::FnType {
314 params: params
315 .iter()
316 .map(|p| self.expand_alias_inner(p, visiting))
317 .collect(),
318 return_type: Box::new(self.expand_alias_inner(return_type, visiting)),
319 },
320 TypeExpr::Applied { name, args } => {
321 let args = args
322 .iter()
323 .map(|arg| self.expand_alias_inner(arg, visiting))
324 .collect::<Vec<_>>();
325 let Some(alias) = self.type_aliases.get(name) else {
326 return TypeExpr::Applied {
327 name: name.clone(),
328 args,
329 };
330 };
331 let Some(body) = alias.body.as_ref() else {
332 return TypeExpr::Applied {
333 name: name.clone(),
334 args,
335 };
336 };
337 if alias.type_params.len() != args.len() || !visiting.insert(name.clone()) {
338 return TypeExpr::Applied {
339 name: name.clone(),
340 args,
341 };
342 }
343 let bindings = alias
344 .type_params
345 .iter()
346 .zip(args.iter().cloned())
347 .map(|(param, arg)| (param.name.clone(), arg))
348 .collect();
349 let instantiated = substitute_type_expr(body, &bindings);
350 let resolved = self.expand_alias_inner(&instantiated, visiting);
351 visiting.remove(name);
352 resolved
353 }
354 TypeExpr::Never => TypeExpr::Never,
355 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
356 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
357 TypeExpr::Owned(inner) => {
358 TypeExpr::Owned(Box::new(self.expand_alias_inner(inner, visiting)))
359 }
360 }
361 }
362
363 pub fn compile_public_type_schema_initializers(
370 program: &[SNode],
371 source_file: Option<String>,
372 ) -> Result<Vec<Chunk>, CompileError> {
373 Self::compile_selected_public_type_schema_initializers(program, source_file, None)
374 }
375
376 pub fn compile_selected_public_type_schema_initializers(
380 program: &[SNode],
381 source_file: Option<String>,
382 selected_names: Option<&std::collections::BTreeSet<String>>,
383 ) -> Result<Vec<Chunk>, CompileError> {
384 let mut compiler = Compiler::new();
385 compiler.collect_type_aliases(program);
386 let mut chunks = Vec::new();
387 for sn in program {
388 let Node::TypeDecl {
389 name, is_pub: true, ..
390 } = peel_node(sn)
391 else {
392 continue;
393 };
394 if selected_names.is_some_and(|selected| !selected.contains(name)) {
395 continue;
396 }
397 compiler.chunk = Chunk::new();
398 compiler.string_constants.clear();
399 compiler.chunk.source_file.clone_from(&source_file);
400 if compiler.emit_schema_for_alias(name) {
401 compiler.emit_define_binding(name, false);
402 compiler.chunk.emit(Op::Nil, compiler.line);
403 compiler.chunk.emit(Op::Return, compiler.line);
404 super::ensure_chunk_addressable(
405 &compiler.chunk,
406 &format!("the public type-schema initializer for `{name}`"),
407 compiler.line,
408 )?;
409 chunks.push(std::mem::take(&mut compiler.chunk));
410 }
411 }
412 Ok(chunks)
413 }
414
415 pub(super) fn is_schema_guard(name: &str) -> bool {
419 matches!(
420 name,
421 "schema_is"
422 | "schema_expect"
423 | "schema_parse"
424 | "schema_check"
425 | "schema_report"
426 | "is_type"
427 | "json_validate"
428 )
429 }
430
431 pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
434 matches!(
435 &key.node,
436 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
437 if name == keyword
438 )
439 }
440
441 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
444 self.prepare_module_context(program);
447
448 for sn in program {
449 match &sn.node {
450 Node::ImportDecl { .. }
451 | Node::SelectiveImport { .. }
452 | Node::NamespaceImport { .. } => {
453 self.compile_node(sn)?;
454 }
455 _ => {}
456 }
457 }
458 let main = program
459 .iter()
460 .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
461 .or_else(|| {
462 program
463 .iter()
464 .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
465 });
466
467 let mut pipeline_emits_value = false;
471 if let Some(sn) = main {
472 self.compile_top_level_declarations(program)?;
473 if let Node::Pipeline {
474 params,
475 body,
476 extends,
477 ..
478 } = peel_node(sn)
479 {
480 self.compile_with_pipeline_captures(
481 program,
482 body,
483 extends.as_deref(),
484 |compiler| {
485 let saved = std::mem::replace(&mut compiler.module_level, false);
486 if let Some(harness) = params.first().filter(|param| {
487 matches!(
488 param.type_expr.as_ref(),
489 Some(TypeExpr::Named(name)) if name == "Harness"
490 )
491 }) {
492 compiler.chunk.emit(Op::RootHarness, compiler.line);
493 compiler.emit_define_binding(&harness.name, false);
494 }
495 if let Some(parent_name) = extends {
496 compiler.compile_parent_pipeline(program, parent_name)?;
497 }
498 let result = compiler.compile_block(body);
499 compiler.module_level = saved;
500 result
501 },
502 )?;
503 pipeline_emits_value = true;
504 }
505 } else {
506 let top_level: Vec<&SNode> = program
508 .iter()
509 .filter(|sn| {
510 !matches!(
511 &sn.node,
512 Node::ImportDecl { .. }
513 | Node::SelectiveImport { .. }
514 | Node::NamespaceImport { .. }
515 )
516 })
517 .collect();
518 for sn in &top_level {
519 self.compile_discarded_stmt(sn)?;
520 }
521 if Self::has_top_level_fn_main(program) {
526 self.chunk.emit(Op::RootHarness, self.line);
527 self.emit_named_call("main", 1);
528 pipeline_emits_value = true;
529 }
530 }
531
532 self.drain_finallys_to_floor(0)?;
533 if !pipeline_emits_value {
534 self.chunk.emit(Op::Nil, self.line);
535 }
536 self.chunk.emit(Op::Return, self.line);
537 super::ensure_chunk_addressable(&self.chunk, "the program body", self.line)?;
538 Ok(self.chunk)
539 }
540
541 fn has_top_level_fn_main(program: &[SNode]) -> bool {
545 program
546 .iter()
547 .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
548 }
549
550 pub fn compile_named(
552 self,
553 program: &[SNode],
554 pipeline_name: &str,
555 ) -> Result<Chunk, CompileError> {
556 self.compile_named_inner(program, pipeline_name)
557 }
558
559 fn compile_named_inner(
560 mut self,
561 program: &[SNode],
562 pipeline_name: &str,
563 ) -> Result<Chunk, CompileError> {
564 self.prepare_module_context(program);
565
566 for sn in program {
567 if matches!(
568 &sn.node,
569 Node::ImportDecl { .. }
570 | Node::SelectiveImport { .. }
571 | Node::NamespaceImport { .. }
572 ) {
573 self.compile_node(sn)?;
574 }
575 }
576 let target = program.iter().find(
577 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
578 );
579
580 if let Some(sn) = target {
581 self.compile_top_level_declarations(program)?;
582 if let Node::Pipeline {
583 body,
584 extends,
585 params,
586 ..
587 } = peel_node(sn)
588 {
589 self.compile_with_pipeline_captures(
590 program,
591 body,
592 extends.as_deref(),
593 |compiler| {
594 let saved = std::mem::replace(&mut compiler.module_level, false);
595 if let Some(harness) = params.first().filter(|param| {
596 matches!(
597 param.type_expr.as_ref(),
598 Some(TypeExpr::Named(name)) if name == "Harness"
599 )
600 }) {
601 compiler.chunk.emit(Op::RootHarness, compiler.line);
602 compiler.emit_define_binding(&harness.name, false);
603 }
604 if let Some(parent_name) = extends {
605 compiler.compile_parent_pipeline(program, parent_name)?;
606 }
607 let result = compiler.compile_block(body);
608 compiler.module_level = saved;
609 result
610 },
611 )?;
612 }
613 }
614
615 self.drain_finallys_to_floor(0)?;
616 self.chunk.emit(Op::Nil, self.line);
617 self.chunk.emit(Op::Return, self.line);
618 super::ensure_chunk_addressable(&self.chunk, "the pipeline body", self.line)?;
619 Ok(self.chunk)
620 }
621
622 pub(super) fn emit_default_preamble(
627 &mut self,
628 params: &[TypedParam],
629 ) -> Result<(), CompileError> {
630 for (i, param) in params.iter().enumerate() {
631 if let Some(default_expr) = ¶m.default_value {
632 self.chunk.emit(Op::GetArgc, self.line);
633 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
634 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
635 self.chunk.emit(Op::GreaterEqual, self.line);
636 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
637 self.chunk.emit(Op::Pop, self.line);
639 let masked = self.mask_param_names(¶ms[i..]);
648 let result = self.compile_node(default_expr);
649 self.restore_param_names(masked);
650 result?;
651 self.emit_init_or_define_binding(¶m.name, false);
652 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
653 self.chunk.patch_jump(skip_jump);
654 self.chunk.emit(Op::Pop, self.line);
655 self.chunk.patch_jump(end_jump);
656 }
657 }
658 Ok(())
659 }
660
661 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
668 for (param_index, param) in params.iter().enumerate() {
669 if let Some(type_expr) = ¶m.type_expr {
670 let check_type = if param.rest {
671 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
672 } else {
673 type_expr.clone()
674 };
675
676 if let harn_parser::TypeExpr::Named(name) = &check_type {
677 if let Some(methods) = self.interface_methods.get(name).cloned() {
678 let fn_idx = self.string_constant("__assert_interface");
679 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
680 self.emit_get_binding(¶m.name);
681 let name_idx = self.string_constant(¶m.name);
682 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
683 let iface_idx = self.string_constant(name);
684 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
685 let methods_str = methods.join(",");
686 let methods_idx = self.owned_string_constant(methods_str);
687 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
688 self.chunk.emit_u8(Op::Call, 4, self.line);
689 self.chunk.emit(Op::Pop, self.line);
690 continue;
691 }
692 }
693
694 if param.default_value.is_some() {
695 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
696 self.emit_default_param_schema_check(param_index, param, &schema);
697 }
698 }
699 }
700 }
701 }
702
703 fn emit_default_param_schema_check(
704 &mut self,
705 param_index: usize,
706 param: &TypedParam,
707 schema: &VmValue,
708 ) {
709 self.chunk.emit(Op::GetArgc, self.line);
710 let threshold_idx = self
711 .chunk
712 .add_constant(Constant::Int((param_index + 1) as i64));
713 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
714 self.chunk.emit(Op::GreaterEqual, self.line);
715 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
716 self.chunk.emit(Op::Pop, self.line);
717 self.emit_schema_assert_call(param, schema);
718 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
719 self.chunk.patch_jump(supplied_jump);
720 self.chunk.emit(Op::Pop, self.line);
721 self.chunk.patch_jump(end_jump);
722 }
723
724 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
725 let fn_idx = self.string_constant("__assert_schema");
726 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
727 self.emit_get_binding(¶m.name);
728 let name_idx = self.string_constant(¶m.name);
729 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
730 self.emit_vm_value_literal(schema);
731 self.chunk.emit_u8(Op::Call, 3, self.line);
732 self.chunk.emit(Op::Pop, self.line);
733 }
734
735 #[doc(hidden)]
736 pub fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
737 match type_expr {
738 harn_parser::TypeExpr::Named(name) => match name.as_str() {
739 "any" | "unknown" => Some(VmValue::dict(BTreeMap::<String, VmValue>::new())),
740 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
741 | "closure" | "bytes" => Some(VmValue::dict(BTreeMap::from([(
742 "type".to_string(),
743 VmValue::String(arcstr::ArcStr::from(name.as_str())),
744 )]))),
745 _ => None,
746 },
747 harn_parser::TypeExpr::Shape(fields) => {
748 let mut properties = BTreeMap::new();
749 let mut required = Vec::new();
750 for field in fields {
751 let mut field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
752 if field.optional {
753 field_schema = VmValue::dict(BTreeMap::from([(
754 "union".to_string(),
755 VmValue::List(std::sync::Arc::new(vec![
756 field_schema,
757 VmValue::dict(BTreeMap::from([(
758 "type".to_string(),
759 VmValue::String(arcstr::ArcStr::from("nil")),
760 )])),
761 ])),
762 )]));
763 }
764 properties.insert(field.name.clone(), field_schema);
765 if !field.optional {
766 required.push(VmValue::String(arcstr::ArcStr::from(field.name.as_str())));
767 }
768 }
769 let mut out = BTreeMap::new();
770 out.put_str("type", "dict");
771 out.insert("properties".to_string(), VmValue::dict(properties));
772 if !required.is_empty() {
773 out.insert(
774 "required".to_string(),
775 VmValue::List(std::sync::Arc::new(required)),
776 );
777 }
778 Some(VmValue::dict(out))
779 }
780 harn_parser::TypeExpr::OpenShape { .. } => None,
781 harn_parser::TypeExpr::List(inner) => {
782 let mut out = BTreeMap::new();
783 out.put_str("type", "list");
784 let item_schema = Self::type_expr_to_schema_value(inner)?;
785 out.insert("items".to_string(), item_schema);
786 Some(VmValue::dict(out))
787 }
788 harn_parser::TypeExpr::Tuple(_) => None,
794 harn_parser::TypeExpr::DictType(key, value) => {
795 let mut out = BTreeMap::new();
796 out.put_str("type", "dict");
797 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
798 let value_schema = Self::type_expr_to_schema_value(value)?;
799 out.insert("additional_properties".to_string(), value_schema);
800 }
801 Some(VmValue::dict(out))
802 }
803 harn_parser::TypeExpr::Union(members) => {
804 if !members.is_empty()
809 && members
810 .iter()
811 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
812 {
813 let values = members
814 .iter()
815 .map(|m| match m {
816 harn_parser::TypeExpr::LitString(s) => {
817 VmValue::String(arcstr::ArcStr::from(s.as_str()))
818 }
819 _ => unreachable!(),
820 })
821 .collect::<Vec<_>>();
822 return Some(VmValue::dict(BTreeMap::from([
823 (
824 "type".to_string(),
825 VmValue::String(arcstr::ArcStr::from("string")),
826 ),
827 (
828 "enum".to_string(),
829 VmValue::List(std::sync::Arc::new(values)),
830 ),
831 ])));
832 }
833 if !members.is_empty()
834 && members
835 .iter()
836 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
837 {
838 let values = members
839 .iter()
840 .map(|m| match m {
841 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
842 _ => unreachable!(),
843 })
844 .collect::<Vec<_>>();
845 return Some(VmValue::dict(BTreeMap::from([
846 (
847 "type".to_string(),
848 VmValue::String(arcstr::ArcStr::from("int")),
849 ),
850 (
851 "enum".to_string(),
852 VmValue::List(std::sync::Arc::new(values)),
853 ),
854 ])));
855 }
856 let branches = members
857 .iter()
858 .map(Self::type_expr_to_schema_value)
859 .collect::<Option<Vec<_>>>()?;
860 if branches.is_empty() {
861 None
862 } else {
863 Some(VmValue::dict(BTreeMap::from([(
864 "union".to_string(),
865 VmValue::List(std::sync::Arc::new(branches)),
866 )])))
867 }
868 }
869 harn_parser::TypeExpr::Intersection(members) => {
870 let branches = members
874 .iter()
875 .map(Self::type_expr_to_schema_value)
876 .collect::<Option<Vec<_>>>()?;
877 if branches.is_empty() {
878 None
879 } else {
880 Some(VmValue::dict(BTreeMap::from([(
881 "all_of".to_string(),
882 VmValue::List(std::sync::Arc::new(branches)),
883 )])))
884 }
885 }
886 harn_parser::TypeExpr::FnType { .. } => Some(VmValue::dict(BTreeMap::from([(
887 "type".to_string(),
888 VmValue::String(arcstr::ArcStr::from("closure")),
889 )]))),
890 harn_parser::TypeExpr::Applied { .. } => None,
891 harn_parser::TypeExpr::Iter(_)
892 | harn_parser::TypeExpr::Generator(_)
893 | harn_parser::TypeExpr::Stream(_) => None,
894 harn_parser::TypeExpr::Never => None,
895 harn_parser::TypeExpr::LitString(s) => Some(VmValue::dict(BTreeMap::from([
896 (
897 "type".to_string(),
898 VmValue::String(arcstr::ArcStr::from("string")),
899 ),
900 (
901 "const".to_string(),
902 VmValue::String(arcstr::ArcStr::from(s.as_str())),
903 ),
904 ]))),
905 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::dict(BTreeMap::from([
906 (
907 "type".to_string(),
908 VmValue::String(arcstr::ArcStr::from("int")),
909 ),
910 ("const".to_string(), VmValue::Int(*v)),
911 ]))),
912 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
913 }
914 }
915
916 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
917 match value {
918 VmValue::String(text) => {
919 let idx = self.string_constant(text);
920 self.chunk.emit_u16(Op::Constant, idx, self.line);
921 }
922 VmValue::Int(number) => {
923 let idx = self.chunk.add_constant(Constant::Int(*number));
924 self.chunk.emit_u16(Op::Constant, idx, self.line);
925 }
926 VmValue::Float(number) => {
927 let idx = self.chunk.add_constant(Constant::Float(*number));
928 self.chunk.emit_u16(Op::Constant, idx, self.line);
929 }
930 VmValue::Bool(value) => {
931 let idx = self.chunk.add_constant(Constant::Bool(*value));
932 self.chunk.emit_u16(Op::Constant, idx, self.line);
933 }
934 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
935 VmValue::List(items) => {
936 for item in items.iter() {
937 self.emit_vm_value_literal(item);
938 }
939 self.chunk
940 .emit_u16(Op::BuildList, items.len() as u16, self.line);
941 }
942 VmValue::Dict(entries) => {
943 for (key, item) in entries.iter() {
944 let key_idx = self.string_constant(key);
945 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
946 self.emit_vm_value_literal(item);
947 }
948 self.chunk
949 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
950 }
951 _ => {}
952 }
953 }
954
955 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
957 let hi = (type_name_idx >> 8) as u8;
958 let lo = type_name_idx as u8;
959 self.chunk.code.push(hi);
960 self.chunk.code.push(lo);
961 self.chunk.lines.push(self.line);
962 self.chunk.columns.push(self.column);
963 self.chunk.lines.push(self.line);
964 self.chunk.columns.push(self.column);
965 }
966
967 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
969 if body.is_empty() {
970 self.chunk.emit(Op::Nil, self.line);
971 } else {
972 self.compile_scoped_block(body)?;
973 }
974 Ok(())
975 }
976
977 pub(super) fn compile_catch_binding(
979 &mut self,
980 error_var: &Option<String>,
981 ) -> Result<(), CompileError> {
982 if let Some(var_name) = error_var {
983 self.emit_define_binding(var_name, false);
984 } else {
985 self.chunk.emit(Op::Pop, self.line);
986 }
987 Ok(())
988 }
989
990 pub(super) fn compile_finally_inline(
997 &mut self,
998 finally_body: &[SNode],
999 ) -> Result<(), CompileError> {
1000 if !finally_body.is_empty() {
1001 self.compile_scoped_block(finally_body)?;
1002 self.chunk.emit(Op::Pop, self.line);
1003 }
1004 Ok(())
1005 }
1006
1007 pub(super) fn has_pending_finally_until_barrier(&self) -> bool {
1011 self.finally_bodies
1012 .iter()
1013 .rev()
1014 .take_while(|entry| !matches!(entry, FinallyEntry::CatchBarrier))
1015 .any(|entry| matches!(entry, FinallyEntry::Finally(_)))
1016 }
1017
1018 pub(super) fn has_pending_finally(&self) -> bool {
1020 self.finally_bodies
1021 .iter()
1022 .any(|e| matches!(e, FinallyEntry::Finally(_)))
1023 }
1024
1025 pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
1034 self.temp_counter += 1;
1035 let temp_name = format!("__finally_err_{}__", self.temp_counter);
1036 self.emit_define_binding(&temp_name, true);
1037 self.emit_get_binding(&temp_name);
1038 self.chunk.emit(Op::Throw, self.line);
1039 Ok(())
1040 }
1041
1042 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
1043 for param in params {
1044 self.define_local_slot(¶m.name, false);
1045 }
1046 }
1047
1048 fn mask_param_names(&mut self, params: &[TypedParam]) -> Vec<(String, super::LocalBinding)> {
1054 let mut removed = Vec::new();
1055 if let Some(scope) = self.local_scopes.last_mut() {
1056 for param in params {
1057 if let Some(binding) = scope.remove(¶m.name) {
1058 removed.push((param.name.clone(), binding));
1059 }
1060 }
1061 }
1062 removed
1063 }
1064
1065 fn restore_param_names(&mut self, removed: Vec<(String, super::LocalBinding)>) {
1067 if let Some(scope) = self.local_scopes.last_mut() {
1068 for (name, binding) in removed {
1069 scope.insert(name, binding);
1070 }
1071 }
1072 }
1073
1074 pub(super) fn seed_captured_idents(&mut self, body: &[SNode]) {
1079 let match_patterns = self.lexical_match_pattern_catalog();
1080 self.captured_bindings =
1081 harn_parser::lexical::captured_bindings_in_nested_callables(body, &match_patterns);
1082 }
1083
1084 fn seed_module_captured_idents(&mut self, body: &[SNode]) {
1085 let match_patterns = self.lexical_match_pattern_catalog();
1086 self.captured_bindings =
1087 harn_parser::lexical::captured_bindings_in_compiled_module(body, &match_patterns);
1088 }
1089
1090 pub(super) fn lexical_match_pattern_catalog(
1091 &self,
1092 ) -> harn_parser::lexical::MatchPatternCatalog {
1093 if self.imported_enum_candidates.is_empty() {
1094 return harn_parser::lexical::MatchPatternCatalog::new(
1095 &self.enum_names,
1096 &self.enum_variant_owners,
1097 );
1098 }
1099 let mut enum_names = self.enum_names.clone();
1100 enum_names.extend(self.imported_enum_candidates.iter().cloned());
1101 harn_parser::lexical::MatchPatternCatalog::new(&enum_names, &self.enum_variant_owners)
1102 }
1103
1104 pub(super) fn begin_scope(&mut self) {
1105 self.chunk.emit(Op::PushScope, self.line);
1106 self.scope_depth += 1;
1107 let enum_catalog = self.enum_catalog_snapshot();
1108 self.enum_catalog_scopes.push(enum_catalog);
1109 self.type_scopes.push(std::collections::HashMap::new());
1110 self.local_scopes.push(std::collections::HashMap::new());
1111 }
1112
1113 pub(super) fn end_scope(&mut self) {
1114 if self.scope_depth > 0 {
1115 self.chunk.emit(Op::PopScope, self.line);
1116 self.scope_depth -= 1;
1117 if let Some(snapshot) = self.enum_catalog_scopes.pop() {
1118 self.restore_enum_catalog(snapshot);
1119 }
1120 self.type_scopes.pop();
1121 self.local_scopes.pop();
1122 }
1123 }
1124
1125 pub(super) fn emit_scope_unwind_to(&mut self, target_depth: usize) {
1128 for _ in target_depth..self.scope_depth {
1129 self.chunk.emit(Op::PopScope, self.line);
1130 }
1131 }
1132
1133 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1134 self.begin_scope();
1135 let finally_floor = self.finally_bodies.len();
1136 if stmts.is_empty() {
1137 self.chunk.emit(Op::Nil, self.line);
1138 } else {
1139 self.compile_block(stmts)?;
1140 }
1141 self.drain_finallys_to_floor(finally_floor)?;
1142 self.end_scope();
1143 Ok(())
1144 }
1145
1146 pub(super) fn compile_scoped_statements(
1147 &mut self,
1148 stmts: &[SNode],
1149 ) -> Result<(), CompileError> {
1150 self.begin_scope();
1151 self.record_monomorphic_var_bindings(stmts);
1152 let finally_floor = self.finally_bodies.len();
1153 for sn in stmts {
1154 self.compile_discarded_stmt(sn)?;
1155 }
1156 self.drain_finallys_to_floor(finally_floor)?;
1157 self.end_scope();
1158 Ok(())
1159 }
1160
1161 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
1166 while self.finally_bodies.len() > floor {
1167 let entry = self.finally_bodies.pop().expect("non-empty by guard");
1168 if let FinallyEntry::Finally(body) = entry {
1169 self.compile_finally_inline(&body)?;
1170 }
1171 }
1172 Ok(())
1173 }
1174
1175 pub(super) fn run_pending_finallys_for_transfer(
1188 &mut self,
1189 floor: usize,
1190 ) -> Result<(), CompileError> {
1191 if self.finally_bodies.len() <= floor {
1192 return Ok(());
1193 }
1194 let saved = self.finally_bodies[floor..].to_vec();
1195 let result = self.drain_finallys_to_floor(floor);
1196 self.finally_bodies.extend(saved);
1197 result
1198 }
1199
1200 pub(super) fn run_pending_finallys_until_barrier(&mut self) -> Result<(), CompileError> {
1205 let floor = self
1206 .finally_bodies
1207 .iter()
1208 .rposition(|e| matches!(e, FinallyEntry::CatchBarrier))
1209 .map(|i| i + 1)
1210 .unwrap_or(0);
1211 self.run_pending_finallys_for_transfer(floor)
1212 }
1213
1214 pub(super) fn maybe_register_owned_drop(
1219 &mut self,
1220 pattern: &harn_parser::BindingPattern,
1221 type_ann: Option<&TypeExpr>,
1222 span: harn_lexer::Span,
1223 ) {
1224 let Some(ty) = type_ann else {
1230 return;
1231 };
1232 if !matches!(ty, TypeExpr::Owned(_)) {
1233 return;
1234 }
1235 let harn_parser::BindingPattern::Identifier(name) = pattern else {
1236 return;
1237 };
1238 if harn_parser::is_discard_name(name) {
1239 return;
1240 }
1241 let call = harn_parser::spanned(
1242 Node::FunctionCall {
1243 name: "drop".to_string(),
1244 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
1245 type_args: Vec::new(),
1246 },
1247 span,
1248 );
1249 self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
1250 }
1251
1252 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
1268 #[cfg(debug_assertions)]
1269 let probe = self.chunk.balance_probe();
1270 self.compile_node(sn)?;
1271 #[allow(unused_mut)]
1272 let mut produces = Self::produces_value(&sn.node);
1273 #[cfg(test)]
1277 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
1278 produces = forced;
1279 }
1280 #[cfg(debug_assertions)]
1281 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1282 let expected = i32::from(produces);
1283 debug_assert_eq!(
1284 delta, expected,
1285 "operand-stack imbalance at line {}: produces_value={produces} but the \
1286 node's emitted bytecode netted {delta} (expected {expected}). A \
1287 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1288 node: {:?}",
1289 self.line, sn.node,
1290 );
1291 }
1292 if produces {
1293 self.chunk.emit(Op::Pop, self.line);
1294 }
1295 Ok(())
1296 }
1297
1298 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1299 self.record_monomorphic_var_bindings(stmts);
1300 let callable_declarations = stmts
1301 .iter()
1302 .enumerate()
1303 .filter_map(|(index, node)| {
1304 harn_parser::lexical::hoisted_callable_name(node)
1305 .map(|name| (index, name.to_string()))
1306 })
1307 .collect::<Vec<_>>();
1308 let callable_names = callable_declarations
1309 .iter()
1310 .map(|(_, name)| name.as_str())
1311 .collect::<std::collections::HashSet<_>>();
1312 let mut emitted_callables = std::collections::HashSet::new();
1313
1314 for (i, snode) in stmts.iter().enumerate() {
1315 if harn_parser::lexical::hoisted_callable_name(snode).is_some() {
1316 if emitted_callables.insert(i) {
1317 self.compile_discarded_stmt(snode)?;
1318 }
1319 if i == stmts.len() - 1 {
1320 self.chunk.emit(Op::Nil, self.line);
1321 }
1322 continue;
1323 }
1324
1325 let mut pending = Vec::new();
1332 Self::collect_callable_references(snode, &callable_names, &mut pending);
1333 let mut reachable = std::collections::HashSet::new();
1334 while let Some(name) = pending.pop() {
1335 if !reachable.insert(name.clone()) {
1336 continue;
1337 }
1338 for (index, declaration_name) in &callable_declarations {
1339 if declaration_name == &name {
1340 Self::collect_callable_references(
1341 &stmts[*index],
1342 &callable_names,
1343 &mut pending,
1344 );
1345 }
1346 }
1347 }
1348 for (index, name) in &callable_declarations {
1349 if reachable.contains(name) && emitted_callables.insert(*index) {
1350 self.compile_discarded_stmt(&stmts[*index])?;
1351 }
1352 }
1353
1354 if i == stmts.len() - 1 {
1355 self.compile_node(snode)?;
1359 if !Self::produces_value(&snode.node) {
1360 self.chunk.emit(Op::Nil, self.line);
1361 }
1362 } else {
1363 self.compile_discarded_stmt(snode)?;
1364 }
1365 }
1366 Ok(())
1367 }
1368
1369 fn collect_callable_references(
1370 node: &SNode,
1371 callable_names: &std::collections::HashSet<&str>,
1372 out: &mut Vec<String>,
1373 ) {
1374 match &node.node {
1375 Node::Identifier(name) | Node::FunctionCall { name, .. }
1376 if callable_names.contains(name.as_str()) =>
1377 {
1378 out.push(name.clone());
1379 }
1380 _ => {}
1381 }
1382 for child in harn_parser::visit::immediate_children(node) {
1383 Self::collect_callable_references(child, callable_names, out);
1384 }
1385 }
1386
1387 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1389 self.begin_scope();
1390 let finally_floor = self.finally_bodies.len();
1391 if body.is_empty() {
1392 self.chunk.emit(Op::Nil, self.line);
1393 } else {
1394 self.compile_block(body)?;
1395 if !Self::produces_value(&body.last().unwrap().node) {
1396 self.chunk.emit(Op::Nil, self.line);
1397 }
1398 }
1399 self.drain_finallys_to_floor(finally_floor)?;
1400 self.end_scope();
1401 Ok(())
1402 }
1403
1404 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1406 match op {
1407 "+" => self.chunk.emit(Op::Add, self.line),
1408 "-" => self.chunk.emit(Op::Sub, self.line),
1409 "*" => self.chunk.emit(Op::Mul, self.line),
1410 "/" => self.chunk.emit(Op::Div, self.line),
1411 "%" => self.chunk.emit(Op::Mod, self.line),
1412 _ => {
1413 return Err(CompileError {
1414 message: format!("Unknown compound operator: {op}"),
1415 line: self.line,
1416 })
1417 }
1418 }
1419 Ok(())
1420 }
1421
1422 pub(super) fn produces_value(node: &Node) -> bool {
1424 harn_parser::node_produces_value(node)
1425 }
1426}
1427
1428impl Default for Compiler {
1429 fn default() -> Self {
1430 Self::new()
1431 }
1432}