Skip to main content

harn_vm/compiler/
state.rs

1use std::collections::BTreeMap;
2use std::rc::Rc;
3
4use harn_parser::{Node, SNode, ShapeField, TypeExpr, TypedParam};
5
6use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
7use crate::value::VmValue;
8
9use super::error::CompileError;
10use super::yield_scan::body_contains_yield;
11use super::{peel_node, Compiler, CompilerOptions, FinallyEntry};
12
13impl Compiler {
14    pub fn new() -> Self {
15        Self::with_options(CompilerOptions::from_env())
16    }
17
18    pub fn with_options(options: CompilerOptions) -> Self {
19        Self {
20            options,
21            chunk: Chunk::new(),
22            line: 1,
23            column: 1,
24            enum_names: std::collections::HashSet::new(),
25            struct_layouts: std::collections::HashMap::new(),
26            interface_methods: std::collections::HashMap::new(),
27            loop_stack: Vec::new(),
28            handler_depth: 0,
29            finally_bodies: Vec::new(),
30            temp_counter: 0,
31            scope_depth: 0,
32            type_aliases: std::collections::HashMap::new(),
33            type_scopes: vec![std::collections::HashMap::new()],
34            string_constants: std::collections::HashMap::new(),
35            local_scopes: vec![std::collections::HashMap::new()],
36            module_level: true,
37        }
38    }
39
40    /// Compiler instance for a nested function-like body (fn, closure,
41    /// tool, parallel arm, etc.). Differs from `new()` only in that
42    /// `module_level` starts false — `try*` is allowed inside.
43    pub(super) fn for_nested_body(options: CompilerOptions) -> Self {
44        let mut c = Self::with_options(options);
45        c.module_level = false;
46        c
47    }
48
49    pub(super) fn nested_body(&self) -> Self {
50        Self::for_nested_body(self.options)
51    }
52
53    pub(super) fn nominal_type_names(&self) -> Vec<String> {
54        let mut names: Vec<String> = self
55            .struct_layouts
56            .keys()
57            .chain(self.enum_names.iter())
58            .cloned()
59            .collect();
60        names.sort();
61        names.dedup();
62        names
63    }
64
65    pub(super) fn string_constant(&mut self, value: &str) -> u16 {
66        if let Some(idx) = self.string_constants.get(value) {
67            return *idx;
68        }
69        let owned = value.to_string();
70        let idx = self.chunk.add_constant(Constant::String(owned.clone()));
71        self.string_constants.insert(owned, idx);
72        idx
73    }
74
75    pub(super) fn owned_string_constant(&mut self, value: String) -> u16 {
76        if let Some(idx) = self.string_constants.get(value.as_str()) {
77            return *idx;
78        }
79        let idx = self.chunk.add_constant(Constant::String(value.clone()));
80        self.string_constants.insert(value, idx);
81        idx
82    }
83
84    /// Populate `type_aliases` from a program's top-level `type T = ...`
85    /// declarations so later lowerings can resolve alias names to their
86    /// canonical `TypeExpr`.
87    pub(super) fn collect_type_aliases(&mut self, program: &[SNode]) {
88        for sn in program {
89            if let Node::TypeDecl {
90                name,
91                type_expr,
92                type_params: _,
93            } = &sn.node
94            {
95                self.type_aliases.insert(name.clone(), type_expr.clone());
96            }
97        }
98    }
99
100    /// Expand a single layer of alias references. Returns the resolved
101    /// `TypeExpr` with all `Named(T)` nodes whose `T` is a known alias
102    /// replaced by the alias's body.
103    pub(super) fn expand_alias(&self, ty: &TypeExpr) -> TypeExpr {
104        match ty {
105            TypeExpr::Named(name) => {
106                if let Some(target) = self.type_aliases.get(name) {
107                    self.expand_alias(target)
108                } else {
109                    TypeExpr::Named(name.clone())
110                }
111            }
112            TypeExpr::Union(types) => {
113                TypeExpr::Union(types.iter().map(|t| self.expand_alias(t)).collect())
114            }
115            TypeExpr::Intersection(types) => {
116                TypeExpr::Intersection(types.iter().map(|t| self.expand_alias(t)).collect())
117            }
118            TypeExpr::Shape(fields) => TypeExpr::Shape(
119                fields
120                    .iter()
121                    .map(|field| ShapeField {
122                        name: field.name.clone(),
123                        type_expr: self.expand_alias(&field.type_expr),
124                        optional: field.optional,
125                    })
126                    .collect(),
127            ),
128            TypeExpr::List(inner) => TypeExpr::List(Box::new(self.expand_alias(inner))),
129            TypeExpr::Iter(inner) => TypeExpr::Iter(Box::new(self.expand_alias(inner))),
130            TypeExpr::Generator(inner) => TypeExpr::Generator(Box::new(self.expand_alias(inner))),
131            TypeExpr::Stream(inner) => TypeExpr::Stream(Box::new(self.expand_alias(inner))),
132            TypeExpr::DictType(k, v) => TypeExpr::DictType(
133                Box::new(self.expand_alias(k)),
134                Box::new(self.expand_alias(v)),
135            ),
136            TypeExpr::FnType {
137                params,
138                return_type,
139            } => TypeExpr::FnType {
140                params: params.iter().map(|p| self.expand_alias(p)).collect(),
141                return_type: Box::new(self.expand_alias(return_type)),
142            },
143            TypeExpr::Applied { name, args } => TypeExpr::Applied {
144                name: name.clone(),
145                args: args.iter().map(|a| self.expand_alias(a)).collect(),
146            },
147            TypeExpr::Never => TypeExpr::Never,
148            TypeExpr::LitString(s) => TypeExpr::LitString(s.clone()),
149            TypeExpr::LitInt(v) => TypeExpr::LitInt(*v),
150            TypeExpr::Owned(inner) => TypeExpr::Owned(Box::new(self.expand_alias(inner))),
151        }
152    }
153
154    /// Build the JSON-Schema VmValue for a named type alias, or `None` if
155    /// the name is unknown or the alias cannot be lowered to a schema.
156    pub(super) fn schema_value_for_alias(&self, name: &str) -> Option<VmValue> {
157        let ty = self.type_aliases.get(name)?;
158        let expanded = self.expand_alias(ty);
159        Self::type_expr_to_schema_value(&expanded)
160    }
161
162    /// Schema-guard builtins that accept a schema as their second argument.
163    /// When callers pass a type-alias identifier here, the compiler lowers
164    /// it to the alias's JSON-Schema dict constant.
165    pub(super) fn is_schema_guard(name: &str) -> bool {
166        matches!(
167            name,
168            "schema_is"
169                | "schema_expect"
170                | "schema_parse"
171                | "schema_check"
172                | "is_type"
173                | "json_validate"
174        )
175    }
176
177    /// Check whether a dict-literal key node matches the given keyword
178    /// (identifier or string literal form).
179    pub(super) fn entry_key_is(key: &SNode, keyword: &str) -> bool {
180        matches!(
181            &key.node,
182            Node::Identifier(name) | Node::StringLiteral(name) | Node::RawStringLiteral(name)
183                if name == keyword
184        )
185    }
186
187    /// Compile a program (list of top-level nodes) into a Chunk.
188    /// Finds the entry pipeline and compiles its body, including inherited bodies.
189    pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
190        // Pre-scan so we can recognize EnumName.Variant as enum construction
191        // even when the enum is declared inside a pipeline.
192        Self::collect_enum_names(program, &mut self.enum_names);
193        self.enum_names.insert("Result".to_string());
194        Self::collect_struct_layouts(program, &mut self.struct_layouts);
195        Self::collect_interface_methods(program, &mut self.interface_methods);
196        self.collect_type_aliases(program);
197
198        for sn in program {
199            match &sn.node {
200                Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
201                    self.compile_node(sn)?;
202                }
203                _ => {}
204            }
205        }
206        let main = program
207            .iter()
208            .find(|sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == "default"))
209            .or_else(|| {
210                program
211                    .iter()
212                    .find(|sn| matches!(peel_node(sn), Node::Pipeline { .. }))
213            });
214
215        // When a pipeline body produces a final value, that value flows
216        // out of `vm.execute()` so the CLI can map it to a process exit
217        // code (int → exit n, Result::Err(msg) → stderr+exit 1).
218        let mut pipeline_emits_value = false;
219        if let Some(sn) = main {
220            self.compile_top_level_declarations(program)?;
221            if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
222                if let Some(parent_name) = extends {
223                    self.compile_parent_pipeline(program, parent_name)?;
224                }
225                let saved = std::mem::replace(&mut self.module_level, false);
226                self.compile_block(body)?;
227                self.module_level = saved;
228                pipeline_emits_value = true;
229            }
230        } else {
231            // Script mode: no pipeline found, treat top-level as implicit entry.
232            let top_level: Vec<&SNode> = program
233                .iter()
234                .filter(|sn| {
235                    !matches!(
236                        &sn.node,
237                        Node::ImportDecl { .. } | Node::SelectiveImport { .. }
238                    )
239                })
240                .collect();
241            for sn in &top_level {
242                self.compile_node(sn)?;
243                if Self::produces_value(&sn.node) {
244                    self.chunk.emit(Op::Pop, self.line);
245                }
246            }
247            // E4.1 entrypoint convention: a top-level `fn main(harness: Harness)`
248            // is invoked automatically with the runtime-provided `harness`
249            // global. The typechecker rejects every other signature with
250            // HARN-NAM-101 so we don't need to re-validate the shape here.
251            if Self::has_top_level_fn_main(program) {
252                let harness_name = self.string_constant("harness");
253                self.chunk.emit_u16(Op::GetVar, harness_name, self.line);
254                self.emit_named_call("main", 1);
255                pipeline_emits_value = true;
256            }
257        }
258
259        for fb in self.all_pending_finallys() {
260            self.compile_finally_inline(&fb)?;
261        }
262        if !pipeline_emits_value {
263            self.chunk.emit(Op::Nil, self.line);
264        }
265        self.chunk.emit(Op::Return, self.line);
266        Ok(self.chunk)
267    }
268
269    /// True when the program declares a top-level `fn main(...)`. Drives the
270    /// auto-call wired by `compile()` for the new `main(harness: Harness)`
271    /// entrypoint convention.
272    fn has_top_level_fn_main(program: &[SNode]) -> bool {
273        program
274            .iter()
275            .any(|sn| matches!(peel_node(sn), Node::FnDecl { name, .. } if name == "main"))
276    }
277
278    /// Compile a specific named pipeline (for test runners).
279    pub fn compile_named(
280        mut self,
281        program: &[SNode],
282        pipeline_name: &str,
283    ) -> Result<Chunk, CompileError> {
284        Self::collect_enum_names(program, &mut self.enum_names);
285        self.enum_names.insert("Result".to_string());
286        Self::collect_struct_layouts(program, &mut self.struct_layouts);
287        Self::collect_interface_methods(program, &mut self.interface_methods);
288        self.collect_type_aliases(program);
289
290        for sn in program {
291            if matches!(
292                &sn.node,
293                Node::ImportDecl { .. } | Node::SelectiveImport { .. }
294            ) {
295                self.compile_node(sn)?;
296            }
297        }
298        let target = program.iter().find(
299            |sn| matches!(peel_node(sn), Node::Pipeline { name, .. } if name == pipeline_name),
300        );
301
302        if let Some(sn) = target {
303            self.compile_top_level_declarations(program)?;
304            if let Node::Pipeline { body, extends, .. } = peel_node(sn) {
305                if let Some(parent_name) = extends {
306                    self.compile_parent_pipeline(program, parent_name)?;
307                }
308                let saved = std::mem::replace(&mut self.module_level, false);
309                self.compile_block(body)?;
310                self.module_level = saved;
311            }
312        }
313
314        for fb in self.all_pending_finallys() {
315            self.compile_finally_inline(&fb)?;
316        }
317        self.chunk.emit(Op::Nil, self.line);
318        self.chunk.emit(Op::Return, self.line);
319        Ok(self.chunk)
320    }
321
322    /// Recursively compile parent pipeline bodies (for extends).
323    pub(super) fn compile_parent_pipeline(
324        &mut self,
325        program: &[SNode],
326        parent_name: &str,
327    ) -> Result<(), CompileError> {
328        let parent = program
329            .iter()
330            .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
331        if let Some(sn) = parent {
332            if let Node::Pipeline { body, extends, .. } = &sn.node {
333                if let Some(grandparent) = extends {
334                    self.compile_parent_pipeline(program, grandparent)?;
335                }
336                for stmt in body {
337                    self.compile_node(stmt)?;
338                    if Self::produces_value(&stmt.node) {
339                        self.chunk.emit(Op::Pop, self.line);
340                    }
341                }
342            }
343        }
344        Ok(())
345    }
346
347    /// Emit bytecode preamble for default parameter values.
348    /// For each param with a default at index i, emits:
349    ///   GetArgc; PushInt (i+1); GreaterEqual; JumpIfTrue <skip>;
350    ///   [compile default expr]; DefLet param_name; <skip>:
351    pub(super) fn emit_default_preamble(
352        &mut self,
353        params: &[TypedParam],
354    ) -> Result<(), CompileError> {
355        for (i, param) in params.iter().enumerate() {
356            if let Some(default_expr) = &param.default_value {
357                self.chunk.emit(Op::GetArgc, self.line);
358                let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
359                self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
360                self.chunk.emit(Op::GreaterEqual, self.line);
361                let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
362                // JumpIfTrue doesn't pop its boolean operand.
363                self.chunk.emit(Op::Pop, self.line);
364                self.compile_node(default_expr)?;
365                self.emit_init_or_define_binding(&param.name, false);
366                let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
367                self.chunk.patch_jump(skip_jump);
368                self.chunk.emit(Op::Pop, self.line);
369                self.chunk.patch_jump(end_jump);
370            }
371        }
372        Ok(())
373    }
374
375    /// Emit body-local type checks that call-site validation cannot cover.
376    /// Ordinary supplied arguments are validated by precomputed
377    /// [`crate::chunk::ParamSlot`] guards before the frame is entered. The
378    /// bytecode preamble still checks interface parameters, because interface
379    /// satisfaction depends on compiler-collected method metadata, and checks
380    /// defaulted schema parameters only when the caller omitted that argument.
381    pub(super) fn emit_type_checks(&mut self, params: &[TypedParam]) {
382        for (param_index, param) in params.iter().enumerate() {
383            if let Some(type_expr) = &param.type_expr {
384                let check_type = if param.rest {
385                    harn_parser::TypeExpr::List(Box::new(type_expr.clone()))
386                } else {
387                    type_expr.clone()
388                };
389
390                if let harn_parser::TypeExpr::Named(name) = &check_type {
391                    if let Some(methods) = self.interface_methods.get(name).cloned() {
392                        let fn_idx = self.string_constant("__assert_interface");
393                        self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
394                        self.emit_get_binding(&param.name);
395                        let name_idx = self.string_constant(&param.name);
396                        self.chunk.emit_u16(Op::Constant, name_idx, self.line);
397                        let iface_idx = self.string_constant(name);
398                        self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
399                        let methods_str = methods.join(",");
400                        let methods_idx = self.owned_string_constant(methods_str);
401                        self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
402                        self.chunk.emit_u8(Op::Call, 4, self.line);
403                        self.chunk.emit(Op::Pop, self.line);
404                        continue;
405                    }
406                }
407
408                if param.default_value.is_some() {
409                    if let Some(schema) = Self::type_expr_to_schema_value(&check_type) {
410                        self.emit_default_param_schema_check(param_index, param, &schema);
411                    }
412                }
413            }
414        }
415    }
416
417    fn emit_default_param_schema_check(
418        &mut self,
419        param_index: usize,
420        param: &TypedParam,
421        schema: &VmValue,
422    ) {
423        self.chunk.emit(Op::GetArgc, self.line);
424        let threshold_idx = self
425            .chunk
426            .add_constant(Constant::Int((param_index + 1) as i64));
427        self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
428        self.chunk.emit(Op::GreaterEqual, self.line);
429        let supplied_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
430        self.chunk.emit(Op::Pop, self.line);
431        self.emit_schema_assert_call(param, schema);
432        let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
433        self.chunk.patch_jump(supplied_jump);
434        self.chunk.emit(Op::Pop, self.line);
435        self.chunk.patch_jump(end_jump);
436    }
437
438    fn emit_schema_assert_call(&mut self, param: &TypedParam, schema: &VmValue) {
439        let fn_idx = self.string_constant("__assert_schema");
440        self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
441        self.emit_get_binding(&param.name);
442        let name_idx = self.string_constant(&param.name);
443        self.chunk.emit_u16(Op::Constant, name_idx, self.line);
444        self.emit_vm_value_literal(schema);
445        self.chunk.emit_u8(Op::Call, 3, self.line);
446        self.chunk.emit(Op::Pop, self.line);
447    }
448
449    pub(crate) fn type_expr_to_schema_value(type_expr: &harn_parser::TypeExpr) -> Option<VmValue> {
450        match type_expr {
451            harn_parser::TypeExpr::Named(name) => match name.as_str() {
452                "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
453                | "closure" | "bytes" => Some(VmValue::Dict(Rc::new(BTreeMap::from([(
454                    "type".to_string(),
455                    VmValue::String(Rc::from(name.as_str())),
456                )])))),
457                _ => None,
458            },
459            harn_parser::TypeExpr::Shape(fields) => {
460                let mut properties = BTreeMap::new();
461                let mut required = Vec::new();
462                for field in fields {
463                    let field_schema = Self::type_expr_to_schema_value(&field.type_expr)?;
464                    properties.insert(field.name.clone(), field_schema);
465                    if !field.optional {
466                        required.push(VmValue::String(Rc::from(field.name.as_str())));
467                    }
468                }
469                let mut out = BTreeMap::new();
470                out.insert("type".to_string(), VmValue::String(Rc::from("dict")));
471                out.insert("properties".to_string(), VmValue::Dict(Rc::new(properties)));
472                if !required.is_empty() {
473                    out.insert("required".to_string(), VmValue::List(Rc::new(required)));
474                }
475                Some(VmValue::Dict(Rc::new(out)))
476            }
477            harn_parser::TypeExpr::List(inner) => {
478                let mut out = BTreeMap::new();
479                out.insert("type".to_string(), VmValue::String(Rc::from("list")));
480                if let Some(item_schema) = Self::type_expr_to_schema_value(inner) {
481                    out.insert("items".to_string(), item_schema);
482                }
483                Some(VmValue::Dict(Rc::new(out)))
484            }
485            harn_parser::TypeExpr::DictType(key, value) => {
486                let mut out = BTreeMap::new();
487                out.insert("type".to_string(), VmValue::String(Rc::from("dict")));
488                if matches!(key.as_ref(), harn_parser::TypeExpr::Named(name) if name == "string") {
489                    if let Some(value_schema) = Self::type_expr_to_schema_value(value) {
490                        out.insert("additional_properties".to_string(), value_schema);
491                    }
492                }
493                Some(VmValue::Dict(Rc::new(out)))
494            }
495            harn_parser::TypeExpr::Union(members) => {
496                // Special-case unions of literals: emit as `enum: [...]`
497                // so the schema round-trips as canonical JSON Schema and
498                // is ACP-/OpenAPI-compatible. Mixed unions fall back to
499                // the `union:` key that validators recognize.
500                if !members.is_empty()
501                    && members
502                        .iter()
503                        .all(|m| matches!(m, harn_parser::TypeExpr::LitString(_)))
504                {
505                    let values = members
506                        .iter()
507                        .map(|m| match m {
508                            harn_parser::TypeExpr::LitString(s) => {
509                                VmValue::String(Rc::from(s.as_str()))
510                            }
511                            _ => unreachable!(),
512                        })
513                        .collect::<Vec<_>>();
514                    return Some(VmValue::Dict(Rc::new(BTreeMap::from([
515                        ("type".to_string(), VmValue::String(Rc::from("string"))),
516                        ("enum".to_string(), VmValue::List(Rc::new(values))),
517                    ]))));
518                }
519                if !members.is_empty()
520                    && members
521                        .iter()
522                        .all(|m| matches!(m, harn_parser::TypeExpr::LitInt(_)))
523                {
524                    let values = members
525                        .iter()
526                        .map(|m| match m {
527                            harn_parser::TypeExpr::LitInt(v) => VmValue::Int(*v),
528                            _ => unreachable!(),
529                        })
530                        .collect::<Vec<_>>();
531                    return Some(VmValue::Dict(Rc::new(BTreeMap::from([
532                        ("type".to_string(), VmValue::String(Rc::from("int"))),
533                        ("enum".to_string(), VmValue::List(Rc::new(values))),
534                    ]))));
535                }
536                let branches = members
537                    .iter()
538                    .filter_map(Self::type_expr_to_schema_value)
539                    .collect::<Vec<_>>();
540                if branches.is_empty() {
541                    None
542                } else {
543                    Some(VmValue::Dict(Rc::new(BTreeMap::from([(
544                        "union".to_string(),
545                        VmValue::List(Rc::new(branches)),
546                    )]))))
547                }
548            }
549            harn_parser::TypeExpr::Intersection(members) => {
550                // Encode `A & B` as JSON-Schema `allOf` (the runtime
551                // accepts the snake_case `all_of` key directly). The
552                // value must validate against every branch.
553                let branches = members
554                    .iter()
555                    .filter_map(Self::type_expr_to_schema_value)
556                    .collect::<Vec<_>>();
557                if branches.is_empty() {
558                    None
559                } else {
560                    Some(VmValue::Dict(Rc::new(BTreeMap::from([(
561                        "all_of".to_string(),
562                        VmValue::List(Rc::new(branches)),
563                    )]))))
564                }
565            }
566            harn_parser::TypeExpr::FnType { .. } => {
567                Some(VmValue::Dict(Rc::new(BTreeMap::from([(
568                    "type".to_string(),
569                    VmValue::String(Rc::from("closure")),
570                )]))))
571            }
572            harn_parser::TypeExpr::Applied { .. } => None,
573            harn_parser::TypeExpr::Iter(_)
574            | harn_parser::TypeExpr::Generator(_)
575            | harn_parser::TypeExpr::Stream(_) => None,
576            harn_parser::TypeExpr::Never => None,
577            harn_parser::TypeExpr::LitString(s) => Some(VmValue::Dict(Rc::new(BTreeMap::from([
578                ("type".to_string(), VmValue::String(Rc::from("string"))),
579                ("const".to_string(), VmValue::String(Rc::from(s.as_str()))),
580            ])))),
581            harn_parser::TypeExpr::LitInt(v) => Some(VmValue::Dict(Rc::new(BTreeMap::from([
582                ("type".to_string(), VmValue::String(Rc::from("int"))),
583                ("const".to_string(), VmValue::Int(*v)),
584            ])))),
585            harn_parser::TypeExpr::Owned(inner) => Self::type_expr_to_schema_value(inner),
586        }
587    }
588
589    pub(super) fn emit_vm_value_literal(&mut self, value: &VmValue) {
590        match value {
591            VmValue::String(text) => {
592                let idx = self.string_constant(text);
593                self.chunk.emit_u16(Op::Constant, idx, self.line);
594            }
595            VmValue::Int(number) => {
596                let idx = self.chunk.add_constant(Constant::Int(*number));
597                self.chunk.emit_u16(Op::Constant, idx, self.line);
598            }
599            VmValue::Float(number) => {
600                let idx = self.chunk.add_constant(Constant::Float(*number));
601                self.chunk.emit_u16(Op::Constant, idx, self.line);
602            }
603            VmValue::Bool(value) => {
604                let idx = self.chunk.add_constant(Constant::Bool(*value));
605                self.chunk.emit_u16(Op::Constant, idx, self.line);
606            }
607            VmValue::Nil => self.chunk.emit(Op::Nil, self.line),
608            VmValue::List(items) => {
609                for item in items.iter() {
610                    self.emit_vm_value_literal(item);
611                }
612                self.chunk
613                    .emit_u16(Op::BuildList, items.len() as u16, self.line);
614            }
615            VmValue::Dict(entries) => {
616                for (key, item) in entries.iter() {
617                    let key_idx = self.string_constant(key);
618                    self.chunk.emit_u16(Op::Constant, key_idx, self.line);
619                    self.emit_vm_value_literal(item);
620                }
621                self.chunk
622                    .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
623            }
624            _ => {}
625        }
626    }
627
628    /// Emit the extra u16 type name index after a TryCatchSetup jump.
629    pub(super) fn emit_type_name_extra(&mut self, type_name_idx: u16) {
630        let hi = (type_name_idx >> 8) as u8;
631        let lo = type_name_idx as u8;
632        self.chunk.code.push(hi);
633        self.chunk.code.push(lo);
634        self.chunk.lines.push(self.line);
635        self.chunk.columns.push(self.column);
636        self.chunk.lines.push(self.line);
637        self.chunk.columns.push(self.column);
638    }
639
640    /// Compile a try/catch body block (produces a value on the stack).
641    pub(super) fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
642        if body.is_empty() {
643            self.chunk.emit(Op::Nil, self.line);
644        } else {
645            self.compile_scoped_block(body)?;
646        }
647        Ok(())
648    }
649
650    /// Compile catch error binding (error value is on stack from handler).
651    pub(super) fn compile_catch_binding(
652        &mut self,
653        error_var: &Option<String>,
654    ) -> Result<(), CompileError> {
655        if let Some(var_name) = error_var {
656            self.emit_define_binding(var_name, false);
657        } else {
658            self.chunk.emit(Op::Pop, self.line);
659        }
660        Ok(())
661    }
662
663    /// Compile finally body inline, discarding its result value.
664    /// `compile_scoped_block` always leaves exactly one value on the stack
665    /// (Nil for non-value tail statements), so the trailing Pop is
666    /// unconditional — otherwise a finally ending in e.g. `x = x + 1`
667    /// would leave a stray Nil that corrupts the surrounding expression
668    /// when the enclosing try/finally is used in expression position.
669    pub(super) fn compile_finally_inline(
670        &mut self,
671        finally_body: &[SNode],
672    ) -> Result<(), CompileError> {
673        if !finally_body.is_empty() {
674            self.compile_scoped_block(finally_body)?;
675            self.chunk.emit(Op::Pop, self.line);
676        }
677        Ok(())
678    }
679
680    /// Collect pending finally bodies from the top of the stack down to
681    /// (but not including) the innermost `CatchBarrier`. Used by `throw`
682    /// lowering: throws caught locally don't unwind past the catch, so
683    /// finallys behind the barrier aren't on the throw's exit path.
684    pub(super) fn pending_finallys_until_barrier(&self) -> Vec<Vec<SNode>> {
685        let mut out = Vec::new();
686        for entry in self.finally_bodies.iter().rev() {
687            match entry {
688                FinallyEntry::CatchBarrier => break,
689                FinallyEntry::Finally(body) => out.push(body.clone()),
690            }
691        }
692        out
693    }
694
695    /// Collect every pending finally body from the top of the stack down
696    /// to `floor` (an index produced by `finally_bodies.len()` at some
697    /// earlier point), skipping `CatchBarrier` markers. Used by `return`,
698    /// `break`, and `continue` lowering — they transfer control past local
699    /// handlers, so every `Finally` up to their target must run.
700    pub(super) fn pending_finallys_down_to(&self, floor: usize) -> Vec<Vec<SNode>> {
701        let mut out = Vec::new();
702        for entry in self.finally_bodies[floor..].iter().rev() {
703            if let FinallyEntry::Finally(body) = entry {
704                out.push(body.clone());
705            }
706        }
707        out
708    }
709
710    /// All pending finally bodies (entire stack), skipping barriers.
711    pub(super) fn all_pending_finallys(&self) -> Vec<Vec<SNode>> {
712        self.pending_finallys_down_to(0)
713    }
714
715    /// True if there are any pending finally bodies (not just barriers).
716    pub(super) fn has_pending_finally(&self) -> bool {
717        self.finally_bodies
718            .iter()
719            .any(|e| matches!(e, FinallyEntry::Finally(_)))
720    }
721
722    /// Save a thrown value to a temp and rethrow without running finally.
723    ///
724    /// Historically this helper also invoked `compile_finally_inline` on the
725    /// thrown path, but that produced observable double-runs: the
726    /// `Node::ThrowStmt` lowering (below) already iterates `finally_bodies`
727    /// and runs each pending finally inline *before* emitting `Op::Throw`, so
728    /// a second run here fired the same side effects twice. Finally now runs
729    /// exactly once — via the throw-emit path during unwinding.
730    pub(super) fn compile_plain_rethrow(&mut self) -> Result<(), CompileError> {
731        self.temp_counter += 1;
732        let temp_name = format!("__finally_err_{}__", self.temp_counter);
733        self.emit_define_binding(&temp_name, true);
734        self.emit_get_binding(&temp_name);
735        self.chunk.emit(Op::Throw, self.line);
736        Ok(())
737    }
738
739    pub(super) fn declare_param_slots(&mut self, params: &[TypedParam]) {
740        for param in params {
741            self.define_local_slot(&param.name, false);
742        }
743    }
744
745    fn define_local_slot(&mut self, name: &str, mutable: bool) -> Option<u16> {
746        if self.module_level || harn_parser::is_discard_name(name) {
747            return None;
748        }
749        let current = self.local_scopes.last_mut()?;
750        if let Some(existing) = current.get_mut(name) {
751            if existing.mutable || mutable {
752                if mutable {
753                    existing.mutable = true;
754                    if let Some(info) = self.chunk.local_slots.get_mut(existing.slot as usize) {
755                        info.mutable = true;
756                    }
757                }
758                return Some(existing.slot);
759            }
760            return None;
761        }
762        let slot = self
763            .chunk
764            .add_local_slot(name.to_string(), mutable, self.scope_depth);
765        current.insert(name.to_string(), super::LocalBinding { slot, mutable });
766        Some(slot)
767    }
768
769    pub(super) fn resolve_local_slot(&self, name: &str) -> Option<super::LocalBinding> {
770        if self.module_level {
771            return None;
772        }
773        self.local_scopes
774            .iter()
775            .rev()
776            .find_map(|scope| scope.get(name).copied())
777    }
778
779    pub(super) fn emit_get_binding(&mut self, name: &str) {
780        if let Some(binding) = self.resolve_local_slot(name) {
781            self.chunk
782                .emit_u16(Op::GetLocalSlot, binding.slot, self.line);
783        } else {
784            let idx = self.string_constant(name);
785            self.chunk.emit_u16(Op::GetVar, idx, self.line);
786        }
787    }
788
789    pub(super) fn emit_define_binding(&mut self, name: &str, mutable: bool) {
790        if let Some(slot) = self.define_local_slot(name, mutable) {
791            self.chunk.emit_u16(Op::DefLocalSlot, slot, self.line);
792        } else {
793            let idx = self.string_constant(name);
794            let op = if mutable { Op::DefVar } else { Op::DefLet };
795            self.chunk.emit_u16(op, idx, self.line);
796        }
797    }
798
799    pub(super) fn emit_init_or_define_binding(&mut self, name: &str, mutable: bool) {
800        if let Some(binding) = self.resolve_local_slot(name) {
801            self.chunk
802                .emit_u16(Op::DefLocalSlot, binding.slot, self.line);
803        } else {
804            self.emit_define_binding(name, mutable);
805        }
806    }
807
808    pub(super) fn emit_set_binding(&mut self, name: &str) {
809        if let Some(binding) = self.resolve_local_slot(name) {
810            let _ = binding.mutable;
811            self.chunk
812                .emit_u16(Op::SetLocalSlot, binding.slot, self.line);
813        } else {
814            let idx = self.string_constant(name);
815            self.chunk.emit_u16(Op::SetVar, idx, self.line);
816        }
817    }
818
819    pub(super) fn begin_scope(&mut self) {
820        self.chunk.emit(Op::PushScope, self.line);
821        self.scope_depth += 1;
822        self.type_scopes.push(std::collections::HashMap::new());
823        self.local_scopes.push(std::collections::HashMap::new());
824    }
825
826    pub(super) fn end_scope(&mut self) {
827        if self.scope_depth > 0 {
828            self.chunk.emit(Op::PopScope, self.line);
829            self.scope_depth -= 1;
830            self.type_scopes.pop();
831            self.local_scopes.pop();
832        }
833    }
834
835    pub(super) fn unwind_scopes_to(&mut self, target_depth: usize) {
836        while self.scope_depth > target_depth {
837            self.chunk.emit(Op::PopScope, self.line);
838            self.scope_depth -= 1;
839            self.type_scopes.pop();
840            self.local_scopes.pop();
841        }
842    }
843
844    pub(super) fn compile_scoped_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
845        self.begin_scope();
846        let finally_floor = self.finally_bodies.len();
847        if stmts.is_empty() {
848            self.chunk.emit(Op::Nil, self.line);
849        } else {
850            self.compile_block(stmts)?;
851        }
852        self.drain_finallys_to_floor(finally_floor)?;
853        self.end_scope();
854        Ok(())
855    }
856
857    pub(super) fn compile_scoped_statements(
858        &mut self,
859        stmts: &[SNode],
860    ) -> Result<(), CompileError> {
861        self.begin_scope();
862        let finally_floor = self.finally_bodies.len();
863        for sn in stmts {
864            self.compile_node(sn)?;
865            if Self::produces_value(&sn.node) {
866                self.chunk.emit(Op::Pop, self.line);
867            }
868        }
869        self.drain_finallys_to_floor(finally_floor)?;
870        self.end_scope();
871        Ok(())
872    }
873
874    /// Drain pending `defer` bodies down to a saved floor and run each inline
875    /// in LIFO order. Each defer body is popped *before* its code is emitted so
876    /// any `return` / `break` lowering inside the body sees the remaining
877    /// pending defers (not itself).
878    pub(super) fn drain_finallys_to_floor(&mut self, floor: usize) -> Result<(), CompileError> {
879        while self.finally_bodies.len() > floor {
880            let entry = self.finally_bodies.pop().expect("non-empty by guard");
881            if let FinallyEntry::Finally(body) = entry {
882                self.compile_finally_inline(&body)?;
883            }
884        }
885        Ok(())
886    }
887
888    /// Register an auto-drop defer for an `owned<T>` binding. The drop runs
889    /// at scope exit alongside any user-written `defer { ... }` blocks (LIFO
890    /// order) and on `return` / `break` / `continue` / `throw` via the
891    /// existing finally-unwinding machinery.
892    pub(super) fn maybe_register_owned_drop(
893        &mut self,
894        pattern: &harn_parser::BindingPattern,
895        type_ann: Option<&TypeExpr>,
896        span: harn_lexer::Span,
897    ) {
898        // Auto-drop only fires when the user explicitly opted in via
899        // `owned<T>` on a single-identifier binding. Destructured patterns
900        // (`{a, b}`, `[a, b]`, pairs) aren't auto-dropped: ownership of a
901        // composite isn't well-defined, and users can wrap individual fields
902        // with `owned<T>` and bind them separately if needed.
903        let Some(ty) = type_ann else {
904            return;
905        };
906        if !matches!(ty, TypeExpr::Owned(_)) {
907            return;
908        }
909        let harn_parser::BindingPattern::Identifier(name) = pattern else {
910            return;
911        };
912        if harn_parser::is_discard_name(name) {
913            return;
914        }
915        let call = harn_parser::spanned(
916            Node::FunctionCall {
917                name: "drop".to_string(),
918                args: vec![harn_parser::spanned(Node::Identifier(name.clone()), span)],
919                type_args: Vec::new(),
920            },
921            span,
922        );
923        self.finally_bodies.push(FinallyEntry::Finally(vec![call]));
924    }
925
926    pub(super) fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
927        for (i, snode) in stmts.iter().enumerate() {
928            self.compile_node(snode)?;
929            let is_last = i == stmts.len() - 1;
930            if is_last {
931                // Ensure the block always leaves exactly one value on the stack.
932                if !Self::produces_value(&snode.node) {
933                    self.chunk.emit(Op::Nil, self.line);
934                }
935            } else if Self::produces_value(&snode.node) {
936                self.chunk.emit(Op::Pop, self.line);
937            }
938        }
939        Ok(())
940    }
941
942    /// Compile a match arm body, ensuring it always pushes exactly one value.
943    pub(super) fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
944        self.begin_scope();
945        let finally_floor = self.finally_bodies.len();
946        if body.is_empty() {
947            self.chunk.emit(Op::Nil, self.line);
948        } else {
949            self.compile_block(body)?;
950            if !Self::produces_value(&body.last().unwrap().node) {
951                self.chunk.emit(Op::Nil, self.line);
952            }
953        }
954        self.drain_finallys_to_floor(finally_floor)?;
955        self.end_scope();
956        Ok(())
957    }
958
959    /// Emit the binary op instruction for a compound assignment operator.
960    pub(super) fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
961        match op {
962            "+" => self.chunk.emit(Op::Add, self.line),
963            "-" => self.chunk.emit(Op::Sub, self.line),
964            "*" => self.chunk.emit(Op::Mul, self.line),
965            "/" => self.chunk.emit(Op::Div, self.line),
966            "%" => self.chunk.emit(Op::Mod, self.line),
967            _ => {
968                return Err(CompileError {
969                    message: format!("Unknown compound operator: {op}"),
970                    line: self.line,
971                })
972            }
973        }
974        Ok(())
975    }
976
977    /// Extract the root variable name from a (possibly nested) access expression.
978    pub(super) fn root_var_name(&self, node: &SNode) -> Option<String> {
979        match &node.node {
980            Node::Identifier(name) => Some(name.clone()),
981            Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
982                self.root_var_name(object)
983            }
984            Node::SubscriptAccess { object, .. } | Node::OptionalSubscriptAccess { object, .. } => {
985                self.root_var_name(object)
986            }
987            _ => None,
988        }
989    }
990
991    pub(super) fn compile_top_level_declarations(
992        &mut self,
993        program: &[SNode],
994    ) -> Result<(), CompileError> {
995        // Phase 1: evaluate module-level `let` / `var` bindings first, in
996        // source order. This ensures function closures compiled in phase 2
997        // capture these names in their env snapshot via `Op::Closure` —
998        // fixing the "Undefined variable: FOO" surprise where a top-level
999        // `let FOO = "..."` was silently dropped because it wasn't in this
1000        // match list. Keep in step with the import-time init path in
1001        // `crates/harn-vm/src/vm/imports.rs` (`module_state` construction).
1002        for sn in program {
1003            if matches!(
1004                &sn.node,
1005                Node::LetBinding { .. } | Node::VarBinding { .. } | Node::ConstBinding { .. }
1006            ) {
1007                self.compile_node(sn)?;
1008            }
1009        }
1010        // Phase 2: compile type and function declarations. Function closures
1011        // created here capture the current env which now includes the
1012        // module-level bindings from phase 1. Attributed declarations are
1013        // compiled here too — the AttributedDecl arm in compile_node
1014        // dispatches to the inner declaration's compile path.
1015        for sn in program {
1016            let inner_kind = match &sn.node {
1017                Node::AttributedDecl { inner, .. } => &inner.node,
1018                other => other,
1019            };
1020            match inner_kind {
1021                Node::EvalPackDecl {
1022                    binding_name,
1023                    pack_id,
1024                    fields,
1025                    body,
1026                    summarize,
1027                    ..
1028                } => {
1029                    self.compile_eval_pack_decl(
1030                        binding_name,
1031                        pack_id,
1032                        fields,
1033                        body,
1034                        summarize,
1035                        false,
1036                    )?;
1037                }
1038                Node::FnDecl { .. }
1039                | Node::ToolDecl { .. }
1040                | Node::SkillDecl { .. }
1041                | Node::ImplBlock { .. }
1042                | Node::StructDecl { .. }
1043                | Node::EnumDecl { .. }
1044                | Node::InterfaceDecl { .. }
1045                | Node::TypeDecl { .. } => {
1046                    self.compile_node(sn)?;
1047                }
1048                _ => {}
1049            }
1050        }
1051        Ok(())
1052    }
1053
1054    /// Recursively collect all enum type names from the AST.
1055    pub(super) fn collect_enum_names(
1056        nodes: &[SNode],
1057        names: &mut std::collections::HashSet<String>,
1058    ) {
1059        for sn in nodes {
1060            match &sn.node {
1061                Node::EnumDecl { name, .. } => {
1062                    names.insert(name.clone());
1063                }
1064                Node::Pipeline { body, .. } => {
1065                    Self::collect_enum_names(body, names);
1066                }
1067                Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
1068                    Self::collect_enum_names(body, names);
1069                }
1070                Node::SkillDecl { fields, .. } => {
1071                    for (_k, v) in fields {
1072                        Self::collect_enum_names(std::slice::from_ref(v), names);
1073                    }
1074                }
1075                Node::EvalPackDecl {
1076                    fields,
1077                    body,
1078                    summarize,
1079                    ..
1080                } => {
1081                    for (_k, v) in fields {
1082                        Self::collect_enum_names(std::slice::from_ref(v), names);
1083                    }
1084                    Self::collect_enum_names(body, names);
1085                    if let Some(summary_body) = summarize {
1086                        Self::collect_enum_names(summary_body, names);
1087                    }
1088                }
1089                Node::Block(stmts) => {
1090                    Self::collect_enum_names(stmts, names);
1091                }
1092                Node::AttributedDecl { inner, .. } => {
1093                    Self::collect_enum_names(std::slice::from_ref(inner), names);
1094                }
1095                _ => {}
1096            }
1097        }
1098    }
1099
1100    pub(super) fn collect_struct_layouts(
1101        nodes: &[SNode],
1102        layouts: &mut std::collections::HashMap<String, Vec<String>>,
1103    ) {
1104        for sn in nodes {
1105            match &sn.node {
1106                Node::StructDecl { name, fields, .. } => {
1107                    layouts.insert(
1108                        name.clone(),
1109                        fields.iter().map(|field| field.name.clone()).collect(),
1110                    );
1111                }
1112                Node::Pipeline { body, .. }
1113                | Node::FnDecl { body, .. }
1114                | Node::ToolDecl { body, .. } => {
1115                    Self::collect_struct_layouts(body, layouts);
1116                }
1117                Node::SkillDecl { fields, .. } => {
1118                    for (_k, v) in fields {
1119                        Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1120                    }
1121                }
1122                Node::EvalPackDecl {
1123                    fields,
1124                    body,
1125                    summarize,
1126                    ..
1127                } => {
1128                    for (_k, v) in fields {
1129                        Self::collect_struct_layouts(std::slice::from_ref(v), layouts);
1130                    }
1131                    Self::collect_struct_layouts(body, layouts);
1132                    if let Some(summary_body) = summarize {
1133                        Self::collect_struct_layouts(summary_body, layouts);
1134                    }
1135                }
1136                Node::Block(stmts) => {
1137                    Self::collect_struct_layouts(stmts, layouts);
1138                }
1139                Node::AttributedDecl { inner, .. } => {
1140                    Self::collect_struct_layouts(std::slice::from_ref(inner), layouts);
1141                }
1142                _ => {}
1143            }
1144        }
1145    }
1146
1147    pub(super) fn collect_interface_methods(
1148        nodes: &[SNode],
1149        interfaces: &mut std::collections::HashMap<String, Vec<String>>,
1150    ) {
1151        for sn in nodes {
1152            match &sn.node {
1153                Node::InterfaceDecl { name, methods, .. } => {
1154                    let method_names: Vec<String> =
1155                        methods.iter().map(|m| m.name.clone()).collect();
1156                    interfaces.insert(name.clone(), method_names);
1157                }
1158                Node::Pipeline { body, .. }
1159                | Node::FnDecl { body, .. }
1160                | Node::ToolDecl { body, .. } => {
1161                    Self::collect_interface_methods(body, interfaces);
1162                }
1163                Node::SkillDecl { fields, .. } => {
1164                    for (_k, v) in fields {
1165                        Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1166                    }
1167                }
1168                Node::EvalPackDecl {
1169                    fields,
1170                    body,
1171                    summarize,
1172                    ..
1173                } => {
1174                    for (_k, v) in fields {
1175                        Self::collect_interface_methods(std::slice::from_ref(v), interfaces);
1176                    }
1177                    Self::collect_interface_methods(body, interfaces);
1178                    if let Some(summary_body) = summarize {
1179                        Self::collect_interface_methods(summary_body, interfaces);
1180                    }
1181                }
1182                Node::Block(stmts) => {
1183                    Self::collect_interface_methods(stmts, interfaces);
1184                }
1185                Node::AttributedDecl { inner, .. } => {
1186                    Self::collect_interface_methods(std::slice::from_ref(inner), interfaces);
1187                }
1188                _ => {}
1189            }
1190        }
1191    }
1192
1193    /// Compile a function body into a CompiledFunction (for import support).
1194    ///
1195    /// This path is used when a module is imported and its top-level `fn`
1196    /// declarations are loaded into the importer's environment. It MUST emit
1197    /// the same function preamble as the in-file `Node::FnDecl` path, or
1198    /// imported functions will behave differently from locally-defined ones —
1199    /// in particular, default parameter values would never be set and typed
1200    /// parameters would not be runtime-checked.
1201    ///
1202    /// `source_file`, when provided, tags the resulting chunk so runtime
1203    /// errors can attribute frames to the imported file rather than the
1204    /// entry-point pipeline.
1205    pub fn compile_fn_body(
1206        &mut self,
1207        type_params: &[harn_parser::TypeParam],
1208        params: &[TypedParam],
1209        body: &[SNode],
1210        source_file: Option<String>,
1211    ) -> Result<CompiledFunction, CompileError> {
1212        let mut fn_compiler = self.nested_body();
1213        fn_compiler.enum_names = self.enum_names.clone();
1214        fn_compiler.interface_methods = self.interface_methods.clone();
1215        fn_compiler.type_aliases = self.type_aliases.clone();
1216        fn_compiler.struct_layouts = self.struct_layouts.clone();
1217        fn_compiler.declare_param_slots(params);
1218        fn_compiler.record_param_types(params);
1219        fn_compiler.emit_default_preamble(params)?;
1220        fn_compiler.emit_type_checks(params);
1221        let is_gen = body_contains_yield(body);
1222        fn_compiler.compile_block(body)?;
1223        fn_compiler.chunk.emit(Op::Nil, 0);
1224        fn_compiler.chunk.emit(Op::Return, 0);
1225        fn_compiler.chunk.source_file = source_file;
1226        let param_slots = crate::chunk::ParamSlot::vec_from_typed(params);
1227        let has_runtime_type_checks =
1228            CompiledFunction::has_runtime_type_checks_for_params(&param_slots);
1229        Ok(CompiledFunction {
1230            name: String::new(),
1231            type_params: type_params.iter().map(|param| param.name.clone()).collect(),
1232            nominal_type_names: fn_compiler.nominal_type_names(),
1233            params: param_slots,
1234            default_start: TypedParam::default_start(params),
1235            chunk: Rc::new(fn_compiler.chunk),
1236            is_generator: is_gen,
1237            is_stream: false,
1238            has_rest_param: false,
1239            has_runtime_type_checks,
1240        })
1241    }
1242
1243    /// Check if a node produces a value on the stack that needs to be popped.
1244    pub(super) fn produces_value(node: &Node) -> bool {
1245        match node {
1246            Node::LetBinding { .. }
1247            | Node::VarBinding { .. }
1248            | Node::ConstBinding { .. }
1249            | Node::Assignment { .. }
1250            | Node::ReturnStmt { .. }
1251            | Node::FnDecl { .. }
1252            | Node::ToolDecl { .. }
1253            | Node::SkillDecl { .. }
1254            | Node::EvalPackDecl { .. }
1255            | Node::ImplBlock { .. }
1256            | Node::StructDecl { .. }
1257            | Node::EnumDecl { .. }
1258            | Node::InterfaceDecl { .. }
1259            | Node::TypeDecl { .. }
1260            | Node::ThrowStmt { .. }
1261            | Node::BreakStmt
1262            | Node::ContinueStmt
1263            | Node::RequireStmt { .. }
1264            | Node::DeferStmt { .. } => false,
1265            Node::TryCatch { has_catch: _, .. }
1266            | Node::TryExpr { .. }
1267            | Node::Retry { .. }
1268            | Node::GuardStmt { .. }
1269            | Node::DeadlineBlock { .. }
1270            | Node::MutexBlock { .. }
1271            | Node::Spread(_) => true,
1272            _ => true,
1273        }
1274    }
1275}
1276
1277impl Default for Compiler {
1278    fn default() -> Self {
1279        Self::new()
1280    }
1281}