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