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