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