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