Skip to main content

harn_vm/
compiler.rs

1use harn_lexer::StringSegment;
2use harn_parser::{BindingPattern, Node, SNode, TypedParam};
3
4use crate::chunk::{Chunk, CompiledFunction, Constant, Op};
5
6/// Compile error.
7#[derive(Debug)]
8pub struct CompileError {
9    pub message: String,
10    pub line: u32,
11}
12
13impl std::fmt::Display for CompileError {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        write!(f, "Compile error at line {}: {}", self.line, self.message)
16    }
17}
18
19impl std::error::Error for CompileError {}
20
21/// Tracks loop context for break/continue compilation.
22struct LoopContext {
23    /// Offset of the loop start (for continue).
24    start_offset: usize,
25    /// Positions of break jumps that need patching to the loop end.
26    break_patches: Vec<usize>,
27    /// True if this is a for-in loop (has an iterator to clean up on break).
28    has_iterator: bool,
29    /// Number of exception handlers active at loop entry.
30    handler_depth: usize,
31    /// Number of pending finally bodies at loop entry.
32    finally_depth: usize,
33}
34
35/// Compiles an AST into bytecode.
36pub struct Compiler {
37    chunk: Chunk,
38    line: u32,
39    column: u32,
40    /// Track enum type names so PropertyAccess on them can produce EnumVariant.
41    enum_names: std::collections::HashSet<String>,
42    /// Track interface names → method names for runtime enforcement.
43    interface_methods: std::collections::HashMap<String, Vec<String>>,
44    /// Stack of active loop contexts for break/continue.
45    loop_stack: Vec<LoopContext>,
46    /// Current depth of exception handlers (for cleanup on break/continue).
47    handler_depth: usize,
48    /// Stack of pending finally bodies for return/break/continue handling.
49    finally_bodies: Vec<Vec<SNode>>,
50    /// Counter for unique temp variable names.
51    temp_counter: usize,
52}
53
54impl Compiler {
55    pub fn new() -> Self {
56        Self {
57            chunk: Chunk::new(),
58            line: 1,
59            column: 1,
60            enum_names: std::collections::HashSet::new(),
61            interface_methods: std::collections::HashMap::new(),
62            loop_stack: Vec::new(),
63            handler_depth: 0,
64            finally_bodies: Vec::new(),
65            temp_counter: 0,
66        }
67    }
68
69    /// Compile a program (list of top-level nodes) into a Chunk.
70    /// Finds the entry pipeline and compiles its body, including inherited bodies.
71    pub fn compile(mut self, program: &[SNode]) -> Result<Chunk, CompileError> {
72        // Pre-scan the entire program for enum declarations (including inside pipelines)
73        // so we can recognize EnumName.Variant as enum construction.
74        Self::collect_enum_names(program, &mut self.enum_names);
75        // Built-in Result enum is always available
76        self.enum_names.insert("Result".to_string());
77        Self::collect_interface_methods(program, &mut self.interface_methods);
78
79        // Compile all top-level non-pipeline declarations first (fn, enum, etc.)
80        for sn in program {
81            match &sn.node {
82                Node::ImportDecl { .. } | Node::SelectiveImport { .. } => {
83                    self.compile_node(sn)?;
84                }
85                _ => {}
86            }
87        }
88
89        // Find entry pipeline
90        let main = program
91            .iter()
92            .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == "default"))
93            .or_else(|| {
94                program
95                    .iter()
96                    .find(|sn| matches!(&sn.node, Node::Pipeline { .. }))
97            });
98
99        if let Some(sn) = main {
100            if let Node::Pipeline { body, extends, .. } = &sn.node {
101                // If this pipeline extends another, compile the parent chain first
102                if let Some(parent_name) = extends {
103                    self.compile_parent_pipeline(program, parent_name)?;
104                }
105                self.compile_block(body)?;
106            }
107        }
108
109        self.chunk.emit(Op::Nil, self.line);
110        self.chunk.emit(Op::Return, self.line);
111        Ok(self.chunk)
112    }
113
114    /// Compile a specific named pipeline (for test runners).
115    pub fn compile_named(
116        mut self,
117        program: &[SNode],
118        pipeline_name: &str,
119    ) -> Result<Chunk, CompileError> {
120        Self::collect_enum_names(program, &mut self.enum_names);
121        Self::collect_interface_methods(program, &mut self.interface_methods);
122
123        for sn in program {
124            if matches!(
125                &sn.node,
126                Node::ImportDecl { .. } | Node::SelectiveImport { .. }
127            ) {
128                self.compile_node(sn)?;
129            }
130        }
131
132        let target = program
133            .iter()
134            .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == pipeline_name));
135
136        if let Some(sn) = target {
137            if let Node::Pipeline { body, extends, .. } = &sn.node {
138                if let Some(parent_name) = extends {
139                    self.compile_parent_pipeline(program, parent_name)?;
140                }
141                self.compile_block(body)?;
142            }
143        }
144
145        self.chunk.emit(Op::Nil, self.line);
146        self.chunk.emit(Op::Return, self.line);
147        Ok(self.chunk)
148    }
149
150    /// Recursively compile parent pipeline bodies (for extends).
151    fn compile_parent_pipeline(
152        &mut self,
153        program: &[SNode],
154        parent_name: &str,
155    ) -> Result<(), CompileError> {
156        let parent = program
157            .iter()
158            .find(|sn| matches!(&sn.node, Node::Pipeline { name, .. } if name == parent_name));
159        if let Some(sn) = parent {
160            if let Node::Pipeline { body, extends, .. } = &sn.node {
161                // Recurse if this parent also extends another
162                if let Some(grandparent) = extends {
163                    self.compile_parent_pipeline(program, grandparent)?;
164                }
165                // Compile parent body - pop all statement values
166                for stmt in body {
167                    self.compile_node(stmt)?;
168                    if Self::produces_value(&stmt.node) {
169                        self.chunk.emit(Op::Pop, self.line);
170                    }
171                }
172            }
173        }
174        Ok(())
175    }
176
177    /// Emit bytecode preamble for default parameter values.
178    /// For each param with a default at index i, emits:
179    ///   GetArgc; PushInt (i+1); GreaterEqual; JumpIfTrue <skip>;
180    ///   [compile default expr]; DefLet param_name; <skip>:
181    fn emit_default_preamble(&mut self, params: &[TypedParam]) -> Result<(), CompileError> {
182        for (i, param) in params.iter().enumerate() {
183            if let Some(default_expr) = &param.default_value {
184                self.chunk.emit(Op::GetArgc, self.line);
185                let threshold_idx = self.chunk.add_constant(Constant::Int((i + 1) as i64));
186                self.chunk.emit_u16(Op::Constant, threshold_idx, self.line);
187                // argc >= (i+1) means arg was provided
188                self.chunk.emit(Op::GreaterEqual, self.line);
189                let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
190                // Pop the boolean from JumpIfTrue (it doesn't pop)
191                self.chunk.emit(Op::Pop, self.line);
192                // Compile the default expression
193                self.compile_node(default_expr)?;
194                let name_idx = self
195                    .chunk
196                    .add_constant(Constant::String(param.name.clone()));
197                self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
198                let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
199                self.chunk.patch_jump(skip_jump);
200                // Pop the boolean left by JumpIfTrue on the true path
201                self.chunk.emit(Op::Pop, self.line);
202                self.chunk.patch_jump(end_jump);
203            }
204        }
205        Ok(())
206    }
207
208    /// Emit runtime type checks for parameters with type annotations.
209    /// For each param with a type annotation, emits CheckType(var_name, type_name)
210    /// or calls __assert_shape for shape types.
211    fn emit_type_checks(&mut self, params: &[TypedParam]) {
212        for param in params {
213            if let Some(type_expr) = &param.type_expr {
214                // Handle shape types via __assert_shape builtin call
215                if let harn_parser::TypeExpr::Shape(fields) = type_expr {
216                    let spec = Self::shape_to_spec_string(fields);
217                    // Emit: __assert_shape(param_value, param_name, spec)
218                    let fn_idx = self
219                        .chunk
220                        .add_constant(Constant::String("__assert_shape".into()));
221                    self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
222                    let var_idx = self
223                        .chunk
224                        .add_constant(Constant::String(param.name.clone()));
225                    self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
226                    let name_idx = self
227                        .chunk
228                        .add_constant(Constant::String(param.name.clone()));
229                    self.chunk.emit_u16(Op::Constant, name_idx, self.line);
230                    let spec_idx = self.chunk.add_constant(Constant::String(spec));
231                    self.chunk.emit_u16(Op::Constant, spec_idx, self.line);
232                    self.chunk.emit_u8(Op::Call, 3, self.line);
233                    self.chunk.emit(Op::Pop, self.line);
234                    continue;
235                }
236
237                // Check if this is an interface type — emit __assert_interface
238                if let harn_parser::TypeExpr::Named(name) = type_expr {
239                    if let Some(methods) = self.interface_methods.get(name) {
240                        let fn_idx = self
241                            .chunk
242                            .add_constant(Constant::String("__assert_interface".into()));
243                        self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
244                        let var_idx = self
245                            .chunk
246                            .add_constant(Constant::String(param.name.clone()));
247                        self.chunk.emit_u16(Op::GetVar, var_idx, self.line);
248                        let name_idx = self
249                            .chunk
250                            .add_constant(Constant::String(param.name.clone()));
251                        self.chunk.emit_u16(Op::Constant, name_idx, self.line);
252                        let iface_idx = self.chunk.add_constant(Constant::String(name.clone()));
253                        self.chunk.emit_u16(Op::Constant, iface_idx, self.line);
254                        let methods_str = methods.join(",");
255                        let methods_idx = self.chunk.add_constant(Constant::String(methods_str));
256                        self.chunk.emit_u16(Op::Constant, methods_idx, self.line);
257                        self.chunk.emit_u8(Op::Call, 4, self.line);
258                        self.chunk.emit(Op::Pop, self.line);
259                        continue;
260                    }
261                }
262
263                let type_name = Self::type_expr_to_runtime_name(type_expr);
264                if let Some(type_name) = type_name {
265                    let var_idx = self
266                        .chunk
267                        .add_constant(Constant::String(param.name.clone()));
268                    let type_idx = self.chunk.add_constant(Constant::String(type_name));
269                    self.chunk.emit_u16(Op::CheckType, var_idx, self.line);
270                    // Emit the type name index as two extra bytes
271                    let hi = (type_idx >> 8) as u8;
272                    let lo = type_idx as u8;
273                    self.chunk.code.push(hi);
274                    self.chunk.code.push(lo);
275                }
276            }
277        }
278    }
279
280    /// Serialize a list of ShapeFields into a spec string for __assert_shape.
281    /// Format: `name:string,age:int,active:?bool,addr:{city:string,zip:string}`
282    fn shape_to_spec_string(fields: &[harn_parser::ShapeField]) -> String {
283        fields
284            .iter()
285            .map(|f| {
286                let opt = if f.optional { "?" } else { "" };
287                let type_str = Self::type_expr_to_spec(&f.type_expr);
288                format!("{}:{}{}", f.name, opt, type_str)
289            })
290            .collect::<Vec<_>>()
291            .join(",")
292    }
293
294    /// Convert a TypeExpr into a spec string fragment for shape validation.
295    fn type_expr_to_spec(type_expr: &harn_parser::TypeExpr) -> String {
296        match type_expr {
297            harn_parser::TypeExpr::Named(name) => name.clone(),
298            harn_parser::TypeExpr::Shape(fields) => {
299                let inner = Self::shape_to_spec_string(fields);
300                format!("{{{}}}", inner)
301            }
302            harn_parser::TypeExpr::List(_) => "list".to_string(),
303            harn_parser::TypeExpr::DictType(_, _) => "dict".to_string(),
304            harn_parser::TypeExpr::Union(members) => {
305                // Serialize union as "type1|type2|type3" for runtime validation
306                members
307                    .iter()
308                    .map(Self::type_expr_to_spec)
309                    .collect::<Vec<_>>()
310                    .join("|")
311            }
312            harn_parser::TypeExpr::FnType { .. } => "closure".to_string(),
313        }
314    }
315
316    /// Convert a TypeExpr to a runtime type name string for CheckType.
317    fn type_expr_to_runtime_name(type_expr: &harn_parser::TypeExpr) -> Option<String> {
318        match type_expr {
319            harn_parser::TypeExpr::Named(name) => match name.as_str() {
320                "int" | "float" | "string" | "bool" | "list" | "dict" | "set" | "nil"
321                | "closure" => Some(name.clone()),
322                _ => None, // Unknown types are not checked at runtime
323            },
324            _ => None, // Union types, shapes, etc. are not checked at runtime
325        }
326    }
327
328    /// Emit the extra u16 type name index after a TryCatchSetup jump.
329    fn emit_type_name_extra(&mut self, type_name_idx: u16) {
330        let hi = (type_name_idx >> 8) as u8;
331        let lo = type_name_idx as u8;
332        self.chunk.code.push(hi);
333        self.chunk.code.push(lo);
334        self.chunk.lines.push(self.line);
335        self.chunk.columns.push(self.column);
336        self.chunk.lines.push(self.line);
337        self.chunk.columns.push(self.column);
338    }
339
340    /// Compile a try/catch body block (produces a value on the stack).
341    fn compile_try_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
342        if body.is_empty() {
343            self.chunk.emit(Op::Nil, self.line);
344        } else {
345            self.compile_block(body)?;
346            if !Self::produces_value(&body.last().unwrap().node) {
347                self.chunk.emit(Op::Nil, self.line);
348            }
349        }
350        Ok(())
351    }
352
353    /// Compile catch error binding (error value is on stack from handler).
354    fn compile_catch_binding(&mut self, error_var: &Option<String>) -> Result<(), CompileError> {
355        if let Some(var_name) = error_var {
356            let idx = self.chunk.add_constant(Constant::String(var_name.clone()));
357            self.chunk.emit_u16(Op::DefLet, idx, self.line);
358        } else {
359            self.chunk.emit(Op::Pop, self.line);
360        }
361        Ok(())
362    }
363
364    /// Compile finally body inline, discarding its result value.
365    fn compile_finally_inline(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
366        if !finally_body.is_empty() {
367            self.compile_block(finally_body)?;
368            // Finally body's value is discarded — only the try/catch value matters
369            if Self::produces_value(&finally_body.last().unwrap().node) {
370                self.chunk.emit(Op::Pop, self.line);
371            }
372        }
373        Ok(())
374    }
375
376    /// Compile rethrow pattern: save error to temp var, run finally, re-throw.
377    fn compile_rethrow_with_finally(&mut self, finally_body: &[SNode]) -> Result<(), CompileError> {
378        // Error is on the stack from the handler
379        self.temp_counter += 1;
380        let temp_name = format!("__finally_err_{}__", self.temp_counter);
381        let err_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
382        self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
383        self.compile_finally_inline(finally_body)?;
384        let get_idx = self.chunk.add_constant(Constant::String(temp_name));
385        self.chunk.emit_u16(Op::GetVar, get_idx, self.line);
386        self.chunk.emit(Op::Throw, self.line);
387        Ok(())
388    }
389
390    fn compile_block(&mut self, stmts: &[SNode]) -> Result<(), CompileError> {
391        for (i, snode) in stmts.iter().enumerate() {
392            self.compile_node(snode)?;
393            let is_last = i == stmts.len() - 1;
394            if is_last {
395                // If the last statement doesn't produce a value, push nil
396                // so the block always leaves exactly one value on the stack.
397                if !Self::produces_value(&snode.node) {
398                    self.chunk.emit(Op::Nil, self.line);
399                }
400            } else {
401                // Only pop if the statement leaves a value on the stack
402                if Self::produces_value(&snode.node) {
403                    self.chunk.emit(Op::Pop, self.line);
404                }
405            }
406        }
407        Ok(())
408    }
409
410    fn compile_node(&mut self, snode: &SNode) -> Result<(), CompileError> {
411        self.line = snode.span.line as u32;
412        self.column = snode.span.column as u32;
413        self.chunk.set_column(self.column);
414        match &snode.node {
415            Node::IntLiteral(n) => {
416                let idx = self.chunk.add_constant(Constant::Int(*n));
417                self.chunk.emit_u16(Op::Constant, idx, self.line);
418            }
419            Node::FloatLiteral(n) => {
420                let idx = self.chunk.add_constant(Constant::Float(*n));
421                self.chunk.emit_u16(Op::Constant, idx, self.line);
422            }
423            Node::StringLiteral(s) => {
424                let idx = self.chunk.add_constant(Constant::String(s.clone()));
425                self.chunk.emit_u16(Op::Constant, idx, self.line);
426            }
427            Node::BoolLiteral(true) => self.chunk.emit(Op::True, self.line),
428            Node::BoolLiteral(false) => self.chunk.emit(Op::False, self.line),
429            Node::NilLiteral => self.chunk.emit(Op::Nil, self.line),
430            Node::DurationLiteral(ms) => {
431                let idx = self.chunk.add_constant(Constant::Duration(*ms));
432                self.chunk.emit_u16(Op::Constant, idx, self.line);
433            }
434
435            Node::Identifier(name) => {
436                let idx = self.chunk.add_constant(Constant::String(name.clone()));
437                self.chunk.emit_u16(Op::GetVar, idx, self.line);
438            }
439
440            Node::LetBinding { pattern, value, .. } => {
441                self.compile_node(value)?;
442                self.compile_destructuring(pattern, false)?;
443            }
444
445            Node::VarBinding { pattern, value, .. } => {
446                self.compile_node(value)?;
447                self.compile_destructuring(pattern, true)?;
448            }
449
450            Node::Assignment {
451                target, value, op, ..
452            } => {
453                if let Node::Identifier(name) = &target.node {
454                    let idx = self.chunk.add_constant(Constant::String(name.clone()));
455                    if let Some(op) = op {
456                        self.chunk.emit_u16(Op::GetVar, idx, self.line);
457                        self.compile_node(value)?;
458                        self.emit_compound_op(op)?;
459                        self.chunk.emit_u16(Op::SetVar, idx, self.line);
460                    } else {
461                        self.compile_node(value)?;
462                        self.chunk.emit_u16(Op::SetVar, idx, self.line);
463                    }
464                } else if let Node::PropertyAccess { object, property } = &target.node {
465                    // obj.field = value → SetProperty
466                    if let Some(var_name) = self.root_var_name(object) {
467                        let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
468                        let prop_idx = self.chunk.add_constant(Constant::String(property.clone()));
469                        if let Some(op) = op {
470                            // compound: obj.field += value
471                            self.compile_node(target)?; // push current obj.field
472                            self.compile_node(value)?;
473                            self.emit_compound_op(op)?;
474                        } else {
475                            self.compile_node(value)?;
476                        }
477                        // Stack: [new_value]
478                        // SetProperty reads var_idx from env, sets prop, writes back
479                        self.chunk.emit_u16(Op::SetProperty, prop_idx, self.line);
480                        // Encode the variable name index as a second u16
481                        let hi = (var_idx >> 8) as u8;
482                        let lo = var_idx as u8;
483                        self.chunk.code.push(hi);
484                        self.chunk.code.push(lo);
485                        self.chunk.lines.push(self.line);
486                        self.chunk.columns.push(self.column);
487                        self.chunk.lines.push(self.line);
488                        self.chunk.columns.push(self.column);
489                    }
490                } else if let Node::SubscriptAccess { object, index } = &target.node {
491                    // obj[idx] = value → SetSubscript
492                    if let Some(var_name) = self.root_var_name(object) {
493                        let var_idx = self.chunk.add_constant(Constant::String(var_name.clone()));
494                        if let Some(op) = op {
495                            self.compile_node(target)?;
496                            self.compile_node(value)?;
497                            self.emit_compound_op(op)?;
498                        } else {
499                            self.compile_node(value)?;
500                        }
501                        self.compile_node(index)?;
502                        self.chunk.emit_u16(Op::SetSubscript, var_idx, self.line);
503                    }
504                }
505            }
506
507            Node::BinaryOp { op, left, right } => {
508                // Short-circuit operators
509                match op.as_str() {
510                    "&&" => {
511                        self.compile_node(left)?;
512                        let jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
513                        self.chunk.emit(Op::Pop, self.line);
514                        self.compile_node(right)?;
515                        self.chunk.patch_jump(jump);
516                        // Normalize to bool
517                        self.chunk.emit(Op::Not, self.line);
518                        self.chunk.emit(Op::Not, self.line);
519                        return Ok(());
520                    }
521                    "||" => {
522                        self.compile_node(left)?;
523                        let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
524                        self.chunk.emit(Op::Pop, self.line);
525                        self.compile_node(right)?;
526                        self.chunk.patch_jump(jump);
527                        self.chunk.emit(Op::Not, self.line);
528                        self.chunk.emit(Op::Not, self.line);
529                        return Ok(());
530                    }
531                    "??" => {
532                        self.compile_node(left)?;
533                        self.chunk.emit(Op::Dup, self.line);
534                        // Check if nil: push nil, compare
535                        self.chunk.emit(Op::Nil, self.line);
536                        self.chunk.emit(Op::NotEqual, self.line);
537                        let jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
538                        self.chunk.emit(Op::Pop, self.line); // pop the not-equal result
539                        self.chunk.emit(Op::Pop, self.line); // pop the nil value
540                        self.compile_node(right)?;
541                        let end = self.chunk.emit_jump(Op::Jump, self.line);
542                        self.chunk.patch_jump(jump);
543                        self.chunk.emit(Op::Pop, self.line); // pop the not-equal result
544                        self.chunk.patch_jump(end);
545                        return Ok(());
546                    }
547                    "|>" => {
548                        self.compile_node(left)?;
549                        // If the RHS contains `_` placeholders, desugar into a closure:
550                        //   value |> func(_, arg)  =>  value |> { __pipe -> func(__pipe, arg) }
551                        if contains_pipe_placeholder(right) {
552                            let replaced = replace_pipe_placeholder(right);
553                            let closure_node = SNode::dummy(Node::Closure {
554                                params: vec![TypedParam {
555                                    name: "__pipe".into(),
556                                    type_expr: None,
557                                    default_value: None,
558                                }],
559                                body: vec![replaced],
560                            });
561                            self.compile_node(&closure_node)?;
562                        } else {
563                            self.compile_node(right)?;
564                        }
565                        self.chunk.emit(Op::Pipe, self.line);
566                        return Ok(());
567                    }
568                    _ => {}
569                }
570
571                self.compile_node(left)?;
572                self.compile_node(right)?;
573                match op.as_str() {
574                    "+" => self.chunk.emit(Op::Add, self.line),
575                    "-" => self.chunk.emit(Op::Sub, self.line),
576                    "*" => self.chunk.emit(Op::Mul, self.line),
577                    "/" => self.chunk.emit(Op::Div, self.line),
578                    "%" => self.chunk.emit(Op::Mod, self.line),
579                    "==" => self.chunk.emit(Op::Equal, self.line),
580                    "!=" => self.chunk.emit(Op::NotEqual, self.line),
581                    "<" => self.chunk.emit(Op::Less, self.line),
582                    ">" => self.chunk.emit(Op::Greater, self.line),
583                    "<=" => self.chunk.emit(Op::LessEqual, self.line),
584                    ">=" => self.chunk.emit(Op::GreaterEqual, self.line),
585                    _ => {
586                        return Err(CompileError {
587                            message: format!("Unknown operator: {op}"),
588                            line: self.line,
589                        })
590                    }
591                }
592            }
593
594            Node::UnaryOp { op, operand } => {
595                self.compile_node(operand)?;
596                match op.as_str() {
597                    "-" => self.chunk.emit(Op::Negate, self.line),
598                    "!" => self.chunk.emit(Op::Not, self.line),
599                    _ => {}
600                }
601            }
602
603            Node::Ternary {
604                condition,
605                true_expr,
606                false_expr,
607            } => {
608                self.compile_node(condition)?;
609                let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
610                self.chunk.emit(Op::Pop, self.line);
611                self.compile_node(true_expr)?;
612                let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
613                self.chunk.patch_jump(else_jump);
614                self.chunk.emit(Op::Pop, self.line);
615                self.compile_node(false_expr)?;
616                self.chunk.patch_jump(end_jump);
617            }
618
619            Node::FunctionCall { name, args } => {
620                let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
621                // Push function name as string constant
622                let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
623                self.chunk.emit_u16(Op::Constant, name_idx, self.line);
624
625                if has_spread {
626                    // Build the args into a single list using the flush-and-concat
627                    // pattern (same as ListLiteral with spreads).
628                    self.chunk.emit_u16(Op::BuildList, 0, self.line);
629                    let mut pending = 0u16;
630                    for arg in args {
631                        if let Node::Spread(inner) = &arg.node {
632                            if pending > 0 {
633                                self.chunk.emit_u16(Op::BuildList, pending, self.line);
634                                self.chunk.emit(Op::Add, self.line);
635                                pending = 0;
636                            }
637                            self.compile_node(inner)?;
638                            self.chunk.emit(Op::Dup, self.line);
639                            let assert_idx = self
640                                .chunk
641                                .add_constant(Constant::String("__assert_list".into()));
642                            self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
643                            self.chunk.emit(Op::Swap, self.line);
644                            self.chunk.emit_u8(Op::Call, 1, self.line);
645                            self.chunk.emit(Op::Pop, self.line);
646                            self.chunk.emit(Op::Add, self.line);
647                        } else {
648                            self.compile_node(arg)?;
649                            pending += 1;
650                        }
651                    }
652                    if pending > 0 {
653                        self.chunk.emit_u16(Op::BuildList, pending, self.line);
654                        self.chunk.emit(Op::Add, self.line);
655                    }
656                    self.chunk.emit(Op::CallSpread, self.line);
657                } else {
658                    // Push arguments normally
659                    for arg in args {
660                        self.compile_node(arg)?;
661                    }
662                    self.chunk.emit_u8(Op::Call, args.len() as u8, self.line);
663                }
664            }
665
666            Node::MethodCall {
667                object,
668                method,
669                args,
670            } => {
671                // Check if this is an enum variant construction with args: EnumName.Variant(args)
672                if let Node::Identifier(name) = &object.node {
673                    if self.enum_names.contains(name) {
674                        // Compile args, then BuildEnum
675                        for arg in args {
676                            self.compile_node(arg)?;
677                        }
678                        let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
679                        let var_idx = self.chunk.add_constant(Constant::String(method.clone()));
680                        self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
681                        let hi = (var_idx >> 8) as u8;
682                        let lo = var_idx as u8;
683                        self.chunk.code.push(hi);
684                        self.chunk.code.push(lo);
685                        self.chunk.lines.push(self.line);
686                        self.chunk.columns.push(self.column);
687                        self.chunk.lines.push(self.line);
688                        self.chunk.columns.push(self.column);
689                        let fc = args.len() as u16;
690                        let fhi = (fc >> 8) as u8;
691                        let flo = fc as u8;
692                        self.chunk.code.push(fhi);
693                        self.chunk.code.push(flo);
694                        self.chunk.lines.push(self.line);
695                        self.chunk.columns.push(self.column);
696                        self.chunk.lines.push(self.line);
697                        self.chunk.columns.push(self.column);
698                        return Ok(());
699                    }
700                }
701                let has_spread = args.iter().any(|a| matches!(&a.node, Node::Spread(_)));
702                self.compile_node(object)?;
703                let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
704                if has_spread {
705                    // Build args into a single list (same pattern as FunctionCall spread)
706                    self.chunk.emit_u16(Op::BuildList, 0, self.line);
707                    let mut pending = 0u16;
708                    for arg in args {
709                        if let Node::Spread(inner) = &arg.node {
710                            if pending > 0 {
711                                self.chunk.emit_u16(Op::BuildList, pending, self.line);
712                                self.chunk.emit(Op::Add, self.line);
713                                pending = 0;
714                            }
715                            self.compile_node(inner)?;
716                            self.chunk.emit(Op::Dup, self.line);
717                            let assert_idx = self
718                                .chunk
719                                .add_constant(Constant::String("__assert_list".into()));
720                            self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
721                            self.chunk.emit(Op::Swap, self.line);
722                            self.chunk.emit_u8(Op::Call, 1, self.line);
723                            self.chunk.emit(Op::Pop, self.line);
724                            self.chunk.emit(Op::Add, self.line);
725                        } else {
726                            self.compile_node(arg)?;
727                            pending += 1;
728                        }
729                    }
730                    if pending > 0 {
731                        self.chunk.emit_u16(Op::BuildList, pending, self.line);
732                        self.chunk.emit(Op::Add, self.line);
733                    }
734                    self.chunk
735                        .emit_u16(Op::MethodCallSpread, name_idx, self.line);
736                } else {
737                    for arg in args {
738                        self.compile_node(arg)?;
739                    }
740                    self.chunk
741                        .emit_method_call(name_idx, args.len() as u8, self.line);
742                }
743            }
744
745            Node::OptionalMethodCall {
746                object,
747                method,
748                args,
749            } => {
750                self.compile_node(object)?;
751                for arg in args {
752                    self.compile_node(arg)?;
753                }
754                let name_idx = self.chunk.add_constant(Constant::String(method.clone()));
755                self.chunk
756                    .emit_method_call_opt(name_idx, args.len() as u8, self.line);
757            }
758
759            Node::PropertyAccess { object, property } => {
760                // Check if this is an enum variant construction: EnumName.Variant
761                if let Node::Identifier(name) = &object.node {
762                    if self.enum_names.contains(name) {
763                        // Emit BuildEnum with 0 fields
764                        let enum_idx = self.chunk.add_constant(Constant::String(name.clone()));
765                        let var_idx = self.chunk.add_constant(Constant::String(property.clone()));
766                        self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
767                        let hi = (var_idx >> 8) as u8;
768                        let lo = var_idx as u8;
769                        self.chunk.code.push(hi);
770                        self.chunk.code.push(lo);
771                        self.chunk.lines.push(self.line);
772                        self.chunk.columns.push(self.column);
773                        self.chunk.lines.push(self.line);
774                        self.chunk.columns.push(self.column);
775                        // 0 fields
776                        self.chunk.code.push(0);
777                        self.chunk.code.push(0);
778                        self.chunk.lines.push(self.line);
779                        self.chunk.columns.push(self.column);
780                        self.chunk.lines.push(self.line);
781                        self.chunk.columns.push(self.column);
782                        return Ok(());
783                    }
784                }
785                self.compile_node(object)?;
786                let idx = self.chunk.add_constant(Constant::String(property.clone()));
787                self.chunk.emit_u16(Op::GetProperty, idx, self.line);
788            }
789
790            Node::OptionalPropertyAccess { object, property } => {
791                self.compile_node(object)?;
792                let idx = self.chunk.add_constant(Constant::String(property.clone()));
793                self.chunk.emit_u16(Op::GetPropertyOpt, idx, self.line);
794            }
795
796            Node::SubscriptAccess { object, index } => {
797                self.compile_node(object)?;
798                self.compile_node(index)?;
799                self.chunk.emit(Op::Subscript, self.line);
800            }
801
802            Node::SliceAccess { object, start, end } => {
803                self.compile_node(object)?;
804                if let Some(s) = start {
805                    self.compile_node(s)?;
806                } else {
807                    self.chunk.emit(Op::Nil, self.line);
808                }
809                if let Some(e) = end {
810                    self.compile_node(e)?;
811                } else {
812                    self.chunk.emit(Op::Nil, self.line);
813                }
814                self.chunk.emit(Op::Slice, self.line);
815            }
816
817            Node::IfElse {
818                condition,
819                then_body,
820                else_body,
821            } => {
822                self.compile_node(condition)?;
823                let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
824                self.chunk.emit(Op::Pop, self.line);
825                self.compile_block(then_body)?;
826                if let Some(else_body) = else_body {
827                    let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
828                    self.chunk.patch_jump(else_jump);
829                    self.chunk.emit(Op::Pop, self.line);
830                    self.compile_block(else_body)?;
831                    self.chunk.patch_jump(end_jump);
832                } else {
833                    self.chunk.patch_jump(else_jump);
834                    self.chunk.emit(Op::Pop, self.line);
835                    self.chunk.emit(Op::Nil, self.line);
836                }
837            }
838
839            Node::WhileLoop { condition, body } => {
840                let loop_start = self.chunk.current_offset();
841                self.loop_stack.push(LoopContext {
842                    start_offset: loop_start,
843                    break_patches: Vec::new(),
844                    has_iterator: false,
845                    handler_depth: self.handler_depth,
846                    finally_depth: self.finally_bodies.len(),
847                });
848                self.compile_node(condition)?;
849                let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
850                self.chunk.emit(Op::Pop, self.line); // pop condition
851                                                     // Compile body statements, popping all results
852                for sn in body {
853                    self.compile_node(sn)?;
854                    if Self::produces_value(&sn.node) {
855                        self.chunk.emit(Op::Pop, self.line);
856                    }
857                }
858                // Jump back to condition
859                self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
860                self.chunk.patch_jump(exit_jump);
861                self.chunk.emit(Op::Pop, self.line); // pop condition
862                                                     // Patch all break jumps to here
863                let ctx = self.loop_stack.pop().unwrap();
864                for patch_pos in ctx.break_patches {
865                    self.chunk.patch_jump(patch_pos);
866                }
867                self.chunk.emit(Op::Nil, self.line);
868            }
869
870            Node::ForIn {
871                pattern,
872                iterable,
873                body,
874            } => {
875                // Compile iterable
876                self.compile_node(iterable)?;
877                // Initialize iterator
878                self.chunk.emit(Op::IterInit, self.line);
879                let loop_start = self.chunk.current_offset();
880                self.loop_stack.push(LoopContext {
881                    start_offset: loop_start,
882                    break_patches: Vec::new(),
883                    has_iterator: true,
884                    handler_depth: self.handler_depth,
885                    finally_depth: self.finally_bodies.len(),
886                });
887                // Try to get next item — jumps to end if exhausted
888                let exit_jump_pos = self.chunk.emit_jump(Op::IterNext, self.line);
889                // Define loop variable(s) with current item (item is on stack from IterNext)
890                self.compile_destructuring(pattern, true)?;
891                // Compile body statements, popping all results
892                for sn in body {
893                    self.compile_node(sn)?;
894                    if Self::produces_value(&sn.node) {
895                        self.chunk.emit(Op::Pop, self.line);
896                    }
897                }
898                // Loop back
899                self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
900                self.chunk.patch_jump(exit_jump_pos);
901                // Patch all break jumps to here
902                let ctx = self.loop_stack.pop().unwrap();
903                for patch_pos in ctx.break_patches {
904                    self.chunk.patch_jump(patch_pos);
905                }
906                // Push nil as result (iterator state was consumed)
907                self.chunk.emit(Op::Nil, self.line);
908            }
909
910            Node::ReturnStmt { value } => {
911                let has_pending_finally = !self.finally_bodies.is_empty();
912
913                if has_pending_finally {
914                    // Inside try-finally: compile value, save to temp,
915                    // run pending finallys, restore value, then return.
916                    if let Some(val) = value {
917                        self.compile_node(val)?;
918                    } else {
919                        self.chunk.emit(Op::Nil, self.line);
920                    }
921                    self.temp_counter += 1;
922                    let temp_name = format!("__return_val_{}__", self.temp_counter);
923                    let save_idx = self.chunk.add_constant(Constant::String(temp_name.clone()));
924                    self.chunk.emit_u16(Op::DefVar, save_idx, self.line);
925                    // Emit all pending finallys (innermost first = reverse order)
926                    let finallys: Vec<_> = self.finally_bodies.iter().rev().cloned().collect();
927                    for fb in &finallys {
928                        self.compile_finally_inline(fb)?;
929                    }
930                    let restore_idx = self.chunk.add_constant(Constant::String(temp_name));
931                    self.chunk.emit_u16(Op::GetVar, restore_idx, self.line);
932                    self.chunk.emit(Op::Return, self.line);
933                } else {
934                    // No pending finally — original behavior with tail call optimization
935                    if let Some(val) = value {
936                        if let Node::FunctionCall { name, args } = &val.node {
937                            let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
938                            self.chunk.emit_u16(Op::Constant, name_idx, self.line);
939                            for arg in args {
940                                self.compile_node(arg)?;
941                            }
942                            self.chunk
943                                .emit_u8(Op::TailCall, args.len() as u8, self.line);
944                        } else if let Node::BinaryOp { op, left, right } = &val.node {
945                            if op == "|>" {
946                                self.compile_node(left)?;
947                                self.compile_node(right)?;
948                                self.chunk.emit(Op::Swap, self.line);
949                                self.chunk.emit_u8(Op::TailCall, 1, self.line);
950                            } else {
951                                self.compile_node(val)?;
952                            }
953                        } else {
954                            self.compile_node(val)?;
955                        }
956                    } else {
957                        self.chunk.emit(Op::Nil, self.line);
958                    }
959                    self.chunk.emit(Op::Return, self.line);
960                }
961            }
962
963            Node::BreakStmt => {
964                if self.loop_stack.is_empty() {
965                    return Err(CompileError {
966                        message: "break outside of loop".to_string(),
967                        line: self.line,
968                    });
969                }
970                // Copy values out to avoid borrow conflict
971                let ctx = self.loop_stack.last().unwrap();
972                let finally_depth = ctx.finally_depth;
973                let handler_depth = ctx.handler_depth;
974                let has_iterator = ctx.has_iterator;
975                // Pop exception handlers that were pushed inside the loop
976                for _ in handler_depth..self.handler_depth {
977                    self.chunk.emit(Op::PopHandler, self.line);
978                }
979                // Emit pending finallys that are inside the loop
980                if self.finally_bodies.len() > finally_depth {
981                    let finallys: Vec<_> = self.finally_bodies[finally_depth..]
982                        .iter()
983                        .rev()
984                        .cloned()
985                        .collect();
986                    for fb in &finallys {
987                        self.compile_finally_inline(fb)?;
988                    }
989                }
990                if has_iterator {
991                    self.chunk.emit(Op::PopIterator, self.line);
992                }
993                let patch = self.chunk.emit_jump(Op::Jump, self.line);
994                self.loop_stack
995                    .last_mut()
996                    .unwrap()
997                    .break_patches
998                    .push(patch);
999            }
1000
1001            Node::ContinueStmt => {
1002                if self.loop_stack.is_empty() {
1003                    return Err(CompileError {
1004                        message: "continue outside of loop".to_string(),
1005                        line: self.line,
1006                    });
1007                }
1008                let ctx = self.loop_stack.last().unwrap();
1009                let finally_depth = ctx.finally_depth;
1010                let handler_depth = ctx.handler_depth;
1011                let loop_start = ctx.start_offset;
1012                for _ in handler_depth..self.handler_depth {
1013                    self.chunk.emit(Op::PopHandler, self.line);
1014                }
1015                if self.finally_bodies.len() > finally_depth {
1016                    let finallys: Vec<_> = self.finally_bodies[finally_depth..]
1017                        .iter()
1018                        .rev()
1019                        .cloned()
1020                        .collect();
1021                    for fb in &finallys {
1022                        self.compile_finally_inline(fb)?;
1023                    }
1024                }
1025                self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
1026            }
1027
1028            Node::ListLiteral(elements) => {
1029                let has_spread = elements.iter().any(|e| matches!(&e.node, Node::Spread(_)));
1030                if !has_spread {
1031                    for el in elements {
1032                        self.compile_node(el)?;
1033                    }
1034                    self.chunk
1035                        .emit_u16(Op::BuildList, elements.len() as u16, self.line);
1036                } else {
1037                    // Build with spreads: accumulate segments into lists and concat
1038                    // Start with empty list
1039                    self.chunk.emit_u16(Op::BuildList, 0, self.line);
1040                    let mut pending = 0u16;
1041                    for el in elements {
1042                        if let Node::Spread(inner) = &el.node {
1043                            // First, build list from pending non-spread elements
1044                            if pending > 0 {
1045                                self.chunk.emit_u16(Op::BuildList, pending, self.line);
1046                                // Concat accumulated + pending segment
1047                                self.chunk.emit(Op::Add, self.line);
1048                                pending = 0;
1049                            }
1050                            // Concat with the spread expression (with type check)
1051                            self.compile_node(inner)?;
1052                            self.chunk.emit(Op::Dup, self.line);
1053                            let assert_idx = self
1054                                .chunk
1055                                .add_constant(Constant::String("__assert_list".into()));
1056                            self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1057                            self.chunk.emit(Op::Swap, self.line);
1058                            self.chunk.emit_u8(Op::Call, 1, self.line);
1059                            self.chunk.emit(Op::Pop, self.line);
1060                            self.chunk.emit(Op::Add, self.line);
1061                        } else {
1062                            self.compile_node(el)?;
1063                            pending += 1;
1064                        }
1065                    }
1066                    if pending > 0 {
1067                        self.chunk.emit_u16(Op::BuildList, pending, self.line);
1068                        self.chunk.emit(Op::Add, self.line);
1069                    }
1070                }
1071            }
1072
1073            Node::DictLiteral(entries) => {
1074                let has_spread = entries
1075                    .iter()
1076                    .any(|e| matches!(&e.value.node, Node::Spread(_)));
1077                if !has_spread {
1078                    for entry in entries {
1079                        self.compile_node(&entry.key)?;
1080                        self.compile_node(&entry.value)?;
1081                    }
1082                    self.chunk
1083                        .emit_u16(Op::BuildDict, entries.len() as u16, self.line);
1084                } else {
1085                    // Build with spreads: use empty dict + Add for merging
1086                    self.chunk.emit_u16(Op::BuildDict, 0, self.line);
1087                    let mut pending = 0u16;
1088                    for entry in entries {
1089                        if let Node::Spread(inner) = &entry.value.node {
1090                            // Flush pending entries
1091                            if pending > 0 {
1092                                self.chunk.emit_u16(Op::BuildDict, pending, self.line);
1093                                self.chunk.emit(Op::Add, self.line);
1094                                pending = 0;
1095                            }
1096                            // Merge spread dict via Add (with type check)
1097                            self.compile_node(inner)?;
1098                            self.chunk.emit(Op::Dup, self.line);
1099                            let assert_idx = self
1100                                .chunk
1101                                .add_constant(Constant::String("__assert_dict".into()));
1102                            self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
1103                            self.chunk.emit(Op::Swap, self.line);
1104                            self.chunk.emit_u8(Op::Call, 1, self.line);
1105                            self.chunk.emit(Op::Pop, self.line);
1106                            self.chunk.emit(Op::Add, self.line);
1107                        } else {
1108                            self.compile_node(&entry.key)?;
1109                            self.compile_node(&entry.value)?;
1110                            pending += 1;
1111                        }
1112                    }
1113                    if pending > 0 {
1114                        self.chunk.emit_u16(Op::BuildDict, pending, self.line);
1115                        self.chunk.emit(Op::Add, self.line);
1116                    }
1117                }
1118            }
1119
1120            Node::InterpolatedString(segments) => {
1121                let mut part_count = 0u16;
1122                for seg in segments {
1123                    match seg {
1124                        StringSegment::Literal(s) => {
1125                            let idx = self.chunk.add_constant(Constant::String(s.clone()));
1126                            self.chunk.emit_u16(Op::Constant, idx, self.line);
1127                            part_count += 1;
1128                        }
1129                        StringSegment::Expression(expr_str) => {
1130                            // Parse and compile the embedded expression
1131                            let mut lexer = harn_lexer::Lexer::new(expr_str);
1132                            if let Ok(tokens) = lexer.tokenize() {
1133                                let mut parser = harn_parser::Parser::new(tokens);
1134                                if let Ok(snode) = parser.parse_single_expression() {
1135                                    self.compile_node(&snode)?;
1136                                    // Convert result to string for concatenation
1137                                    let to_str = self
1138                                        .chunk
1139                                        .add_constant(Constant::String("to_string".into()));
1140                                    self.chunk.emit_u16(Op::Constant, to_str, self.line);
1141                                    self.chunk.emit(Op::Swap, self.line);
1142                                    self.chunk.emit_u8(Op::Call, 1, self.line);
1143                                    part_count += 1;
1144                                } else {
1145                                    // Fallback: treat as literal string
1146                                    let idx =
1147                                        self.chunk.add_constant(Constant::String(expr_str.clone()));
1148                                    self.chunk.emit_u16(Op::Constant, idx, self.line);
1149                                    part_count += 1;
1150                                }
1151                            }
1152                        }
1153                    }
1154                }
1155                if part_count > 1 {
1156                    self.chunk.emit_u16(Op::Concat, part_count, self.line);
1157                }
1158            }
1159
1160            Node::FnDecl {
1161                name, params, body, ..
1162            } => {
1163                // Compile function body into a separate chunk
1164                let mut fn_compiler = Compiler::new();
1165                fn_compiler.enum_names = self.enum_names.clone();
1166                fn_compiler.emit_default_preamble(params)?;
1167                fn_compiler.emit_type_checks(params);
1168                let is_gen = body_contains_yield(body);
1169                fn_compiler.compile_block(body)?;
1170                fn_compiler.chunk.emit(Op::Nil, self.line);
1171                fn_compiler.chunk.emit(Op::Return, self.line);
1172
1173                let func = CompiledFunction {
1174                    name: name.clone(),
1175                    params: TypedParam::names(params),
1176                    default_start: TypedParam::default_start(params),
1177                    chunk: fn_compiler.chunk,
1178                    is_generator: is_gen,
1179                };
1180                let fn_idx = self.chunk.functions.len();
1181                self.chunk.functions.push(func);
1182
1183                self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1184                let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1185                self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1186            }
1187
1188            Node::Closure { params, body } => {
1189                let mut fn_compiler = Compiler::new();
1190                fn_compiler.enum_names = self.enum_names.clone();
1191                fn_compiler.emit_default_preamble(params)?;
1192                fn_compiler.emit_type_checks(params);
1193                let is_gen = body_contains_yield(body);
1194                fn_compiler.compile_block(body)?;
1195                // If block didn't end with return, the last value is on the stack
1196                fn_compiler.chunk.emit(Op::Return, self.line);
1197
1198                let func = CompiledFunction {
1199                    name: "<closure>".to_string(),
1200                    params: TypedParam::names(params),
1201                    default_start: TypedParam::default_start(params),
1202                    chunk: fn_compiler.chunk,
1203                    is_generator: is_gen,
1204                };
1205                let fn_idx = self.chunk.functions.len();
1206                self.chunk.functions.push(func);
1207
1208                self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1209            }
1210
1211            Node::ThrowStmt { value } => {
1212                self.compile_node(value)?;
1213                self.chunk.emit(Op::Throw, self.line);
1214            }
1215
1216            Node::MatchExpr { value, arms } => {
1217                self.compile_node(value)?;
1218                let mut end_jumps = Vec::new();
1219                for arm in arms {
1220                    match &arm.pattern.node {
1221                        // Wildcard `_` — always matches
1222                        Node::Identifier(name) if name == "_" => {
1223                            self.chunk.emit(Op::Pop, self.line); // pop match value
1224                            self.compile_match_body(&arm.body)?;
1225                            end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1226                        }
1227                        // Enum destructuring: EnumConstruct pattern
1228                        Node::EnumConstruct {
1229                            enum_name,
1230                            variant,
1231                            args: pat_args,
1232                        } => {
1233                            // Check if the match value is this enum variant
1234                            self.chunk.emit(Op::Dup, self.line);
1235                            let en_idx =
1236                                self.chunk.add_constant(Constant::String(enum_name.clone()));
1237                            let vn_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1238                            self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1239                            let hi = (vn_idx >> 8) as u8;
1240                            let lo = vn_idx as u8;
1241                            self.chunk.code.push(hi);
1242                            self.chunk.code.push(lo);
1243                            self.chunk.lines.push(self.line);
1244                            self.chunk.columns.push(self.column);
1245                            self.chunk.lines.push(self.line);
1246                            self.chunk.columns.push(self.column);
1247                            // Stack: [match_value, bool]
1248                            let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1249                            self.chunk.emit(Op::Pop, self.line); // pop bool
1250
1251                            // Destructure: bind field variables from the enum's fields
1252                            // The match value is still on the stack; we need to extract fields
1253                            for (i, pat_arg) in pat_args.iter().enumerate() {
1254                                if let Node::Identifier(binding_name) = &pat_arg.node {
1255                                    // Dup the match value, get .fields, subscript [i]
1256                                    self.chunk.emit(Op::Dup, self.line);
1257                                    let fields_idx = self
1258                                        .chunk
1259                                        .add_constant(Constant::String("fields".to_string()));
1260                                    self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1261                                    let idx_const =
1262                                        self.chunk.add_constant(Constant::Int(i as i64));
1263                                    self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1264                                    self.chunk.emit(Op::Subscript, self.line);
1265                                    let name_idx = self
1266                                        .chunk
1267                                        .add_constant(Constant::String(binding_name.clone()));
1268                                    self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1269                                }
1270                            }
1271
1272                            self.chunk.emit(Op::Pop, self.line); // pop match value
1273                            self.compile_match_body(&arm.body)?;
1274                            end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1275                            self.chunk.patch_jump(skip);
1276                            self.chunk.emit(Op::Pop, self.line); // pop bool
1277                        }
1278                        // Enum variant without args: PropertyAccess(EnumName, Variant)
1279                        Node::PropertyAccess { object, property } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1280                        {
1281                            let enum_name = if let Node::Identifier(n) = &object.node {
1282                                n.clone()
1283                            } else {
1284                                unreachable!()
1285                            };
1286                            self.chunk.emit(Op::Dup, self.line);
1287                            let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1288                            let vn_idx =
1289                                self.chunk.add_constant(Constant::String(property.clone()));
1290                            self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1291                            let hi = (vn_idx >> 8) as u8;
1292                            let lo = vn_idx as u8;
1293                            self.chunk.code.push(hi);
1294                            self.chunk.code.push(lo);
1295                            self.chunk.lines.push(self.line);
1296                            self.chunk.columns.push(self.column);
1297                            self.chunk.lines.push(self.line);
1298                            self.chunk.columns.push(self.column);
1299                            let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1300                            self.chunk.emit(Op::Pop, self.line); // pop bool
1301                            self.chunk.emit(Op::Pop, self.line); // pop match value
1302                            self.compile_match_body(&arm.body)?;
1303                            end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1304                            self.chunk.patch_jump(skip);
1305                            self.chunk.emit(Op::Pop, self.line); // pop bool
1306                        }
1307                        // Enum destructuring via MethodCall: EnumName.Variant(bindings...)
1308                        // Parser produces MethodCall for EnumName.Variant(x) patterns
1309                        Node::MethodCall {
1310                            object,
1311                            method,
1312                            args: pat_args,
1313                        } if matches!(&object.node, Node::Identifier(n) if self.enum_names.contains(n)) =>
1314                        {
1315                            let enum_name = if let Node::Identifier(n) = &object.node {
1316                                n.clone()
1317                            } else {
1318                                unreachable!()
1319                            };
1320                            // Check if the match value is this enum variant
1321                            self.chunk.emit(Op::Dup, self.line);
1322                            let en_idx = self.chunk.add_constant(Constant::String(enum_name));
1323                            let vn_idx = self.chunk.add_constant(Constant::String(method.clone()));
1324                            self.chunk.emit_u16(Op::MatchEnum, en_idx, self.line);
1325                            let hi = (vn_idx >> 8) as u8;
1326                            let lo = vn_idx as u8;
1327                            self.chunk.code.push(hi);
1328                            self.chunk.code.push(lo);
1329                            self.chunk.lines.push(self.line);
1330                            self.chunk.columns.push(self.column);
1331                            self.chunk.lines.push(self.line);
1332                            self.chunk.columns.push(self.column);
1333                            let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1334                            self.chunk.emit(Op::Pop, self.line); // pop bool
1335
1336                            // Destructure: bind field variables
1337                            for (i, pat_arg) in pat_args.iter().enumerate() {
1338                                if let Node::Identifier(binding_name) = &pat_arg.node {
1339                                    self.chunk.emit(Op::Dup, self.line);
1340                                    let fields_idx = self
1341                                        .chunk
1342                                        .add_constant(Constant::String("fields".to_string()));
1343                                    self.chunk.emit_u16(Op::GetProperty, fields_idx, self.line);
1344                                    let idx_const =
1345                                        self.chunk.add_constant(Constant::Int(i as i64));
1346                                    self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1347                                    self.chunk.emit(Op::Subscript, self.line);
1348                                    let name_idx = self
1349                                        .chunk
1350                                        .add_constant(Constant::String(binding_name.clone()));
1351                                    self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1352                                }
1353                            }
1354
1355                            self.chunk.emit(Op::Pop, self.line); // pop match value
1356                            self.compile_match_body(&arm.body)?;
1357                            end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1358                            self.chunk.patch_jump(skip);
1359                            self.chunk.emit(Op::Pop, self.line); // pop bool
1360                        }
1361                        // Binding pattern: bare identifier (not a literal)
1362                        Node::Identifier(name) => {
1363                            // Bind the match value to this name, always matches
1364                            self.chunk.emit(Op::Dup, self.line); // dup for binding
1365                            let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1366                            self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1367                            self.chunk.emit(Op::Pop, self.line); // pop match value
1368                            self.compile_match_body(&arm.body)?;
1369                            end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1370                        }
1371                        // Dict pattern: {key: literal, key: binding, ...}
1372                        Node::DictLiteral(entries)
1373                            if entries
1374                                .iter()
1375                                .all(|e| matches!(&e.key.node, Node::StringLiteral(_))) =>
1376                        {
1377                            // Check type is dict: dup, call type_of, compare "dict"
1378                            self.chunk.emit(Op::Dup, self.line);
1379                            let typeof_idx =
1380                                self.chunk.add_constant(Constant::String("type_of".into()));
1381                            self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1382                            self.chunk.emit(Op::Swap, self.line);
1383                            self.chunk.emit_u8(Op::Call, 1, self.line);
1384                            let dict_str = self.chunk.add_constant(Constant::String("dict".into()));
1385                            self.chunk.emit_u16(Op::Constant, dict_str, self.line);
1386                            self.chunk.emit(Op::Equal, self.line);
1387                            let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1388                            self.chunk.emit(Op::Pop, self.line); // pop bool
1389
1390                            // Check literal constraints
1391                            let mut constraint_skips = Vec::new();
1392                            let mut bindings = Vec::new();
1393                            for entry in entries {
1394                                if let Node::StringLiteral(key) = &entry.key.node {
1395                                    match &entry.value.node {
1396                                        // Literal value → constraint: dict[key] == value
1397                                        Node::StringLiteral(_)
1398                                        | Node::IntLiteral(_)
1399                                        | Node::FloatLiteral(_)
1400                                        | Node::BoolLiteral(_)
1401                                        | Node::NilLiteral => {
1402                                            self.chunk.emit(Op::Dup, self.line);
1403                                            let key_idx = self
1404                                                .chunk
1405                                                .add_constant(Constant::String(key.clone()));
1406                                            self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1407                                            self.chunk.emit(Op::Subscript, self.line);
1408                                            self.compile_node(&entry.value)?;
1409                                            self.chunk.emit(Op::Equal, self.line);
1410                                            let skip =
1411                                                self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1412                                            self.chunk.emit(Op::Pop, self.line); // pop bool
1413                                            constraint_skips.push(skip);
1414                                        }
1415                                        // Identifier → binding: bind dict[key] to variable
1416                                        Node::Identifier(binding) => {
1417                                            bindings.push((key.clone(), binding.clone()));
1418                                        }
1419                                        _ => {
1420                                            // Complex expression constraint
1421                                            self.chunk.emit(Op::Dup, self.line);
1422                                            let key_idx = self
1423                                                .chunk
1424                                                .add_constant(Constant::String(key.clone()));
1425                                            self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1426                                            self.chunk.emit(Op::Subscript, self.line);
1427                                            self.compile_node(&entry.value)?;
1428                                            self.chunk.emit(Op::Equal, self.line);
1429                                            let skip =
1430                                                self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1431                                            self.chunk.emit(Op::Pop, self.line);
1432                                            constraint_skips.push(skip);
1433                                        }
1434                                    }
1435                                }
1436                            }
1437
1438                            // All constraints passed — emit bindings
1439                            for (key, binding) in &bindings {
1440                                self.chunk.emit(Op::Dup, self.line);
1441                                let key_idx =
1442                                    self.chunk.add_constant(Constant::String(key.clone()));
1443                                self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1444                                self.chunk.emit(Op::Subscript, self.line);
1445                                let name_idx =
1446                                    self.chunk.add_constant(Constant::String(binding.clone()));
1447                                self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1448                            }
1449
1450                            self.chunk.emit(Op::Pop, self.line); // pop match value
1451                            self.compile_match_body(&arm.body)?;
1452                            end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1453
1454                            // All failures jump here: pop the false bool, leave match_value
1455                            let fail_target = self.chunk.code.len();
1456                            self.chunk.emit(Op::Pop, self.line); // pop bool
1457                                                                 // Patch all failure jumps to the shared cleanup point
1458                            for skip in constraint_skips {
1459                                self.chunk.patch_jump_to(skip, fail_target);
1460                            }
1461                            self.chunk.patch_jump_to(skip_type, fail_target);
1462                        }
1463                        // List pattern: [literal, binding, ...]
1464                        Node::ListLiteral(elements) => {
1465                            // Check type is list: dup, call type_of, compare "list"
1466                            self.chunk.emit(Op::Dup, self.line);
1467                            let typeof_idx =
1468                                self.chunk.add_constant(Constant::String("type_of".into()));
1469                            self.chunk.emit_u16(Op::Constant, typeof_idx, self.line);
1470                            self.chunk.emit(Op::Swap, self.line);
1471                            self.chunk.emit_u8(Op::Call, 1, self.line);
1472                            let list_str = self.chunk.add_constant(Constant::String("list".into()));
1473                            self.chunk.emit_u16(Op::Constant, list_str, self.line);
1474                            self.chunk.emit(Op::Equal, self.line);
1475                            let skip_type = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1476                            self.chunk.emit(Op::Pop, self.line); // pop bool
1477
1478                            // Check length: dup, call len, compare >= elements.len()
1479                            self.chunk.emit(Op::Dup, self.line);
1480                            let len_idx = self.chunk.add_constant(Constant::String("len".into()));
1481                            self.chunk.emit_u16(Op::Constant, len_idx, self.line);
1482                            self.chunk.emit(Op::Swap, self.line);
1483                            self.chunk.emit_u8(Op::Call, 1, self.line);
1484                            let count = self
1485                                .chunk
1486                                .add_constant(Constant::Int(elements.len() as i64));
1487                            self.chunk.emit_u16(Op::Constant, count, self.line);
1488                            self.chunk.emit(Op::GreaterEqual, self.line);
1489                            let skip_len = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1490                            self.chunk.emit(Op::Pop, self.line); // pop bool
1491
1492                            // Check literal constraints and collect bindings
1493                            let mut constraint_skips = Vec::new();
1494                            let mut bindings = Vec::new();
1495                            for (i, elem) in elements.iter().enumerate() {
1496                                match &elem.node {
1497                                    Node::Identifier(name) if name != "_" => {
1498                                        bindings.push((i, name.clone()));
1499                                    }
1500                                    Node::Identifier(_) => {} // wildcard _
1501                                    // Literal constraint
1502                                    _ => {
1503                                        self.chunk.emit(Op::Dup, self.line);
1504                                        let idx_const =
1505                                            self.chunk.add_constant(Constant::Int(i as i64));
1506                                        self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1507                                        self.chunk.emit(Op::Subscript, self.line);
1508                                        self.compile_node(elem)?;
1509                                        self.chunk.emit(Op::Equal, self.line);
1510                                        let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1511                                        self.chunk.emit(Op::Pop, self.line);
1512                                        constraint_skips.push(skip);
1513                                    }
1514                                }
1515                            }
1516
1517                            // Emit bindings
1518                            for (i, name) in &bindings {
1519                                self.chunk.emit(Op::Dup, self.line);
1520                                let idx_const = self.chunk.add_constant(Constant::Int(*i as i64));
1521                                self.chunk.emit_u16(Op::Constant, idx_const, self.line);
1522                                self.chunk.emit(Op::Subscript, self.line);
1523                                let name_idx =
1524                                    self.chunk.add_constant(Constant::String(name.clone()));
1525                                self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1526                            }
1527
1528                            self.chunk.emit(Op::Pop, self.line); // pop match value
1529                            self.compile_match_body(&arm.body)?;
1530                            end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1531
1532                            // All failures jump here: pop the false bool
1533                            let fail_target = self.chunk.code.len();
1534                            self.chunk.emit(Op::Pop, self.line); // pop bool
1535                            for skip in constraint_skips {
1536                                self.chunk.patch_jump_to(skip, fail_target);
1537                            }
1538                            self.chunk.patch_jump_to(skip_len, fail_target);
1539                            self.chunk.patch_jump_to(skip_type, fail_target);
1540                        }
1541                        // Literal/expression pattern — compare with Equal
1542                        _ => {
1543                            self.chunk.emit(Op::Dup, self.line);
1544                            self.compile_node(&arm.pattern)?;
1545                            self.chunk.emit(Op::Equal, self.line);
1546                            let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
1547                            self.chunk.emit(Op::Pop, self.line); // pop bool
1548                            self.chunk.emit(Op::Pop, self.line); // pop match value
1549                            self.compile_match_body(&arm.body)?;
1550                            end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
1551                            self.chunk.patch_jump(skip);
1552                            self.chunk.emit(Op::Pop, self.line); // pop bool
1553                        }
1554                    }
1555                }
1556                // No match — pop value, push nil
1557                self.chunk.emit(Op::Pop, self.line);
1558                self.chunk.emit(Op::Nil, self.line);
1559                for j in end_jumps {
1560                    self.chunk.patch_jump(j);
1561                }
1562            }
1563
1564            Node::RangeExpr {
1565                start,
1566                end,
1567                inclusive,
1568            } => {
1569                // Compile as __range__(start, end, inclusive_bool) builtin call
1570                let name_idx = self
1571                    .chunk
1572                    .add_constant(Constant::String("__range__".to_string()));
1573                self.chunk.emit_u16(Op::Constant, name_idx, self.line);
1574                self.compile_node(start)?;
1575                self.compile_node(end)?;
1576                if *inclusive {
1577                    self.chunk.emit(Op::True, self.line);
1578                } else {
1579                    self.chunk.emit(Op::False, self.line);
1580                }
1581                self.chunk.emit_u8(Op::Call, 3, self.line);
1582            }
1583
1584            Node::GuardStmt {
1585                condition,
1586                else_body,
1587            } => {
1588                // guard condition else { body }
1589                // Compile condition; if truthy, skip else_body
1590                self.compile_node(condition)?;
1591                let skip_jump = self.chunk.emit_jump(Op::JumpIfTrue, self.line);
1592                self.chunk.emit(Op::Pop, self.line); // pop condition
1593                                                     // Compile else_body
1594                self.compile_block(else_body)?;
1595                // Pop result of else_body (guard is a statement, not expression)
1596                if !else_body.is_empty() && Self::produces_value(&else_body.last().unwrap().node) {
1597                    self.chunk.emit(Op::Pop, self.line);
1598                }
1599                let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1600                self.chunk.patch_jump(skip_jump);
1601                self.chunk.emit(Op::Pop, self.line); // pop condition
1602                self.chunk.patch_jump(end_jump);
1603                self.chunk.emit(Op::Nil, self.line);
1604            }
1605
1606            Node::Block(stmts) => {
1607                if stmts.is_empty() {
1608                    self.chunk.emit(Op::Nil, self.line);
1609                } else {
1610                    self.compile_block(stmts)?;
1611                }
1612            }
1613
1614            Node::DeadlineBlock { duration, body } => {
1615                self.compile_node(duration)?;
1616                self.chunk.emit(Op::DeadlineSetup, self.line);
1617                if body.is_empty() {
1618                    self.chunk.emit(Op::Nil, self.line);
1619                } else {
1620                    self.compile_block(body)?;
1621                }
1622                self.chunk.emit(Op::DeadlineEnd, self.line);
1623            }
1624
1625            Node::MutexBlock { body } => {
1626                // v1: single-threaded, just compile the body
1627                if body.is_empty() {
1628                    self.chunk.emit(Op::Nil, self.line);
1629                } else {
1630                    // Compile body, but pop intermediate values and push nil at the end.
1631                    // The body typically contains statements (assignments) that don't produce values.
1632                    for sn in body {
1633                        self.compile_node(sn)?;
1634                        if Self::produces_value(&sn.node) {
1635                            self.chunk.emit(Op::Pop, self.line);
1636                        }
1637                    }
1638                    self.chunk.emit(Op::Nil, self.line);
1639                }
1640            }
1641
1642            Node::YieldExpr { value } => {
1643                if let Some(val) = value {
1644                    self.compile_node(val)?;
1645                } else {
1646                    self.chunk.emit(Op::Nil, self.line);
1647                }
1648                self.chunk.emit(Op::Yield, self.line);
1649            }
1650
1651            Node::AskExpr { fields } => {
1652                // Compile as a dict literal and call llm_call builtin
1653                // For v1, just build the dict (llm_call requires async)
1654                for entry in fields {
1655                    self.compile_node(&entry.key)?;
1656                    self.compile_node(&entry.value)?;
1657                }
1658                self.chunk
1659                    .emit_u16(Op::BuildDict, fields.len() as u16, self.line);
1660            }
1661
1662            Node::EnumConstruct {
1663                enum_name,
1664                variant,
1665                args,
1666            } => {
1667                // Push field values onto the stack, then BuildEnum
1668                for arg in args {
1669                    self.compile_node(arg)?;
1670                }
1671                let enum_idx = self.chunk.add_constant(Constant::String(enum_name.clone()));
1672                let var_idx = self.chunk.add_constant(Constant::String(variant.clone()));
1673                // BuildEnum: enum_name_idx, variant_idx, field_count
1674                self.chunk.emit_u16(Op::BuildEnum, enum_idx, self.line);
1675                let hi = (var_idx >> 8) as u8;
1676                let lo = var_idx as u8;
1677                self.chunk.code.push(hi);
1678                self.chunk.code.push(lo);
1679                self.chunk.lines.push(self.line);
1680                self.chunk.columns.push(self.column);
1681                self.chunk.lines.push(self.line);
1682                self.chunk.columns.push(self.column);
1683                let fc = args.len() as u16;
1684                let fhi = (fc >> 8) as u8;
1685                let flo = fc as u8;
1686                self.chunk.code.push(fhi);
1687                self.chunk.code.push(flo);
1688                self.chunk.lines.push(self.line);
1689                self.chunk.columns.push(self.column);
1690                self.chunk.lines.push(self.line);
1691                self.chunk.columns.push(self.column);
1692            }
1693
1694            Node::StructConstruct {
1695                struct_name,
1696                fields,
1697            } => {
1698                // Build as a dict with a __struct__ key for metadata
1699                let struct_key = self
1700                    .chunk
1701                    .add_constant(Constant::String("__struct__".to_string()));
1702                let struct_val = self
1703                    .chunk
1704                    .add_constant(Constant::String(struct_name.clone()));
1705                self.chunk.emit_u16(Op::Constant, struct_key, self.line);
1706                self.chunk.emit_u16(Op::Constant, struct_val, self.line);
1707
1708                for entry in fields {
1709                    self.compile_node(&entry.key)?;
1710                    self.compile_node(&entry.value)?;
1711                }
1712                self.chunk
1713                    .emit_u16(Op::BuildDict, (fields.len() + 1) as u16, self.line);
1714            }
1715
1716            Node::ImportDecl { path } => {
1717                let idx = self.chunk.add_constant(Constant::String(path.clone()));
1718                self.chunk.emit_u16(Op::Import, idx, self.line);
1719            }
1720
1721            Node::SelectiveImport { names, path } => {
1722                let path_idx = self.chunk.add_constant(Constant::String(path.clone()));
1723                let names_str = names.join(",");
1724                let names_idx = self.chunk.add_constant(Constant::String(names_str));
1725                self.chunk
1726                    .emit_u16(Op::SelectiveImport, path_idx, self.line);
1727                let hi = (names_idx >> 8) as u8;
1728                let lo = names_idx as u8;
1729                self.chunk.code.push(hi);
1730                self.chunk.code.push(lo);
1731                self.chunk.lines.push(self.line);
1732                self.chunk.columns.push(self.column);
1733                self.chunk.lines.push(self.line);
1734                self.chunk.columns.push(self.column);
1735            }
1736
1737            Node::TryOperator { operand } => {
1738                self.compile_node(operand)?;
1739                self.chunk.emit(Op::TryUnwrap, self.line);
1740            }
1741
1742            Node::ImplBlock { type_name, methods } => {
1743                // Compile each method as a closure and store in __impl_TypeName dict.
1744                // Build key-value pairs on stack, then BuildDict.
1745                for method_sn in methods {
1746                    if let Node::FnDecl {
1747                        name, params, body, ..
1748                    } = &method_sn.node
1749                    {
1750                        // Method name key
1751                        let key_idx = self.chunk.add_constant(Constant::String(name.clone()));
1752                        self.chunk.emit_u16(Op::Constant, key_idx, self.line);
1753
1754                        // Compile method body as closure
1755                        let mut fn_compiler = Compiler::new();
1756                        fn_compiler.enum_names = self.enum_names.clone();
1757                        fn_compiler.emit_default_preamble(params)?;
1758                        fn_compiler.emit_type_checks(params);
1759                        fn_compiler.compile_block(body)?;
1760                        fn_compiler.chunk.emit(Op::Nil, self.line);
1761                        fn_compiler.chunk.emit(Op::Return, self.line);
1762
1763                        let func = CompiledFunction {
1764                            name: format!("{}.{}", type_name, name),
1765                            params: TypedParam::names(params),
1766                            default_start: TypedParam::default_start(params),
1767                            chunk: fn_compiler.chunk,
1768                            is_generator: false,
1769                        };
1770                        let fn_idx = self.chunk.functions.len();
1771                        self.chunk.functions.push(func);
1772                        self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1773                    }
1774                }
1775                let method_count = methods
1776                    .iter()
1777                    .filter(|m| matches!(m.node, Node::FnDecl { .. }))
1778                    .count();
1779                self.chunk
1780                    .emit_u16(Op::BuildDict, method_count as u16, self.line);
1781                let impl_name = format!("__impl_{}", type_name);
1782                let name_idx = self.chunk.add_constant(Constant::String(impl_name));
1783                self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1784            }
1785
1786            Node::StructDecl { name, .. } => {
1787                // Compile a constructor function: StructName({field: val, ...}) -> StructInstance
1788                let mut fn_compiler = Compiler::new();
1789                fn_compiler.enum_names = self.enum_names.clone();
1790                let params = vec![TypedParam::untyped("__fields")];
1791                fn_compiler.emit_default_preamble(&params)?;
1792
1793                // Call __make_struct(struct_name, fields_dict) to tag the dict
1794                let make_idx = fn_compiler
1795                    .chunk
1796                    .add_constant(Constant::String("__make_struct".into()));
1797                fn_compiler
1798                    .chunk
1799                    .emit_u16(Op::Constant, make_idx, self.line);
1800                let sname_idx = fn_compiler
1801                    .chunk
1802                    .add_constant(Constant::String(name.clone()));
1803                fn_compiler
1804                    .chunk
1805                    .emit_u16(Op::Constant, sname_idx, self.line);
1806                let fields_idx = fn_compiler
1807                    .chunk
1808                    .add_constant(Constant::String("__fields".into()));
1809                fn_compiler
1810                    .chunk
1811                    .emit_u16(Op::GetVar, fields_idx, self.line);
1812                fn_compiler.chunk.emit_u8(Op::Call, 2, self.line);
1813                fn_compiler.chunk.emit(Op::Return, self.line);
1814
1815                let func = CompiledFunction {
1816                    name: name.clone(),
1817                    params: TypedParam::names(&params),
1818                    default_start: None,
1819                    chunk: fn_compiler.chunk,
1820                    is_generator: false,
1821                };
1822                let fn_idx = self.chunk.functions.len();
1823                self.chunk.functions.push(func);
1824                self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
1825                let name_idx = self.chunk.add_constant(Constant::String(name.clone()));
1826                self.chunk.emit_u16(Op::DefLet, name_idx, self.line);
1827            }
1828
1829            // Declarations that only register metadata (no runtime effect needed for v1)
1830            Node::Pipeline { .. }
1831            | Node::OverrideDecl { .. }
1832            | Node::TypeDecl { .. }
1833            | Node::EnumDecl { .. }
1834            | Node::InterfaceDecl { .. } => {
1835                self.chunk.emit(Op::Nil, self.line);
1836            }
1837
1838            Node::TryCatch {
1839                body,
1840                error_var,
1841                error_type,
1842                catch_body,
1843                finally_body,
1844            } => {
1845                // Extract the type name for typed catch (e.g., "AppError")
1846                let type_name = error_type.as_ref().and_then(|te| {
1847                    if let harn_parser::TypeExpr::Named(name) = te {
1848                        Some(name.clone())
1849                    } else {
1850                        None
1851                    }
1852                });
1853
1854                let type_name_idx = if let Some(ref tn) = type_name {
1855                    self.chunk.add_constant(Constant::String(tn.clone()))
1856                } else {
1857                    self.chunk.add_constant(Constant::String(String::new()))
1858                };
1859
1860                let has_catch = !catch_body.is_empty() || error_var.is_some();
1861                let has_finally = finally_body.is_some();
1862
1863                if has_catch && has_finally {
1864                    // === try-catch-finally ===
1865                    let finally_body = finally_body.as_ref().unwrap();
1866
1867                    // Push finally body onto pending stack for return/break handling
1868                    self.finally_bodies.push(finally_body.clone());
1869
1870                    // 1. TryCatchSetup for try body
1871                    self.handler_depth += 1;
1872                    let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1873                    self.emit_type_name_extra(type_name_idx);
1874
1875                    // 2. Compile try body
1876                    self.compile_try_body(body)?;
1877
1878                    // 3. PopHandler + inline finally (success path)
1879                    self.handler_depth -= 1;
1880                    self.chunk.emit(Op::PopHandler, self.line);
1881                    self.compile_finally_inline(finally_body)?;
1882                    let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1883
1884                    // 4. Catch entry
1885                    self.chunk.patch_jump(catch_jump);
1886                    self.compile_catch_binding(error_var)?;
1887
1888                    // 5. Inner try around catch body (so finally runs if catch throws)
1889                    self.handler_depth += 1;
1890                    let rethrow_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1891                    let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1892                    self.emit_type_name_extra(empty_type);
1893
1894                    // 6. Compile catch body
1895                    self.compile_try_body(catch_body)?;
1896
1897                    // 7. PopHandler + inline finally (catch success path)
1898                    self.handler_depth -= 1;
1899                    self.chunk.emit(Op::PopHandler, self.line);
1900                    self.compile_finally_inline(finally_body)?;
1901                    let end_jump2 = self.chunk.emit_jump(Op::Jump, self.line);
1902
1903                    // 8. Rethrow handler: save error, run finally, re-throw
1904                    self.chunk.patch_jump(rethrow_jump);
1905                    self.compile_rethrow_with_finally(finally_body)?;
1906
1907                    self.chunk.patch_jump(end_jump);
1908                    self.chunk.patch_jump(end_jump2);
1909
1910                    self.finally_bodies.pop();
1911                } else if has_finally {
1912                    // === try-finally (no catch) ===
1913                    let finally_body = finally_body.as_ref().unwrap();
1914
1915                    self.finally_bodies.push(finally_body.clone());
1916
1917                    // 1. TryCatchSetup to error path
1918                    self.handler_depth += 1;
1919                    let error_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1920                    let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1921                    self.emit_type_name_extra(empty_type);
1922
1923                    // 2. Compile try body
1924                    self.compile_try_body(body)?;
1925
1926                    // 3. PopHandler + inline finally (success path)
1927                    self.handler_depth -= 1;
1928                    self.chunk.emit(Op::PopHandler, self.line);
1929                    self.compile_finally_inline(finally_body)?;
1930                    let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1931
1932                    // 4. Error path: save error, run finally, re-throw
1933                    self.chunk.patch_jump(error_jump);
1934                    self.compile_rethrow_with_finally(finally_body)?;
1935
1936                    self.chunk.patch_jump(end_jump);
1937
1938                    self.finally_bodies.pop();
1939                } else {
1940                    // === try-catch (no finally) — original behavior ===
1941
1942                    // 1. TryCatchSetup
1943                    self.handler_depth += 1;
1944                    let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1945                    self.emit_type_name_extra(type_name_idx);
1946
1947                    // 2. Compile try body
1948                    self.compile_try_body(body)?;
1949
1950                    // 3. PopHandler + jump past catch
1951                    self.handler_depth -= 1;
1952                    self.chunk.emit(Op::PopHandler, self.line);
1953                    let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1954
1955                    // 4. Catch entry
1956                    self.chunk.patch_jump(catch_jump);
1957                    self.compile_catch_binding(error_var)?;
1958
1959                    // 5. Compile catch body
1960                    self.compile_try_body(catch_body)?;
1961
1962                    // 6. Patch end
1963                    self.chunk.patch_jump(end_jump);
1964                }
1965            }
1966
1967            Node::TryExpr { body } => {
1968                // try { body } — returns Result.Ok(value) or Result.Err(error)
1969
1970                // 1. Set up try-catch handler (untyped)
1971                self.handler_depth += 1;
1972                let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
1973                let empty_type = self.chunk.add_constant(Constant::String(String::new()));
1974                self.emit_type_name_extra(empty_type);
1975
1976                // 2. Compile try body (leaves value on stack)
1977                self.compile_try_body(body)?;
1978
1979                // 3. PopHandler (success path)
1980                self.handler_depth -= 1;
1981                self.chunk.emit(Op::PopHandler, self.line);
1982
1983                // 4. Wrap in Result.Ok: push "Ok", swap, call Ok(value)
1984                let ok_idx = self.chunk.add_constant(Constant::String("Ok".to_string()));
1985                self.chunk.emit_u16(Op::Constant, ok_idx, self.line);
1986                self.chunk.emit(Op::Swap, self.line);
1987                self.chunk.emit_u8(Op::Call, 1, self.line);
1988
1989                // 5. Jump past error handler
1990                let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
1991
1992                // 6. Error handler: error value is on stack
1993                self.chunk.patch_jump(catch_jump);
1994
1995                // 7. Wrap in Result.Err: push "Err", swap, call Err(error)
1996                let err_idx = self.chunk.add_constant(Constant::String("Err".to_string()));
1997                self.chunk.emit_u16(Op::Constant, err_idx, self.line);
1998                self.chunk.emit(Op::Swap, self.line);
1999                self.chunk.emit_u8(Op::Call, 1, self.line);
2000
2001                // 8. Patch end
2002                self.chunk.patch_jump(end_jump);
2003            }
2004
2005            Node::Retry { count, body } => {
2006                // Compile count expression into a mutable counter variable
2007                self.compile_node(count)?;
2008                let counter_name = "__retry_counter__";
2009                let counter_idx = self
2010                    .chunk
2011                    .add_constant(Constant::String(counter_name.to_string()));
2012                self.chunk.emit_u16(Op::DefVar, counter_idx, self.line);
2013
2014                // Also store the last error for re-throwing
2015                self.chunk.emit(Op::Nil, self.line);
2016                let err_name = "__retry_last_error__";
2017                let err_idx = self
2018                    .chunk
2019                    .add_constant(Constant::String(err_name.to_string()));
2020                self.chunk.emit_u16(Op::DefVar, err_idx, self.line);
2021
2022                // Loop start
2023                let loop_start = self.chunk.current_offset();
2024
2025                // Set up try/catch (untyped - empty type name)
2026                let catch_jump = self.chunk.emit_jump(Op::TryCatchSetup, self.line);
2027                // Emit empty type name for untyped catch
2028                let empty_type = self.chunk.add_constant(Constant::String(String::new()));
2029                let hi = (empty_type >> 8) as u8;
2030                let lo = empty_type as u8;
2031                self.chunk.code.push(hi);
2032                self.chunk.code.push(lo);
2033                self.chunk.lines.push(self.line);
2034                self.chunk.columns.push(self.column);
2035                self.chunk.lines.push(self.line);
2036                self.chunk.columns.push(self.column);
2037
2038                // Compile body
2039                self.compile_block(body)?;
2040
2041                // Success: pop handler, jump to end
2042                self.chunk.emit(Op::PopHandler, self.line);
2043                let end_jump = self.chunk.emit_jump(Op::Jump, self.line);
2044
2045                // Catch handler
2046                self.chunk.patch_jump(catch_jump);
2047                // Save the error value for potential re-throw
2048                self.chunk.emit(Op::Dup, self.line);
2049                self.chunk.emit_u16(Op::SetVar, err_idx, self.line);
2050                // Pop the error value
2051                self.chunk.emit(Op::Pop, self.line);
2052
2053                // Decrement counter
2054                self.chunk.emit_u16(Op::GetVar, counter_idx, self.line);
2055                let one_idx = self.chunk.add_constant(Constant::Int(1));
2056                self.chunk.emit_u16(Op::Constant, one_idx, self.line);
2057                self.chunk.emit(Op::Sub, self.line);
2058                self.chunk.emit(Op::Dup, self.line);
2059                self.chunk.emit_u16(Op::SetVar, counter_idx, self.line);
2060
2061                // If counter > 0, jump to loop start
2062                let zero_idx = self.chunk.add_constant(Constant::Int(0));
2063                self.chunk.emit_u16(Op::Constant, zero_idx, self.line);
2064                self.chunk.emit(Op::Greater, self.line);
2065                let retry_jump = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2066                self.chunk.emit(Op::Pop, self.line); // pop condition
2067                self.chunk.emit_u16(Op::Jump, loop_start as u16, self.line);
2068
2069                // No more retries — re-throw the last error
2070                self.chunk.patch_jump(retry_jump);
2071                self.chunk.emit(Op::Pop, self.line); // pop condition
2072                self.chunk.emit_u16(Op::GetVar, err_idx, self.line);
2073                self.chunk.emit(Op::Throw, self.line);
2074
2075                self.chunk.patch_jump(end_jump);
2076                // Push nil as the result of a successful retry block
2077                self.chunk.emit(Op::Nil, self.line);
2078            }
2079
2080            Node::Parallel {
2081                count,
2082                variable,
2083                body,
2084            } => {
2085                self.compile_node(count)?;
2086                let mut fn_compiler = Compiler::new();
2087                fn_compiler.enum_names = self.enum_names.clone();
2088                fn_compiler.compile_block(body)?;
2089                fn_compiler.chunk.emit(Op::Return, self.line);
2090                let params = vec![variable.clone().unwrap_or_else(|| "__i__".to_string())];
2091                let func = CompiledFunction {
2092                    name: "<parallel>".to_string(),
2093                    params,
2094                    default_start: None,
2095                    chunk: fn_compiler.chunk,
2096                    is_generator: false,
2097                };
2098                let fn_idx = self.chunk.functions.len();
2099                self.chunk.functions.push(func);
2100                self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2101                self.chunk.emit(Op::Parallel, self.line);
2102            }
2103
2104            Node::ParallelMap {
2105                list,
2106                variable,
2107                body,
2108            } => {
2109                self.compile_node(list)?;
2110                let mut fn_compiler = Compiler::new();
2111                fn_compiler.enum_names = self.enum_names.clone();
2112                fn_compiler.compile_block(body)?;
2113                fn_compiler.chunk.emit(Op::Return, self.line);
2114                let func = CompiledFunction {
2115                    name: "<parallel_map>".to_string(),
2116                    params: vec![variable.clone()],
2117                    default_start: None,
2118                    chunk: fn_compiler.chunk,
2119                    is_generator: false,
2120                };
2121                let fn_idx = self.chunk.functions.len();
2122                self.chunk.functions.push(func);
2123                self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2124                self.chunk.emit(Op::ParallelMap, self.line);
2125            }
2126
2127            Node::ParallelSettle {
2128                list,
2129                variable,
2130                body,
2131            } => {
2132                self.compile_node(list)?;
2133                let mut fn_compiler = Compiler::new();
2134                fn_compiler.enum_names = self.enum_names.clone();
2135                fn_compiler.compile_block(body)?;
2136                fn_compiler.chunk.emit(Op::Return, self.line);
2137                let func = CompiledFunction {
2138                    name: "<parallel_settle>".to_string(),
2139                    params: vec![variable.clone()],
2140                    default_start: None,
2141                    chunk: fn_compiler.chunk,
2142                    is_generator: false,
2143                };
2144                let fn_idx = self.chunk.functions.len();
2145                self.chunk.functions.push(func);
2146                self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2147                self.chunk.emit(Op::ParallelSettle, self.line);
2148            }
2149
2150            Node::SpawnExpr { body } => {
2151                let mut fn_compiler = Compiler::new();
2152                fn_compiler.enum_names = self.enum_names.clone();
2153                fn_compiler.compile_block(body)?;
2154                fn_compiler.chunk.emit(Op::Return, self.line);
2155                let func = CompiledFunction {
2156                    name: "<spawn>".to_string(),
2157                    params: vec![],
2158                    default_start: None,
2159                    chunk: fn_compiler.chunk,
2160                    is_generator: false,
2161                };
2162                let fn_idx = self.chunk.functions.len();
2163                self.chunk.functions.push(func);
2164                self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
2165                self.chunk.emit(Op::Spawn, self.line);
2166            }
2167            Node::SelectExpr {
2168                cases,
2169                timeout,
2170                default_body,
2171            } => {
2172                // Desugar select into: builtin call + index-based dispatch.
2173                //
2174                // Step 1: Push builtin name, compile channel list, optionally
2175                //         compile timeout duration, then Call.
2176                // Step 2: Store result dict in temp, dispatch on result.index.
2177
2178                let builtin_name = if timeout.is_some() {
2179                    "__select_timeout"
2180                } else if default_body.is_some() {
2181                    "__select_try"
2182                } else {
2183                    "__select_list"
2184                };
2185
2186                // Push builtin name (callee goes below args on stack)
2187                let name_idx = self
2188                    .chunk
2189                    .add_constant(Constant::String(builtin_name.into()));
2190                self.chunk.emit_u16(Op::Constant, name_idx, self.line);
2191
2192                // Build channel list (arg 1)
2193                for case in cases {
2194                    self.compile_node(&case.channel)?;
2195                }
2196                self.chunk
2197                    .emit_u16(Op::BuildList, cases.len() as u16, self.line);
2198
2199                // If timeout, compile duration (arg 2)
2200                if let Some((duration_expr, _)) = timeout {
2201                    self.compile_node(duration_expr)?;
2202                    self.chunk.emit_u8(Op::Call, 2, self.line);
2203                } else {
2204                    self.chunk.emit_u8(Op::Call, 1, self.line);
2205                }
2206
2207                // Store result in temp var
2208                self.temp_counter += 1;
2209                let result_name = format!("__sel_result_{}__", self.temp_counter);
2210                let result_idx = self
2211                    .chunk
2212                    .add_constant(Constant::String(result_name.clone()));
2213                self.chunk.emit_u16(Op::DefVar, result_idx, self.line);
2214
2215                // Dispatch on result.index
2216                let mut end_jumps = Vec::new();
2217
2218                for (i, case) in cases.iter().enumerate() {
2219                    let get_r = self
2220                        .chunk
2221                        .add_constant(Constant::String(result_name.clone()));
2222                    self.chunk.emit_u16(Op::GetVar, get_r, self.line);
2223                    let idx_prop = self.chunk.add_constant(Constant::String("index".into()));
2224                    self.chunk.emit_u16(Op::GetProperty, idx_prop, self.line);
2225                    let case_i = self.chunk.add_constant(Constant::Int(i as i64));
2226                    self.chunk.emit_u16(Op::Constant, case_i, self.line);
2227                    self.chunk.emit(Op::Equal, self.line);
2228                    let skip = self.chunk.emit_jump(Op::JumpIfFalse, self.line);
2229                    self.chunk.emit(Op::Pop, self.line);
2230
2231                    // Bind variable = result.value
2232                    let get_r2 = self
2233                        .chunk
2234                        .add_constant(Constant::String(result_name.clone()));
2235                    self.chunk.emit_u16(Op::GetVar, get_r2, self.line);
2236                    let val_prop = self.chunk.add_constant(Constant::String("value".into()));
2237                    self.chunk.emit_u16(Op::GetProperty, val_prop, self.line);
2238                    let var_idx = self
2239                        .chunk
2240                        .add_constant(Constant::String(case.variable.clone()));
2241                    self.chunk.emit_u16(Op::DefLet, var_idx, self.line);
2242
2243                    self.compile_try_body(&case.body)?;
2244                    end_jumps.push(self.chunk.emit_jump(Op::Jump, self.line));
2245                    self.chunk.patch_jump(skip);
2246                    self.chunk.emit(Op::Pop, self.line);
2247                }
2248
2249                // Timeout/default fallthrough (index == -1)
2250                if let Some((_, ref timeout_body)) = timeout {
2251                    self.compile_try_body(timeout_body)?;
2252                } else if let Some(ref def_body) = default_body {
2253                    self.compile_try_body(def_body)?;
2254                } else {
2255                    self.chunk.emit(Op::Nil, self.line);
2256                }
2257
2258                for ej in end_jumps {
2259                    self.chunk.patch_jump(ej);
2260                }
2261            }
2262            Node::Spread(_) => {
2263                return Err(CompileError {
2264                    message: "spread (...) can only be used inside list literals, dict literals, or function call arguments".into(),
2265                    line: self.line,
2266                });
2267            }
2268        }
2269        Ok(())
2270    }
2271
2272    /// Compile a destructuring binding pattern.
2273    /// Expects the RHS value to already be on the stack.
2274    /// After this, the value is consumed (popped) and each binding is defined.
2275    fn compile_destructuring(
2276        &mut self,
2277        pattern: &BindingPattern,
2278        is_mutable: bool,
2279    ) -> Result<(), CompileError> {
2280        let def_op = if is_mutable { Op::DefVar } else { Op::DefLet };
2281        match pattern {
2282            BindingPattern::Identifier(name) => {
2283                // Simple case: just define the variable
2284                let idx = self.chunk.add_constant(Constant::String(name.clone()));
2285                self.chunk.emit_u16(def_op, idx, self.line);
2286            }
2287            BindingPattern::Dict(fields) => {
2288                // Stack has the dict value.
2289                // Emit runtime type check: __assert_dict(value)
2290                self.chunk.emit(Op::Dup, self.line);
2291                let assert_idx = self
2292                    .chunk
2293                    .add_constant(Constant::String("__assert_dict".into()));
2294                self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2295                self.chunk.emit(Op::Swap, self.line);
2296                self.chunk.emit_u8(Op::Call, 1, self.line);
2297                self.chunk.emit(Op::Pop, self.line); // discard nil result
2298
2299                // For each non-rest field: dup dict, push key string, subscript, define var.
2300                // For rest field: dup dict, call __dict_rest builtin.
2301                let non_rest: Vec<_> = fields.iter().filter(|f| !f.is_rest).collect();
2302                let rest_field = fields.iter().find(|f| f.is_rest);
2303
2304                for field in &non_rest {
2305                    self.chunk.emit(Op::Dup, self.line);
2306                    let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2307                    self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2308                    self.chunk.emit(Op::Subscript, self.line);
2309                    let binding_name = field.alias.as_deref().unwrap_or(&field.key);
2310                    let name_idx = self
2311                        .chunk
2312                        .add_constant(Constant::String(binding_name.to_string()));
2313                    self.chunk.emit_u16(def_op, name_idx, self.line);
2314                }
2315
2316                if let Some(rest) = rest_field {
2317                    // Call the __dict_rest builtin: __dict_rest(dict, [keys_to_exclude])
2318                    // Push function name
2319                    let fn_idx = self
2320                        .chunk
2321                        .add_constant(Constant::String("__dict_rest".into()));
2322                    self.chunk.emit_u16(Op::Constant, fn_idx, self.line);
2323                    // Swap so dict is above function name: [fn, dict]
2324                    self.chunk.emit(Op::Swap, self.line);
2325                    // Build the exclusion keys list
2326                    for field in &non_rest {
2327                        let key_idx = self.chunk.add_constant(Constant::String(field.key.clone()));
2328                        self.chunk.emit_u16(Op::Constant, key_idx, self.line);
2329                    }
2330                    self.chunk
2331                        .emit_u16(Op::BuildList, non_rest.len() as u16, self.line);
2332                    // Call __dict_rest(dict, keys_list) — 2 args
2333                    self.chunk.emit_u8(Op::Call, 2, self.line);
2334                    let rest_name = &rest.key;
2335                    let rest_idx = self.chunk.add_constant(Constant::String(rest_name.clone()));
2336                    self.chunk.emit_u16(def_op, rest_idx, self.line);
2337                } else {
2338                    // Pop the source dict
2339                    self.chunk.emit(Op::Pop, self.line);
2340                }
2341            }
2342            BindingPattern::List(elements) => {
2343                // Stack has the list value.
2344                // Emit runtime type check: __assert_list(value)
2345                self.chunk.emit(Op::Dup, self.line);
2346                let assert_idx = self
2347                    .chunk
2348                    .add_constant(Constant::String("__assert_list".into()));
2349                self.chunk.emit_u16(Op::Constant, assert_idx, self.line);
2350                self.chunk.emit(Op::Swap, self.line);
2351                self.chunk.emit_u8(Op::Call, 1, self.line);
2352                self.chunk.emit(Op::Pop, self.line); // discard nil result
2353
2354                let non_rest: Vec<_> = elements.iter().filter(|e| !e.is_rest).collect();
2355                let rest_elem = elements.iter().find(|e| e.is_rest);
2356
2357                for (i, elem) in non_rest.iter().enumerate() {
2358                    self.chunk.emit(Op::Dup, self.line);
2359                    let idx_const = self.chunk.add_constant(Constant::Int(i as i64));
2360                    self.chunk.emit_u16(Op::Constant, idx_const, self.line);
2361                    self.chunk.emit(Op::Subscript, self.line);
2362                    let name_idx = self.chunk.add_constant(Constant::String(elem.name.clone()));
2363                    self.chunk.emit_u16(def_op, name_idx, self.line);
2364                }
2365
2366                if let Some(rest) = rest_elem {
2367                    // Slice the list from index non_rest.len() to end: list[n..]
2368                    // Slice op takes: object, start, end on stack
2369                    // self.chunk.emit(Op::Dup, self.line); -- list is still on stack
2370                    let start_idx = self
2371                        .chunk
2372                        .add_constant(Constant::Int(non_rest.len() as i64));
2373                    self.chunk.emit_u16(Op::Constant, start_idx, self.line);
2374                    self.chunk.emit(Op::Nil, self.line); // end = nil (to end)
2375                    self.chunk.emit(Op::Slice, self.line);
2376                    let rest_name_idx =
2377                        self.chunk.add_constant(Constant::String(rest.name.clone()));
2378                    self.chunk.emit_u16(def_op, rest_name_idx, self.line);
2379                } else {
2380                    // Pop the source list
2381                    self.chunk.emit(Op::Pop, self.line);
2382                }
2383            }
2384        }
2385        Ok(())
2386    }
2387
2388    /// Check if a node produces a value on the stack that needs to be popped.
2389    fn produces_value(node: &Node) -> bool {
2390        match node {
2391            // These nodes do NOT produce a value on the stack
2392            Node::LetBinding { .. }
2393            | Node::VarBinding { .. }
2394            | Node::Assignment { .. }
2395            | Node::ReturnStmt { .. }
2396            | Node::FnDecl { .. }
2397            | Node::ImplBlock { .. }
2398            | Node::StructDecl { .. }
2399            | Node::ThrowStmt { .. }
2400            | Node::BreakStmt
2401            | Node::ContinueStmt => false,
2402            // These compound nodes explicitly produce a value
2403            Node::TryCatch { .. }
2404            | Node::TryExpr { .. }
2405            | Node::Retry { .. }
2406            | Node::GuardStmt { .. }
2407            | Node::DeadlineBlock { .. }
2408            | Node::MutexBlock { .. }
2409            | Node::Spread(_) => true,
2410            // All other expressions produce values
2411            _ => true,
2412        }
2413    }
2414}
2415
2416impl Compiler {
2417    /// Compile a function body into a CompiledFunction (for import support).
2418    pub fn compile_fn_body(
2419        &mut self,
2420        params: &[TypedParam],
2421        body: &[SNode],
2422    ) -> Result<CompiledFunction, CompileError> {
2423        let mut fn_compiler = Compiler::new();
2424        fn_compiler.compile_block(body)?;
2425        fn_compiler.chunk.emit(Op::Nil, 0);
2426        fn_compiler.chunk.emit(Op::Return, 0);
2427        Ok(CompiledFunction {
2428            name: String::new(),
2429            params: TypedParam::names(params),
2430            default_start: TypedParam::default_start(params),
2431            chunk: fn_compiler.chunk,
2432            is_generator: false,
2433        })
2434    }
2435
2436    /// Compile a match arm body, ensuring it always pushes exactly one value.
2437    fn compile_match_body(&mut self, body: &[SNode]) -> Result<(), CompileError> {
2438        if body.is_empty() {
2439            self.chunk.emit(Op::Nil, self.line);
2440        } else {
2441            self.compile_block(body)?;
2442            // If the last statement doesn't produce a value, push nil
2443            if !Self::produces_value(&body.last().unwrap().node) {
2444                self.chunk.emit(Op::Nil, self.line);
2445            }
2446        }
2447        Ok(())
2448    }
2449
2450    /// Emit the binary op instruction for a compound assignment operator.
2451    fn emit_compound_op(&mut self, op: &str) -> Result<(), CompileError> {
2452        match op {
2453            "+" => self.chunk.emit(Op::Add, self.line),
2454            "-" => self.chunk.emit(Op::Sub, self.line),
2455            "*" => self.chunk.emit(Op::Mul, self.line),
2456            "/" => self.chunk.emit(Op::Div, self.line),
2457            "%" => self.chunk.emit(Op::Mod, self.line),
2458            _ => {
2459                return Err(CompileError {
2460                    message: format!("Unknown compound operator: {op}"),
2461                    line: self.line,
2462                })
2463            }
2464        }
2465        Ok(())
2466    }
2467
2468    /// Extract the root variable name from a (possibly nested) access expression.
2469    fn root_var_name(&self, node: &SNode) -> Option<String> {
2470        match &node.node {
2471            Node::Identifier(name) => Some(name.clone()),
2472            Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
2473                self.root_var_name(object)
2474            }
2475            Node::SubscriptAccess { object, .. } => self.root_var_name(object),
2476            _ => None,
2477        }
2478    }
2479}
2480
2481impl Compiler {
2482    /// Recursively collect all enum type names from the AST.
2483    fn collect_enum_names(nodes: &[SNode], names: &mut std::collections::HashSet<String>) {
2484        for sn in nodes {
2485            match &sn.node {
2486                Node::EnumDecl { name, .. } => {
2487                    names.insert(name.clone());
2488                }
2489                Node::Pipeline { body, .. } => {
2490                    Self::collect_enum_names(body, names);
2491                }
2492                Node::FnDecl { body, .. } => {
2493                    Self::collect_enum_names(body, names);
2494                }
2495                Node::Block(stmts) => {
2496                    Self::collect_enum_names(stmts, names);
2497                }
2498                _ => {}
2499            }
2500        }
2501    }
2502
2503    fn collect_interface_methods(
2504        nodes: &[SNode],
2505        interfaces: &mut std::collections::HashMap<String, Vec<String>>,
2506    ) {
2507        for sn in nodes {
2508            match &sn.node {
2509                Node::InterfaceDecl { name, methods } => {
2510                    let method_names: Vec<String> =
2511                        methods.iter().map(|m| m.name.clone()).collect();
2512                    interfaces.insert(name.clone(), method_names);
2513                }
2514                Node::Pipeline { body, .. } | Node::FnDecl { body, .. } => {
2515                    Self::collect_interface_methods(body, interfaces);
2516                }
2517                Node::Block(stmts) => {
2518                    Self::collect_interface_methods(stmts, interfaces);
2519                }
2520                _ => {}
2521            }
2522        }
2523    }
2524}
2525
2526impl Default for Compiler {
2527    fn default() -> Self {
2528        Self::new()
2529    }
2530}
2531
2532/// Check if a list of AST nodes contains any `yield` expression (used to detect generator functions).
2533fn body_contains_yield(nodes: &[SNode]) -> bool {
2534    nodes.iter().any(|sn| node_contains_yield(&sn.node))
2535}
2536
2537fn node_contains_yield(node: &Node) -> bool {
2538    match node {
2539        Node::YieldExpr { .. } => true,
2540        // Don't recurse into nested function/closure declarations — yield in a nested
2541        // function does NOT make the outer function a generator.
2542        Node::FnDecl { .. } | Node::Closure { .. } => false,
2543        Node::Block(stmts) => body_contains_yield(stmts),
2544        Node::IfElse {
2545            condition,
2546            then_body,
2547            else_body,
2548        } => {
2549            node_contains_yield(&condition.node)
2550                || body_contains_yield(then_body)
2551                || else_body.as_ref().is_some_and(|b| body_contains_yield(b))
2552        }
2553        Node::WhileLoop { condition, body } => {
2554            node_contains_yield(&condition.node) || body_contains_yield(body)
2555        }
2556        Node::ForIn { iterable, body, .. } => {
2557            node_contains_yield(&iterable.node) || body_contains_yield(body)
2558        }
2559        Node::TryCatch {
2560            body, catch_body, ..
2561        } => body_contains_yield(body) || body_contains_yield(catch_body),
2562        Node::TryExpr { body } => body_contains_yield(body),
2563        _ => false,
2564    }
2565}
2566
2567/// Check if an AST node contains `_` identifier (pipe placeholder).
2568fn contains_pipe_placeholder(node: &SNode) -> bool {
2569    match &node.node {
2570        Node::Identifier(name) if name == "_" => true,
2571        Node::FunctionCall { args, .. } => args.iter().any(contains_pipe_placeholder),
2572        Node::MethodCall { object, args, .. } => {
2573            contains_pipe_placeholder(object) || args.iter().any(contains_pipe_placeholder)
2574        }
2575        Node::BinaryOp { left, right, .. } => {
2576            contains_pipe_placeholder(left) || contains_pipe_placeholder(right)
2577        }
2578        Node::UnaryOp { operand, .. } => contains_pipe_placeholder(operand),
2579        Node::ListLiteral(items) => items.iter().any(contains_pipe_placeholder),
2580        Node::PropertyAccess { object, .. } => contains_pipe_placeholder(object),
2581        Node::SubscriptAccess { object, index } => {
2582            contains_pipe_placeholder(object) || contains_pipe_placeholder(index)
2583        }
2584        _ => false,
2585    }
2586}
2587
2588/// Replace all `_` identifiers with `__pipe` in an AST node (for pipe placeholder desugaring).
2589fn replace_pipe_placeholder(node: &SNode) -> SNode {
2590    let new_node = match &node.node {
2591        Node::Identifier(name) if name == "_" => Node::Identifier("__pipe".into()),
2592        Node::FunctionCall { name, args } => Node::FunctionCall {
2593            name: name.clone(),
2594            args: args.iter().map(replace_pipe_placeholder).collect(),
2595        },
2596        Node::MethodCall {
2597            object,
2598            method,
2599            args,
2600        } => Node::MethodCall {
2601            object: Box::new(replace_pipe_placeholder(object)),
2602            method: method.clone(),
2603            args: args.iter().map(replace_pipe_placeholder).collect(),
2604        },
2605        Node::BinaryOp { op, left, right } => Node::BinaryOp {
2606            op: op.clone(),
2607            left: Box::new(replace_pipe_placeholder(left)),
2608            right: Box::new(replace_pipe_placeholder(right)),
2609        },
2610        Node::UnaryOp { op, operand } => Node::UnaryOp {
2611            op: op.clone(),
2612            operand: Box::new(replace_pipe_placeholder(operand)),
2613        },
2614        Node::ListLiteral(items) => {
2615            Node::ListLiteral(items.iter().map(replace_pipe_placeholder).collect())
2616        }
2617        Node::PropertyAccess { object, property } => Node::PropertyAccess {
2618            object: Box::new(replace_pipe_placeholder(object)),
2619            property: property.clone(),
2620        },
2621        Node::SubscriptAccess { object, index } => Node::SubscriptAccess {
2622            object: Box::new(replace_pipe_placeholder(object)),
2623            index: Box::new(replace_pipe_placeholder(index)),
2624        },
2625        _ => return node.clone(),
2626    };
2627    SNode::new(new_node, node.span)
2628}
2629
2630#[cfg(test)]
2631mod tests {
2632    use super::*;
2633    use harn_lexer::Lexer;
2634    use harn_parser::Parser;
2635
2636    fn compile_source(source: &str) -> Chunk {
2637        let mut lexer = Lexer::new(source);
2638        let tokens = lexer.tokenize().unwrap();
2639        let mut parser = Parser::new(tokens);
2640        let program = parser.parse().unwrap();
2641        Compiler::new().compile(&program).unwrap()
2642    }
2643
2644    #[test]
2645    fn test_compile_arithmetic() {
2646        let chunk = compile_source("pipeline test(task) { let x = 2 + 3 }");
2647        assert!(!chunk.code.is_empty());
2648        // Should have constants: 2, 3, "x"
2649        assert!(chunk.constants.contains(&Constant::Int(2)));
2650        assert!(chunk.constants.contains(&Constant::Int(3)));
2651    }
2652
2653    #[test]
2654    fn test_compile_function_call() {
2655        let chunk = compile_source("pipeline test(task) { log(42) }");
2656        let disasm = chunk.disassemble("test");
2657        assert!(disasm.contains("CALL"));
2658    }
2659
2660    #[test]
2661    fn test_compile_if_else() {
2662        let chunk =
2663            compile_source(r#"pipeline test(task) { if true { log("yes") } else { log("no") } }"#);
2664        let disasm = chunk.disassemble("test");
2665        assert!(disasm.contains("JUMP_IF_FALSE"));
2666        assert!(disasm.contains("JUMP"));
2667    }
2668
2669    #[test]
2670    fn test_compile_while() {
2671        let chunk = compile_source("pipeline test(task) { var i = 0\n while i < 5 { i = i + 1 } }");
2672        let disasm = chunk.disassemble("test");
2673        assert!(disasm.contains("JUMP_IF_FALSE"));
2674        // Should have a backward jump
2675        assert!(disasm.contains("JUMP"));
2676    }
2677
2678    #[test]
2679    fn test_compile_closure() {
2680        let chunk = compile_source("pipeline test(task) { let f = { x -> x * 2 } }");
2681        assert!(!chunk.functions.is_empty());
2682        assert_eq!(chunk.functions[0].params, vec!["x"]);
2683    }
2684
2685    #[test]
2686    fn test_compile_list() {
2687        let chunk = compile_source("pipeline test(task) { let a = [1, 2, 3] }");
2688        let disasm = chunk.disassemble("test");
2689        assert!(disasm.contains("BUILD_LIST"));
2690    }
2691
2692    #[test]
2693    fn test_compile_dict() {
2694        let chunk = compile_source(r#"pipeline test(task) { let d = {name: "test"} }"#);
2695        let disasm = chunk.disassemble("test");
2696        assert!(disasm.contains("BUILD_DICT"));
2697    }
2698
2699    #[test]
2700    fn test_disassemble() {
2701        let chunk = compile_source("pipeline test(task) { log(2 + 3) }");
2702        let disasm = chunk.disassemble("test");
2703        // Should be readable
2704        assert!(disasm.contains("CONSTANT"));
2705        assert!(disasm.contains("ADD"));
2706        assert!(disasm.contains("CALL"));
2707    }
2708}