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