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