Skip to main content

cjc_mir/
lib.rs

1//! CJC MIR (Mid-level Intermediate Representation)
2//!
3//! MIR is a control-flow graph (CFG) of basic blocks. Every value is an
4//! explicit temporary. This is the level where:
5//! - Pattern matching is compiled to decision trees (Stage 2.2)
6//! - Closures are lambda-lifted (Stage 2.1)
7//! - `nogc` verification runs (Stage 2.4)
8//! - Optimization passes operate (Stage 2.4)
9//!
10//! For Milestone 2.0, MIR is a simplified representation that mirrors HIR
11//! closely — we lower HIR items into MIR functions with basic blocks for
12//! straight-line code, if/else, while, and function calls.
13
14pub mod cfg;
15pub mod dominators;
16pub mod escape;
17pub mod inspect;
18pub mod loop_analysis;
19pub mod monomorph;
20pub mod nogc_verify;
21pub mod optimize;
22pub mod reduction;
23pub mod ssa;
24pub mod ssa_loop_overlay;
25pub mod ssa_optimize;
26pub mod verify;
27
28use cjc_ast::{BinOp, UnaryOp, Visibility};
29pub use escape::AllocHint;
30
31// ---------------------------------------------------------------------------
32// IDs
33// ---------------------------------------------------------------------------
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub struct MirFnId(pub u32);
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
39pub struct BlockId(pub u32);
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub struct TempId(pub u32);
43
44// ---------------------------------------------------------------------------
45// Program
46// ---------------------------------------------------------------------------
47
48/// A MIR program is a collection of functions + struct defs + an entry point.
49#[derive(Debug, Clone)]
50pub struct MirProgram {
51    pub functions: Vec<MirFunction>,
52    pub struct_defs: Vec<MirStructDef>,
53    pub enum_defs: Vec<MirEnumDef>,
54    /// Top-level statements (let bindings, expr stmts) are collected into
55    /// a synthetic `__main` function.
56    pub entry: MirFnId,
57}
58
59#[derive(Debug, Clone)]
60pub struct MirStructDef {
61    pub name: String,
62    pub fields: Vec<(String, String)>, // (name, type_name)
63    /// True if this is a record (immutable value type).
64    pub is_record: bool,
65    pub vis: Visibility,
66}
67
68#[derive(Debug, Clone)]
69pub struct MirEnumDef {
70    pub name: String,
71    pub variants: Vec<MirVariantDef>,
72}
73
74#[derive(Debug, Clone)]
75pub struct MirVariantDef {
76    pub name: String,
77    pub fields: Vec<String>, // type names
78}
79
80// ---------------------------------------------------------------------------
81// Functions
82// ---------------------------------------------------------------------------
83
84#[derive(Debug, Clone)]
85pub struct MirFunction {
86    pub id: MirFnId,
87    pub name: String,
88    pub type_params: Vec<(String, Vec<String>)>, // (param_name, bounds)
89    pub params: Vec<MirParam>,
90    pub return_type: Option<String>,
91    pub body: MirBody,
92    pub is_nogc: bool,
93    /// CFG representation of this function's body.
94    /// Built lazily from tree-form `body` via `build_cfg()`.
95    /// When present, this is the canonical representation for the CFG executor.
96    pub cfg_body: Option<cfg::MirCfg>,
97    /// Decorator names applied to this function (e.g., `@memoize`, `@trace`).
98    pub decorators: Vec<String>,
99    pub vis: Visibility,
100}
101
102#[derive(Debug, Clone)]
103pub struct MirParam {
104    pub name: String,
105    pub ty_name: String,
106    /// Optional default value expression for this parameter.
107    pub default: Option<MirExpr>,
108    /// Variadic parameter: collects remaining args into an array.
109    pub is_variadic: bool,
110}
111
112impl MirFunction {
113    /// Build the CFG representation from the tree-form body.
114    /// Stores the result in `cfg_body`.
115    pub fn build_cfg(&mut self) {
116        let cfg = cfg::CfgBuilder::build(&self.body);
117        self.cfg_body = Some(cfg);
118    }
119
120    /// Return a reference to the CFG body, building it on demand if needed.
121    pub fn cfg(&mut self) -> &cfg::MirCfg {
122        if self.cfg_body.is_none() {
123            self.build_cfg();
124        }
125        self.cfg_body.as_ref().unwrap()
126    }
127}
128
129impl MirProgram {
130    /// Build CFG for all functions in this program.
131    pub fn build_all_cfgs(&mut self) {
132        for func in &mut self.functions {
133            func.build_cfg();
134        }
135    }
136}
137
138/// The body of a MIR function — a list of MIR statements.
139/// In Milestone 2.0 we use a simplified tree-form (not full CFG with basic
140/// blocks). This is extended to a proper CFG in Milestone 2.2+ for pattern
141/// matching compilation.
142#[derive(Debug, Clone)]
143pub struct MirBody {
144    pub stmts: Vec<MirStmt>,
145    pub result: Option<Box<MirExpr>>,
146}
147
148// ---------------------------------------------------------------------------
149// Statements
150// ---------------------------------------------------------------------------
151
152#[derive(Debug, Clone)]
153pub enum MirStmt {
154    Let {
155        name: String,
156        mutable: bool,
157        init: MirExpr,
158        /// Escape analysis annotation. `None` before analysis runs.
159        alloc_hint: Option<AllocHint>,
160    },
161    Expr(MirExpr),
162    If {
163        cond: MirExpr,
164        then_body: MirBody,
165        else_body: Option<MirBody>,
166    },
167    While {
168        cond: MirExpr,
169        body: MirBody,
170    },
171    Return(Option<MirExpr>),
172    Break,
173    Continue,
174    NoGcBlock(MirBody),
175}
176
177// ---------------------------------------------------------------------------
178// Expressions
179// ---------------------------------------------------------------------------
180
181#[derive(Debug, Clone)]
182pub struct MirExpr {
183    pub kind: MirExprKind,
184}
185
186#[derive(Debug, Clone)]
187pub enum MirExprKind {
188    IntLit(i64),
189    FloatLit(f64),
190    BoolLit(bool),
191    StringLit(String),
192    ByteStringLit(Vec<u8>),
193    ByteCharLit(u8),
194    RawStringLit(String),
195    RawByteStringLit(Vec<u8>),
196    RegexLit { pattern: String, flags: String },
197    TensorLit { rows: Vec<Vec<MirExpr>> },
198    Var(String),
199    Binary {
200        op: BinOp,
201        left: Box<MirExpr>,
202        right: Box<MirExpr>,
203    },
204    Unary {
205        op: UnaryOp,
206        operand: Box<MirExpr>,
207    },
208    Call {
209        callee: Box<MirExpr>,
210        args: Vec<MirExpr>,
211    },
212    Field {
213        object: Box<MirExpr>,
214        name: String,
215    },
216    Index {
217        object: Box<MirExpr>,
218        index: Box<MirExpr>,
219    },
220    MultiIndex {
221        object: Box<MirExpr>,
222        indices: Vec<MirExpr>,
223    },
224    Assign {
225        target: Box<MirExpr>,
226        value: Box<MirExpr>,
227    },
228    Block(MirBody),
229    StructLit {
230        name: String,
231        fields: Vec<(String, MirExpr)>,
232    },
233    ArrayLit(Vec<MirExpr>),
234    Col(String),
235    Lambda {
236        params: Vec<MirParam>,
237        body: Box<MirExpr>,
238    },
239    /// Create a closure: captures + a reference to the lifted function.
240    /// At runtime, evaluates each capture expression and bundles them with
241    /// the function name into a Closure value.
242    MakeClosure {
243        /// Name of the lambda-lifted top-level function.
244        fn_name: String,
245        /// Expressions that produce the captured values (evaluated at closure
246        /// creation time). Order matches the extra leading params of the
247        /// lifted function.
248        captures: Vec<MirExpr>,
249    },
250    If {
251        cond: Box<MirExpr>,
252        then_body: MirBody,
253        else_body: Option<MirBody>,
254    },
255    /// Match expression compiled as a decision tree.
256    /// Each arm is tried in order; first matching arm's body is evaluated.
257    Match {
258        scrutinee: Box<MirExpr>,
259        arms: Vec<MirMatchArm>,
260    },
261    /// Enum variant literal
262    VariantLit {
263        enum_name: String,
264        variant: String,
265        fields: Vec<MirExpr>,
266    },
267    /// Tuple literal
268    TupleLit(Vec<MirExpr>),
269    /// Linalg opcodes — dedicated MIR nodes for matrix decompositions.
270    LinalgLU { operand: Box<MirExpr> },
271    LinalgQR { operand: Box<MirExpr> },
272    LinalgCholesky { operand: Box<MirExpr> },
273    LinalgInv { operand: Box<MirExpr> },
274    /// Broadcast a tensor to a target shape (zero-copy view with stride=0).
275    Broadcast {
276        operand: Box<MirExpr>,
277        target_shape: Vec<MirExpr>,
278    },
279    Void,
280}
281
282// ---------------------------------------------------------------------------
283// Match / Pattern types (MIR level)
284// ---------------------------------------------------------------------------
285
286/// A match arm at MIR level: pattern + body.
287#[derive(Debug, Clone)]
288pub struct MirMatchArm {
289    pub pattern: MirPattern,
290    pub body: MirBody,
291}
292
293/// A pattern at MIR level.
294#[derive(Debug, Clone)]
295pub enum MirPattern {
296    /// Wildcard: matches anything, binds nothing.
297    Wildcard,
298    /// Binding: matches anything, binds the value to a name.
299    Binding(String),
300    /// Literal patterns
301    LitInt(i64),
302    LitFloat(f64),
303    LitBool(bool),
304    LitString(String),
305    /// Tuple destructuring
306    Tuple(Vec<MirPattern>),
307    /// Struct destructuring
308    Struct {
309        name: String,
310        fields: Vec<(String, MirPattern)>,
311    },
312    /// Enum variant pattern
313    Variant {
314        enum_name: String,
315        variant: String,
316        fields: Vec<MirPattern>,
317    },
318}
319
320// ===========================================================================
321// HIR -> MIR Lowering
322// ===========================================================================
323
324use cjc_hir::*;
325
326/// Lowers HIR into MIR.
327pub struct HirToMir {
328    next_fn_id: u32,
329    next_lambda_id: u32,
330    /// Lambda-lifted functions accumulated during lowering.
331    /// These are appended to the MirProgram's function list.
332    lifted_functions: Vec<MirFunction>,
333}
334
335impl HirToMir {
336    pub fn new() -> Self {
337        Self {
338            next_fn_id: 0,
339            next_lambda_id: 0,
340            lifted_functions: Vec::new(),
341        }
342    }
343
344    fn fresh_fn_id(&mut self) -> MirFnId {
345        let id = MirFnId(self.next_fn_id);
346        self.next_fn_id += 1;
347        id
348    }
349
350    fn fresh_lambda_name(&mut self) -> String {
351        let name = format!("__closure_{}", self.next_lambda_id);
352        self.next_lambda_id += 1;
353        name
354    }
355
356    /// Lower a HIR program to MIR.
357    pub fn lower_program(&mut self, hir: &HirProgram) -> MirProgram {
358        let mut functions = Vec::new();
359        let mut struct_defs = Vec::new();
360        let mut enum_defs = Vec::new();
361        let mut main_stmts: Vec<MirStmt> = Vec::new();
362
363        for item in &hir.items {
364            match item {
365                HirItem::Fn(f) => {
366                    functions.push(self.lower_fn(f));
367                }
368                HirItem::Struct(s) => {
369                    struct_defs.push(MirStructDef {
370                        name: s.name.clone(),
371                        fields: s.fields.clone(),
372                        is_record: false,
373                        vis: s.vis,
374                    });
375                }
376                HirItem::Class(c) => {
377                    struct_defs.push(MirStructDef {
378                        name: c.name.clone(),
379                        fields: c.fields.clone(),
380                        is_record: false,
381                        vis: c.vis,
382                    });
383                }
384                HirItem::Record(r) => {
385                    struct_defs.push(MirStructDef {
386                        name: r.name.clone(),
387                        fields: r.fields.clone(),
388                        is_record: true,
389                        vis: r.vis,
390                    });
391                }
392                HirItem::Enum(e) => {
393                    enum_defs.push(MirEnumDef {
394                        name: e.name.clone(),
395                        variants: e
396                            .variants
397                            .iter()
398                            .map(|v| MirVariantDef {
399                                name: v.name.clone(),
400                                fields: v.fields.clone(),
401                            })
402                            .collect(),
403                    });
404                }
405                HirItem::Let(l) => {
406                    main_stmts.push(MirStmt::Let {
407                        name: l.name.clone(),
408                        mutable: l.mutable,
409                        init: self.lower_expr(&l.init),
410                        alloc_hint: None,
411                    });
412                }
413                HirItem::Stmt(s) => {
414                    main_stmts.push(self.lower_stmt(s));
415                }
416                HirItem::Impl(i) => {
417                    for method in &i.methods {
418                        // Register as qualified name: Target.method
419                        let mut mir_fn = self.lower_fn(method);
420                        mir_fn.name = format!("{}.{}", i.target, method.name);
421                        functions.push(mir_fn);
422                    }
423                }
424                HirItem::Trait(_) => {
425                    // Traits are metadata only; no MIR output
426                }
427            }
428        }
429
430        // Create __main entry function from top-level statements
431        let main_id = self.fresh_fn_id();
432        functions.push(MirFunction {
433            id: main_id,
434            name: "__main".to_string(),
435            type_params: vec![],
436            params: vec![],
437            return_type: None,
438            body: MirBody {
439                stmts: main_stmts,
440                result: None,
441            },
442            is_nogc: false,
443            cfg_body: None,
444            decorators: vec![],
445            vis: Visibility::Private,
446        });
447
448        // Append all lambda-lifted functions
449        functions.append(&mut self.lifted_functions);
450
451        MirProgram {
452            functions,
453            struct_defs,
454            enum_defs,
455            entry: main_id,
456        }
457    }
458
459    pub fn lower_fn(&mut self, f: &HirFn) -> MirFunction {
460        let id = self.fresh_fn_id();
461        let params = f
462            .params
463            .iter()
464            .map(|p| MirParam {
465                name: p.name.clone(),
466                ty_name: p.ty_name.clone(),
467                default: p.default.as_ref().map(|d| self.lower_expr(d)),
468                is_variadic: p.is_variadic,
469            })
470            .collect();
471        let body = self.lower_block(&f.body);
472        MirFunction {
473            id,
474            name: f.name.clone(),
475            type_params: f.type_params.clone(),
476            params,
477            return_type: f.return_type.clone(),
478            body,
479            is_nogc: f.is_nogc,
480            cfg_body: None,
481            decorators: f.decorators.clone(),
482            vis: f.vis,
483        }
484    }
485
486    fn lower_block(&mut self, block: &HirBlock) -> MirBody {
487        let stmts = block.stmts.iter().map(|s| self.lower_stmt(s)).collect();
488        let result = block.expr.as_ref().map(|e| Box::new(self.lower_expr(e)));
489        MirBody { stmts, result }
490    }
491
492    fn lower_stmt(&mut self, stmt: &HirStmt) -> MirStmt {
493        match &stmt.kind {
494            HirStmtKind::Let {
495                name,
496                mutable,
497                init,
498                ..
499            } => MirStmt::Let {
500                name: name.clone(),
501                mutable: *mutable,
502                init: self.lower_expr(init),
503                alloc_hint: None,
504            },
505            HirStmtKind::Expr(e) => MirStmt::Expr(self.lower_expr(e)),
506            HirStmtKind::If(if_expr) => self.lower_if_stmt(if_expr),
507            HirStmtKind::While { cond, body } => MirStmt::While {
508                cond: self.lower_expr(cond),
509                body: self.lower_block(body),
510            },
511            HirStmtKind::Return(e) => {
512                MirStmt::Return(e.as_ref().map(|ex| self.lower_expr(ex)))
513            }
514            HirStmtKind::Break => MirStmt::Break,
515            HirStmtKind::Continue => MirStmt::Continue,
516            HirStmtKind::NoGcBlock(block) => MirStmt::NoGcBlock(self.lower_block(block)),
517        }
518    }
519
520    pub fn lower_if_stmt(&mut self, if_expr: &HirIfExpr) -> MirStmt {
521        let cond = self.lower_expr(&if_expr.cond);
522        let then_body = self.lower_block(&if_expr.then_block);
523        let else_body = if_expr.else_branch.as_ref().map(|eb| match eb {
524            HirElseBranch::ElseIf(elif) => {
525                // Nested if-else becomes a block containing the if stmt
526                let nested = self.lower_if_stmt(elif);
527                MirBody {
528                    stmts: vec![nested],
529                    result: None,
530                }
531            }
532            HirElseBranch::Else(block) => self.lower_block(block),
533        });
534        MirStmt::If {
535            cond,
536            then_body,
537            else_body,
538        }
539    }
540
541    pub fn lower_expr(&mut self, expr: &HirExpr) -> MirExpr {
542        let kind = match &expr.kind {
543            HirExprKind::IntLit(v) => MirExprKind::IntLit(*v),
544            HirExprKind::FloatLit(v) => MirExprKind::FloatLit(*v),
545            HirExprKind::BoolLit(b) => MirExprKind::BoolLit(*b),
546            HirExprKind::StringLit(s) => MirExprKind::StringLit(s.clone()),
547            HirExprKind::ByteStringLit(bytes) => MirExprKind::ByteStringLit(bytes.clone()),
548            HirExprKind::ByteCharLit(b) => MirExprKind::ByteCharLit(*b),
549            HirExprKind::RawStringLit(s) => MirExprKind::RawStringLit(s.clone()),
550            HirExprKind::RawByteStringLit(bytes) => MirExprKind::RawByteStringLit(bytes.clone()),
551            HirExprKind::RegexLit { pattern, flags } => MirExprKind::RegexLit { pattern: pattern.clone(), flags: flags.clone() },
552            HirExprKind::TensorLit { rows } => {
553                let mir_rows = rows.iter().map(|row| {
554                    row.iter().map(|e| self.lower_expr(e)).collect()
555                }).collect();
556                MirExprKind::TensorLit { rows: mir_rows }
557            }
558            HirExprKind::Var(name) => MirExprKind::Var(name.clone()),
559            HirExprKind::Binary { op, left, right } => MirExprKind::Binary {
560                op: *op,
561                left: Box::new(self.lower_expr(left)),
562                right: Box::new(self.lower_expr(right)),
563            },
564            HirExprKind::Unary { op, operand } => MirExprKind::Unary {
565                op: *op,
566                operand: Box::new(self.lower_expr(operand)),
567            },
568            HirExprKind::Call { callee, args } => MirExprKind::Call {
569                callee: Box::new(self.lower_expr(callee)),
570                args: args.iter().map(|a| self.lower_expr(a)).collect(),
571            },
572            HirExprKind::Field { object, name } => MirExprKind::Field {
573                object: Box::new(self.lower_expr(object)),
574                name: name.clone(),
575            },
576            HirExprKind::Index { object, index } => MirExprKind::Index {
577                object: Box::new(self.lower_expr(object)),
578                index: Box::new(self.lower_expr(index)),
579            },
580            HirExprKind::MultiIndex { object, indices } => MirExprKind::MultiIndex {
581                object: Box::new(self.lower_expr(object)),
582                indices: indices.iter().map(|i| self.lower_expr(i)).collect(),
583            },
584            HirExprKind::Assign { target, value } => MirExprKind::Assign {
585                target: Box::new(self.lower_expr(target)),
586                value: Box::new(self.lower_expr(value)),
587            },
588            HirExprKind::Block(block) => MirExprKind::Block(self.lower_block(block)),
589            HirExprKind::StructLit { name, fields } => MirExprKind::StructLit {
590                name: name.clone(),
591                fields: fields
592                    .iter()
593                    .map(|(n, e)| (n.clone(), self.lower_expr(e)))
594                    .collect(),
595            },
596            HirExprKind::ArrayLit(elems) => {
597                MirExprKind::ArrayLit(elems.iter().map(|e| self.lower_expr(e)).collect())
598            }
599            HirExprKind::Col(name) => MirExprKind::Col(name.clone()),
600            HirExprKind::Lambda { params, body } => MirExprKind::Lambda {
601                params: params
602                    .iter()
603                    .map(|p| MirParam {
604                        name: p.name.clone(),
605                        ty_name: p.ty_name.clone(),
606                        default: p.default.as_ref().map(|d| self.lower_expr(d)),
607                        is_variadic: p.is_variadic,
608                    })
609                    .collect(),
610                body: Box::new(self.lower_expr(body)),
611            },
612            HirExprKind::Closure {
613                params,
614                body,
615                captures,
616            } => {
617                // Lambda-lift: create a top-level function with extra
618                // leading parameters for the captured values.
619                let lifted_name = self.fresh_lambda_name();
620                let lifted_id = self.fresh_fn_id();
621
622                // Build params: captures first, then the original params
623                let mut lifted_params: Vec<MirParam> = captures
624                    .iter()
625                    .map(|c| MirParam {
626                        name: c.name.clone(),
627                        ty_name: "any".to_string(), // Type erasure at MIR level
628                        default: None,
629                        is_variadic: false,
630                    })
631                    .collect();
632                for p in params {
633                    lifted_params.push(MirParam {
634                        name: p.name.clone(),
635                        ty_name: p.ty_name.clone(),
636                        default: p.default.as_ref().map(|d| self.lower_expr(d)),
637                        is_variadic: p.is_variadic,
638                    });
639                }
640
641                let lifted_body = MirBody {
642                    stmts: vec![],
643                    result: Some(Box::new(self.lower_expr(body))),
644                };
645
646                self.lifted_functions.push(MirFunction {
647                    id: lifted_id,
648                    name: lifted_name.clone(),
649                    type_params: vec![],
650                    params: lifted_params,
651                    return_type: None,
652                    body: lifted_body,
653                    is_nogc: false,
654                    cfg_body: None,
655                    decorators: vec![],
656                    vis: Visibility::Private,
657                });
658
659                // At the call site, emit MakeClosure with the capture
660                // variable references as capture expressions
661                let capture_exprs: Vec<MirExpr> = captures
662                    .iter()
663                    .map(|c| MirExpr {
664                        kind: MirExprKind::Var(c.name.clone()),
665                    })
666                    .collect();
667
668                MirExprKind::MakeClosure {
669                    fn_name: lifted_name,
670                    captures: capture_exprs,
671                }
672            }
673            HirExprKind::Match { scrutinee, arms } => {
674                let mir_scrutinee = Box::new(self.lower_expr(scrutinee));
675                let mir_arms = arms
676                    .iter()
677                    .map(|arm| {
678                        let pattern = self.lower_pattern(&arm.pattern);
679                        let body = MirBody {
680                            stmts: vec![],
681                            result: Some(Box::new(self.lower_expr(&arm.body))),
682                        };
683                        MirMatchArm { pattern, body }
684                    })
685                    .collect();
686                MirExprKind::Match {
687                    scrutinee: mir_scrutinee,
688                    arms: mir_arms,
689                }
690            }
691            HirExprKind::TupleLit(elems) => {
692                MirExprKind::TupleLit(elems.iter().map(|e| self.lower_expr(e)).collect())
693            }
694            HirExprKind::VariantLit {
695                enum_name,
696                variant,
697                fields,
698            } => MirExprKind::VariantLit {
699                enum_name: enum_name.clone(),
700                variant: variant.clone(),
701                fields: fields.iter().map(|f| self.lower_expr(f)).collect(),
702            },
703            HirExprKind::If { cond, then_block, else_branch } => {
704                let mir_cond = Box::new(self.lower_expr(cond));
705                let mir_then = self.lower_block(then_block);
706                let mir_else = else_branch.as_ref().map(|eb| match eb {
707                    HirElseBranch::ElseIf(elif) => {
708                        // Nested else-if: lower as MirStmt::If inside a MirBody
709                        let nested = self.lower_if_stmt(elif);
710                        MirBody {
711                            stmts: vec![nested],
712                            result: None,
713                        }
714                    }
715                    HirElseBranch::Else(block) => self.lower_block(block),
716                });
717                MirExprKind::If {
718                    cond: mir_cond,
719                    then_body: mir_then,
720                    else_body: mir_else,
721                }
722            }
723            HirExprKind::Void => MirExprKind::Void,
724        };
725        MirExpr { kind }
726    }
727
728    fn lower_pattern(&self, pat: &HirPattern) -> MirPattern {
729        match &pat.kind {
730            HirPatternKind::Wildcard => MirPattern::Wildcard,
731            HirPatternKind::Binding(name) => MirPattern::Binding(name.clone()),
732            HirPatternKind::LitInt(v) => MirPattern::LitInt(*v),
733            HirPatternKind::LitFloat(v) => MirPattern::LitFloat(*v),
734            HirPatternKind::LitBool(b) => MirPattern::LitBool(*b),
735            HirPatternKind::LitString(s) => MirPattern::LitString(s.clone()),
736            HirPatternKind::Tuple(pats) => {
737                MirPattern::Tuple(pats.iter().map(|p| self.lower_pattern(p)).collect())
738            }
739            HirPatternKind::Struct { name, fields } => MirPattern::Struct {
740                name: name.clone(),
741                fields: fields
742                    .iter()
743                    .map(|f| (f.name.clone(), self.lower_pattern(&f.pattern)))
744                    .collect(),
745            },
746            HirPatternKind::Variant {
747                enum_name,
748                variant,
749                fields,
750            } => MirPattern::Variant {
751                enum_name: enum_name.clone(),
752                variant: variant.clone(),
753                fields: fields.iter().map(|f| self.lower_pattern(f)).collect(),
754            },
755        }
756    }
757}
758
759impl Default for HirToMir {
760    fn default() -> Self {
761        Self::new()
762    }
763}
764
765// ---------------------------------------------------------------------------
766// Tests
767// ---------------------------------------------------------------------------
768
769#[cfg(test)]
770mod tests {
771    use super::*;
772    use cjc_hir::*;
773
774    fn hir_id(n: u32) -> HirId {
775        HirId(n)
776    }
777
778    fn hir_int(v: i64) -> HirExpr {
779        HirExpr {
780            kind: HirExprKind::IntLit(v),
781            hir_id: hir_id(0),
782        }
783    }
784
785    fn hir_var(name: &str) -> HirExpr {
786        HirExpr {
787            kind: HirExprKind::Var(name.to_string()),
788            hir_id: hir_id(0),
789        }
790    }
791
792    #[test]
793    fn test_lower_hir_literal() {
794        let mut lowering = HirToMir::new();
795        let hir = hir_int(42);
796        let mir = lowering.lower_expr(&hir);
797        assert!(matches!(mir.kind, MirExprKind::IntLit(42)));
798    }
799
800    #[test]
801    fn test_lower_hir_binary() {
802        let mut lowering = HirToMir::new();
803        let hir = HirExpr {
804            kind: HirExprKind::Binary {
805                op: BinOp::Add,
806                left: Box::new(hir_int(1)),
807                right: Box::new(hir_int(2)),
808            },
809            hir_id: hir_id(0),
810        };
811        let mir = lowering.lower_expr(&hir);
812        match &mir.kind {
813            MirExprKind::Binary { op, .. } => assert_eq!(*op, BinOp::Add),
814            _ => panic!("expected Binary"),
815        }
816    }
817
818    #[test]
819    fn test_lower_hir_fn() {
820        let mut lowering = HirToMir::new();
821        let hir_fn = HirFn {
822            name: "add".to_string(),
823            type_params: vec![],
824            params: vec![
825                HirParam {
826                    name: "a".to_string(),
827                    ty_name: "i64".to_string(),
828                    default: None,
829                    is_variadic: false,
830                    hir_id: hir_id(1),
831                },
832                HirParam {
833                    name: "b".to_string(),
834                    ty_name: "i64".to_string(),
835                    default: None,
836                    is_variadic: false,
837                    hir_id: hir_id(2),
838                },
839            ],
840            return_type: Some("i64".to_string()),
841            body: HirBlock {
842                stmts: vec![],
843                expr: Some(Box::new(HirExpr {
844                    kind: HirExprKind::Binary {
845                        op: BinOp::Add,
846                        left: Box::new(hir_var("a")),
847                        right: Box::new(hir_var("b")),
848                    },
849                    hir_id: hir_id(3),
850                })),
851                hir_id: hir_id(4),
852            },
853            is_nogc: false,
854            hir_id: hir_id(5),
855            decorators: vec![],
856            vis: cjc_ast::Visibility::Private,
857        };
858        let mir_fn = lowering.lower_fn(&hir_fn);
859        assert_eq!(mir_fn.name, "add");
860        assert_eq!(mir_fn.params.len(), 2);
861        assert!(mir_fn.body.result.is_some());
862    }
863
864    #[test]
865    fn test_lower_hir_program_entry() {
866        let mut lowering = HirToMir::new();
867        let hir = HirProgram {
868            items: vec![
869                HirItem::Let(HirLetDecl {
870                    name: "x".to_string(),
871                    mutable: false,
872                    ty_name: None,
873                    init: hir_int(42),
874                    hir_id: hir_id(0),
875                }),
876                HirItem::Fn(HirFn {
877                    name: "f".to_string(),
878                    type_params: vec![],
879                    params: vec![],
880                    return_type: None,
881                    body: HirBlock {
882                        stmts: vec![],
883                        expr: Some(Box::new(hir_var("x"))),
884                        hir_id: hir_id(1),
885                    },
886                    is_nogc: false,
887                    hir_id: hir_id(2),
888                    decorators: vec![],
889                    vis: cjc_ast::Visibility::Private,
890                }),
891            ],
892        };
893        let mir = lowering.lower_program(&hir);
894        // Should have: function 'f' + synthetic __main
895        assert_eq!(mir.functions.len(), 2);
896        let main = mir.functions.iter().find(|f| f.name == "__main").unwrap();
897        assert_eq!(main.body.stmts.len(), 1); // the let x = 42
898        assert_eq!(mir.entry, main.id);
899    }
900
901    #[test]
902    fn test_lower_hir_if_stmt() {
903        let mut lowering = HirToMir::new();
904        let hir_if = HirIfExpr {
905            cond: Box::new(HirExpr {
906                kind: HirExprKind::BoolLit(true),
907                hir_id: hir_id(0),
908            }),
909            then_block: HirBlock {
910                stmts: vec![],
911                expr: Some(Box::new(hir_int(1))),
912                hir_id: hir_id(1),
913            },
914            else_branch: Some(HirElseBranch::Else(HirBlock {
915                stmts: vec![],
916                expr: Some(Box::new(hir_int(2))),
917                hir_id: hir_id(2),
918            })),
919            hir_id: hir_id(3),
920        };
921        let mir_stmt = lowering.lower_if_stmt(&hir_if);
922        match &mir_stmt {
923            MirStmt::If {
924                then_body,
925                else_body,
926                ..
927            } => {
928                assert!(then_body.result.is_some());
929                assert!(else_body.is_some());
930            }
931            _ => panic!("expected If"),
932        }
933    }
934
935    #[test]
936    fn test_lower_struct_def() {
937        let mut lowering = HirToMir::new();
938        let hir = HirProgram {
939            items: vec![HirItem::Struct(HirStructDef {
940                name: "Point".to_string(),
941                fields: vec![
942                    ("x".to_string(), "f64".to_string()),
943                    ("y".to_string(), "f64".to_string()),
944                ],
945                hir_id: hir_id(0),
946                vis: cjc_ast::Visibility::Private,
947            })],
948        };
949        let mir = lowering.lower_program(&hir);
950        assert_eq!(mir.struct_defs.len(), 1);
951        assert_eq!(mir.struct_defs[0].name, "Point");
952        assert_eq!(mir.struct_defs[0].fields.len(), 2);
953    }
954}