1use crate::value::VmDictExt;
2use std::collections::BTreeMap;
3use std::sync::Arc;
4
5use harn_parser::{Node, SNode, ShapeField, TypeExpr, TypedParam};
6
7use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
8use crate::value::VmValue;
9
10use super::error::CompileError;
11use super::yield_scan::body_contains_yield;
12use super::{peel_node, Compiler, CompilerOptions, FinallyEntry};
13
14#[cfg(test)]
15thread_local! {
16 pub(super) static FORCE_DISCARDED_PRODUCES_VALUE: std::cell::Cell<Option<bool>> =
23 const { std::cell::Cell::new(None) };
24}
25
26impl Compiler {
27 pub fn new() -> Self {
28 Self::with_options(CompilerOptions::from_env())
29 }
30
31 pub fn with_options(options: CompilerOptions) -> Self {
32 Self {
33 options,
34 chunk: Chunk::new(),
35 line: 1,
36 column: 1,
37 enum_names: std::collections::HashSet::new(),
38 enum_variant_owners: std::collections::HashMap::new(),
39 struct_layouts: std::collections::HashMap::new(),
40 interface_methods: std::collections::HashMap::new(),
41 loop_stack: Vec::new(),
42 handler_depth: 0,
43 finally_bodies: Vec::new(),
44 temp_counter: 0,
45 scope_depth: 0,
46 type_aliases: std::collections::HashMap::new(),
47 type_scopes: vec![std::collections::HashMap::new()],
48 monomorphic_bindings: std::collections::HashSet::new(),
49 string_constants: std::collections::HashMap::new(),
50 local_scopes: vec![std::collections::HashMap::new()],
51 module_level: true,
52 captured_idents: std::collections::HashSet::new(),
53 }
54 }
55
56 pub(super) fn for_nested_body(options: CompilerOptions) -> Self {
60 let mut c = Self::with_options(options);
61 c.module_level = false;
62 c
63 }
64
65 pub(super) fn nested_body(&self) -> Self {
66 Self::for_nested_body(self.options)
67 }
68
69 pub(super) fn nominal_type_names(&self) -> Vec<String> {
70 let mut names: Vec<String> = self
71 .struct_layouts
72 .keys()
73 .chain(self.enum_names.iter())
74 .cloned()
75 .collect();
76 names.sort();
77 names.dedup();
78 names
79 }
80
81 pub(super) fn string_constant(&mut self, value: &str) -> u16 {
82 if let Some(idx) = self.string_constants.get(value) {
83 return *idx;
84 }
85 let owned = value.to_string();
86 let idx = self.chunk.add_constant(Constant::String(owned.clone()));
87 self.string_constants.insert(owned, idx);
88 idx
89 }
90
91 pub(super) fn owned_string_constant(&mut self, value: String) -> u16 {
92 if let Some(idx) = self.string_constants.get(value.as_str()) {
93 return *idx;
94 }
95 let idx = self.chunk.add_constant(Constant::String(value.clone()));
96 self.string_constants.insert(value, idx);
97 idx
98 }
99
100 pub(crate) fn collect_type_aliases(&mut self, program: &[SNode]) {
104 for sn in program {
105 if let Node::TypeDecl {
106 name,
107 type_expr,
108 type_params: _,
109 is_pub: _,
110 } = peel_node(sn)
111 {
112 self.type_aliases.insert(name.clone(), type_expr.clone());
113 }
114 }
115 }
116
117 pub(crate) fn expand_alias(&self, ty: &TypeExpr) -> TypeExpr {
127 let mut visiting = std::collections::HashSet::new();
128 self.expand_alias_inner(ty, &mut visiting)
129 }
130
131 fn expand_alias_inner(
132 &self,
133 ty: &TypeExpr,
134 visiting: &mut std::collections::HashSet<String>,
135 ) -> TypeExpr {
136 match ty {
137 TypeExpr::Named(name) => {
138 if let Some(target) = self.type_aliases.get(name) {
139 if !visiting.insert(name.clone()) {
140 return TypeExpr::Named(name.clone());
141 }
142 let resolved = self.expand_alias_inner(target, visiting);
143 visiting.remove(name);
144 resolved
145 } else {
146 TypeExpr::Named(name.clone())
147 }
148 }
149 TypeExpr::Union(types) => TypeExpr::Union(
150 types
151 .iter()
152 .map(|t| self.expand_alias_inner(t, visiting))
153 .collect(),
154 ),
155 TypeExpr::Intersection(types) => TypeExpr::Intersection(
156 types
157 .iter()
158 .map(|t| self.expand_alias_inner(t, visiting))
159 .collect(),
160 ),
161 TypeExpr::Shape(fields) => TypeExpr::Shape(
162 fields
163 .iter()
164 .map(|field| ShapeField {
165 name: field.name.clone(),
166 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
167 optional: field.optional,
168 })
169 .collect(),
170 ),
171 TypeExpr::OpenShape { fields, rests } => TypeExpr::OpenShape {
172 fields: fields
173 .iter()
174 .map(|field| ShapeField {
175 name: field.name.clone(),
176 type_expr: self.expand_alias_inner(&field.type_expr, visiting),
177 optional: field.optional,
178 })
179 .collect(),
180 rests: rests
181 .iter()
182 .map(|r| self.expand_alias_inner(r, visiting))
183 .collect(),
184 },
185 TypeExpr::List(inner) => {
186 TypeExpr::List(Box::new(self.expand_alias_inner(inner, visiting)))
187 }
188 TypeExpr::Iter(inner) => {
189 TypeExpr::Iter(Box::new(self.expand_alias_inner(inner, visiting)))
190 }
191 TypeExpr::Generator(inner) => {
192 TypeExpr::Generator(Box::new(self.expand_alias_inner(inner, visiting)))
193 }
194 TypeExpr::Stream(inner) => {
195 TypeExpr::Stream(Box::new(self.expand_alias_inner(inner, visiting)))
196 }
197 TypeExpr::DictType(k, v) => TypeExpr::DictType(
198 Box::new(self.expand_alias_inner(k, visiting)),
199 Box::new(self.expand_alias_inner(v, visiting)),
200 ),
201 TypeExpr::FnType {
202 params,
203 return_type,
204 } => TypeExpr::FnType {
205 params: params
206 .iter()
207 .map(|p| self.expand_alias_inner(p, visiting))
208 .collect(),
209 return_type: Box::new(self.expand_alias_inner(return_type, visiting)),
210 },
211 TypeExpr::Applied { name, args } => TypeExpr::Applied {
212 name: name.clone(),
213 args: args
214 .iter()
215 .map(|a| self.expand_alias_inner(a, visiting))
216 .collect(),
217 },
218 TypeExpr::Never => TypeExpr::Never,
219 TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
220 TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
221 TypeExpr::Owned(inner) => {
222 TypeExpr::Owned(Box::new(self.expand_alias_inner(inner, visiting)))
223 }
224 }
225 }
226
227 pub(super) fn schema_value_for_alias(&self, name: &str) -> Option<VmValue> {
230 let ty = self.type_aliases.get(name)?;
231 let expanded = self.expand_alias(ty);
232 Self::type_expr_to_schema_value(&expanded)
233 }
234
235 pub fn lower_public_type_schemas(
242 program: &[SNode],
243 ) -> std::collections::BTreeMap<String, VmValue> {
244 let mut compiler = Compiler::new();
245 compiler.collect_type_aliases(program);
246 let mut schemas = std::collections::BTreeMap::new();
247 for sn in program {
248 let inner = peel_node(sn);
249 if let Node::TypeDecl {
250 name, is_pub: true, ..
251 } = inner
252 {
253 if let Some(schema) = compiler.schema_value_for_alias(name) {
254 schemas.insert(name.clone(), schema);
255 }
256 }
257 }
258 schemas
259 }
260
261 pub(super) fn is_schema_guard(name: &str) -> bool {
265 matches!(
266 name,
267 "schema_is"
268 | "schema_expect"
269 | "schema_parse"
270 | "schema_check"
271 | "schema_report"
272 | "is_type"
273 | "json_validate"
274 )
275 }
276
277 pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
280 matches!(
281 &key.node,
282 Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
283 if name == keyword
284 )
285 }
286
287 pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
290 Self::collect_enum_names(program, &mut self.enum_names);
293 self.enum_names.insert("Result".to_string());
294 Self::collect_enum_variant_owners(program, &mut self.enum_variant_owners);
295 Self::seed_builtin_variant_owners(&mut self.enum_variant_owners);
296 Self::collect_struct_layouts(program, &mut self.struct_layouts);
297 Self::collect_interface_methods(program, &mut self.interface_methods);
298 self.collect_type_aliases(program);
299 self.seed_captured_idents(program);
304
305 for sn in program {
306 match &sn.node {
307 Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
308 self.compile_node(sn)?;
309 }
310 _ => {}
311 }
312 }
313 let main = program
314 .iter()
315 .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
316 .or_else(|| {
317 program
318 .iter()
319 .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
320 });
321
322 let mut pipeline_emits_value = false;
326 if let Some(sn) = main {
327 self.compile_top_level_declarations(program)?;
328 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
329 if let Some(parent_name) = extends {
330 self.compile_parent_pipeline(program, parent_name)?;
331 }
332 let saved = std::mem::replace(&mut self.module_level, false);
333 self.compile_block(body)?;
334 self.module_level = saved;
335 pipeline_emits_value = true;
336 }
337 } else {
338 let top_level: Vec<&SNode> = program
340 .iter()
341 .filter(|sn| {
342 !matches!(
343 &sn.node,
344 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
345 )
346 })
347 .collect();
348 for sn in &top_level {
349 self.compile_discarded_stmt(sn)?;
350 }
351 if Self::has_top_level_fn_main(program) {
356 let harness_name = self.string_constant("harness");
357 self.chunk.emit_u16(Op::GetVar, harness_name, self.line);
358 self.emit_named_call("main", 1);
359 pipeline_emits_value = true;
360 }
361 }
362
363 self.drain_finallys_to_floor(0)?;
364 if !pipeline_emits_value {
365 self.chunk.emit(Op::Nil, self.line);
366 }
367 self.chunk.emit(Op::Return, self.line);
368 super::ensure_chunk_addressable(&self.chunk, "the program body", self.line)?;
369 Ok(self.chunk)
370 }
371
372 fn has_top_level_fn_main(program: &[SNode]) -> bool {
376 program
377 .iter()
378 .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
379 }
380
381 pub fn compile_named(
383 self,
384 program: &[SNode],
385 pipeline_name: &str,
386 ) -> Result<Chunk, CompileError> {
387 self.compile_named_inner(program, pipeline_name, false)
388 }
389
390 pub fn compile_named_with_param_globals(
397 self,
398 program: &[SNode],
399 pipeline_name: &str,
400 ) -> Result<Chunk, CompileError> {
401 self.compile_named_inner(program, pipeline_name, true)
402 }
403
404 fn compile_named_inner(
405 mut self,
406 program: &[SNode],
407 pipeline_name: &str,
408 bind_params_from_globals: bool,
409 ) -> Result<Chunk, CompileError> {
410 Self::collect_enum_names(program, &mut self.enum_names);
411 self.enum_names.insert("Result".to_string());
412 Self::collect_enum_variant_owners(program, &mut self.enum_variant_owners);
413 Self::seed_builtin_variant_owners(&mut self.enum_variant_owners);
414 Self::collect_struct_layouts(program, &mut self.struct_layouts);
415 Self::collect_interface_methods(program, &mut self.interface_methods);
416 self.collect_type_aliases(program);
417 self.seed_captured_idents(program);
422
423 for sn in program {
424 if matches!(
425 &sn.node,
426 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
427 ) {
428 self.compile_node(sn)?;
429 }
430 }
431 let target = program.iter().find(
432 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
433 );
434
435 if let Some(sn) = target {
436 self.compile_top_level_declarations(program)?;
437 if let Node::Pipeline {
438 body,
439 extends,
440 params,
441 ..
442 } = peel_node(sn)
443 {
444 if let Some(parent_name) = extends {
445 self.compile_parent_pipeline(program, parent_name)?;
446 }
447 let saved = std::mem::replace(&mut self.module_level, false);
448 if bind_params_from_globals {
449 for param in params {
450 self.define_local_slot(param, false);
451 let idx = self.string_constant(param);
452 self.chunk.emit_u16(Op::GetVar, idx, self.line);
453 self.emit_init_or_define_binding(param, false);
454 }
455 }
456 self.compile_block(body)?;
457 self.module_level = saved;
458 }
459 }
460
461 self.drain_finallys_to_floor(0)?;
462 self.chunk.emit(Op::Nil, self.line);
463 self.chunk.emit(Op::Return, self.line);
464 super::ensure_chunk_addressable(&self.chunk, "the pipeline body", self.line)?;
465 Ok(self.chunk)
466 }
467
468 pub(super) fn compile_parent_pipeline(
470 &mut self,
471 program: &[SNode],
472 parent_name: &str,
473 ) -> Result<(), CompileError> {
474 let parent = program
475 .iter()
476 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
477 if let Some(sn) = parent {
478 if let Node::Pipeline { body, extends, .. } = &sn.node {
479 if let Some(grandparent) = extends {
480 self.compile_parent_pipeline(program, grandparent)?;
481 }
482 for stmt in body {
483 self.compile_discarded_stmt(stmt)?;
484 }
485 }
486 }
487 Ok(())
488 }
489
490 pub(super) fn emit_default_preamble(
495 &mut self,
496 params: &[TypedParam],
497 ) -> Result<(), CompileError> {
498 for (i, param) in params.iter().enumerate() {
499 if let Some(default_expr) = ¶m.default_value {
500 self.chunk.emit(Op::GetArgc, self.line);
501 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
502 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
503 self.chunk.emit(Op::GreaterEqual, self.line);
504 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
505 self.chunk.emit(Op::Pop, self.line);
507 let masked = self.mask_param_names(¶ms[i..]);
516 let result = self.compile_node(default_expr);
517 self.restore_param_names(masked);
518 result?;
519 self.emit_init_or_define_binding(¶m.name, false);
520 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
521 self.chunk.patch_jump(skip_jump);
522 self.chunk.emit(Op::Pop, self.line);
523 self.chunk.patch_jump(end_jump);
524 }
525 }
526 Ok(())
527 }
528
529 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
536 for (param_index, param) in params.iter().enumerate() {
537 if let Some(type_expr) = ¶m.type_expr {
538 let check_type = if param.rest {
539 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
540 } else {
541 type_expr.clone()
542 };
543
544 if let harn_parser::TypeExpr::Named(name) = &check_type {
545 if let Some(methods) = self.interface_methods.get(name).cloned() {
546 let fn_idx = self.string_constant("__assert_interface");
547 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
548 self.emit_get_binding(¶m.name);
549 let name_idx = self.string_constant(¶m.name);
550 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
551 let iface_idx = self.string_constant(name);
552 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
553 let methods_str = methods.join(",");
554 let methods_idx = self.owned_string_constant(methods_str);
555 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
556 self.chunk.emit_u8(Op::Call, 4, self.line);
557 self.chunk.emit(Op::Pop, self.line);
558 continue;
559 }
560 }
561
562 if param.default_value.is_some() {
563 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
564 self.emit_default_param_schema_check(param_index, param, &schema);
565 }
566 }
567 }
568 }
569 }
570
571 fn emit_default_param_schema_check(
572 &mut self,
573 param_index: usize,
574 param: &TypedParam,
575 schema: &VmValue,
576 ) {
577 self.chunk.emit(Op::GetArgc, self.line);
578 let threshold_idx = self
579 .chunk
580 .add_constant(Constant::Int((param_index + 1) as i64));
581 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
582 self.chunk.emit(Op::GreaterEqual, self.line);
583 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
584 self.chunk.emit(Op::Pop, self.line);
585 self.emit_schema_assert_call(param, schema);
586 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
587 self.chunk.patch_jump(supplied_jump);
588 self.chunk.emit(Op::Pop, self.line);
589 self.chunk.patch_jump(end_jump);
590 }
591
592 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
593 let fn_idx = self.string_constant("__assert_schema");
594 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
595 self.emit_get_binding(¶m.name);
596 let name_idx = self.string_constant(¶m.name);
597 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
598 self.emit_vm_value_literal(schema);
599 self.chunk.emit_u8(Op::Call, 3, self.line);
600 self.chunk.emit(Op::Pop, self.line);
601 }
602
603 pub(crate) fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
604 match type_expr {
605 harn_parser::TypeExpr::Named(name) => match name.as_str() {
606 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
607 | "closure" | "bytes" => Some(VmValue::dict(BTreeMap::from([(
608 "type".to_string(),
609 VmValue::String(arcstr::ArcStr::from(name.as_str())),
610 )]))),
611 _ => None,
612 },
613 harn_parser::TypeExpr::Shape(fields)
614 | harn_parser::TypeExpr::OpenShape { fields, .. } => {
615 let mut properties = BTreeMap::new();
616 let mut required = Vec::new();
617 for field in fields {
618 let field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
619 properties.insert(field.name.clone(), field_schema);
620 if !field.optional {
621 required.push(VmValue::String(arcstr::ArcStr::from(field.name.as_str())));
622 }
623 }
624 let mut out = BTreeMap::new();
625 out.put_str("type", "dict");
626 out.insert("properties".to_string(), VmValue::dict(properties));
627 if !required.is_empty() {
628 out.insert(
629 "required".to_string(),
630 VmValue::List(std::sync::Arc::new(required)),
631 );
632 }
633 Some(VmValue::dict(out))
634 }
635 harn_parser::TypeExpr::List(inner) => {
636 let mut out = BTreeMap::new();
637 out.put_str("type", "list");
638 if let Some(item_schema) = Self::type_expr_to_schema_value(inner) {
639 out.insert("items".to_string(), item_schema);
640 }
641 Some(VmValue::dict(out))
642 }
643 harn_parser::TypeExpr::DictType(key, value) => {
644 let mut out = BTreeMap::new();
645 out.put_str("type", "dict");
646 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
647 if let Some(value_schema) = Self::type_expr_to_schema_value(value) {
648 out.insert("additional_properties".to_string(), value_schema);
649 }
650 }
651 Some(VmValue::dict(out))
652 }
653 harn_parser::TypeExpr::Union(members) => {
654 if !members.is_empty()
659 && members
660 .iter()
661 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
662 {
663 let values = members
664 .iter()
665 .map(|m| match m {
666 harn_parser::TypeExpr::LitString(s) => {
667 VmValue::String(arcstr::ArcStr::from(s.as_str()))
668 }
669 _ => unreachable!(),
670 })
671 .collect::<Vec<_>>();
672 return Some(VmValue::dict(BTreeMap::from([
673 (
674 "type".to_string(),
675 VmValue::String(arcstr::ArcStr::from("string")),
676 ),
677 (
678 "enum".to_string(),
679 VmValue::List(std::sync::Arc::new(values)),
680 ),
681 ])));
682 }
683 if !members.is_empty()
684 && members
685 .iter()
686 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
687 {
688 let values = members
689 .iter()
690 .map(|m| match m {
691 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
692 _ => unreachable!(),
693 })
694 .collect::<Vec<_>>();
695 return Some(VmValue::dict(BTreeMap::from([
696 (
697 "type".to_string(),
698 VmValue::String(arcstr::ArcStr::from("int")),
699 ),
700 (
701 "enum".to_string(),
702 VmValue::List(std::sync::Arc::new(values)),
703 ),
704 ])));
705 }
706 let branches = members
707 .iter()
708 .map(Self::type_expr_to_schema_value)
709 .collect::<Option<Vec<_>>>()?;
710 if branches.is_empty() {
711 None
712 } else {
713 Some(VmValue::dict(BTreeMap::from([(
714 "union".to_string(),
715 VmValue::List(std::sync::Arc::new(branches)),
716 )])))
717 }
718 }
719 harn_parser::TypeExpr::Intersection(members) => {
720 let branches = members
724 .iter()
725 .map(Self::type_expr_to_schema_value)
726 .collect::<Option<Vec<_>>>()?;
727 if branches.is_empty() {
728 None
729 } else {
730 Some(VmValue::dict(BTreeMap::from([(
731 "all_of".to_string(),
732 VmValue::List(std::sync::Arc::new(branches)),
733 )])))
734 }
735 }
736 harn_parser::TypeExpr::FnType { .. } => Some(VmValue::dict(BTreeMap::from([(
737 "type".to_string(),
738 VmValue::String(arcstr::ArcStr::from("closure")),
739 )]))),
740 harn_parser::TypeExpr::Applied { .. } => None,
741 harn_parser::TypeExpr::Iter(_)
742 | harn_parser::TypeExpr::Generator(_)
743 | harn_parser::TypeExpr::Stream(_) => None,
744 harn_parser::TypeExpr::Never => None,
745 harn_parser::TypeExpr::LitString(s) => Some(VmValue::dict(BTreeMap::from([
746 (
747 "type".to_string(),
748 VmValue::String(arcstr::ArcStr::from("string")),
749 ),
750 (
751 "const".to_string(),
752 VmValue::String(arcstr::ArcStr::from(s.as_str())),
753 ),
754 ]))),
755 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::dict(BTreeMap::from([
756 (
757 "type".to_string(),
758 VmValue::String(arcstr::ArcStr::from("int")),
759 ),
760 ("const".to_string(), VmValue::Int(*v)),
761 ]))),
762 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
763 }
764 }
765
766 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
767 match value {
768 VmValue::String(text) => {
769 let idx = self.string_constant(text);
770 self.chunk.emit_u16(Op::Constant, idx, self.line);
771 }
772 VmValue::Int(number) => {
773 let idx = self.chunk.add_constant(Constant::Int(*number));
774 self.chunk.emit_u16(Op::Constant, idx, self.line);
775 }
776 VmValue::Float(number) => {
777 let idx = self.chunk.add_constant(Constant::Float(*number));
778 self.chunk.emit_u16(Op::Constant, idx, self.line);
779 }
780 VmValue::Bool(value) => {
781 let idx = self.chunk.add_constant(Constant::Bool(*value));
782 self.chunk.emit_u16(Op::Constant, idx, self.line);
783 }
784 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
785 VmValue::List(items) => {
786 for item in items.iter() {
787 self.emit_vm_value_literal(item);
788 }
789 self.chunk
790 .emit_u16(Op::BuildList, items.len() as u16, self.line);
791 }
792 VmValue::Dict(entries) => {
793 for (key, item) in entries.iter() {
794 let key_idx = self.string_constant(key);
795 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
796 self.emit_vm_value_literal(item);
797 }
798 self.chunk
799 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
800 }
801 _ => {}
802 }
803 }
804
805 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
807 let hi = (type_name_idx >> 8) as u8;
808 let lo = type_name_idx as u8;
809 self.chunk.code.push(hi);
810 self.chunk.code.push(lo);
811 self.chunk.lines.push(self.line);
812 self.chunk.columns.push(self.column);
813 self.chunk.lines.push(self.line);
814 self.chunk.columns.push(self.column);
815 }
816
817 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
819 if body.is_empty() {
820 self.chunk.emit(Op::Nil, self.line);
821 } else {
822 self.compile_scoped_block(body)?;
823 }
824 Ok(())
825 }
826
827 pub(super) fn compile_catch_binding(
829 &mut self,
830 error_var: &Option<String>,
831 ) -> Result<(), CompileError> {
832 if let Some(var_name) = error_var {
833 self.emit_define_binding(var_name, false);
834 } else {
835 self.chunk.emit(Op::Pop, self.line);
836 }
837 Ok(())
838 }
839
840 pub(super) fn compile_finally_inline(
847 &mut self,
848 finally_body: &[SNode],
849 ) -> Result<(), CompileError> {
850 if !finally_body.is_empty() {
851 self.compile_scoped_block(finally_body)?;
852 self.chunk.emit(Op::Pop, self.line);
853 }
854 Ok(())
855 }
856
857 pub(super) fn pending_finallys_until_barrier(&self) -> Vec<Vec<SNode>> {
862 let mut out = Vec::new();
863 for entry in self.finally_bodies.iter().rev() {
864 match entry {
865 FinallyEntry::CatchBarrier => break,
866 FinallyEntry::Finally(body) => out.push(body.clone()),
867 }
868 }
869 out
870 }
871
872 pub(super) fn has_pending_finally(&self) -> bool {
874 self.finally_bodies
875 .iter()
876 .any(|e| matches!(e, FinallyEntry::Finally(_)))
877 }
878
879 pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
888 self.temp_counter += 1;
889 let temp_name = format!("__finally_err_{}__", self.temp_counter);
890 self.emit_define_binding(&temp_name, true);
891 self.emit_get_binding(&temp_name);
892 self.chunk.emit(Op::Throw, self.line);
893 Ok(())
894 }
895
896 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
897 for param in params {
898 self.define_local_slot(¶m.name, false);
899 }
900 }
901
902 fn mask_param_names(&mut self, params: &[TypedParam]) -> Vec<(String, super::LocalBinding)> {
908 let mut removed = Vec::new();
909 if let Some(scope) = self.local_scopes.last_mut() {
910 for param in params {
911 if let Some(binding) = scope.remove(¶m.name) {
912 removed.push((param.name.clone(), binding));
913 }
914 }
915 }
916 removed
917 }
918
919 fn restore_param_names(&mut self, removed: Vec<(String, super::LocalBinding)>) {
921 if let Some(scope) = self.local_scopes.last_mut() {
922 for (name, binding) in removed {
923 scope.insert(name, binding);
924 }
925 }
926 }
927
928 pub(super) fn seed_captured_idents(&mut self, body: &[SNode]) {
935 let mut set = std::collections::HashSet::new();
936 for sn in body {
937 collect_closure_capture_idents(sn, &mut set);
938 }
939 self.captured_idents = set;
940 }
941
942 #[inline]
948 fn is_boxed_capture(&self, name: &str, mutable: bool) -> bool {
949 mutable && self.captured_idents.contains(name)
950 }
951
952 fn define_local_slot(&mut self, name: &str, mutable: bool) -> Option<u16> {
953 if self.module_level
954 || harn_parser::is_discard_name(name)
955 || self.is_boxed_capture(name, mutable)
956 {
957 return None;
961 }
962 let current = self.local_scopes.last_mut()?;
963 if let Some(existing) = current.get_mut(name) {
964 if existing.mutable || mutable {
965 if mutable {
966 existing.mutable = true;
967 if let Some(info) = self.chunk.local_slots.get_mut(existing.slot as usize) {
968 info.mutable = true;
969 }
970 }
971 return Some(existing.slot);
972 }
973 return None;
974 }
975 let slot = self
976 .chunk
977 .add_local_slot(name.to_string(), mutable, self.scope_depth);
978 current.insert(name.to_string(), super::LocalBinding { slot, mutable });
979 Some(slot)
980 }
981
982 pub(super) fn resolve_local_slot(&self, name: &str) -> Option<super::LocalBinding> {
983 if self.module_level {
984 return None;
985 }
986 self.local_scopes
987 .iter()
988 .rev()
989 .find_map(|scope| scope.get(name).copied())
990 }
991
992 pub(super) fn emit_get_binding(&mut self, name: &str) {
993 if let Some(binding) = self.resolve_local_slot(name) {
994 self.chunk
995 .emit_u16(Op::GetLocalSlot, binding.slot, self.line);
996 } else {
997 let idx = self.string_constant(name);
998 self.chunk.emit_u16(Op::GetVar, idx, self.line);
999 }
1000 }
1001
1002 pub(super) fn emit_define_binding(&mut self, name: &str, mutable: bool) {
1003 if self.is_boxed_capture(name, mutable) {
1004 let idx = self.string_constant(name);
1008 self.chunk.emit_u16(Op::DefCell, idx, self.line);
1009 } else if let Some(slot) = self.define_local_slot(name, mutable) {
1010 self.chunk.emit_u16(Op::DefLocalSlot, slot, self.line);
1011 } else {
1012 let idx = self.string_constant(name);
1013 let op = if mutable { Op::DefVar } else { Op::DefLet };
1014 self.chunk.emit_u16(op, idx, self.line);
1015 }
1016 }
1017
1018 pub(super) fn emit_init_or_define_binding(&mut self, name: &str, mutable: bool) {
1019 if let Some(binding) = self.resolve_local_slot(name) {
1020 self.chunk
1021 .emit_u16(Op::DefLocalSlot, binding.slot, self.line);
1022 } else {
1023 self.emit_define_binding(name, mutable);
1024 }
1025 }
1026
1027 pub(super) fn emit_set_binding(&mut self, name: &str) {
1028 if let Some(binding) = self.resolve_local_slot(name) {
1029 let _ = binding.mutable;
1030 self.chunk
1031 .emit_u16(Op::SetLocalSlot, binding.slot, self.line);
1032 } else {
1033 let idx = self.string_constant(name);
1034 self.chunk.emit_u16(Op::SetVar, idx, self.line);
1035 }
1036 }
1037
1038 pub(super) fn begin_scope(&mut self) {
1039 self.chunk.emit(Op::PushScope, self.line);
1040 self.scope_depth += 1;
1041 self.type_scopes.push(std::collections::HashMap::new());
1042 self.local_scopes.push(std::collections::HashMap::new());
1043 }
1044
1045 pub(super) fn end_scope(&mut self) {
1046 if self.scope_depth > 0 {
1047 self.chunk.emit(Op::PopScope, self.line);
1048 self.scope_depth -= 1;
1049 self.type_scopes.pop();
1050 self.local_scopes.pop();
1051 }
1052 }
1053
1054 pub(super) fn emit_scope_unwind_to(&mut self, target_depth: usize) {
1057 for _ in target_depth..self.scope_depth {
1058 self.chunk.emit(Op::PopScope, self.line);
1059 }
1060 }
1061
1062 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1063 self.begin_scope();
1064 let finally_floor = self.finally_bodies.len();
1065 if stmts.is_empty() {
1066 self.chunk.emit(Op::Nil, self.line);
1067 } else {
1068 self.compile_block(stmts)?;
1069 }
1070 self.drain_finallys_to_floor(finally_floor)?;
1071 self.end_scope();
1072 Ok(())
1073 }
1074
1075 pub(super) fn compile_scoped_statements(
1076 &mut self,
1077 stmts: &[SNode],
1078 ) -> Result<(), CompileError> {
1079 self.begin_scope();
1080 self.record_monomorphic_var_bindings(stmts);
1081 let finally_floor = self.finally_bodies.len();
1082 for sn in stmts {
1083 self.compile_discarded_stmt(sn)?;
1084 }
1085 self.drain_finallys_to_floor(finally_floor)?;
1086 self.end_scope();
1087 Ok(())
1088 }
1089
1090 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
1095 while self.finally_bodies.len() > floor {
1096 let entry = self.finally_bodies.pop().expect("non-empty by guard");
1097 if let FinallyEntry::Finally(body) = entry {
1098 self.compile_finally_inline(&body)?;
1099 }
1100 }
1101 Ok(())
1102 }
1103
1104 pub(super) fn run_pending_finallys_for_transfer(
1117 &mut self,
1118 floor: usize,
1119 ) -> Result<(), CompileError> {
1120 if self.finally_bodies.len() <= floor {
1121 return Ok(());
1122 }
1123 let saved = self.finally_bodies[floor..].to_vec();
1124 let result = self.drain_finallys_to_floor(floor);
1125 self.finally_bodies.extend(saved);
1126 result
1127 }
1128
1129 pub(super) fn run_pending_finallys_until_barrier(&mut self) -> Result<(), CompileError> {
1134 let floor = self
1135 .finally_bodies
1136 .iter()
1137 .rposition(|e| matches!(e, FinallyEntry::CatchBarrier))
1138 .map(|i| i + 1)
1139 .unwrap_or(0);
1140 self.run_pending_finallys_for_transfer(floor)
1141 }
1142
1143 pub(super) fn maybe_register_owned_drop(
1148 &mut self,
1149 pattern: &harn_parser::BindingPattern,
1150 type_ann: Option<&TypeExpr>,
1151 span: harn_lexer::Span,
1152 ) {
1153 let Some(ty) = type_ann else {
1159 return;
1160 };
1161 if !matches!(ty, TypeExpr::Owned(_)) {
1162 return;
1163 }
1164 let harn_parser::BindingPattern::Identifier(name) = pattern else {
1165 return;
1166 };
1167 if harn_parser::is_discard_name(name) {
1168 return;
1169 }
1170 let call = harn_parser::spanned(
1171 Node::FunctionCall {
1172 name: "drop".to_string(),
1173 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
1174 type_args: Vec::new(),
1175 },
1176 span,
1177 );
1178 self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
1179 }
1180
1181 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
1197 #[cfg(debug_assertions)]
1198 let probe = self.chunk.balance_probe();
1199 self.compile_node(sn)?;
1200 #[allow(unused_mut)]
1201 let mut produces = Self::produces_value(&sn.node);
1202 #[cfg(test)]
1206 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
1207 produces = forced;
1208 }
1209 #[cfg(debug_assertions)]
1210 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1211 let expected = i32::from(produces);
1212 debug_assert_eq!(
1213 delta, expected,
1214 "operand-stack imbalance at line {}: produces_value={produces} but the \
1215 node's emitted bytecode netted {delta} (expected {expected}). A \
1216 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1217 node: {:?}",
1218 self.line, sn.node,
1219 );
1220 }
1221 if produces {
1222 self.chunk.emit(Op::Pop, self.line);
1223 }
1224 Ok(())
1225 }
1226
1227 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1228 self.record_monomorphic_var_bindings(stmts);
1229 for (i, snode) in stmts.iter().enumerate() {
1230 if i == stmts.len() - 1 {
1231 self.compile_node(snode)?;
1235 if !Self::produces_value(&snode.node) {
1236 self.chunk.emit(Op::Nil, self.line);
1237 }
1238 } else {
1239 self.compile_discarded_stmt(snode)?;
1240 }
1241 }
1242 Ok(())
1243 }
1244
1245 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1247 self.begin_scope();
1248 let finally_floor = self.finally_bodies.len();
1249 if body.is_empty() {
1250 self.chunk.emit(Op::Nil, self.line);
1251 } else {
1252 self.compile_block(body)?;
1253 if !Self::produces_value(&body.last().unwrap().node) {
1254 self.chunk.emit(Op::Nil, self.line);
1255 }
1256 }
1257 self.drain_finallys_to_floor(finally_floor)?;
1258 self.end_scope();
1259 Ok(())
1260 }
1261
1262 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1264 match op {
1265 "+" => self.chunk.emit(Op::Add, self.line),
1266 "-" => self.chunk.emit(Op::Sub, self.line),
1267 "*" => self.chunk.emit(Op::Mul, self.line),
1268 "/" => self.chunk.emit(Op::Div, self.line),
1269 "%" => self.chunk.emit(Op::Mod, self.line),
1270 _ => {
1271 return Err(CompileError {
1272 message: format!("Unknown compound operator: {op}"),
1273 line: self.line,
1274 })
1275 }
1276 }
1277 Ok(())
1278 }
1279
1280 pub(super) fn compile_top_level_declarations(
1281 &mut self,
1282 program: &[SNode],
1283 ) -> Result<(), CompileError> {
1284 for sn in program {
1296 let handled_elsewhere = matches!(
1297 peel_node(sn),
1298 Node::Pipeline { .. }
1299 | Node::ImportDecl { .. }
1300 | Node::SelectiveImport { .. }
1301 | Node::OverrideDecl { .. }
1302 | Node::EvalPackDecl { .. }
1303 | Node::FnDecl { .. }
1304 | Node::ToolDecl { .. }
1305 | Node::SkillDecl { .. }
1306 | Node::ImplBlock { .. }
1307 | Node::StructDecl { .. }
1308 | Node::EnumDecl { .. }
1309 | Node::InterfaceDecl { .. }
1310 | Node::TypeDecl { .. }
1311 );
1312 if !handled_elsewhere {
1313 self.compile_discarded_stmt(sn)?;
1314 }
1315 }
1316 for sn in program {
1322 let inner_kind = match &sn.node {
1323 Node::AttributedDecl { inner, .. } => &inner.node,
1324 other => other,
1325 };
1326 match inner_kind {
1327 Node::EvalPackDecl {
1328 binding_name,
1329 pack_id,
1330 fields,
1331 body,
1332 summarize,
1333 ..
1334 } => {
1335 self.compile_eval_pack_decl(
1336 binding_name,
1337 pack_id,
1338 fields,
1339 body,
1340 summarize,
1341 false,
1342 )?;
1343 }
1344 Node::FnDecl { .. }
1345 | Node::ToolDecl { .. }
1346 | Node::SkillDecl { .. }
1347 | Node::ImplBlock { .. }
1348 | Node::StructDecl { .. }
1349 | Node::EnumDecl { .. }
1350 | Node::InterfaceDecl { .. } => {
1351 self.compile_node(sn)?;
1352 }
1353 Node::TypeDecl { .. } => {}
1354 _ => {}
1355 }
1356 }
1357 Ok(())
1358 }
1359
1360 pub(super) fn collect_enum_names(
1362 nodes: &[SNode],
1363 names: &mut std::collections::HashSet<String>,
1364 ) {
1365 for sn in nodes {
1366 match &sn.node {
1367 Node::EnumDecl { name, .. } => {
1368 names.insert(name.clone());
1369 }
1370 Node::Pipeline { body, .. } => {
1371 Self::collect_enum_names(body, names);
1372 }
1373 Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
1374 Self::collect_enum_names(body, names);
1375 }
1376 Node::SkillDecl { fields, .. } => {
1377 for (_k, v) in fields {
1378 Self::collect_enum_names(std::slice::from_ref(v), names);
1379 }
1380 }
1381 Node::EvalPackDecl {
1382 fields,
1383 body,
1384 summarize,
1385 ..
1386 } => {
1387 for (_k, v) in fields {
1388 Self::collect_enum_names(std::slice::from_ref(v), names);
1389 }
1390 Self::collect_enum_names(body, names);
1391 if let Some(summary_body) = summarize {
1392 Self::collect_enum_names(summary_body, names);
1393 }
1394 }
1395 Node::Block(stmts) => {
1396 Self::collect_enum_names(stmts, names);
1397 }
1398 Node::AttributedDecl { inner, .. } => {
1399 Self::collect_enum_names(std::slice::from_ref(inner), names);
1400 }
1401 _ => {}
1402 }
1403 }
1404 }
1405
1406 pub(super) fn collect_enum_variant_owners(
1411 nodes: &[SNode],
1412 owners: &mut std::collections::HashMap<String, Vec<String>>,
1413 ) {
1414 harn_parser::visit::walk_program(nodes, &mut |sn| {
1415 if let Node::EnumDecl { name, variants, .. } = &sn.node {
1416 for variant in variants {
1417 let entry = owners.entry(variant.name.clone()).or_default();
1418 if !entry.contains(name) {
1419 entry.push(name.clone());
1420 }
1421 }
1422 }
1423 });
1424 }
1425
1426 pub(super) fn seed_builtin_variant_owners(
1429 owners: &mut std::collections::HashMap<String, Vec<String>>,
1430 ) {
1431 for variant in ["Ok", "Err"] {
1432 let entry = owners.entry(variant.to_string()).or_default();
1433 if !entry.contains(&"Result".to_string()) {
1434 entry.push("Result".to_string());
1435 }
1436 }
1437 }
1438
1439 pub(super) fn collect_struct_layouts(
1440 nodes: &[SNode],
1441 layouts: &mut std::collections::HashMap<String, Vec<String>>,
1442 ) {
1443 for sn in nodes {
1444 match &sn.node {
1445 Node::StructDecl { name, fields, .. } => {
1446 layouts.insert(
1447 name.clone(),
1448 fields.iter().map(|field| field.name.clone()).collect(),
1449 );
1450 }
1451 Node::Pipeline { body, .. }
1452 | Node::FnDecl { body, .. }
1453 | Node::ToolDecl { body, .. } => {
1454 Self::collect_struct_layouts(body, layouts);
1455 }
1456 Node::SkillDecl { fields, .. } => {
1457 for (_k, v) in fields {
1458 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1459 }
1460 }
1461 Node::EvalPackDecl {
1462 fields,
1463 body,
1464 summarize,
1465 ..
1466 } => {
1467 for (_k, v) in fields {
1468 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1469 }
1470 Self::collect_struct_layouts(body, layouts);
1471 if let Some(summary_body) = summarize {
1472 Self::collect_struct_layouts(summary_body, layouts);
1473 }
1474 }
1475 Node::Block(stmts) => {
1476 Self::collect_struct_layouts(stmts, layouts);
1477 }
1478 Node::AttributedDecl { inner, .. } => {
1479 Self::collect_struct_layouts(std::slice::from_ref(inner), layouts);
1480 }
1481 _ => {}
1482 }
1483 }
1484 }
1485
1486 pub(super) fn collect_interface_methods(
1487 nodes: &[SNode],
1488 interfaces: &mut std::collections::HashMap<String, Vec<String>>,
1489 ) {
1490 for sn in nodes {
1491 match &sn.node {
1492 Node::InterfaceDecl { name, methods, .. } => {
1493 let method_names: Vec<String> =
1494 methods.iter().map(|m| m.name.clone()).collect();
1495 interfaces.insert(name.clone(), method_names);
1496 }
1497 Node::Pipeline { body, .. }
1498 | Node::FnDecl { body, .. }
1499 | Node::ToolDecl { body, .. } => {
1500 Self::collect_interface_methods(body, interfaces);
1501 }
1502 Node::SkillDecl { fields, .. } => {
1503 for (_k, v) in fields {
1504 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1505 }
1506 }
1507 Node::EvalPackDecl {
1508 fields,
1509 body,
1510 summarize,
1511 ..
1512 } => {
1513 for (_k, v) in fields {
1514 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1515 }
1516 Self::collect_interface_methods(body, interfaces);
1517 if let Some(summary_body) = summarize {
1518 Self::collect_interface_methods(summary_body, interfaces);
1519 }
1520 }
1521 Node::Block(stmts) => {
1522 Self::collect_interface_methods(stmts, interfaces);
1523 }
1524 Node::AttributedDecl { inner, .. } => {
1525 Self::collect_interface_methods(std::slice::from_ref(inner), interfaces);
1526 }
1527 _ => {}
1528 }
1529 }
1530 }
1531
1532 pub fn compile_fn_body(
1545 &mut self,
1546 type_params: &[harn_parser::TypeParam],
1547 params: &[TypedParam],
1548 body: &[SNode],
1549 source_file: Option<String>,
1550 ) -> Result<CompiledFunction, CompileError> {
1551 let mut fn_compiler = self.nested_body();
1552 fn_compiler.enum_names = self.enum_names.clone();
1553 fn_compiler.enum_variant_owners = self.enum_variant_owners.clone();
1554 fn_compiler.interface_methods = self.interface_methods.clone();
1555 fn_compiler.type_aliases = self.type_aliases.clone();
1556 fn_compiler.struct_layouts = self.struct_layouts.clone();
1557 fn_compiler.declare_param_slots(params);
1558 fn_compiler.record_param_types(params);
1559 fn_compiler.emit_default_preamble(params)?;
1560 fn_compiler.emit_type_checks(params);
1561 let is_gen = body_contains_yield(body);
1562 fn_compiler.seed_captured_idents(body);
1563 fn_compiler.compile_block(body)?;
1564 fn_compiler.chunk.emit(Op::Nil, 0);
1565 fn_compiler.chunk.emit(Op::Return, 0);
1566 fn_compiler.chunk.source_file = source_file;
1567 let param_slots = crate::chunk::ParamSlot::vec_from_typed(params);
1568 let has_runtime_type_checks =
1569 CompiledFunction::has_runtime_type_checks_for_params(¶m_slots);
1570 super::ensure_chunk_addressable(&fn_compiler.chunk, "function body", self.line)?;
1571 Ok(CompiledFunction {
1572 name: String::new(),
1573 type_params: type_params.iter().map(|param| param.name.clone()).collect(),
1574 nominal_type_names: fn_compiler.nominal_type_names(),
1575 params: param_slots,
1576 default_start: TypedParam::default_start(params),
1577 chunk: Arc::new(fn_compiler.chunk),
1578 is_generator: is_gen,
1579 is_stream: false,
1580 has_rest_param: false,
1581 has_runtime_type_checks,
1582 })
1583 }
1584
1585 pub(super) fn produces_value(node: &Node) -> bool {
1587 match node {
1588 Node::AttributedDecl { inner, .. } => Self::produces_value(&inner.node),
1595 Node::LetBinding { .. }
1596 | Node::ConstBinding { .. }
1597 | Node::Assignment { .. }
1598 | Node::ReturnStmt { .. }
1599 | Node::FnDecl { .. }
1600 | Node::ToolDecl { .. }
1601 | Node::SkillDecl { .. }
1602 | Node::EvalPackDecl { .. }
1603 | Node::ImplBlock { .. }
1604 | Node::StructDecl { .. }
1605 | Node::EnumDecl { .. }
1606 | Node::InterfaceDecl { .. }
1607 | Node::TypeDecl { .. }
1608 | Node::OverrideDecl { .. }
1611 | Node::Pipeline { .. }
1612 | Node::ThrowStmt { .. }
1613 | Node::BreakStmt
1614 | Node::ContinueStmt
1615 | Node::RequireStmt { .. }
1616 | Node::DeferStmt { .. } => false,
1617 Node::TryCatch { has_catch: _, .. }
1618 | Node::TryExpr { .. }
1619 | Node::Retry { .. }
1620 | Node::GuardStmt { .. }
1621 | Node::DeadlineBlock { .. }
1622 | Node::MutexBlock { .. }
1623 | Node::Spread(_) => true,
1624 _ => true,
1625 }
1626 }
1627}
1628
1629impl Default for Compiler {
1630 fn default() -> Self {
1631 Self::new()
1632 }
1633}
1634
1635fn collect_closure_capture_idents(node: &SNode, set: &mut std::collections::HashSet<String>) {
1645 match super::peel_node(node) {
1646 Node::Closure { body, .. } | Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
1647 for sn in body {
1648 collect_all_idents(sn, set);
1649 }
1650 }
1651 Node::Parallel { expr, body, .. } => {
1662 for sn in body {
1663 collect_all_idents(sn, set);
1664 }
1665 collect_closure_capture_idents(expr, set);
1666 }
1667 Node::SpawnExpr { body } => {
1668 for sn in body {
1669 collect_all_idents(sn, set);
1670 }
1671 }
1672 _ => {
1673 for child in harn_parser::visit::immediate_children(node) {
1674 collect_closure_capture_idents(child, set);
1675 }
1676 }
1677 }
1678}
1679
1680fn collect_all_idents(node: &SNode, set: &mut std::collections::HashSet<String>) {
1682 if let Node::Identifier(name) = super::peel_node(node) {
1683 set.insert(name.clone());
1684 }
1685 for child in harn_parser::visit::immediate_children(node) {
1686 collect_all_idents(child, set);
1687 }
1688}