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(super) 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 mut self,
384 program: &[SNode],
385 pipeline_name: &str,
386 ) -> Result<Chunk, CompileError> {
387 Self::collect_enum_names(program, &mut self.enum_names);
388 self.enum_names.insert("Result".to_string());
389 Self::collect_enum_variant_owners(program, &mut self.enum_variant_owners);
390 Self::seed_builtin_variant_owners(&mut self.enum_variant_owners);
391 Self::collect_struct_layouts(program, &mut self.struct_layouts);
392 Self::collect_interface_methods(program, &mut self.interface_methods);
393 self.collect_type_aliases(program);
394 self.seed_captured_idents(program);
399
400 for sn in program {
401 if matches!(
402 &sn.node,
403 Node::ImportDecl { .. } | Node::SelectiveImport { .. }
404 ) {
405 self.compile_node(sn)?;
406 }
407 }
408 let target = program.iter().find(
409 |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
410 );
411
412 if let Some(sn) = target {
413 self.compile_top_level_declarations(program)?;
414 if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
415 if let Some(parent_name) = extends {
416 self.compile_parent_pipeline(program, parent_name)?;
417 }
418 let saved = std::mem::replace(&mut self.module_level, false);
419 self.compile_block(body)?;
420 self.module_level = saved;
421 }
422 }
423
424 self.drain_finallys_to_floor(0)?;
425 self.chunk.emit(Op::Nil, self.line);
426 self.chunk.emit(Op::Return, self.line);
427 super::ensure_chunk_addressable(&self.chunk, "the pipeline body", self.line)?;
428 Ok(self.chunk)
429 }
430
431 pub(super) fn compile_parent_pipeline(
433 &mut self,
434 program: &[SNode],
435 parent_name: &str,
436 ) -> Result<(), CompileError> {
437 let parent = program
438 .iter()
439 .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
440 if let Some(sn) = parent {
441 if let Node::Pipeline { body, extends, .. } = &sn.node {
442 if let Some(grandparent) = extends {
443 self.compile_parent_pipeline(program, grandparent)?;
444 }
445 for stmt in body {
446 self.compile_discarded_stmt(stmt)?;
447 }
448 }
449 }
450 Ok(())
451 }
452
453 pub(super) fn emit_default_preamble(
458 &mut self,
459 params: &[TypedParam],
460 ) -> Result<(), CompileError> {
461 for (i, param) in params.iter().enumerate() {
462 if let Some(default_expr) = ¶m.default_value {
463 self.chunk.emit(Op::GetArgc, self.line);
464 let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
465 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
466 self.chunk.emit(Op::GreaterEqual, self.line);
467 let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
468 self.chunk.emit(Op::Pop, self.line);
470 let masked = self.mask_param_names(¶ms[i..]);
479 let result = self.compile_node(default_expr);
480 self.restore_param_names(masked);
481 result?;
482 self.emit_init_or_define_binding(¶m.name, false);
483 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
484 self.chunk.patch_jump(skip_jump);
485 self.chunk.emit(Op::Pop, self.line);
486 self.chunk.patch_jump(end_jump);
487 }
488 }
489 Ok(())
490 }
491
492 pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
499 for (param_index, param) in params.iter().enumerate() {
500 if let Some(type_expr) = ¶m.type_expr {
501 let check_type = if param.rest {
502 harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
503 } else {
504 type_expr.clone()
505 };
506
507 if let harn_parser::TypeExpr::Named(name) = &check_type {
508 if let Some(methods) = self.interface_methods.get(name).cloned() {
509 let fn_idx = self.string_constant("__assert_interface");
510 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
511 self.emit_get_binding(¶m.name);
512 let name_idx = self.string_constant(¶m.name);
513 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
514 let iface_idx = self.string_constant(name);
515 self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
516 let methods_str = methods.join(",");
517 let methods_idx = self.owned_string_constant(methods_str);
518 self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
519 self.chunk.emit_u8(Op::Call, 4, self.line);
520 self.chunk.emit(Op::Pop, self.line);
521 continue;
522 }
523 }
524
525 if param.default_value.is_some() {
526 if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
527 self.emit_default_param_schema_check(param_index, param, &schema);
528 }
529 }
530 }
531 }
532 }
533
534 fn emit_default_param_schema_check(
535 &mut self,
536 param_index: usize,
537 param: &TypedParam,
538 schema: &VmValue,
539 ) {
540 self.chunk.emit(Op::GetArgc, self.line);
541 let threshold_idx = self
542 .chunk
543 .add_constant(Constant::Int((param_index + 1) as i64));
544 self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
545 self.chunk.emit(Op::GreaterEqual, self.line);
546 let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
547 self.chunk.emit(Op::Pop, self.line);
548 self.emit_schema_assert_call(param, schema);
549 let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
550 self.chunk.patch_jump(supplied_jump);
551 self.chunk.emit(Op::Pop, self.line);
552 self.chunk.patch_jump(end_jump);
553 }
554
555 fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
556 let fn_idx = self.string_constant("__assert_schema");
557 self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
558 self.emit_get_binding(¶m.name);
559 let name_idx = self.string_constant(¶m.name);
560 self.chunk.emit_u16(Op::Constant, name_idx, self.line);
561 self.emit_vm_value_literal(schema);
562 self.chunk.emit_u8(Op::Call, 3, self.line);
563 self.chunk.emit(Op::Pop, self.line);
564 }
565
566 pub(crate) fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
567 match type_expr {
568 harn_parser::TypeExpr::Named(name) => match name.as_str() {
569 "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
570 | "closure" | "bytes" => Some(VmValue::dict(BTreeMap::from([(
571 "type".to_string(),
572 VmValue::String(arcstr::ArcStr::from(name.as_str())),
573 )]))),
574 _ => None,
575 },
576 harn_parser::TypeExpr::Shape(fields)
577 | harn_parser::TypeExpr::OpenShape { fields, .. } => {
578 let mut properties = BTreeMap::new();
579 let mut required = Vec::new();
580 for field in fields {
581 let field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
582 properties.insert(field.name.clone(), field_schema);
583 if !field.optional {
584 required.push(VmValue::String(arcstr::ArcStr::from(field.name.as_str())));
585 }
586 }
587 let mut out = BTreeMap::new();
588 out.put_str("type", "dict");
589 out.insert("properties".to_string(), VmValue::dict(properties));
590 if !required.is_empty() {
591 out.insert(
592 "required".to_string(),
593 VmValue::List(std::sync::Arc::new(required)),
594 );
595 }
596 Some(VmValue::dict(out))
597 }
598 harn_parser::TypeExpr::List(inner) => {
599 let mut out = BTreeMap::new();
600 out.put_str("type", "list");
601 if let Some(item_schema) = Self::type_expr_to_schema_value(inner) {
602 out.insert("items".to_string(), item_schema);
603 }
604 Some(VmValue::dict(out))
605 }
606 harn_parser::TypeExpr::DictType(key, value) => {
607 let mut out = BTreeMap::new();
608 out.put_str("type", "dict");
609 if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
610 if let Some(value_schema) = Self::type_expr_to_schema_value(value) {
611 out.insert("additional_properties".to_string(), value_schema);
612 }
613 }
614 Some(VmValue::dict(out))
615 }
616 harn_parser::TypeExpr::Union(members) => {
617 if !members.is_empty()
622 && members
623 .iter()
624 .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
625 {
626 let values = members
627 .iter()
628 .map(|m| match m {
629 harn_parser::TypeExpr::LitString(s) => {
630 VmValue::String(arcstr::ArcStr::from(s.as_str()))
631 }
632 _ => unreachable!(),
633 })
634 .collect::<Vec<_>>();
635 return Some(VmValue::dict(BTreeMap::from([
636 (
637 "type".to_string(),
638 VmValue::String(arcstr::ArcStr::from("string")),
639 ),
640 (
641 "enum".to_string(),
642 VmValue::List(std::sync::Arc::new(values)),
643 ),
644 ])));
645 }
646 if !members.is_empty()
647 && members
648 .iter()
649 .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
650 {
651 let values = members
652 .iter()
653 .map(|m| match m {
654 harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
655 _ => unreachable!(),
656 })
657 .collect::<Vec<_>>();
658 return Some(VmValue::dict(BTreeMap::from([
659 (
660 "type".to_string(),
661 VmValue::String(arcstr::ArcStr::from("int")),
662 ),
663 (
664 "enum".to_string(),
665 VmValue::List(std::sync::Arc::new(values)),
666 ),
667 ])));
668 }
669 let branches = members
670 .iter()
671 .filter_map(Self::type_expr_to_schema_value)
672 .collect::<Vec<_>>();
673 if branches.is_empty() {
674 None
675 } else {
676 Some(VmValue::dict(BTreeMap::from([(
677 "union".to_string(),
678 VmValue::List(std::sync::Arc::new(branches)),
679 )])))
680 }
681 }
682 harn_parser::TypeExpr::Intersection(members) => {
683 let branches = members
687 .iter()
688 .filter_map(Self::type_expr_to_schema_value)
689 .collect::<Vec<_>>();
690 if branches.is_empty() {
691 None
692 } else {
693 Some(VmValue::dict(BTreeMap::from([(
694 "all_of".to_string(),
695 VmValue::List(std::sync::Arc::new(branches)),
696 )])))
697 }
698 }
699 harn_parser::TypeExpr::FnType { .. } => Some(VmValue::dict(BTreeMap::from([(
700 "type".to_string(),
701 VmValue::String(arcstr::ArcStr::from("closure")),
702 )]))),
703 harn_parser::TypeExpr::Applied { .. } => None,
704 harn_parser::TypeExpr::Iter(_)
705 | harn_parser::TypeExpr::Generator(_)
706 | harn_parser::TypeExpr::Stream(_) => None,
707 harn_parser::TypeExpr::Never => None,
708 harn_parser::TypeExpr::LitString(s) => Some(VmValue::dict(BTreeMap::from([
709 (
710 "type".to_string(),
711 VmValue::String(arcstr::ArcStr::from("string")),
712 ),
713 (
714 "const".to_string(),
715 VmValue::String(arcstr::ArcStr::from(s.as_str())),
716 ),
717 ]))),
718 harn_parser::TypeExpr::LitInt(v) => Some(VmValue::dict(BTreeMap::from([
719 (
720 "type".to_string(),
721 VmValue::String(arcstr::ArcStr::from("int")),
722 ),
723 ("const".to_string(), VmValue::Int(*v)),
724 ]))),
725 harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
726 }
727 }
728
729 pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
730 match value {
731 VmValue::String(text) => {
732 let idx = self.string_constant(text);
733 self.chunk.emit_u16(Op::Constant, idx, self.line);
734 }
735 VmValue::Int(number) => {
736 let idx = self.chunk.add_constant(Constant::Int(*number));
737 self.chunk.emit_u16(Op::Constant, idx, self.line);
738 }
739 VmValue::Float(number) => {
740 let idx = self.chunk.add_constant(Constant::Float(*number));
741 self.chunk.emit_u16(Op::Constant, idx, self.line);
742 }
743 VmValue::Bool(value) => {
744 let idx = self.chunk.add_constant(Constant::Bool(*value));
745 self.chunk.emit_u16(Op::Constant, idx, self.line);
746 }
747 VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
748 VmValue::List(items) => {
749 for item in items.iter() {
750 self.emit_vm_value_literal(item);
751 }
752 self.chunk
753 .emit_u16(Op::BuildList, items.len() as u16, self.line);
754 }
755 VmValue::Dict(entries) => {
756 for (key, item) in entries.iter() {
757 let key_idx = self.string_constant(key);
758 self.chunk.emit_u16(Op::Constant, key_idx, self.line);
759 self.emit_vm_value_literal(item);
760 }
761 self.chunk
762 .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
763 }
764 _ => {}
765 }
766 }
767
768 pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
770 let hi = (type_name_idx >> 8) as u8;
771 let lo = type_name_idx as u8;
772 self.chunk.code.push(hi);
773 self.chunk.code.push(lo);
774 self.chunk.lines.push(self.line);
775 self.chunk.columns.push(self.column);
776 self.chunk.lines.push(self.line);
777 self.chunk.columns.push(self.column);
778 }
779
780 pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
782 if body.is_empty() {
783 self.chunk.emit(Op::Nil, self.line);
784 } else {
785 self.compile_scoped_block(body)?;
786 }
787 Ok(())
788 }
789
790 pub(super) fn compile_catch_binding(
792 &mut self,
793 error_var: &Option<String>,
794 ) -> Result<(), CompileError> {
795 if let Some(var_name) = error_var {
796 self.emit_define_binding(var_name, false);
797 } else {
798 self.chunk.emit(Op::Pop, self.line);
799 }
800 Ok(())
801 }
802
803 pub(super) fn compile_finally_inline(
810 &mut self,
811 finally_body: &[SNode],
812 ) -> Result<(), CompileError> {
813 if !finally_body.is_empty() {
814 self.compile_scoped_block(finally_body)?;
815 self.chunk.emit(Op::Pop, self.line);
816 }
817 Ok(())
818 }
819
820 pub(super) fn pending_finallys_until_barrier(&self) -> Vec<Vec<SNode>> {
825 let mut out = Vec::new();
826 for entry in self.finally_bodies.iter().rev() {
827 match entry {
828 FinallyEntry::CatchBarrier => break,
829 FinallyEntry::Finally(body) => out.push(body.clone()),
830 }
831 }
832 out
833 }
834
835 pub(super) fn has_pending_finally(&self) -> bool {
837 self.finally_bodies
838 .iter()
839 .any(|e| matches!(e, FinallyEntry::Finally(_)))
840 }
841
842 pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
851 self.temp_counter += 1;
852 let temp_name = format!("__finally_err_{}__", self.temp_counter);
853 self.emit_define_binding(&temp_name, true);
854 self.emit_get_binding(&temp_name);
855 self.chunk.emit(Op::Throw, self.line);
856 Ok(())
857 }
858
859 pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
860 for param in params {
861 self.define_local_slot(¶m.name, false);
862 }
863 }
864
865 fn mask_param_names(&mut self, params: &[TypedParam]) -> Vec<(String, super::LocalBinding)> {
871 let mut removed = Vec::new();
872 if let Some(scope) = self.local_scopes.last_mut() {
873 for param in params {
874 if let Some(binding) = scope.remove(¶m.name) {
875 removed.push((param.name.clone(), binding));
876 }
877 }
878 }
879 removed
880 }
881
882 fn restore_param_names(&mut self, removed: Vec<(String, super::LocalBinding)>) {
884 if let Some(scope) = self.local_scopes.last_mut() {
885 for (name, binding) in removed {
886 scope.insert(name, binding);
887 }
888 }
889 }
890
891 pub(super) fn seed_captured_idents(&mut self, body: &[SNode]) {
898 let mut set = std::collections::HashSet::new();
899 for sn in body {
900 collect_closure_capture_idents(sn, &mut set);
901 }
902 self.captured_idents = set;
903 }
904
905 #[inline]
911 fn is_boxed_capture(&self, name: &str, mutable: bool) -> bool {
912 mutable && self.captured_idents.contains(name)
913 }
914
915 fn define_local_slot(&mut self, name: &str, mutable: bool) -> Option<u16> {
916 if self.module_level
917 || harn_parser::is_discard_name(name)
918 || self.is_boxed_capture(name, mutable)
919 {
920 return None;
924 }
925 let current = self.local_scopes.last_mut()?;
926 if let Some(existing) = current.get_mut(name) {
927 if existing.mutable || mutable {
928 if mutable {
929 existing.mutable = true;
930 if let Some(info) = self.chunk.local_slots.get_mut(existing.slot as usize) {
931 info.mutable = true;
932 }
933 }
934 return Some(existing.slot);
935 }
936 return None;
937 }
938 let slot = self
939 .chunk
940 .add_local_slot(name.to_string(), mutable, self.scope_depth);
941 current.insert(name.to_string(), super::LocalBinding { slot, mutable });
942 Some(slot)
943 }
944
945 pub(super) fn resolve_local_slot(&self, name: &str) -> Option<super::LocalBinding> {
946 if self.module_level {
947 return None;
948 }
949 self.local_scopes
950 .iter()
951 .rev()
952 .find_map(|scope| scope.get(name).copied())
953 }
954
955 pub(super) fn emit_get_binding(&mut self, name: &str) {
956 if let Some(binding) = self.resolve_local_slot(name) {
957 self.chunk
958 .emit_u16(Op::GetLocalSlot, binding.slot, self.line);
959 } else {
960 let idx = self.string_constant(name);
961 self.chunk.emit_u16(Op::GetVar, idx, self.line);
962 }
963 }
964
965 pub(super) fn emit_define_binding(&mut self, name: &str, mutable: bool) {
966 if self.is_boxed_capture(name, mutable) {
967 let idx = self.string_constant(name);
971 self.chunk.emit_u16(Op::DefCell, idx, self.line);
972 } else if let Some(slot) = self.define_local_slot(name, mutable) {
973 self.chunk.emit_u16(Op::DefLocalSlot, slot, self.line);
974 } else {
975 let idx = self.string_constant(name);
976 let op = if mutable { Op::DefVar } else { Op::DefLet };
977 self.chunk.emit_u16(op, idx, self.line);
978 }
979 }
980
981 pub(super) fn emit_init_or_define_binding(&mut self, name: &str, mutable: bool) {
982 if let Some(binding) = self.resolve_local_slot(name) {
983 self.chunk
984 .emit_u16(Op::DefLocalSlot, binding.slot, self.line);
985 } else {
986 self.emit_define_binding(name, mutable);
987 }
988 }
989
990 pub(super) fn emit_set_binding(&mut self, name: &str) {
991 if let Some(binding) = self.resolve_local_slot(name) {
992 let _ = binding.mutable;
993 self.chunk
994 .emit_u16(Op::SetLocalSlot, binding.slot, self.line);
995 } else {
996 let idx = self.string_constant(name);
997 self.chunk.emit_u16(Op::SetVar, idx, self.line);
998 }
999 }
1000
1001 pub(super) fn begin_scope(&mut self) {
1002 self.chunk.emit(Op::PushScope, self.line);
1003 self.scope_depth += 1;
1004 self.type_scopes.push(std::collections::HashMap::new());
1005 self.local_scopes.push(std::collections::HashMap::new());
1006 }
1007
1008 pub(super) fn end_scope(&mut self) {
1009 if self.scope_depth > 0 {
1010 self.chunk.emit(Op::PopScope, self.line);
1011 self.scope_depth -= 1;
1012 self.type_scopes.pop();
1013 self.local_scopes.pop();
1014 }
1015 }
1016
1017 pub(super) fn emit_scope_unwind_to(&mut self, target_depth: usize) {
1020 for _ in target_depth..self.scope_depth {
1021 self.chunk.emit(Op::PopScope, self.line);
1022 }
1023 }
1024
1025 pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1026 self.begin_scope();
1027 let finally_floor = self.finally_bodies.len();
1028 if stmts.is_empty() {
1029 self.chunk.emit(Op::Nil, self.line);
1030 } else {
1031 self.compile_block(stmts)?;
1032 }
1033 self.drain_finallys_to_floor(finally_floor)?;
1034 self.end_scope();
1035 Ok(())
1036 }
1037
1038 pub(super) fn compile_scoped_statements(
1039 &mut self,
1040 stmts: &[SNode],
1041 ) -> Result<(), CompileError> {
1042 self.begin_scope();
1043 self.record_monomorphic_var_bindings(stmts);
1044 let finally_floor = self.finally_bodies.len();
1045 for sn in stmts {
1046 self.compile_discarded_stmt(sn)?;
1047 }
1048 self.drain_finallys_to_floor(finally_floor)?;
1049 self.end_scope();
1050 Ok(())
1051 }
1052
1053 pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
1058 while self.finally_bodies.len() > floor {
1059 let entry = self.finally_bodies.pop().expect("non-empty by guard");
1060 if let FinallyEntry::Finally(body) = entry {
1061 self.compile_finally_inline(&body)?;
1062 }
1063 }
1064 Ok(())
1065 }
1066
1067 pub(super) fn run_pending_finallys_for_transfer(
1080 &mut self,
1081 floor: usize,
1082 ) -> Result<(), CompileError> {
1083 if self.finally_bodies.len() <= floor {
1084 return Ok(());
1085 }
1086 let saved = self.finally_bodies[floor..].to_vec();
1087 let result = self.drain_finallys_to_floor(floor);
1088 self.finally_bodies.extend(saved);
1089 result
1090 }
1091
1092 pub(super) fn run_pending_finallys_until_barrier(&mut self) -> Result<(), CompileError> {
1097 let floor = self
1098 .finally_bodies
1099 .iter()
1100 .rposition(|e| matches!(e, FinallyEntry::CatchBarrier))
1101 .map(|i| i + 1)
1102 .unwrap_or(0);
1103 self.run_pending_finallys_for_transfer(floor)
1104 }
1105
1106 pub(super) fn maybe_register_owned_drop(
1111 &mut self,
1112 pattern: &harn_parser::BindingPattern,
1113 type_ann: Option<&TypeExpr>,
1114 span: harn_lexer::Span,
1115 ) {
1116 let Some(ty) = type_ann else {
1122 return;
1123 };
1124 if !matches!(ty, TypeExpr::Owned(_)) {
1125 return;
1126 }
1127 let harn_parser::BindingPattern::Identifier(name) = pattern else {
1128 return;
1129 };
1130 if harn_parser::is_discard_name(name) {
1131 return;
1132 }
1133 let call = harn_parser::spanned(
1134 Node::FunctionCall {
1135 name: "drop".to_string(),
1136 args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
1137 type_args: Vec::new(),
1138 },
1139 span,
1140 );
1141 self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
1142 }
1143
1144 pub(super) fn compile_discarded_stmt(&mut self, sn: &SNode) -> Result<(), CompileError> {
1160 #[cfg(debug_assertions)]
1161 let probe = self.chunk.balance_probe();
1162 self.compile_node(sn)?;
1163 #[allow(unused_mut)]
1164 let mut produces = Self::produces_value(&sn.node);
1165 #[cfg(test)]
1169 if let Some(forced) = FORCE_DISCARDED_PRODUCES_VALUE.with(std::cell::Cell::get) {
1170 produces = forced;
1171 }
1172 #[cfg(debug_assertions)]
1173 if let Some(delta) = self.chunk.balance_delta_since(probe) {
1174 let expected = i32::from(produces);
1175 debug_assert_eq!(
1176 delta, expected,
1177 "operand-stack imbalance at line {}: produces_value={produces} but the \
1178 node's emitted bytecode netted {delta} (expected {expected}). A \
1179 `produces_value` arm is out of sync with this node's codegen — see #2622.\n\
1180 node: {:?}",
1181 self.line, sn.node,
1182 );
1183 }
1184 if produces {
1185 self.chunk.emit(Op::Pop, self.line);
1186 }
1187 Ok(())
1188 }
1189
1190 pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
1191 self.record_monomorphic_var_bindings(stmts);
1192 for (i, snode) in stmts.iter().enumerate() {
1193 if i == stmts.len() - 1 {
1194 self.compile_node(snode)?;
1198 if !Self::produces_value(&snode.node) {
1199 self.chunk.emit(Op::Nil, self.line);
1200 }
1201 } else {
1202 self.compile_discarded_stmt(snode)?;
1203 }
1204 }
1205 Ok(())
1206 }
1207
1208 pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
1210 self.begin_scope();
1211 let finally_floor = self.finally_bodies.len();
1212 if body.is_empty() {
1213 self.chunk.emit(Op::Nil, self.line);
1214 } else {
1215 self.compile_block(body)?;
1216 if !Self::produces_value(&body.last().unwrap().node) {
1217 self.chunk.emit(Op::Nil, self.line);
1218 }
1219 }
1220 self.drain_finallys_to_floor(finally_floor)?;
1221 self.end_scope();
1222 Ok(())
1223 }
1224
1225 pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
1227 match op {
1228 "+" => self.chunk.emit(Op::Add, self.line),
1229 "-" => self.chunk.emit(Op::Sub, self.line),
1230 "*" => self.chunk.emit(Op::Mul, self.line),
1231 "/" => self.chunk.emit(Op::Div, self.line),
1232 "%" => self.chunk.emit(Op::Mod, self.line),
1233 _ => {
1234 return Err(CompileError {
1235 message: format!("Unknown compound operator: {op}"),
1236 line: self.line,
1237 })
1238 }
1239 }
1240 Ok(())
1241 }
1242
1243 pub(super) fn compile_top_level_declarations(
1244 &mut self,
1245 program: &[SNode],
1246 ) -> Result<(), CompileError> {
1247 for sn in program {
1259 let handled_elsewhere = matches!(
1260 peel_node(sn),
1261 Node::Pipeline { .. }
1262 | Node::ImportDecl { .. }
1263 | Node::SelectiveImport { .. }
1264 | Node::OverrideDecl { .. }
1265 | Node::EvalPackDecl { .. }
1266 | Node::FnDecl { .. }
1267 | Node::ToolDecl { .. }
1268 | Node::SkillDecl { .. }
1269 | Node::ImplBlock { .. }
1270 | Node::StructDecl { .. }
1271 | Node::EnumDecl { .. }
1272 | Node::InterfaceDecl { .. }
1273 | Node::TypeDecl { .. }
1274 );
1275 if !handled_elsewhere {
1276 self.compile_discarded_stmt(sn)?;
1277 }
1278 }
1279 for sn in program {
1285 let inner_kind = match &sn.node {
1286 Node::AttributedDecl { inner, .. } => &inner.node,
1287 other => other,
1288 };
1289 match inner_kind {
1290 Node::EvalPackDecl {
1291 binding_name,
1292 pack_id,
1293 fields,
1294 body,
1295 summarize,
1296 ..
1297 } => {
1298 self.compile_eval_pack_decl(
1299 binding_name,
1300 pack_id,
1301 fields,
1302 body,
1303 summarize,
1304 false,
1305 )?;
1306 }
1307 Node::FnDecl { .. }
1308 | Node::ToolDecl { .. }
1309 | Node::SkillDecl { .. }
1310 | Node::ImplBlock { .. }
1311 | Node::StructDecl { .. }
1312 | Node::EnumDecl { .. }
1313 | Node::InterfaceDecl { .. } => {
1314 self.compile_node(sn)?;
1315 }
1316 Node::TypeDecl { .. } => {}
1317 _ => {}
1318 }
1319 }
1320 Ok(())
1321 }
1322
1323 pub(super) fn collect_enum_names(
1325 nodes: &[SNode],
1326 names: &mut std::collections::HashSet<String>,
1327 ) {
1328 for sn in nodes {
1329 match &sn.node {
1330 Node::EnumDecl { name, .. } => {
1331 names.insert(name.clone());
1332 }
1333 Node::Pipeline { body, .. } => {
1334 Self::collect_enum_names(body, names);
1335 }
1336 Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
1337 Self::collect_enum_names(body, names);
1338 }
1339 Node::SkillDecl { fields, .. } => {
1340 for (_k, v) in fields {
1341 Self::collect_enum_names(std::slice::from_ref(v), names);
1342 }
1343 }
1344 Node::EvalPackDecl {
1345 fields,
1346 body,
1347 summarize,
1348 ..
1349 } => {
1350 for (_k, v) in fields {
1351 Self::collect_enum_names(std::slice::from_ref(v), names);
1352 }
1353 Self::collect_enum_names(body, names);
1354 if let Some(summary_body) = summarize {
1355 Self::collect_enum_names(summary_body, names);
1356 }
1357 }
1358 Node::Block(stmts) => {
1359 Self::collect_enum_names(stmts, names);
1360 }
1361 Node::AttributedDecl { inner, .. } => {
1362 Self::collect_enum_names(std::slice::from_ref(inner), names);
1363 }
1364 _ => {}
1365 }
1366 }
1367 }
1368
1369 pub(super) fn collect_enum_variant_owners(
1374 nodes: &[SNode],
1375 owners: &mut std::collections::HashMap<String, Vec<String>>,
1376 ) {
1377 harn_parser::visit::walk_program(nodes, &mut |sn| {
1378 if let Node::EnumDecl { name, variants, .. } = &sn.node {
1379 for variant in variants {
1380 let entry = owners.entry(variant.name.clone()).or_default();
1381 if !entry.contains(name) {
1382 entry.push(name.clone());
1383 }
1384 }
1385 }
1386 });
1387 }
1388
1389 pub(super) fn seed_builtin_variant_owners(
1392 owners: &mut std::collections::HashMap<String, Vec<String>>,
1393 ) {
1394 for variant in ["Ok", "Err"] {
1395 let entry = owners.entry(variant.to_string()).or_default();
1396 if !entry.contains(&"Result".to_string()) {
1397 entry.push("Result".to_string());
1398 }
1399 }
1400 }
1401
1402 pub(super) fn collect_struct_layouts(
1403 nodes: &[SNode],
1404 layouts: &mut std::collections::HashMap<String, Vec<String>>,
1405 ) {
1406 for sn in nodes {
1407 match &sn.node {
1408 Node::StructDecl { name, fields, .. } => {
1409 layouts.insert(
1410 name.clone(),
1411 fields.iter().map(|field| field.name.clone()).collect(),
1412 );
1413 }
1414 Node::Pipeline { body, .. }
1415 | Node::FnDecl { body, .. }
1416 | Node::ToolDecl { body, .. } => {
1417 Self::collect_struct_layouts(body, layouts);
1418 }
1419 Node::SkillDecl { fields, .. } => {
1420 for (_k, v) in fields {
1421 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1422 }
1423 }
1424 Node::EvalPackDecl {
1425 fields,
1426 body,
1427 summarize,
1428 ..
1429 } => {
1430 for (_k, v) in fields {
1431 Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1432 }
1433 Self::collect_struct_layouts(body, layouts);
1434 if let Some(summary_body) = summarize {
1435 Self::collect_struct_layouts(summary_body, layouts);
1436 }
1437 }
1438 Node::Block(stmts) => {
1439 Self::collect_struct_layouts(stmts, layouts);
1440 }
1441 Node::AttributedDecl { inner, .. } => {
1442 Self::collect_struct_layouts(std::slice::from_ref(inner), layouts);
1443 }
1444 _ => {}
1445 }
1446 }
1447 }
1448
1449 pub(super) fn collect_interface_methods(
1450 nodes: &[SNode],
1451 interfaces: &mut std::collections::HashMap<String, Vec<String>>,
1452 ) {
1453 for sn in nodes {
1454 match &sn.node {
1455 Node::InterfaceDecl { name, methods, .. } => {
1456 let method_names: Vec<String> =
1457 methods.iter().map(|m| m.name.clone()).collect();
1458 interfaces.insert(name.clone(), method_names);
1459 }
1460 Node::Pipeline { body, .. }
1461 | Node::FnDecl { body, .. }
1462 | Node::ToolDecl { body, .. } => {
1463 Self::collect_interface_methods(body, interfaces);
1464 }
1465 Node::SkillDecl { fields, .. } => {
1466 for (_k, v) in fields {
1467 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1468 }
1469 }
1470 Node::EvalPackDecl {
1471 fields,
1472 body,
1473 summarize,
1474 ..
1475 } => {
1476 for (_k, v) in fields {
1477 Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1478 }
1479 Self::collect_interface_methods(body, interfaces);
1480 if let Some(summary_body) = summarize {
1481 Self::collect_interface_methods(summary_body, interfaces);
1482 }
1483 }
1484 Node::Block(stmts) => {
1485 Self::collect_interface_methods(stmts, interfaces);
1486 }
1487 Node::AttributedDecl { inner, .. } => {
1488 Self::collect_interface_methods(std::slice::from_ref(inner), interfaces);
1489 }
1490 _ => {}
1491 }
1492 }
1493 }
1494
1495 pub fn compile_fn_body(
1508 &mut self,
1509 type_params: &[harn_parser::TypeParam],
1510 params: &[TypedParam],
1511 body: &[SNode],
1512 source_file: Option<String>,
1513 ) -> Result<CompiledFunction, CompileError> {
1514 let mut fn_compiler = self.nested_body();
1515 fn_compiler.enum_names = self.enum_names.clone();
1516 fn_compiler.enum_variant_owners = self.enum_variant_owners.clone();
1517 fn_compiler.interface_methods = self.interface_methods.clone();
1518 fn_compiler.type_aliases = self.type_aliases.clone();
1519 fn_compiler.struct_layouts = self.struct_layouts.clone();
1520 fn_compiler.declare_param_slots(params);
1521 fn_compiler.record_param_types(params);
1522 fn_compiler.emit_default_preamble(params)?;
1523 fn_compiler.emit_type_checks(params);
1524 let is_gen = body_contains_yield(body);
1525 fn_compiler.seed_captured_idents(body);
1526 fn_compiler.compile_block(body)?;
1527 fn_compiler.chunk.emit(Op::Nil, 0);
1528 fn_compiler.chunk.emit(Op::Return, 0);
1529 fn_compiler.chunk.source_file = source_file;
1530 let param_slots = crate::chunk::ParamSlot::vec_from_typed(params);
1531 let has_runtime_type_checks =
1532 CompiledFunction::has_runtime_type_checks_for_params(¶m_slots);
1533 super::ensure_chunk_addressable(&fn_compiler.chunk, "function body", self.line)?;
1534 Ok(CompiledFunction {
1535 name: String::new(),
1536 type_params: type_params.iter().map(|param| param.name.clone()).collect(),
1537 nominal_type_names: fn_compiler.nominal_type_names(),
1538 params: param_slots,
1539 default_start: TypedParam::default_start(params),
1540 chunk: Arc::new(fn_compiler.chunk),
1541 is_generator: is_gen,
1542 is_stream: false,
1543 has_rest_param: false,
1544 has_runtime_type_checks,
1545 })
1546 }
1547
1548 pub(super) fn produces_value(node: &Node) -> bool {
1550 match node {
1551 Node::AttributedDecl { inner, .. } => Self::produces_value(&inner.node),
1558 Node::LetBinding { .. }
1559 | Node::ConstBinding { .. }
1560 | Node::Assignment { .. }
1561 | Node::ReturnStmt { .. }
1562 | Node::FnDecl { .. }
1563 | Node::ToolDecl { .. }
1564 | Node::SkillDecl { .. }
1565 | Node::EvalPackDecl { .. }
1566 | Node::ImplBlock { .. }
1567 | Node::StructDecl { .. }
1568 | Node::EnumDecl { .. }
1569 | Node::InterfaceDecl { .. }
1570 | Node::TypeDecl { .. }
1571 | Node::OverrideDecl { .. }
1574 | Node::Pipeline { .. }
1575 | Node::ThrowStmt { .. }
1576 | Node::BreakStmt
1577 | Node::ContinueStmt
1578 | Node::RequireStmt { .. }
1579 | Node::DeferStmt { .. } => false,
1580 Node::TryCatch { has_catch: _, .. }
1581 | Node::TryExpr { .. }
1582 | Node::Retry { .. }
1583 | Node::GuardStmt { .. }
1584 | Node::DeadlineBlock { .. }
1585 | Node::MutexBlock { .. }
1586 | Node::Spread(_) => true,
1587 _ => true,
1588 }
1589 }
1590}
1591
1592impl Default for Compiler {
1593 fn default() -> Self {
1594 Self::new()
1595 }
1596}
1597
1598fn collect_closure_capture_idents(node: &SNode, set: &mut std::collections::HashSet<String>) {
1608 match super::peel_node(node) {
1609 Node::Closure { body, .. } | Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
1610 for sn in body {
1611 collect_all_idents(sn, set);
1612 }
1613 }
1614 Node::Parallel { expr, body, .. } => {
1625 for sn in body {
1626 collect_all_idents(sn, set);
1627 }
1628 collect_closure_capture_idents(expr, set);
1629 }
1630 Node::SpawnExpr { body } => {
1631 for sn in body {
1632 collect_all_idents(sn, set);
1633 }
1634 }
1635 _ => {
1636 for child in harn_parser::visit::immediate_children(node) {
1637 collect_closure_capture_idents(child, set);
1638 }
1639 }
1640 }
1641}
1642
1643fn collect_all_idents(node: &SNode, set: &mut std::collections::HashSet<String>) {
1645 if let Node::Identifier(name) = super::peel_node(node) {
1646 set.insert(name.clone());
1647 }
1648 for child in harn_parser::visit::immediate_children(node) {
1649 collect_all_idents(child, set);
1650 }
1651}