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 finally_bodies: Vec::new(),
157 temp_counter: 0,
158 scope_depth: 0,
159 type_aliases: std::collections::HashMap::new(),
160 type_scopes: vec![std::collections::HashMap::new()],
161 monomorphic_bindings: std::collections::HashSet::new(),
162 string_constants: std::collections::HashMap::new(),
163 local_scopes: vec![std::collections::HashMap::new()],
164 module_level: true,
165 captured_bindings: std::collections::HashSet::new(),
166 namespace_import_demands: std::collections::BTreeMap::new(),
167 }
168 }
169
170 pub(super) fn for_nested_body(options: CompilerOptions) -> Self {
174 let mut c = Self::with_options(options);
175 c.module_level = false;
176 c
177 }
178
179 pub(super) fn nested_body(&self) -> Self {
180 let mut nested = Self::for_nested_body(self.options);
181 nested.source_callable_names = self.source_callable_names.clone();
182 nested
183 }
184
185 pub(super) fn nominal_type_names(&self) -> Vec<String> {
186 let mut names: Vec<String> = self
187 .struct_layouts
188 .keys()
189 .chain(self.enum_names.iter())
190 .cloned()
191 .collect();
192 names.sort();
193 names.dedup();
194 names
195 }
196
197 pub(super) fn string_constant(&mut self, value: &str) -> u16 {
198 if let Some(idx) = self.string_constants.get(value) {
199 return *idx;
200 }
201 let owned = value.to_string();
202 let idx = self.chunk.add_constant(Constant::String(owned.clone()));
203 self.string_constants.insert(owned, idx);
204 idx
205 }
206
207 pub(super) fn owned_string_constant(&mut self, value: String) -> u16 {
208 if let Some(idx) = self.string_constants.get(value.as_str()) {
209 return *idx;
210 }
211 let idx = self.chunk.add_constant(Constant::String(value.clone()));
212 self.string_constants.insert(value, idx);
213 idx
214 }
215
216 #[doc(hidden)]
220 pub fn collect_type_aliases(&mut self, program: &[SNode]) {
221 for sn in program {
222 match peel_node(sn) {
223 Node::SelectiveImport { names, .. } => {
224 for name in names {
225 self.type_aliases.entry(name.clone()).or_insert_with(|| {
226 super::TypeAliasDefinition {
227 type_params: Vec::new(),
228 body: None,
229 }
230 });
231 }
232 }
233 Node::TypeDecl {
234 name,
235 type_expr,
236 type_params,
237 is_pub: _,
238 } => {
239 self.type_aliases.insert(
240 name.clone(),
241 super::TypeAliasDefinition {
242 type_params: type_params.clone(),
243 body: Some(type_expr.clone()),
244 },
245 );
246 }
247 _ => {}
248 }
249 }
250 }
251
252 #[doc(hidden)]
262 pub fn expand_alias(&self, ty: &TypeExpr) -> TypeExpr {
263 let mut visiting = std::collections::HashSet::new();
264 self.expand_alias_inner(ty, &mut visiting)
265 }
266
267 fn expand_alias_inner(
268 &self,
269 ty: &TypeExpr,
270 visiting: &mut std::collections::HashSet<String>,
271 ) -> TypeExpr {
272 match ty {
273 TypeExpr::Named(name) => {
274 if let Some(target) = self
275 .type_aliases
276 .get(name)
277 .filter(|alias| alias.type_params.is_empty() && alias.body.is_some())
278 {
279 if !visiting.insert(name.clone()) {
280 return TypeExpr::Named(name.clone());
281 }
282 let resolved = self.expand_alias_inner(target.body.as_ref().unwrap(), visiting);
283 visiting.remove(name);
284 resolved
285 } else {
286 TypeExpr::Named(name.clone())
287 }
288 }
289 TypeExpr::Union(types) => TypeExpr::Union(
290 types
291 .iter()
292 .map(|t| self.expand_alias_inner(t, visiting))
293 .collect(),
294 ),
295 TypeExpr::Intersection(types) => TypeExpr::Intersection(
296 types
297 .iter()
298 .map(|t| self.expand_alias_inner(t, visiting))
299 .collect(),
300 ),
301 TypeExpr::Shape(fields) => TypeExpr::Shape(
302 fields
303 .iter()
304 .map(|field| ShapeField {
305 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
306 ..field.clone()
307 })
308 .collect(),
309 ),
310 TypeExpr::OpenShape { fields, rests } => TypeExpr::OpenShape {
311 fields: fields
312 .iter()
313 .map(|field| ShapeField {
314 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
315 ..field.clone()
316 })
317 .collect(),
318 rests: rests
319 .iter()
320 .map(|r| self.expand_alias_inner(r, visiting))
321 .collect(),
322 },
323 TypeExpr::List(inner) => {
324 TypeExpr::List(Box::new(self.expand_alias_inner(inner, visiting)))
325 }
326 TypeExpr::Tuple(elements) => TypeExpr::Tuple(
327 elements
328 .iter()
329 .map(|element| self.expand_alias_inner(element, visiting))
330 .collect(),
331 ),
332 TypeExpr::Iter(inner) => {
333 TypeExpr::Iter(Box::new(self.expand_alias_inner(inner, visiting)))
334 }
335 TypeExpr::Generator(inner) => {
336 TypeExpr::Generator(Box::new(self.expand_alias_inner(inner, visiting)))
337 }
338 TypeExpr::Stream(inner) => {
339 TypeExpr::Stream(Box::new(self.expand_alias_inner(inner, visiting)))
340 }
341 TypeExpr::DictType(k, v) => TypeExpr::DictType(
342 Box::new(self.expand_alias_inner(k, visiting)),
343 Box::new(self.expand_alias_inner(v, visiting)),
344 ),
345 TypeExpr::FnType {
346 params,
347 return_type,
348 } => TypeExpr::FnType {
349 params: params
350 .iter()
351 .map(|p| self.expand_alias_inner(p, visiting))
352 .collect(),
353 return_type: Box::new(self.expand_alias_inner(return_type, visiting)),
354 },
355 TypeExpr::Applied { name, args } => {
356 let args = args
357 .iter()
358 .map(|arg| self.expand_alias_inner(arg, visiting))
359 .collect::<Vec<_>>();
360 let Some(alias) = self.type_aliases.get(name) else {
361 return TypeExpr::Applied {
362 name: name.clone(),
363 args,
364 };
365 };
366 let Some(body) = alias.body.as_ref() else {
367 return TypeExpr::Applied {
368 name: name.clone(),
369 args,
370 };
371 };
372 if alias.type_params.len() != args.len() || !visiting.insert(name.clone()) {
373 return TypeExpr::Applied {
374 name: name.clone(),
375 args,
376 };
377 }
378 let bindings = alias
379 .type_params
380 .iter()
381 .zip(args.iter().cloned())
382 .map(|(param, arg)| (param.name.clone(), arg))
383 .collect();
384 let instantiated = substitute_type_expr(body, &bindings);
385 let resolved = self.expand_alias_inner(&instantiated, visiting);
386 visiting.remove(name);
387 resolved
388 }
389 TypeExpr::Never => TypeExpr::Never,
390 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
391 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
392 TypeExpr::Owned(inner) => {
393 TypeExpr::Owned(Box::new(self.expand_alias_inner(inner, visiting)))
394 }
395 }
396 }
397
398 pub fn compile_public_type_schema_initializers(
405 program: &[SNode],
406 source_file: Option<String>,
407 ) -> Result<Vec<Chunk>, CompileError> {
408 Self::compile_selected_public_type_schema_initializers(program, source_file, None)
409 }
410
411 pub fn compile_selected_public_type_schema_initializers(
415 program: &[SNode],
416 source_file: Option<String>,
417 selected_names: Option<&std::collections::BTreeSet<String>>,
418 ) -> Result<Vec<Chunk>, CompileError> {
419 let mut compiler = Compiler::new();
420 compiler.collect_type_aliases(program);
421 let mut chunks = Vec::new();
422 for sn in program {
423 let Node::TypeDecl {
424 name, is_pub: true, ..
425 } = peel_node(sn)
426 else {
427 continue;
428 };
429 if selected_names.is_some_and(|selected| !selected.contains(name)) {
430 continue;
431 }
432 compiler.chunk = Chunk::new();
433 compiler.string_constants.clear();
434 compiler.chunk.source_file.clone_from(&source_file);
435 if compiler.emit_schema_for_alias(name) {
436 compiler.emit_define_binding(name, false);
437 compiler.chunk.emit(Op::Nil, compiler.line);
438 compiler.chunk.emit(Op::Return, compiler.line);
439 super::ensure_chunk_addressable(
440 &compiler.chunk,
441 &format!("the public type-schema initializer for `{name}`"),
442 compiler.line,
443 )?;
444 chunks.push(std::mem::take(&mut compiler.chunk));
445 }
446 }
447 Ok(chunks)
448 }
449
450 pub(super) fn is_schema_guard(name: &str) -> bool {
454 matches!(
455 name,
456 "schema_is"
457 | "schema_expect"
458 | "schema_parse"
459 | "schema_check"
460 | "schema_report"
461 | "is_type"
462 | "json_validate"
463 )
464 }
465
466 pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
469 matches!(
470 &key.node,
471 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
472 if name == keyword
473 )
474 }
475
476 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
479 self.prepare_module_context(program);
482
483 for sn in program {
484 match &sn.node {
485 Node::ImportDecl { .. }
486 | Node::SelectiveImport { .. }
487 | Node::NamespaceImport { .. } => {
488 self.compile_node(sn)?;
489 }
490 _ => {}
491 }
492 }
493 let main = program
494 .iter()
495 .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
496 .or_else(|| {
497 program
498 .iter()
499 .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
500 });
501
502 let mut pipeline_emits_value = false;
506 if let Some(sn) = main {
507 self.compile_top_level_declarations(program)?;
508 if let Node::Pipeline {
509 params,
510 body,
511 extends,
512 ..
513 } = peel_node(sn)
514 {
515 self.compile_with_pipeline_captures(
516 program,
517 body,
518 extends.as_deref(),
519 |compiler| {
520 let saved = std::mem::replace(&mut compiler.module_level, false);
521 if let Some(harness) = params.first().filter(|param| {
522 matches!(
523 param.type_expr.as_ref(),
524 Some(TypeExpr::Named(name)) if name == "Harness"
525 )
526 }) {
527 compiler.chunk.emit(Op::RootHarness, compiler.line);
528 compiler.emit_define_binding(&harness.name, false);
529 }
530 if let Some(parent_name) = extends {
531 compiler.compile_parent_pipeline(program, parent_name)?;
532 }
533 let result = compiler.compile_block(body);
534 compiler.module_level = saved;
535 result
536 },
537 )?;
538 pipeline_emits_value = true;
539 }
540 } else {
541 let top_level: Vec<&SNode> = program
543 .iter()
544 .filter(|sn| {
545 !matches!(
546 &sn.node,
547 Node::ImportDecl { .. }
548 | Node::SelectiveImport { .. }
549 | Node::NamespaceImport { .. }
550 )
551 })
552 .collect();
553 for sn in &top_level {
554 self.compile_discarded_stmt(sn)?;
555 }
556 if Self::has_top_level_fn_main(program) {
561 self.chunk.emit(Op::RootHarness, self.line);
562 self.emit_named_call("main", 1);
563 pipeline_emits_value = true;
564 }
565 }
566
567 self.drain_finallys_to_floor(0)?;
568 if !pipeline_emits_value {
569 self.chunk.emit(Op::Nil, self.line);
570 }
571 self.chunk.emit(Op::Return, self.line);
572 super::ensure_chunk_addressable(&self.chunk, "the program body", self.line)?;
573 Ok(self.chunk)
574 }
575
576 fn has_top_level_fn_main(program: &[SNode]) -> bool {
580 program
581 .iter()
582 .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
583 }
584
585 pub fn compile_named(
587 self,
588 program: &[SNode],
589 pipeline_name: &str,
590 ) -> Result<Chunk, CompileError> {
591 self.compile_named_inner(program, pipeline_name)
592 }
593
594 fn compile_named_inner(
595 mut self,
596 program: &[SNode],
597 pipeline_name: &str,
598 ) -> Result<Chunk, CompileError> {
599 self.prepare_module_context(program);
600
601 for sn in program {
602 if matches!(
603 &sn.node,
604 Node::ImportDecl { .. }
605 | Node::SelectiveImport { .. }
606 | Node::NamespaceImport { .. }
607 ) {
608 self.compile_node(sn)?;
609 }
610 }
611 let target = program.iter().find(
612 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
613 );
614
615 if let Some(sn) = target {
616 self.compile_top_level_declarations(program)?;
617 if let Node::Pipeline {
618 body,
619 extends,
620 params,
621 ..
622 } = peel_node(sn)
623 {
624 self.compile_with_pipeline_captures(
625 program,
626 body,
627 extends.as_deref(),
628 |compiler| {
629 let saved = std::mem::replace(&mut compiler.module_level, false);
630 if let Some(harness) = params.first().filter(|param| {
631 matches!(
632 param.type_expr.as_ref(),
633 Some(TypeExpr::Named(name)) if name == "Harness"
634 )
635 }) {
636 compiler.chunk.emit(Op::RootHarness, compiler.line);
637 compiler.emit_define_binding(&harness.name, false);
638 }
639 if let Some(parent_name) = extends {
640 compiler.compile_parent_pipeline(program, parent_name)?;
641 }
642 let result = compiler.compile_block(body);
643 compiler.module_level = saved;
644 result
645 },
646 )?;
647 }
648 }
649
650 self.drain_finallys_to_floor(0)?;
651 self.chunk.emit(Op::Nil, self.line);
652 self.chunk.emit(Op::Return, self.line);
653 super::ensure_chunk_addressable(&self.chunk, "the pipeline body", self.line)?;
654 Ok(self.chunk)
655 }
656
657 pub(super) fn emit_default_preamble(
662 &mut self,
663 params: &[TypedParam],
664 ) -> Result<(), CompileError> {
665 for (i, param) in params.iter().enumerate() {
666 if let Some(default_expr) = ¶m.default_value {
667 self.chunk.emit(Op::GetArgc, self.line);
668 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
669 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
670 self.chunk.emit(Op::GreaterEqual, self.line);
671 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
672 self.chunk.emit(Op::Pop, self.line);
674 let masked = self.mask_param_names(¶ms[i..]);
683 let result = self.compile_node(default_expr);
684 self.restore_param_names(masked);
685 result?;
686 self.emit_init_or_define_binding(¶m.name, false);
687 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
688 self.chunk.patch_jump(skip_jump);
689 self.chunk.emit(Op::Pop, self.line);
690 self.chunk.patch_jump(end_jump);
691 }
692 }
693 Ok(())
694 }
695
696 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
703 for (param_index, param) in params.iter().enumerate() {
704 if let Some(type_expr) = ¶m.type_expr {
705 let check_type = if param.rest {
706 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
707 } else {
708 type_expr.clone()
709 };
710
711 if let harn_parser::TypeExpr::Named(name) = &check_type {
712 if let Some(methods) = self.interface_methods.get(name).cloned() {
713 let fn_idx = self.string_constant("__assert_interface");
714 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
715 self.emit_get_binding(¶m.name);
716 let name_idx = self.string_constant(¶m.name);
717 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
718 let iface_idx = self.string_constant(name);
719 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
720 let methods_str = methods.join(",");
721 let methods_idx = self.owned_string_constant(methods_str);
722 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
723 self.chunk.emit_u8(Op::Call, 4, self.line);
724 self.chunk.emit(Op::Pop, self.line);
725 continue;
726 }
727 }
728
729 if param.default_value.is_some() {
730 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
731 self.emit_default_param_schema_check(param_index, param, &schema);
732 }
733 }
734 }
735 }
736 }
737
738 fn emit_default_param_schema_check(
739 &mut self,
740 param_index: usize,
741 param: &TypedParam,
742 schema: &VmValue,
743 ) {
744 self.chunk.emit(Op::GetArgc, self.line);
745 let threshold_idx = self
746 .chunk
747 .add_constant(Constant::Int((param_index + 1) as i64));
748 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
749 self.chunk.emit(Op::GreaterEqual, self.line);
750 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
751 self.chunk.emit(Op::Pop, self.line);
752 self.emit_schema_assert_call(param, schema);
753 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
754 self.chunk.patch_jump(supplied_jump);
755 self.chunk.emit(Op::Pop, self.line);
756 self.chunk.patch_jump(end_jump);
757 }
758
759 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
760 let fn_idx = self.string_constant("__assert_schema");
761 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
762 self.emit_get_binding(¶m.name);
763 let name_idx = self.string_constant(¶m.name);
764 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
765 self.emit_vm_value_literal(schema);
766 self.chunk.emit_u8(Op::Call, 3, self.line);
767 self.chunk.emit(Op::Pop, self.line);
768 }
769
770 #[doc(hidden)]
771 pub fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
772 match type_expr {
773 harn_parser::TypeExpr::Named(name) => match name.as_str() {
774 "any" | "unknown" => Some(VmValue::dict(BTreeMap::<String, VmValue>::new())),
775 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
776 | "closure" | "bytes" => Some(VmValue::dict(BTreeMap::from([(
777 "type".to_string(),
778 VmValue::String(arcstr::ArcStr::from(name.as_str())),
779 )]))),
780 _ => None,
781 },
782 harn_parser::TypeExpr::Shape(fields) => {
783 let mut properties = BTreeMap::new();
784 let mut required = Vec::new();
785 for field in fields {
786 let mut field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
787 if field.optional {
788 field_schema = VmValue::dict(BTreeMap::from([(
789 "union".to_string(),
790 VmValue::List(std::sync::Arc::new(vec![
791 field_schema,
792 VmValue::dict(BTreeMap::from([(
793 "type".to_string(),
794 VmValue::String(arcstr::ArcStr::from("nil")),
795 )])),
796 ])),
797 )]));
798 }
799 properties.insert(field.name.clone(), field_schema);
800 if !field.optional {
801 required.push(VmValue::String(arcstr::ArcStr::from(field.name.as_str())));
802 }
803 }
804 let mut out = BTreeMap::new();
805 out.put_str("type", "dict");
806 out.insert("properties".to_string(), VmValue::dict(properties));
807 if !required.is_empty() {
808 out.insert(
809 "required".to_string(),
810 VmValue::List(std::sync::Arc::new(required)),
811 );
812 }
813 Some(VmValue::dict(out))
814 }
815 harn_parser::TypeExpr::OpenShape { .. } => None,
816 harn_parser::TypeExpr::List(inner) => {
817 let mut out = BTreeMap::new();
818 out.put_str("type", "list");
819 let item_schema = Self::type_expr_to_schema_value(inner)?;
820 out.insert("items".to_string(), item_schema);
821 Some(VmValue::dict(out))
822 }
823 harn_parser::TypeExpr::Tuple(_) => None,
829 harn_parser::TypeExpr::DictType(key, value) => {
830 let mut out = BTreeMap::new();
831 out.put_str("type", "dict");
832 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
833 let value_schema = Self::type_expr_to_schema_value(value)?;
834 out.insert("additional_properties".to_string(), value_schema);
835 }
836 Some(VmValue::dict(out))
837 }
838 harn_parser::TypeExpr::Union(members) => {
839 if !members.is_empty()
844 && members
845 .iter()
846 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
847 {
848 let values = members
849 .iter()
850 .map(|m| match m {
851 harn_parser::TypeExpr::LitString(s) => {
852 VmValue::String(arcstr::ArcStr::from(s.as_str()))
853 }
854 _ => unreachable!(),
855 })
856 .collect::<Vec<_>>();
857 return Some(VmValue::dict(BTreeMap::from([
858 (
859 "type".to_string(),
860 VmValue::String(arcstr::ArcStr::from("string")),
861 ),
862 (
863 "enum".to_string(),
864 VmValue::List(std::sync::Arc::new(values)),
865 ),
866 ])));
867 }
868 if !members.is_empty()
869 && members
870 .iter()
871 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
872 {
873 let values = members
874 .iter()
875 .map(|m| match m {
876 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
877 _ => unreachable!(),
878 })
879 .collect::<Vec<_>>();
880 return Some(VmValue::dict(BTreeMap::from([
881 (
882 "type".to_string(),
883 VmValue::String(arcstr::ArcStr::from("int")),
884 ),
885 (
886 "enum".to_string(),
887 VmValue::List(std::sync::Arc::new(values)),
888 ),
889 ])));
890 }
891 let branches = members
892 .iter()
893 .map(Self::type_expr_to_schema_value)
894 .collect::<Option<Vec<_>>>()?;
895 if branches.is_empty() {
896 None
897 } else {
898 Some(VmValue::dict(BTreeMap::from([(
899 "union".to_string(),
900 VmValue::List(std::sync::Arc::new(branches)),
901 )])))
902 }
903 }
904 harn_parser::TypeExpr::Intersection(members) => {
905 let branches = members
909 .iter()
910 .map(Self::type_expr_to_schema_value)
911 .collect::<Option<Vec<_>>>()?;
912 if branches.is_empty() {
913 None
914 } else {
915 Some(VmValue::dict(BTreeMap::from([(
916 "all_of".to_string(),
917 VmValue::List(std::sync::Arc::new(branches)),
918 )])))
919 }
920 }
921 harn_parser::TypeExpr::FnType { .. } => Some(VmValue::dict(BTreeMap::from([(
922 "type".to_string(),
923 VmValue::String(arcstr::ArcStr::from("closure")),
924 )]))),
925 harn_parser::TypeExpr::Applied { .. } => None,
926 harn_parser::TypeExpr::Iter(_)
927 | harn_parser::TypeExpr::Generator(_)
928 | harn_parser::TypeExpr::Stream(_) => None,
929 harn_parser::TypeExpr::Never => None,
930 harn_parser::TypeExpr::LitString(s) => Some(VmValue::dict(BTreeMap::from([
931 (
932 "type".to_string(),
933 VmValue::String(arcstr::ArcStr::from("string")),
934 ),
935 (
936 "const".to_string(),
937 VmValue::String(arcstr::ArcStr::from(s.as_str())),
938 ),
939 ]))),
940 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::dict(BTreeMap::from([
941 (
942 "type".to_string(),
943 VmValue::String(arcstr::ArcStr::from("int")),
944 ),
945 ("const".to_string(), VmValue::Int(*v)),
946 ]))),
947 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
948 }
949 }
950
951 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
952 match value {
953 VmValue::String(text) => {
954 let idx = self.string_constant(text);
955 self.chunk.emit_u16(Op::Constant, idx, self.line);
956 }
957 VmValue::Int(number) => {
958 let idx = self.chunk.add_constant(Constant::Int(*number));
959 self.chunk.emit_u16(Op::Constant, idx, self.line);
960 }
961 VmValue::Float(number) => {
962 let idx = self.chunk.add_constant(Constant::Float(*number));
963 self.chunk.emit_u16(Op::Constant, idx, self.line);
964 }
965 VmValue::Bool(value) => {
966 let idx = self.chunk.add_constant(Constant::Bool(*value));
967 self.chunk.emit_u16(Op::Constant, idx, self.line);
968 }
969 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
970 VmValue::List(items) => {
971 for item in items.iter() {
972 self.emit_vm_value_literal(item);
973 }
974 self.chunk
975 .emit_u16(Op::BuildList, items.len() as u16, self.line);
976 }
977 VmValue::Dict(entries) => {
978 for (key, item) in entries.iter() {
979 let key_idx = self.string_constant(key);
980 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
981 self.emit_vm_value_literal(item);
982 }
983 self.chunk
984 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
985 }
986 _ => {}
987 }
988 }
989
990 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
992 let hi = (type_name_idx >> 8) as u8;
993 let lo = type_name_idx as u8;
994 self.chunk.code.push(hi);
995 self.chunk.code.push(lo);
996 self.chunk.lines.push(self.line);
997 self.chunk.columns.push(self.column);
998 self.chunk.lines.push(self.line);
999 self.chunk.columns.push(self.column);
1000 }
1001
1002 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1004 if body.is_empty() {
1005 self.chunk.emit(Op::Nil, self.line);
1006 } else {
1007 self.compile_scoped_block(body)?;
1008 }
1009 Ok(())
1010 }
1011
1012 pub(super) fn compile_catch_binding(
1014 &mut self,
1015 error_var: &Option<String>,
1016 ) -> Result<(), CompileError> {
1017 if let Some(var_name) = error_var {
1018 self.emit_define_binding(var_name, false);
1019 } else {
1020 self.chunk.emit(Op::Pop, self.line);
1021 }
1022 Ok(())
1023 }
1024
1025 pub(super) fn compile_finally_inline(
1032 &mut self,
1033 finally_body: &[SNode],
1034 ) -> Result<(), CompileError> {
1035 if !finally_body.is_empty() {
1036 self.compile_scoped_block(finally_body)?;
1037 self.chunk.emit(Op::Pop, self.line);
1038 }
1039 Ok(())
1040 }
1041
1042 pub(super) fn has_pending_finally_until_barrier(&self) -> bool {
1046 self.finally_bodies
1047 .iter()
1048 .rev()
1049 .take_while(|entry| !matches!(entry, FinallyEntry::CatchBarrier))
1050 .any(|entry| matches!(entry, FinallyEntry::Finally(_)))
1051 }
1052
1053 pub(super) fn has_pending_finally(&self) -> bool {
1055 self.finally_bodies
1056 .iter()
1057 .any(|e| matches!(e, FinallyEntry::Finally(_)))
1058 }
1059
1060 pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
1069 self.temp_counter += 1;
1070 let temp_name = format!("__finally_err_{}__", self.temp_counter);
1071 self.emit_define_binding(&temp_name, true);
1072 self.emit_get_binding(&temp_name);
1073 self.chunk.emit(Op::Throw, self.line);
1074 Ok(())
1075 }
1076
1077 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
1078 for param in params {
1079 self.define_local_slot(¶m.name, false);
1080 }
1081 }
1082
1083 fn mask_param_names(&mut self, params: &[TypedParam]) -> Vec<(String, super::LocalBinding)> {
1089 let mut removed = Vec::new();
1090 if let Some(scope) = self.local_scopes.last_mut() {
1091 for param in params {
1092 if let Some(binding) = scope.remove(¶m.name) {
1093 removed.push((param.name.clone(), binding));
1094 }
1095 }
1096 }
1097 removed
1098 }
1099
1100 fn restore_param_names(&mut self, removed: Vec<(String, super::LocalBinding)>) {
1102 if let Some(scope) = self.local_scopes.last_mut() {
1103 for (name, binding) in removed {
1104 scope.insert(name, binding);
1105 }
1106 }
1107 }
1108
1109 pub(super) fn seed_captured_idents(&mut self, body: &[SNode]) {
1114 let match_patterns = self.lexical_match_pattern_catalog();
1115 self.captured_bindings =
1116 harn_parser::lexical::captured_bindings_in_nested_callables(body, &match_patterns);
1117 }
1118
1119 fn seed_module_captured_idents(&mut self, body: &[SNode]) {
1120 let match_patterns = self.lexical_match_pattern_catalog();
1121 self.captured_bindings =
1122 harn_parser::lexical::captured_bindings_in_compiled_module(body, &match_patterns);
1123 }
1124
1125 pub(super) fn lexical_match_pattern_catalog(
1126 &self,
1127 ) -> harn_parser::lexical::MatchPatternCatalog {
1128 if self.imported_enum_candidates.is_empty() {
1129 return harn_parser::lexical::MatchPatternCatalog::new(
1130 &self.enum_names,
1131 &self.enum_variant_owners,
1132 );
1133 }
1134 let mut enum_names = self.enum_names.clone();
1135 enum_names.extend(self.imported_enum_candidates.iter().cloned());
1136 harn_parser::lexical::MatchPatternCatalog::new(&enum_names, &self.enum_variant_owners)
1137 }
1138
1139 pub(super) fn begin_scope(&mut self) {
1140 self.chunk.emit(Op::PushScope, self.line);
1141 self.scope_depth += 1;
1142 let enum_catalog = self.enum_catalog_snapshot();
1143 self.enum_catalog_scopes.push(enum_catalog);
1144 self.type_scopes.push(std::collections::HashMap::new());
1145 self.local_scopes.push(std::collections::HashMap::new());
1146 }
1147
1148 pub(super) fn end_scope(&mut self) {
1149 if self.scope_depth > 0 {
1150 self.chunk.emit(Op::PopScope, self.line);
1151 self.scope_depth -= 1;
1152 if let Some(snapshot) = self.enum_catalog_scopes.pop() {
1153 self.restore_enum_catalog(snapshot);
1154 }
1155 self.type_scopes.pop();
1156 self.local_scopes.pop();
1157 }
1158 }
1159
1160 pub(super) fn emit_scope_unwind_to(&mut self, target_depth: usize) {
1163 for _ in target_depth..self.scope_depth {
1164 self.chunk.emit(Op::PopScope, self.line);
1165 }
1166 }
1167
1168 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1169 self.begin_scope();
1170 let finally_floor = self.finally_bodies.len();
1171 if stmts.is_empty() {
1172 self.chunk.emit(Op::Nil, self.line);
1173 } else {
1174 self.compile_block(stmts)?;
1175 }
1176 self.drain_finallys_to_floor(finally_floor)?;
1177 self.end_scope();
1178 Ok(())
1179 }
1180
1181 pub(super) fn compile_scoped_statements(
1182 &mut self,
1183 stmts: &[SNode],
1184 ) -> Result<(), CompileError> {
1185 self.begin_scope();
1186 self.record_monomorphic_var_bindings(stmts);
1187 let finally_floor = self.finally_bodies.len();
1188 for sn in stmts {
1189 self.compile_discarded_stmt(sn)?;
1190 }
1191 self.drain_finallys_to_floor(finally_floor)?;
1192 self.end_scope();
1193 Ok(())
1194 }
1195
1196 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
1201 while self.finally_bodies.len() > floor {
1202 let entry = self.finally_bodies.pop().expect("non-empty by guard");
1203 if let FinallyEntry::Finally(body) = entry {
1204 self.compile_finally_inline(&body)?;
1205 }
1206 }
1207 Ok(())
1208 }
1209
1210 pub(super) fn run_pending_finallys_for_transfer(
1223 &mut self,
1224 floor: usize,
1225 ) -> Result<(), CompileError> {
1226 if self.finally_bodies.len() <= floor {
1227 return Ok(());
1228 }
1229 let saved = self.finally_bodies[floor..].to_vec();
1230 let result = self.drain_finallys_to_floor(floor);
1231 self.finally_bodies.extend(saved);
1232 result
1233 }
1234
1235 pub(super) fn run_pending_finallys_until_barrier(&mut self) -> Result<(), CompileError> {
1240 let floor = self
1241 .finally_bodies
1242 .iter()
1243 .rposition(|e| matches!(e, FinallyEntry::CatchBarrier))
1244 .map(|i| i + 1)
1245 .unwrap_or(0);
1246 self.run_pending_finallys_for_transfer(floor)
1247 }
1248
1249 pub(super) fn maybe_register_owned_drop(
1254 &mut self,
1255 pattern: &harn_parser::BindingPattern,
1256 type_ann: Option<&TypeExpr>,
1257 span: harn_lexer::Span,
1258 ) {
1259 let Some(ty) = type_ann else {
1265 return;
1266 };
1267 if !matches!(ty, TypeExpr::Owned(_)) {
1268 return;
1269 }
1270 let harn_parser::BindingPattern::Identifier(name) = pattern else {
1271 return;
1272 };
1273 if harn_parser::is_discard_name(name) {
1274 return;
1275 }
1276 let call = harn_parser::spanned(
1277 Node::FunctionCall {
1278 name: "drop".to_string(),
1279 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
1280 type_args: Vec::new(),
1281 },
1282 span,
1283 );
1284 self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
1285 }
1286
1287 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
1303 #[cfg(debug_assertions)]
1304 let probe = self.chunk.balance_probe();
1305 self.compile_node(sn)?;
1306 #[allow(unused_mut)]
1307 let mut produces = Self::produces_value(&sn.node);
1308 #[cfg(test)]
1312 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
1313 produces = forced;
1314 }
1315 #[cfg(debug_assertions)]
1316 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1317 let expected = i32::from(produces);
1318 debug_assert_eq!(
1319 delta, expected,
1320 "operand-stack imbalance at line {}: produces_value={produces} but the \
1321 node's emitted bytecode netted {delta} (expected {expected}). A \
1322 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1323 node: {:?}",
1324 self.line, sn.node,
1325 );
1326 }
1327 if produces {
1328 self.chunk.emit(Op::Pop, self.line);
1329 }
1330 Ok(())
1331 }
1332
1333 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1334 self.record_monomorphic_var_bindings(stmts);
1335 let callable_declarations = stmts
1336 .iter()
1337 .enumerate()
1338 .filter_map(|(index, node)| {
1339 harn_parser::lexical::hoisted_callable_name(node)
1340 .map(|name| (index, name.to_string()))
1341 })
1342 .collect::<Vec<_>>();
1343 let callable_names = callable_declarations
1344 .iter()
1345 .map(|(_, name)| name.as_str())
1346 .collect::<std::collections::HashSet<_>>();
1347 let mut emitted_callables = std::collections::HashSet::new();
1348
1349 for (i, snode) in stmts.iter().enumerate() {
1350 if harn_parser::lexical::hoisted_callable_name(snode).is_some() {
1351 if emitted_callables.insert(i) {
1352 self.compile_discarded_stmt(snode)?;
1353 }
1354 if i == stmts.len() - 1 {
1355 self.chunk.emit(Op::Nil, self.line);
1356 }
1357 continue;
1358 }
1359
1360 let mut pending = Vec::new();
1367 Self::collect_callable_references(snode, &callable_names, &mut pending);
1368 let mut reachable = std::collections::HashSet::new();
1369 while let Some(name) = pending.pop() {
1370 if !reachable.insert(name.clone()) {
1371 continue;
1372 }
1373 for (index, declaration_name) in &callable_declarations {
1374 if declaration_name == &name {
1375 Self::collect_callable_references(
1376 &stmts[*index],
1377 &callable_names,
1378 &mut pending,
1379 );
1380 }
1381 }
1382 }
1383 for (index, name) in &callable_declarations {
1384 if reachable.contains(name) && emitted_callables.insert(*index) {
1385 self.compile_discarded_stmt(&stmts[*index])?;
1386 }
1387 }
1388
1389 if i == stmts.len() - 1 {
1390 self.compile_node(snode)?;
1394 if !Self::produces_value(&snode.node) {
1395 self.chunk.emit(Op::Nil, self.line);
1396 }
1397 } else {
1398 self.compile_discarded_stmt(snode)?;
1399 }
1400 }
1401 Ok(())
1402 }
1403
1404 fn collect_callable_references(
1405 node: &SNode,
1406 callable_names: &std::collections::HashSet<&str>,
1407 out: &mut Vec<String>,
1408 ) {
1409 match &node.node {
1410 Node::Identifier(name) | Node::FunctionCall { name, .. }
1411 if callable_names.contains(name.as_str()) =>
1412 {
1413 out.push(name.clone());
1414 }
1415 _ => {}
1416 }
1417 for child in harn_parser::visit::immediate_children(node) {
1418 Self::collect_callable_references(child, callable_names, out);
1419 }
1420 }
1421
1422 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1424 self.begin_scope();
1425 let finally_floor = self.finally_bodies.len();
1426 if body.is_empty() {
1427 self.chunk.emit(Op::Nil, self.line);
1428 } else {
1429 self.compile_block(body)?;
1430 if !Self::produces_value(&body.last().unwrap().node) {
1431 self.chunk.emit(Op::Nil, self.line);
1432 }
1433 }
1434 self.drain_finallys_to_floor(finally_floor)?;
1435 self.end_scope();
1436 Ok(())
1437 }
1438
1439 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1441 match op {
1442 "+" => self.chunk.emit(Op::Add, self.line),
1443 "-" => self.chunk.emit(Op::Sub, self.line),
1444 "*" => self.chunk.emit(Op::Mul, self.line),
1445 "/" => self.chunk.emit(Op::Div, self.line),
1446 "%" => self.chunk.emit(Op::Mod, self.line),
1447 _ => {
1448 return Err(CompileError {
1449 message: format!("Unknown compound operator: {op}"),
1450 line: self.line,
1451 })
1452 }
1453 }
1454 Ok(())
1455 }
1456
1457 pub(super) fn produces_value(node: &Node) -> bool {
1459 harn_parser::node_produces_value(node)
1460 }
1461}
1462
1463impl Default for Compiler {
1464 fn default() -> Self {
1465 Self::new()
1466 }
1467}