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 #[doc(hidden)]
266 pub fn expand_alias(&self, ty: &TypeExpr) -> TypeExpr {
267 let mut visiting = std::collections::HashSet::new();
268 self.expand_alias_inner(ty, &mut visiting)
269 }
270
271 fn expand_alias_inner(
272 &self,
273 ty: &TypeExpr,
274 visiting: &mut std::collections::HashSet<String>,
275 ) -> TypeExpr {
276 match ty {
277 TypeExpr::Named(name) => {
278 if let Some(target) = self
279 .type_aliases
280 .get(name)
281 .filter(|alias| alias.type_params.is_empty() && alias.body.is_some())
282 {
283 if !visiting.insert(name.clone()) {
284 return TypeExpr::Named(name.clone());
285 }
286 let resolved = self.expand_alias_inner(target.body.as_ref().unwrap(), visiting);
287 visiting.remove(name);
288 resolved
289 } else {
290 TypeExpr::Named(name.clone())
291 }
292 }
293 TypeExpr::Union(types) => TypeExpr::Union(
294 types
295 .iter()
296 .map(|t| self.expand_alias_inner(t, visiting))
297 .collect(),
298 ),
299 TypeExpr::Intersection(types) => TypeExpr::Intersection(
300 types
301 .iter()
302 .map(|t| self.expand_alias_inner(t, visiting))
303 .collect(),
304 ),
305 TypeExpr::Shape(fields) => TypeExpr::Shape(
306 fields
307 .iter()
308 .map(|field| ShapeField {
309 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
310 ..field.clone()
311 })
312 .collect(),
313 ),
314 TypeExpr::OpenShape { fields, rests } => TypeExpr::OpenShape {
315 fields: fields
316 .iter()
317 .map(|field| ShapeField {
318 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
319 ..field.clone()
320 })
321 .collect(),
322 rests: rests
323 .iter()
324 .map(|r| self.expand_alias_inner(r, visiting))
325 .collect(),
326 },
327 TypeExpr::List(inner) => {
328 TypeExpr::List(Box::new(self.expand_alias_inner(inner, visiting)))
329 }
330 TypeExpr::Tuple(elements) => TypeExpr::Tuple(
331 elements
332 .iter()
333 .map(|element| self.expand_alias_inner(element, visiting))
334 .collect(),
335 ),
336 TypeExpr::Iter(inner) => {
337 TypeExpr::Iter(Box::new(self.expand_alias_inner(inner, visiting)))
338 }
339 TypeExpr::Generator(inner) => {
340 TypeExpr::Generator(Box::new(self.expand_alias_inner(inner, visiting)))
341 }
342 TypeExpr::Stream(inner) => {
343 TypeExpr::Stream(Box::new(self.expand_alias_inner(inner, visiting)))
344 }
345 TypeExpr::DictType(k, v) => TypeExpr::DictType(
346 Box::new(self.expand_alias_inner(k, visiting)),
347 Box::new(self.expand_alias_inner(v, visiting)),
348 ),
349 TypeExpr::FnType {
350 params,
351 return_type,
352 } => TypeExpr::FnType {
353 params: params
354 .iter()
355 .map(|p| self.expand_alias_inner(p, visiting))
356 .collect(),
357 return_type: Box::new(self.expand_alias_inner(return_type, visiting)),
358 },
359 TypeExpr::Applied { name, args } => {
360 let args = args
361 .iter()
362 .map(|arg| self.expand_alias_inner(arg, visiting))
363 .collect::<Vec<_>>();
364 let Some(alias) = self.type_aliases.get(name) else {
365 return TypeExpr::Applied {
366 name: name.clone(),
367 args,
368 };
369 };
370 let Some(body) = alias.body.as_ref() else {
371 return TypeExpr::Applied {
372 name: name.clone(),
373 args,
374 };
375 };
376 if alias.type_params.len() != args.len() || !visiting.insert(name.clone()) {
377 return TypeExpr::Applied {
378 name: name.clone(),
379 args,
380 };
381 }
382 let bindings = alias
383 .type_params
384 .iter()
385 .zip(args.iter().cloned())
386 .map(|(param, arg)| (param.name.clone(), arg))
387 .collect();
388 let instantiated = substitute_type_expr(body, &bindings);
389 let resolved = self.expand_alias_inner(&instantiated, visiting);
390 visiting.remove(name);
391 resolved
392 }
393 TypeExpr::Never => TypeExpr::Never,
394 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
395 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
396 TypeExpr::Owned(inner) => {
397 TypeExpr::Owned(Box::new(self.expand_alias_inner(inner, visiting)))
398 }
399 }
400 }
401
402 pub fn compile_public_type_schema_initializers(
409 program: &[SNode],
410 source_file: Option<String>,
411 ) -> Result<Vec<Chunk>, CompileError> {
412 Self::compile_selected_public_type_schema_initializers(program, source_file, None)
413 }
414
415 pub fn compile_selected_public_type_schema_initializers(
419 program: &[SNode],
420 source_file: Option<String>,
421 selected_names: Option<&std::collections::BTreeSet<String>>,
422 ) -> Result<Vec<Chunk>, CompileError> {
423 let mut compiler = Compiler::new();
424 compiler.collect_type_aliases(program);
425 let mut chunks = Vec::new();
426 for sn in program {
427 let Node::TypeDecl {
428 name, is_pub: true, ..
429 } = peel_node(sn)
430 else {
431 continue;
432 };
433 if selected_names.is_some_and(|selected| !selected.contains(name)) {
434 continue;
435 }
436 compiler.chunk = Chunk::new();
437 compiler.string_constants.clear();
438 compiler.chunk.source_file.clone_from(&source_file);
439 if compiler.emit_schema_for_alias(name) {
440 compiler.emit_define_binding(name, false);
441 compiler.chunk.emit(Op::Nil, compiler.line);
442 compiler.chunk.emit(Op::Return, compiler.line);
443 super::ensure_chunk_addressable(
444 &compiler.chunk,
445 &format!("the public type-schema initializer for `{name}`"),
446 compiler.line,
447 )?;
448 chunks.push(std::mem::take(&mut compiler.chunk));
449 }
450 }
451 Ok(chunks)
452 }
453
454 pub(super) fn is_schema_guard(name: &str) -> bool {
458 matches!(
459 name,
460 "schema_is"
461 | "schema_expect"
462 | "schema_parse"
463 | "json_decode"
464 | "schema_check"
465 | "schema_report"
466 | "is_type"
467 | "json_validate"
468 )
469 }
470
471 pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
474 matches!(
475 &key.node,
476 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
477 if name == keyword
478 )
479 }
480
481 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
484 self.prepare_module_context(program);
487
488 for sn in program {
489 match &sn.node {
490 Node::ImportDecl { .. }
491 | Node::SelectiveImport { .. }
492 | Node::NamespaceImport { .. } => {
493 self.compile_node(sn)?;
494 }
495 _ => {}
496 }
497 }
498 let main = program
499 .iter()
500 .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
501 .or_else(|| {
502 program
503 .iter()
504 .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
505 });
506
507 let mut pipeline_emits_value = false;
511 if let Some(sn) = main {
512 self.compile_top_level_declarations(program)?;
513 if let Node::Pipeline {
514 params,
515 body,
516 extends,
517 throws,
518 ..
519 } = peel_node(sn)
520 {
521 self.compile_with_pipeline_captures(
522 program,
523 body,
524 extends.as_deref(),
525 |compiler| {
526 let saved = std::mem::replace(&mut compiler.module_level, false);
527 let saved_throw =
528 std::mem::replace(&mut compiler.declared_throw, throws.is_some());
529 if let Some(harness) = params.first().filter(|param| {
530 matches!(
531 param.type_expr.as_ref(),
532 Some(TypeExpr::Named(name)) if name == "Harness"
533 )
534 }) {
535 compiler.chunk.emit(Op::RootHarness, compiler.line);
536 compiler.emit_define_binding(&harness.name, false);
537 }
538 if let Some(parent_name) = extends {
539 compiler.compile_parent_pipeline(program, parent_name)?;
540 }
541 let result = compiler.compile_block(body);
542 compiler.module_level = saved;
543 compiler.declared_throw = saved_throw;
544 result
545 },
546 )?;
547 pipeline_emits_value = true;
548 }
549 } else {
550 let top_level: Vec<&SNode> = program
552 .iter()
553 .filter(|sn| {
554 !matches!(
555 &sn.node,
556 Node::ImportDecl { .. }
557 | Node::SelectiveImport { .. }
558 | Node::NamespaceImport { .. }
559 )
560 })
561 .collect();
562 for sn in &top_level {
563 self.compile_discarded_stmt(sn)?;
564 }
565 if Self::has_top_level_fn_main(program) {
570 self.chunk.emit(Op::RootHarness, self.line);
571 self.emit_named_call("main", 1);
572 pipeline_emits_value = true;
573 }
574 }
575
576 self.drain_finallys_to_floor(0)?;
577 if !pipeline_emits_value {
578 self.chunk.emit(Op::Nil, self.line);
579 }
580 self.chunk.emit(Op::Return, self.line);
581 super::ensure_chunk_addressable(&self.chunk, "the program body", self.line)?;
582 Ok(self.chunk)
583 }
584
585 fn has_top_level_fn_main(program: &[SNode]) -> bool {
589 program
590 .iter()
591 .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
592 }
593
594 pub fn compile_named(
596 self,
597 program: &[SNode],
598 pipeline_name: &str,
599 ) -> Result<Chunk, CompileError> {
600 self.compile_named_inner(program, pipeline_name)
601 }
602
603 fn compile_named_inner(
604 mut self,
605 program: &[SNode],
606 pipeline_name: &str,
607 ) -> Result<Chunk, CompileError> {
608 self.prepare_module_context(program);
609
610 for sn in program {
611 if matches!(
612 &sn.node,
613 Node::ImportDecl { .. }
614 | Node::SelectiveImport { .. }
615 | Node::NamespaceImport { .. }
616 ) {
617 self.compile_node(sn)?;
618 }
619 }
620 let target = program.iter().find(
621 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
622 );
623
624 if let Some(sn) = target {
625 self.compile_top_level_declarations(program)?;
626 if let Node::Pipeline {
627 body,
628 extends,
629 params,
630 throws,
631 ..
632 } = peel_node(sn)
633 {
634 self.compile_with_pipeline_captures(
635 program,
636 body,
637 extends.as_deref(),
638 |compiler| {
639 let saved = std::mem::replace(&mut compiler.module_level, false);
640 let saved_throw =
641 std::mem::replace(&mut compiler.declared_throw, throws.is_some());
642 if let Some(harness) = params.first().filter(|param| {
643 matches!(
644 param.type_expr.as_ref(),
645 Some(TypeExpr::Named(name)) if name == "Harness"
646 )
647 }) {
648 compiler.chunk.emit(Op::RootHarness, compiler.line);
649 compiler.emit_define_binding(&harness.name, false);
650 }
651 if let Some(parent_name) = extends {
652 compiler.compile_parent_pipeline(program, parent_name)?;
653 }
654 let result = compiler.compile_block(body);
655 compiler.module_level = saved;
656 compiler.declared_throw = saved_throw;
657 result
658 },
659 )?;
660 }
661 }
662
663 self.drain_finallys_to_floor(0)?;
664 self.chunk.emit(Op::Nil, self.line);
665 self.chunk.emit(Op::Return, self.line);
666 super::ensure_chunk_addressable(&self.chunk, "the pipeline body", self.line)?;
667 Ok(self.chunk)
668 }
669
670 pub(super) fn emit_default_preamble(
675 &mut self,
676 params: &[TypedParam],
677 ) -> Result<(), CompileError> {
678 for (i, param) in params.iter().enumerate() {
679 if let Some(default_expr) = ¶m.default_value {
680 self.chunk.emit(Op::GetArgc, self.line);
681 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
682 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
683 self.chunk.emit(Op::GreaterEqual, self.line);
684 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
685 self.chunk.emit(Op::Pop, self.line);
687 let masked = self.mask_param_names(¶ms[i..]);
696 let result = self.compile_node(default_expr);
697 self.restore_param_names(masked);
698 result?;
699 self.emit_init_or_define_binding(¶m.name, false);
700 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
701 self.chunk.patch_jump(skip_jump);
702 self.chunk.emit(Op::Pop, self.line);
703 self.chunk.patch_jump(end_jump);
704 }
705 }
706 Ok(())
707 }
708
709 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
716 for (param_index, param) in params.iter().enumerate() {
717 if let Some(type_expr) = ¶m.type_expr {
718 let check_type = if param.rest {
719 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
720 } else {
721 type_expr.clone()
722 };
723
724 if let harn_parser::TypeExpr::Named(name) = &check_type {
725 if let Some(methods) = self.interface_methods.get(name).cloned() {
726 let fn_idx = self.string_constant("__assert_interface");
727 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
728 self.emit_get_binding(¶m.name);
729 let name_idx = self.string_constant(¶m.name);
730 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
731 let iface_idx = self.string_constant(name);
732 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
733 let methods_str = methods.join(",");
734 let methods_idx = self.owned_string_constant(methods_str);
735 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
736 self.chunk.emit_u8(Op::Call, 4, self.line);
737 self.chunk.emit(Op::Pop, self.line);
738 continue;
739 }
740 }
741
742 if param.default_value.is_some() {
743 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
744 self.emit_default_param_schema_check(param_index, param, &schema);
745 }
746 }
747 }
748 }
749 }
750
751 fn emit_default_param_schema_check(
752 &mut self,
753 param_index: usize,
754 param: &TypedParam,
755 schema: &VmValue,
756 ) {
757 self.chunk.emit(Op::GetArgc, self.line);
758 let threshold_idx = self
759 .chunk
760 .add_constant(Constant::Int((param_index + 1) as i64));
761 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
762 self.chunk.emit(Op::GreaterEqual, self.line);
763 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
764 self.chunk.emit(Op::Pop, self.line);
765 self.emit_schema_assert_call(param, schema);
766 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
767 self.chunk.patch_jump(supplied_jump);
768 self.chunk.emit(Op::Pop, self.line);
769 self.chunk.patch_jump(end_jump);
770 }
771
772 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
773 let fn_idx = self.string_constant("__assert_schema");
774 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
775 self.emit_get_binding(¶m.name);
776 let name_idx = self.string_constant(¶m.name);
777 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
778 self.emit_vm_value_literal(schema);
779 self.chunk.emit_u8(Op::Call, 3, self.line);
780 self.chunk.emit(Op::Pop, self.line);
781 }
782
783 #[doc(hidden)]
784 pub fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
785 match type_expr {
786 harn_parser::TypeExpr::Named(name) => match name.as_str() {
787 "any" | "unknown" => Some(VmValue::dict(BTreeMap::<String, VmValue>::new())),
788 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
789 | "closure" | "bytes" => Some(VmValue::dict(BTreeMap::from([(
790 "type".to_string(),
791 VmValue::String(arcstr::ArcStr::from(name.as_str())),
792 )]))),
793 _ => None,
794 },
795 harn_parser::TypeExpr::Shape(fields) => {
796 let mut properties = BTreeMap::new();
797 let mut required = Vec::new();
798 for field in fields {
799 let mut field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
800 if field.optional {
801 field_schema = VmValue::dict(BTreeMap::from([(
802 "union".to_string(),
803 VmValue::List(std::sync::Arc::new(vec![
804 field_schema,
805 VmValue::dict(BTreeMap::from([(
806 "type".to_string(),
807 VmValue::String(arcstr::ArcStr::from("nil")),
808 )])),
809 ])),
810 )]));
811 }
812 properties.insert(field.name.clone(), field_schema);
813 if !field.optional {
814 required.push(VmValue::String(arcstr::ArcStr::from(field.name.as_str())));
815 }
816 }
817 let mut out = BTreeMap::new();
818 out.put_str("type", "dict");
819 out.insert("properties".to_string(), VmValue::dict(properties));
820 if !required.is_empty() {
821 out.insert(
822 "required".to_string(),
823 VmValue::List(std::sync::Arc::new(required)),
824 );
825 }
826 Some(VmValue::dict(out))
827 }
828 harn_parser::TypeExpr::OpenShape { .. } => None,
829 harn_parser::TypeExpr::List(inner) => {
830 let mut out = BTreeMap::new();
831 out.put_str("type", "list");
832 let item_schema = Self::type_expr_to_schema_value(inner)?;
833 out.insert("items".to_string(), item_schema);
834 Some(VmValue::dict(out))
835 }
836 harn_parser::TypeExpr::Tuple(_) => None,
842 harn_parser::TypeExpr::DictType(key, value) => {
843 let mut out = BTreeMap::new();
844 out.put_str("type", "dict");
845 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
846 let value_schema = Self::type_expr_to_schema_value(value)?;
847 out.insert("additional_properties".to_string(), value_schema);
848 }
849 Some(VmValue::dict(out))
850 }
851 harn_parser::TypeExpr::Union(members) => {
852 if !members.is_empty()
857 && members
858 .iter()
859 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
860 {
861 let values = members
862 .iter()
863 .map(|m| match m {
864 harn_parser::TypeExpr::LitString(s) => {
865 VmValue::String(arcstr::ArcStr::from(s.as_str()))
866 }
867 _ => unreachable!(),
868 })
869 .collect::<Vec<_>>();
870 return Some(VmValue::dict(BTreeMap::from([
871 (
872 "type".to_string(),
873 VmValue::String(arcstr::ArcStr::from("string")),
874 ),
875 (
876 "enum".to_string(),
877 VmValue::List(std::sync::Arc::new(values)),
878 ),
879 ])));
880 }
881 if !members.is_empty()
882 && members
883 .iter()
884 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
885 {
886 let values = members
887 .iter()
888 .map(|m| match m {
889 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
890 _ => unreachable!(),
891 })
892 .collect::<Vec<_>>();
893 return Some(VmValue::dict(BTreeMap::from([
894 (
895 "type".to_string(),
896 VmValue::String(arcstr::ArcStr::from("int")),
897 ),
898 (
899 "enum".to_string(),
900 VmValue::List(std::sync::Arc::new(values)),
901 ),
902 ])));
903 }
904 let branches = members
905 .iter()
906 .map(Self::type_expr_to_schema_value)
907 .collect::<Option<Vec<_>>>()?;
908 if branches.is_empty() {
909 None
910 } else {
911 Some(VmValue::dict(BTreeMap::from([(
912 "union".to_string(),
913 VmValue::List(std::sync::Arc::new(branches)),
914 )])))
915 }
916 }
917 harn_parser::TypeExpr::Intersection(members) => {
918 let branches = members
922 .iter()
923 .map(Self::type_expr_to_schema_value)
924 .collect::<Option<Vec<_>>>()?;
925 if branches.is_empty() {
926 None
927 } else {
928 Some(VmValue::dict(BTreeMap::from([(
929 "all_of".to_string(),
930 VmValue::List(std::sync::Arc::new(branches)),
931 )])))
932 }
933 }
934 harn_parser::TypeExpr::FnType { .. } => Some(VmValue::dict(BTreeMap::from([(
935 "type".to_string(),
936 VmValue::String(arcstr::ArcStr::from("closure")),
937 )]))),
938 harn_parser::TypeExpr::Applied { .. } => None,
939 harn_parser::TypeExpr::Iter(_)
940 | harn_parser::TypeExpr::Generator(_)
941 | harn_parser::TypeExpr::Stream(_) => None,
942 harn_parser::TypeExpr::Never => None,
943 harn_parser::TypeExpr::LitString(s) => Some(VmValue::dict(BTreeMap::from([
944 (
945 "type".to_string(),
946 VmValue::String(arcstr::ArcStr::from("string")),
947 ),
948 (
949 "const".to_string(),
950 VmValue::String(arcstr::ArcStr::from(s.as_str())),
951 ),
952 ]))),
953 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::dict(BTreeMap::from([
954 (
955 "type".to_string(),
956 VmValue::String(arcstr::ArcStr::from("int")),
957 ),
958 ("const".to_string(), VmValue::Int(*v)),
959 ]))),
960 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
961 }
962 }
963
964 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
965 match value {
966 VmValue::String(text) => {
967 let idx = self.string_constant(text);
968 self.chunk.emit_u16(Op::Constant, idx, self.line);
969 }
970 VmValue::Int(number) => {
971 let idx = self.chunk.add_constant(Constant::Int(*number));
972 self.chunk.emit_u16(Op::Constant, idx, self.line);
973 }
974 VmValue::Float(number) => {
975 let idx = self.chunk.add_constant(Constant::Float(*number));
976 self.chunk.emit_u16(Op::Constant, idx, self.line);
977 }
978 VmValue::Bool(value) => {
979 let idx = self.chunk.add_constant(Constant::Bool(*value));
980 self.chunk.emit_u16(Op::Constant, idx, self.line);
981 }
982 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
983 VmValue::List(items) => {
984 for item in items.iter() {
985 self.emit_vm_value_literal(item);
986 }
987 self.chunk
988 .emit_u16(Op::BuildList, items.len() as u16, self.line);
989 }
990 VmValue::Dict(entries) => {
991 for (key, item) in entries.iter() {
992 let key_idx = self.string_constant(key);
993 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
994 self.emit_vm_value_literal(item);
995 }
996 self.chunk
997 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
998 }
999 _ => {}
1000 }
1001 }
1002
1003 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
1005 let hi = (type_name_idx >> 8) as u8;
1006 let lo = type_name_idx as u8;
1007 self.chunk.code.push(hi);
1008 self.chunk.code.push(lo);
1009 self.chunk.lines.push(self.line);
1010 self.chunk.columns.push(self.column);
1011 self.chunk.lines.push(self.line);
1012 self.chunk.columns.push(self.column);
1013 }
1014
1015 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1017 if body.is_empty() {
1018 self.chunk.emit(Op::Nil, self.line);
1019 } else {
1020 self.compile_scoped_block(body)?;
1021 }
1022 Ok(())
1023 }
1024
1025 pub(super) fn compile_catch_binding(
1027 &mut self,
1028 error_var: &Option<String>,
1029 ) -> Result<(), CompileError> {
1030 if let Some(var_name) = error_var {
1031 self.emit_define_binding(var_name, false);
1032 } else {
1033 self.chunk.emit(Op::Pop, self.line);
1034 }
1035 Ok(())
1036 }
1037
1038 pub(super) fn has_pending_finally(&self) -> bool {
1040 !self.finally_bodies.is_empty()
1041 }
1042
1043 pub(super) fn push_cleanup(&mut self, body: Vec<SNode>) {
1046 let handler_depth = self.handler_depth;
1047 self.handler_depth += 1;
1048 let error_jump = self.chunk.emit_jump(Op::TryCatchPreserve, self.line);
1049 let empty_type = self.string_constant("");
1050 self.emit_type_name_extra(empty_type);
1051 self.finally_bodies.push(FinallyEntry {
1052 body,
1053 handler_depth,
1054 declared_throw: self.declared_throw,
1055 error_jump,
1056 });
1057 }
1058
1059 fn pop_cleanup(&mut self) -> Result<(), CompileError> {
1067 let entry = self.finally_bodies.pop().expect("pending cleanup");
1068 debug_assert_eq!(self.handler_depth, entry.handler_depth + 1);
1069 self.handler_depth = entry.handler_depth;
1070 self.chunk.emit(Op::PopHandler, self.line);
1071 self.compile_finally_inline(&entry.body, entry.declared_throw)?;
1072 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1073
1074 self.chunk.patch_jump(entry.error_jump);
1075 self.temp_counter += 1;
1076 let temp_name = format!("__finally_err_{}__", self.temp_counter);
1077 self.emit_define_binding(&temp_name, true);
1078 self.compile_finally_inline(&entry.body, entry.declared_throw)?;
1079 self.emit_get_binding(&temp_name);
1080 self.chunk.emit(Op::Rethrow, self.line);
1081
1082 self.chunk.patch_jump(end_jump);
1083 Ok(())
1084 }
1085
1086 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
1087 for param in params {
1088 self.define_local_slot(¶m.name, false);
1089 }
1090 }
1091
1092 pub(super) fn mask_param_names(
1098 &mut self,
1099 params: &[TypedParam],
1100 ) -> Vec<(String, super::LocalBinding)> {
1101 let mut removed = Vec::new();
1102 if let Some(scope) = self.local_scopes.last_mut() {
1103 for param in params {
1104 if let Some(binding) = scope.remove(¶m.name) {
1105 removed.push((param.name.clone(), binding));
1106 }
1107 }
1108 }
1109 removed
1110 }
1111
1112 pub(super) fn restore_param_names(&mut self, removed: Vec<(String, super::LocalBinding)>) {
1114 if let Some(scope) = self.local_scopes.last_mut() {
1115 for (name, binding) in removed {
1116 scope.insert(name, binding);
1117 }
1118 }
1119 }
1120
1121 pub(super) fn seed_captured_idents(&mut self, body: &[SNode]) {
1126 let match_patterns = self.lexical_match_pattern_catalog();
1127 self.captured_bindings =
1128 harn_parser::lexical::captured_bindings_in_nested_callables(body, &match_patterns);
1129 }
1130
1131 fn seed_module_captured_idents(&mut self, body: &[SNode]) {
1132 let match_patterns = self.lexical_match_pattern_catalog();
1133 self.captured_bindings =
1134 harn_parser::lexical::captured_bindings_in_compiled_module(body, &match_patterns);
1135 }
1136
1137 pub(super) fn lexical_match_pattern_catalog(
1138 &self,
1139 ) -> harn_parser::lexical::MatchPatternCatalog {
1140 if self.imported_enum_candidates.is_empty() {
1141 return harn_parser::lexical::MatchPatternCatalog::new(
1142 &self.enum_names,
1143 &self.enum_variant_owners,
1144 );
1145 }
1146 let mut enum_names = self.enum_names.clone();
1147 enum_names.extend(self.imported_enum_candidates.iter().cloned());
1148 harn_parser::lexical::MatchPatternCatalog::new(&enum_names, &self.enum_variant_owners)
1149 }
1150
1151 pub(super) fn begin_scope(&mut self) {
1152 self.chunk.emit(Op::PushScope, self.line);
1153 self.scope_depth += 1;
1154 let enum_catalog = self.enum_catalog_snapshot();
1155 self.enum_catalog_scopes.push(enum_catalog);
1156 self.type_scopes.push(std::collections::HashMap::new());
1157 self.local_scopes.push(std::collections::HashMap::new());
1158 }
1159
1160 pub(super) fn end_scope(&mut self) {
1161 if self.scope_depth > 0 {
1162 self.chunk.emit(Op::PopScope, self.line);
1163 self.scope_depth -= 1;
1164 if let Some(snapshot) = self.enum_catalog_scopes.pop() {
1165 self.restore_enum_catalog(snapshot);
1166 }
1167 self.type_scopes.pop();
1168 self.local_scopes.pop();
1169 }
1170 }
1171
1172 pub(super) fn emit_scope_unwind_to(&mut self, target_depth: usize) {
1175 for _ in target_depth..self.scope_depth {
1176 self.chunk.emit(Op::PopScope, self.line);
1177 }
1178 }
1179
1180 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1181 self.begin_scope();
1182 let finally_floor = self.finally_bodies.len();
1183 if stmts.is_empty() {
1184 self.chunk.emit(Op::Nil, self.line);
1185 } else {
1186 self.compile_block(stmts)?;
1187 }
1188 self.drain_finallys_to_floor(finally_floor)?;
1189 self.end_scope();
1190 Ok(())
1191 }
1192
1193 pub(super) fn compile_scoped_statements(
1194 &mut self,
1195 stmts: &[SNode],
1196 ) -> Result<(), CompileError> {
1197 self.begin_scope();
1198 self.record_monomorphic_var_bindings(stmts);
1199 let finally_floor = self.finally_bodies.len();
1200 for sn in stmts {
1201 self.compile_discarded_stmt(sn)?;
1202 }
1203 self.drain_finallys_to_floor(finally_floor)?;
1204 self.end_scope();
1205 Ok(())
1206 }
1207
1208 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
1211 while self.finally_bodies.len() > floor {
1212 self.pop_cleanup()?;
1213 }
1214 Ok(())
1215 }
1216
1217 pub(super) fn run_pending_finallys_for_transfer(
1234 &mut self,
1235 floor: usize,
1236 handler_floor: Option<usize>,
1237 ) -> Result<(), CompileError> {
1238 let saved_entries = self.finally_bodies[floor..].to_vec();
1239 let saved_handler_depth = self.handler_depth;
1240 let result = self.unwind_for_transfer(floor, handler_floor);
1241 self.finally_bodies.truncate(floor);
1242 self.finally_bodies.extend(saved_entries);
1243 self.handler_depth = saved_handler_depth;
1244 result
1245 }
1246
1247 fn unwind_for_transfer(
1248 &mut self,
1249 floor: usize,
1250 handler_floor: Option<usize>,
1251 ) -> Result<(), CompileError> {
1252 while self.finally_bodies.len() > floor {
1253 let entry = self.finally_bodies.pop().expect("non-empty by guard");
1254 self.emit_pop_handlers_to(entry.handler_depth);
1255 self.compile_finally_inline(&entry.body, entry.declared_throw)?;
1256 }
1257 if let Some(handler_floor) = handler_floor {
1258 self.emit_pop_handlers_to(handler_floor);
1259 }
1260 Ok(())
1261 }
1262
1263 fn emit_pop_handlers_to(&mut self, depth: usize) {
1264 while self.handler_depth > depth {
1265 self.chunk.emit(Op::PopHandler, self.line);
1266 self.handler_depth -= 1;
1267 }
1268 }
1269
1270 pub(super) fn maybe_register_owned_drop(
1275 &mut self,
1276 pattern: &harn_parser::BindingPattern,
1277 type_ann: Option<&TypeExpr>,
1278 span: harn_lexer::Span,
1279 ) {
1280 let Some(ty) = type_ann else {
1286 return;
1287 };
1288 if !matches!(ty, TypeExpr::Owned(_)) {
1289 return;
1290 }
1291 let harn_parser::BindingPattern::Identifier(name) = pattern else {
1292 return;
1293 };
1294 if harn_parser::is_discard_name(name) {
1295 return;
1296 }
1297 let call = harn_parser::spanned(
1298 Node::FunctionCall {
1299 name: "drop".to_string(),
1300 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
1301 type_args: Vec::new(),
1302 },
1303 span,
1304 );
1305 self.push_cleanup(vec![call]);
1306 }
1307
1308 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
1324 #[cfg(debug_assertions)]
1325 let probe = self.chunk.balance_probe();
1326 self.compile_node(sn)?;
1327 #[allow(unused_mut)]
1328 let mut produces = Self::produces_value(&sn.node);
1329 #[cfg(test)]
1333 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
1334 produces = forced;
1335 }
1336 #[cfg(debug_assertions)]
1337 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1338 let expected = i32::from(produces);
1339 debug_assert_eq!(
1340 delta, expected,
1341 "operand-stack imbalance at line {}: produces_value={produces} but the \
1342 node's emitted bytecode netted {delta} (expected {expected}). A \
1343 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1344 node: {:?}",
1345 self.line, sn.node,
1346 );
1347 }
1348 if produces {
1349 self.chunk.emit(Op::Pop, self.line);
1350 }
1351 Ok(())
1352 }
1353
1354 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1355 self.record_monomorphic_var_bindings(stmts);
1356 let callable_declarations = stmts
1357 .iter()
1358 .enumerate()
1359 .filter_map(|(index, node)| {
1360 harn_parser::lexical::hoisted_callable_name(node)
1361 .map(|name| (index, name.to_string()))
1362 })
1363 .collect::<Vec<_>>();
1364 let callable_names = callable_declarations
1365 .iter()
1366 .map(|(_, name)| name.as_str())
1367 .collect::<std::collections::HashSet<_>>();
1368 let mut emitted_callables = std::collections::HashSet::new();
1369
1370 for (i, snode) in stmts.iter().enumerate() {
1371 if harn_parser::lexical::hoisted_callable_name(snode).is_some() {
1372 if emitted_callables.insert(i) {
1373 self.compile_discarded_stmt(snode)?;
1374 }
1375 if i == stmts.len() - 1 {
1376 self.chunk.emit(Op::Nil, self.line);
1377 }
1378 continue;
1379 }
1380
1381 let mut pending = Vec::new();
1388 Self::collect_callable_references(snode, &callable_names, &mut pending);
1389 let mut reachable = std::collections::HashSet::new();
1390 while let Some(name) = pending.pop() {
1391 if !reachable.insert(name.clone()) {
1392 continue;
1393 }
1394 for (index, declaration_name) in &callable_declarations {
1395 if declaration_name == &name {
1396 Self::collect_callable_references(
1397 &stmts[*index],
1398 &callable_names,
1399 &mut pending,
1400 );
1401 }
1402 }
1403 }
1404 for (index, name) in &callable_declarations {
1405 if reachable.contains(name) && emitted_callables.insert(*index) {
1406 self.compile_discarded_stmt(&stmts[*index])?;
1407 }
1408 }
1409
1410 if i == stmts.len() - 1 {
1411 self.compile_node(snode)?;
1415 if !Self::produces_value(&snode.node) {
1416 self.chunk.emit(Op::Nil, self.line);
1417 }
1418 } else {
1419 self.compile_discarded_stmt(snode)?;
1420 }
1421 }
1422 Ok(())
1423 }
1424
1425 fn collect_callable_references(
1426 node: &SNode,
1427 callable_names: &std::collections::HashSet<&str>,
1428 out: &mut Vec<String>,
1429 ) {
1430 match &node.node {
1431 Node::Identifier(name) | Node::FunctionCall { name, .. }
1432 if callable_names.contains(name.as_str()) =>
1433 {
1434 out.push(name.clone());
1435 }
1436 _ => {}
1437 }
1438 for child in harn_parser::visit::immediate_children(node) {
1439 Self::collect_callable_references(child, callable_names, out);
1440 }
1441 }
1442
1443 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1445 self.begin_scope();
1446 let finally_floor = self.finally_bodies.len();
1447 if body.is_empty() {
1448 self.chunk.emit(Op::Nil, self.line);
1449 } else {
1450 self.compile_block(body)?;
1451 if !Self::produces_value(&body.last().unwrap().node) {
1452 self.chunk.emit(Op::Nil, self.line);
1453 }
1454 }
1455 self.drain_finallys_to_floor(finally_floor)?;
1456 self.end_scope();
1457 Ok(())
1458 }
1459
1460 pub(super) fn emit_compound_op(&mut self, op: BinaryOperator) -> Result<(), CompileError> {
1462 match op {
1463 BinaryOperator::Add => self.chunk.emit(Op::Add, self.line),
1464 BinaryOperator::Sub => self.chunk.emit(Op::Sub, self.line),
1465 BinaryOperator::Mul => self.chunk.emit(Op::Mul, self.line),
1466 BinaryOperator::Div => self.chunk.emit(Op::Div, self.line),
1467 BinaryOperator::Mod => self.chunk.emit(Op::Mod, self.line),
1468 _ => {
1469 return Err(CompileError {
1470 message: format!("Unknown compound operator: {op}"),
1471 line: self.line,
1472 })
1473 }
1474 }
1475 Ok(())
1476 }
1477
1478 pub(super) fn produces_value(node: &Node) -> bool {
1480 harn_parser::node_produces_value(node)
1481 }
1482}
1483
1484impl Default for Compiler {
1485 fn default() -> Self {
1486 Self::new()
1487 }
1488}