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/// Unique identifier for a MIR function within a [`MirProgram`].
36///
37/// Assigned sequentially during HIR-to-MIR lowering. The synthetic `__main`
38/// entry function and lambda-lifted closures each receive their own ID.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub struct MirFnId(pub u32);
41
42/// Unique identifier for a basic block within a [`cfg::MirCfg`].
43///
44/// Block IDs are dense indices into `MirCfg::basic_blocks`. `BlockId(0)` is
45/// always the entry block. IDs are assigned deterministically in creation order.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
47pub struct BlockId(pub u32);
48
49/// Unique identifier for a temporary value in the MIR.
50///
51/// Reserved for future use when MIR transitions to explicit temporaries
52/// instead of named variables.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub struct TempId(pub u32);
55
56// ---------------------------------------------------------------------------
57// Program
58// ---------------------------------------------------------------------------
59
60/// A MIR program is a collection of functions + struct defs + an entry point.
61#[derive(Debug, Clone)]
62pub struct MirProgram {
63 pub functions: Vec<MirFunction>,
64 pub struct_defs: Vec<MirStructDef>,
65 pub enum_defs: Vec<MirEnumDef>,
66 /// Top-level statements (let bindings, expr stmts) are collected into
67 /// a synthetic `__main` function.
68 pub entry: MirFnId,
69}
70
71/// A struct (or record/class) type definition at the MIR level.
72///
73/// Struct definitions carry through from HIR without modification.
74/// The [`is_record`](MirStructDef::is_record) flag distinguishes immutable
75/// value-type records from mutable class-style structs.
76#[derive(Debug, Clone)]
77pub struct MirStructDef {
78 /// Name of the struct type.
79 pub name: String,
80 /// Fields as `(field_name, type_name)` pairs, in declaration order.
81 pub fields: Vec<(String, String)>,
82 /// True if this is a record (immutable value type).
83 pub is_record: bool,
84 /// Visibility of this struct definition.
85 pub vis: Visibility,
86}
87
88/// An enum type definition at the MIR level.
89///
90/// Contains the enum name and its ordered list of variant definitions.
91#[derive(Debug, Clone)]
92pub struct MirEnumDef {
93 /// Name of the enum type.
94 pub name: String,
95 /// Variant definitions in declaration order.
96 pub variants: Vec<MirVariantDef>,
97}
98
99/// A single variant of a [`MirEnumDef`].
100///
101/// Each variant can carry zero or more positional fields identified by
102/// their type names.
103#[derive(Debug, Clone)]
104pub struct MirVariantDef {
105 /// Name of this variant.
106 pub name: String,
107 /// Positional field type names.
108 pub fields: Vec<String>,
109}
110
111// ---------------------------------------------------------------------------
112// Functions
113// ---------------------------------------------------------------------------
114
115/// A MIR function definition.
116///
117/// Contains both the tree-form [`MirBody`] and an optional CFG representation.
118/// The tree-form body is canonical after lowering; the CFG is built on demand
119/// via [`build_cfg`](MirFunction::build_cfg) for analyses that require
120/// explicit control-flow edges (SSA, dominators, loop analysis).
121///
122/// Lambda-lifted closures and the synthetic `__main` entry function are
123/// represented as regular `MirFunction` instances.
124#[derive(Debug, Clone)]
125pub struct MirFunction {
126 /// Unique function ID within the program.
127 pub id: MirFnId,
128 /// Function name. Lambda-lifted closures use `__closure_N` names.
129 /// Impl methods use `Target.method` qualified names.
130 pub name: String,
131 /// Generic type parameters as `(param_name, trait_bounds)` pairs.
132 pub type_params: Vec<(String, Vec<String>)>,
133 /// Function parameters in declaration order.
134 pub params: Vec<MirParam>,
135 /// Return type name, if explicitly annotated.
136 pub return_type: Option<String>,
137 /// Tree-form function body (statements + optional tail expression).
138 pub body: MirBody,
139 /// Whether this function is annotated with `@nogc`.
140 /// When true, the [`nogc_verify`] module rejects any GC-triggering operations.
141 pub is_nogc: bool,
142 /// CFG representation of this function's body.
143 /// Built lazily from tree-form `body` via `build_cfg()`.
144 /// When present, this is the canonical representation for the CFG executor.
145 pub cfg_body: Option<cfg::MirCfg>,
146 /// Decorator names applied to this function (e.g., `@memoize`, `@trace`).
147 pub decorators: Vec<String>,
148 /// Visibility of this function definition.
149 pub vis: Visibility,
150}
151
152/// A function parameter at the MIR level.
153///
154/// Parameters carry their type annotation name and optional default value.
155/// For lambda-lifted closures, capture parameters appear first with type
156/// `"any"` (type-erased at MIR level).
157#[derive(Debug, Clone)]
158pub struct MirParam {
159 /// Parameter name.
160 pub name: String,
161 /// Type annotation name (e.g., `"i64"`, `"f64"`, `"any"`).
162 pub ty_name: String,
163 /// Optional default value expression for this parameter.
164 pub default: Option<MirExpr>,
165 /// Variadic parameter: collects remaining args into an array.
166 pub is_variadic: bool,
167}
168
169impl MirFunction {
170 /// Build the CFG representation from the tree-form body.
171 /// Stores the result in `cfg_body`.
172 pub fn build_cfg(&mut self) {
173 let cfg = cfg::CfgBuilder::build(&self.body);
174 self.cfg_body = Some(cfg);
175 }
176
177 /// Return a reference to the CFG body, building it on demand if needed.
178 ///
179 /// Subsequent calls reuse the cached CFG. Prefer [`build_cfg`](Self::build_cfg)
180 /// if you need to force a rebuild.
181 pub fn cfg(&mut self) -> &cfg::MirCfg {
182 if self.cfg_body.is_none() {
183 self.build_cfg();
184 }
185 self.cfg_body.as_ref().unwrap()
186 }
187}
188
189impl MirProgram {
190 /// Build CFG for all functions in this program.
191 pub fn build_all_cfgs(&mut self) {
192 for func in &mut self.functions {
193 func.build_cfg();
194 }
195 }
196}
197
198/// The body of a MIR function — a list of MIR statements.
199/// In Milestone 2.0 we use a simplified tree-form (not full CFG with basic
200/// blocks). This is extended to a proper CFG in Milestone 2.2+ for pattern
201/// matching compilation.
202#[derive(Debug, Clone)]
203pub struct MirBody {
204 pub stmts: Vec<MirStmt>,
205 pub result: Option<Box<MirExpr>>,
206}
207
208// ---------------------------------------------------------------------------
209// Statements
210// ---------------------------------------------------------------------------
211
212/// A MIR statement.
213///
214/// Statements represent side-effecting or control-flow operations in the
215/// tree-form MIR body. In the CFG representation, control-flow statements
216/// (`If`, `While`, `Break`, `Continue`) are compiled into basic block
217/// terminators and edges.
218#[derive(Debug, Clone)]
219pub enum MirStmt {
220 /// Variable binding: `let [mut] name = init;`
221 ///
222 /// The [`alloc_hint`](AllocHint) is populated by escape analysis after
223 /// lowering to guide allocation strategy.
224 Let {
225 /// Binding name.
226 name: String,
227 /// Whether the binding is mutable.
228 mutable: bool,
229 /// Initializer expression.
230 init: MirExpr,
231 /// Escape analysis annotation. `None` before analysis runs.
232 alloc_hint: Option<AllocHint>,
233 },
234 /// A standalone expression statement (e.g., function call, assignment).
235 Expr(MirExpr),
236 /// Conditional statement: `if cond { then } [else { else_ }]`.
237 If {
238 /// Condition expression (must evaluate to a boolean).
239 cond: MirExpr,
240 /// Body executed when the condition is true.
241 then_body: MirBody,
242 /// Optional body executed when the condition is false.
243 else_body: Option<MirBody>,
244 },
245 /// While loop: `while cond { body }`.
246 While {
247 /// Loop condition expression.
248 cond: MirExpr,
249 /// Loop body.
250 body: MirBody,
251 },
252 /// Return from the current function with an optional value.
253 Return(Option<MirExpr>),
254 /// Break out of the innermost enclosing loop.
255 Break,
256 /// Continue to the next iteration of the innermost enclosing loop.
257 Continue,
258 /// A `nogc { ... }` block where GC-triggering operations are forbidden.
259 ///
260 /// Verified by [`nogc_verify::verify_nogc`].
261 NoGcBlock(MirBody),
262}
263
264// ---------------------------------------------------------------------------
265// Expressions
266// ---------------------------------------------------------------------------
267
268/// A MIR expression node.
269///
270/// Wraps a [`MirExprKind`] discriminant. All MIR expressions are trees
271/// (no sharing / DAG structure).
272#[derive(Debug, Clone)]
273pub struct MirExpr {
274 /// The kind of this expression.
275 pub kind: MirExprKind,
276}
277
278/// The discriminant for a MIR expression.
279///
280/// Covers literals, variables, operators, control flow, pattern matching,
281/// closures, linalg opcodes, and container constructors. Each variant
282/// corresponds to a distinct runtime operation in both the tree-walk
283/// interpreter (`cjc-eval`) and the MIR executor (`cjc-mir-exec`).
284#[derive(Debug, Clone)]
285pub enum MirExprKind {
286 /// 64-bit signed integer literal.
287 IntLit(i64),
288 /// 64-bit IEEE 754 floating-point literal.
289 FloatLit(f64),
290 /// Boolean literal (`true` or `false`).
291 BoolLit(bool),
292 /// UTF-8 string literal.
293 StringLit(String),
294 /// Byte string literal (`b"..."`).
295 ByteStringLit(Vec<u8>),
296 /// Single byte character literal (`b'x'`).
297 ByteCharLit(u8),
298 /// Raw string literal (`r"..."`).
299 RawStringLit(String),
300 /// Raw byte string literal (`rb"..."`).
301 RawByteStringLit(Vec<u8>),
302 /// Regex literal with pattern and flags.
303 RegexLit {
304 /// The regex pattern string.
305 pattern: String,
306 /// Regex flags (e.g., `"gi"`).
307 flags: String,
308 },
309 /// Tensor literal: a 2D grid of expressions (rows x columns).
310 TensorLit {
311 /// Each inner `Vec` is one row of the tensor.
312 rows: Vec<Vec<MirExpr>>,
313 },
314 /// NA (missing value) literal.
315 NaLit,
316 /// Variable reference by name.
317 Var(String),
318 /// Binary operation.
319 /// Binary operation.
320 Binary {
321 /// The binary operator.
322 op: BinOp,
323 /// Left-hand operand.
324 left: Box<MirExpr>,
325 /// Right-hand operand.
326 right: Box<MirExpr>,
327 },
328 /// Unary operation (negation, logical not, bitwise not).
329 Unary {
330 /// The unary operator.
331 op: UnaryOp,
332 /// The operand.
333 operand: Box<MirExpr>,
334 },
335 /// Function or closure call.
336 Call {
337 /// The callee expression (usually a [`Var`](MirExprKind::Var) or
338 /// [`Field`](MirExprKind::Field) for method calls).
339 callee: Box<MirExpr>,
340 /// Positional arguments.
341 args: Vec<MirExpr>,
342 },
343 /// Field access: `object.name`.
344 Field {
345 /// The object being accessed.
346 object: Box<MirExpr>,
347 /// Field name.
348 name: String,
349 },
350 /// Single-index access: `object[index]`.
351 Index {
352 /// The collection being indexed.
353 object: Box<MirExpr>,
354 /// The index expression.
355 index: Box<MirExpr>,
356 },
357 /// Multi-dimensional index access: `object[i, j, ...]`.
358 MultiIndex {
359 /// The collection being indexed.
360 object: Box<MirExpr>,
361 /// Index expressions for each dimension.
362 indices: Vec<MirExpr>,
363 },
364 /// Assignment: `target = value`.
365 Assign {
366 /// Assignment target (variable, field, or index expression).
367 target: Box<MirExpr>,
368 /// Value being assigned.
369 value: Box<MirExpr>,
370 },
371 /// Block expression: evaluates a [`MirBody`] and returns its result.
372 Block(MirBody),
373 /// Struct literal: `Name { field1: expr1, field2: expr2, ... }`.
374 StructLit {
375 /// Struct type name.
376 name: String,
377 /// Field initializers as `(name, value)` pairs.
378 fields: Vec<(String, MirExpr)>,
379 },
380 /// Array literal: `[expr1, expr2, ...]`.
381 ArrayLit(Vec<MirExpr>),
382 /// Column reference in a data DSL context (e.g., `col("name")`).
383 Col(String),
384 /// Lambda expression (non-capturing).
385 Lambda {
386 /// Lambda parameters.
387 params: Vec<MirParam>,
388 /// Lambda body expression.
389 body: Box<MirExpr>,
390 },
391 /// Create a closure: captures + a reference to the lifted function.
392 /// At runtime, evaluates each capture expression and bundles them with
393 /// the function name into a Closure value.
394 MakeClosure {
395 /// Name of the lambda-lifted top-level function.
396 fn_name: String,
397 /// Expressions that produce the captured values (evaluated at closure
398 /// creation time). Order matches the extra leading params of the
399 /// lifted function.
400 captures: Vec<MirExpr>,
401 },
402 /// If expression: `if cond { then } [else { else_ }]`.
403 ///
404 /// Used as both a statement and an expression (the branch bodies can
405 /// produce values).
406 If {
407 /// Condition expression.
408 cond: Box<MirExpr>,
409 /// Body evaluated when the condition is true.
410 then_body: MirBody,
411 /// Optional body evaluated when the condition is false.
412 else_body: Option<MirBody>,
413 },
414 /// Match expression compiled as a decision tree.
415 /// Each arm is tried in order; first matching arm's body is evaluated.
416 Match {
417 /// The value being matched against.
418 scrutinee: Box<MirExpr>,
419 /// Match arms in order of priority.
420 arms: Vec<MirMatchArm>,
421 },
422 /// Enum variant literal constructor: `EnumName::Variant(fields...)`.
423 VariantLit {
424 /// Enum type name.
425 enum_name: String,
426 /// Variant name.
427 variant: String,
428 /// Positional field values.
429 fields: Vec<MirExpr>,
430 },
431 /// Tuple literal: `(expr1, expr2, ...)`.
432 TupleLit(Vec<MirExpr>),
433 /// LU decomposition opcode.
434 LinalgLU {
435 /// Matrix operand.
436 operand: Box<MirExpr>,
437 },
438 /// QR decomposition opcode.
439 LinalgQR {
440 /// Matrix operand.
441 operand: Box<MirExpr>,
442 },
443 /// Cholesky decomposition opcode.
444 LinalgCholesky {
445 /// Matrix operand (must be symmetric positive-definite).
446 operand: Box<MirExpr>,
447 },
448 /// Matrix inverse opcode.
449 LinalgInv {
450 /// Matrix operand.
451 operand: Box<MirExpr>,
452 },
453 /// Broadcast a tensor to a target shape (zero-copy view with stride=0).
454 Broadcast {
455 /// Tensor operand to broadcast.
456 operand: Box<MirExpr>,
457 /// Target shape dimensions.
458 target_shape: Vec<MirExpr>,
459 },
460 /// Unit/void value (no meaningful result).
461 Void,
462}
463
464// ---------------------------------------------------------------------------
465// Match / Pattern types (MIR level)
466// ---------------------------------------------------------------------------
467
468/// A match arm at MIR level: pattern + body.
469#[derive(Debug, Clone)]
470pub struct MirMatchArm {
471 pub pattern: MirPattern,
472 pub body: MirBody,
473}
474
475/// A pattern at MIR level.
476#[derive(Debug, Clone)]
477pub enum MirPattern {
478 /// Wildcard: matches anything, binds nothing.
479 Wildcard,
480 /// Binding: matches anything, binds the value to a name.
481 Binding(String),
482 /// Literal patterns
483 LitInt(i64),
484 LitFloat(f64),
485 LitBool(bool),
486 LitString(String),
487 /// Tuple destructuring
488 Tuple(Vec<MirPattern>),
489 /// Struct destructuring
490 Struct {
491 name: String,
492 fields: Vec<(String, MirPattern)>,
493 },
494 /// Enum variant pattern
495 Variant {
496 enum_name: String,
497 variant: String,
498 fields: Vec<MirPattern>,
499 },
500}
501
502// ===========================================================================
503// HIR -> MIR Lowering
504// ===========================================================================
505
506use cjc_hir::*;
507
508/// Lowers HIR into MIR.
509///
510/// Performs a single-pass traversal of the [`HirProgram`], converting each
511/// HIR item into its MIR equivalent. During lowering:
512///
513/// - Top-level statements are collected into a synthetic `__main` function.
514/// - Closures are lambda-lifted into top-level functions with extra leading
515/// parameters for captured values, and replaced with [`MirExprKind::MakeClosure`].
516/// - Impl methods are flattened to qualified `Target.method` names.
517/// - Traits produce no MIR output (metadata only).
518///
519/// # Usage
520///
521/// ```rust,ignore
522/// let mut lowering = HirToMir::new();
523/// let mir_program = lowering.lower_program(&hir_program);
524/// ```
525pub struct HirToMir {
526 next_fn_id: u32,
527 next_lambda_id: u32,
528 /// Lambda-lifted functions accumulated during lowering.
529 /// These are appended to the MirProgram's function list.
530 lifted_functions: Vec<MirFunction>,
531}
532
533impl HirToMir {
534 /// Create a new HIR-to-MIR lowering pass with fresh ID counters.
535 pub fn new() -> Self {
536 Self {
537 next_fn_id: 0,
538 next_lambda_id: 0,
539 lifted_functions: Vec::new(),
540 }
541 }
542
543 fn fresh_fn_id(&mut self) -> MirFnId {
544 let id = MirFnId(self.next_fn_id);
545 self.next_fn_id += 1;
546 id
547 }
548
549 fn fresh_lambda_name(&mut self) -> String {
550 let name = format!("__closure_{}", self.next_lambda_id);
551 self.next_lambda_id += 1;
552 name
553 }
554
555 /// Lower a HIR program to MIR.
556 pub fn lower_program(&mut self, hir: &HirProgram) -> MirProgram {
557 let mut functions = Vec::new();
558 let mut struct_defs = Vec::new();
559 let mut enum_defs = Vec::new();
560 let mut main_stmts: Vec<MirStmt> = Vec::new();
561
562 for item in &hir.items {
563 match item {
564 HirItem::Fn(f) => {
565 functions.push(self.lower_fn(f));
566 }
567 HirItem::Struct(s) => {
568 struct_defs.push(MirStructDef {
569 name: s.name.clone(),
570 fields: s.fields.clone(),
571 is_record: false,
572 vis: s.vis,
573 });
574 }
575 HirItem::Class(c) => {
576 struct_defs.push(MirStructDef {
577 name: c.name.clone(),
578 fields: c.fields.clone(),
579 is_record: false,
580 vis: c.vis,
581 });
582 }
583 HirItem::Record(r) => {
584 struct_defs.push(MirStructDef {
585 name: r.name.clone(),
586 fields: r.fields.clone(),
587 is_record: true,
588 vis: r.vis,
589 });
590 }
591 HirItem::Enum(e) => {
592 enum_defs.push(MirEnumDef {
593 name: e.name.clone(),
594 variants: e
595 .variants
596 .iter()
597 .map(|v| MirVariantDef {
598 name: v.name.clone(),
599 fields: v.fields.clone(),
600 })
601 .collect(),
602 });
603 }
604 HirItem::Let(l) => {
605 main_stmts.push(MirStmt::Let {
606 name: l.name.clone(),
607 mutable: l.mutable,
608 init: self.lower_expr(&l.init),
609 alloc_hint: None,
610 });
611 }
612 HirItem::Stmt(s) => {
613 main_stmts.push(self.lower_stmt(s));
614 }
615 HirItem::Impl(i) => {
616 for method in &i.methods {
617 // Register as qualified name: Target.method
618 let mut mir_fn = self.lower_fn(method);
619 mir_fn.name = format!("{}.{}", i.target, method.name);
620 functions.push(mir_fn);
621 }
622 }
623 HirItem::Trait(_) => {
624 // Traits are metadata only; no MIR output
625 }
626 }
627 }
628
629 // Create __main entry function from top-level statements
630 let main_id = self.fresh_fn_id();
631 functions.push(MirFunction {
632 id: main_id,
633 name: "__main".to_string(),
634 type_params: vec![],
635 params: vec![],
636 return_type: None,
637 body: MirBody {
638 stmts: main_stmts,
639 result: None,
640 },
641 is_nogc: false,
642 cfg_body: None,
643 decorators: vec![],
644 vis: Visibility::Private,
645 });
646
647 // Append all lambda-lifted functions
648 functions.append(&mut self.lifted_functions);
649
650 MirProgram {
651 functions,
652 struct_defs,
653 enum_defs,
654 entry: main_id,
655 }
656 }
657
658 /// Lower a single HIR function definition to a [`MirFunction`].
659 ///
660 /// Assigns a fresh [`MirFnId`] and recursively lowers parameters, body
661 /// statements, and the tail expression. Closures encountered within the
662 /// body are lambda-lifted and accumulated in `self.lifted_functions`.
663 pub fn lower_fn(&mut self, f: &HirFn) -> MirFunction {
664 let id = self.fresh_fn_id();
665 let params = f
666 .params
667 .iter()
668 .map(|p| MirParam {
669 name: p.name.clone(),
670 ty_name: p.ty_name.clone(),
671 default: p.default.as_ref().map(|d| self.lower_expr(d)),
672 is_variadic: p.is_variadic,
673 })
674 .collect();
675 let body = self.lower_block(&f.body);
676 MirFunction {
677 id,
678 name: f.name.clone(),
679 type_params: f.type_params.clone(),
680 params,
681 return_type: f.return_type.clone(),
682 body,
683 is_nogc: f.is_nogc,
684 cfg_body: None,
685 decorators: f.decorators.clone(),
686 vis: f.vis,
687 }
688 }
689
690 fn lower_block(&mut self, block: &HirBlock) -> MirBody {
691 let stmts = block.stmts.iter().map(|s| self.lower_stmt(s)).collect();
692 let result = block.expr.as_ref().map(|e| Box::new(self.lower_expr(e)));
693 MirBody { stmts, result }
694 }
695
696 fn lower_stmt(&mut self, stmt: &HirStmt) -> MirStmt {
697 match &stmt.kind {
698 HirStmtKind::Let {
699 name,
700 mutable,
701 init,
702 ..
703 } => MirStmt::Let {
704 name: name.clone(),
705 mutable: *mutable,
706 init: self.lower_expr(init),
707 alloc_hint: None,
708 },
709 HirStmtKind::Expr(e) => MirStmt::Expr(self.lower_expr(e)),
710 HirStmtKind::If(if_expr) => self.lower_if_stmt(if_expr),
711 HirStmtKind::While { cond, body } => MirStmt::While {
712 cond: self.lower_expr(cond),
713 body: self.lower_block(body),
714 },
715 HirStmtKind::Return(e) => {
716 MirStmt::Return(e.as_ref().map(|ex| self.lower_expr(ex)))
717 }
718 HirStmtKind::Break => MirStmt::Break,
719 HirStmtKind::Continue => MirStmt::Continue,
720 HirStmtKind::NoGcBlock(block) => MirStmt::NoGcBlock(self.lower_block(block)),
721 }
722 }
723
724 /// Lower a HIR `if` expression to a [`MirStmt::If`].
725 ///
726 /// Nested `else if` chains are recursively lowered into nested
727 /// [`MirStmt::If`] nodes wrapped in a [`MirBody`].
728 pub fn lower_if_stmt(&mut self, if_expr: &HirIfExpr) -> MirStmt {
729 let cond = self.lower_expr(&if_expr.cond);
730 let then_body = self.lower_block(&if_expr.then_block);
731 let else_body = if_expr.else_branch.as_ref().map(|eb| match eb {
732 HirElseBranch::ElseIf(elif) => {
733 // Nested if-else becomes a block containing the if stmt
734 let nested = self.lower_if_stmt(elif);
735 MirBody {
736 stmts: vec![nested],
737 result: None,
738 }
739 }
740 HirElseBranch::Else(block) => self.lower_block(block),
741 });
742 MirStmt::If {
743 cond,
744 then_body,
745 else_body,
746 }
747 }
748
749 /// Lower a HIR expression to a [`MirExpr`].
750 ///
751 /// Handles all HIR expression kinds including closures (lambda-lifted),
752 /// match expressions (compiled to [`MirExprKind::Match`] decision trees),
753 /// and if-expressions.
754 pub fn lower_expr(&mut self, expr: &HirExpr) -> MirExpr {
755 let kind = match &expr.kind {
756 HirExprKind::IntLit(v) => MirExprKind::IntLit(*v),
757 HirExprKind::FloatLit(v) => MirExprKind::FloatLit(*v),
758 HirExprKind::BoolLit(b) => MirExprKind::BoolLit(*b),
759 HirExprKind::NaLit => MirExprKind::NaLit,
760 HirExprKind::StringLit(s) => MirExprKind::StringLit(s.clone()),
761 HirExprKind::ByteStringLit(bytes) => MirExprKind::ByteStringLit(bytes.clone()),
762 HirExprKind::ByteCharLit(b) => MirExprKind::ByteCharLit(*b),
763 HirExprKind::RawStringLit(s) => MirExprKind::RawStringLit(s.clone()),
764 HirExprKind::RawByteStringLit(bytes) => MirExprKind::RawByteStringLit(bytes.clone()),
765 HirExprKind::RegexLit { pattern, flags } => MirExprKind::RegexLit { pattern: pattern.clone(), flags: flags.clone() },
766 HirExprKind::TensorLit { rows } => {
767 let mir_rows = rows.iter().map(|row| {
768 row.iter().map(|e| self.lower_expr(e)).collect()
769 }).collect();
770 MirExprKind::TensorLit { rows: mir_rows }
771 }
772 HirExprKind::Var(name) => MirExprKind::Var(name.clone()),
773 HirExprKind::Binary { op, left, right } => MirExprKind::Binary {
774 op: *op,
775 left: Box::new(self.lower_expr(left)),
776 right: Box::new(self.lower_expr(right)),
777 },
778 HirExprKind::Unary { op, operand } => MirExprKind::Unary {
779 op: *op,
780 operand: Box::new(self.lower_expr(operand)),
781 },
782 HirExprKind::Call { callee, args } => MirExprKind::Call {
783 callee: Box::new(self.lower_expr(callee)),
784 args: args.iter().map(|a| self.lower_expr(a)).collect(),
785 },
786 HirExprKind::Field { object, name } => MirExprKind::Field {
787 object: Box::new(self.lower_expr(object)),
788 name: name.clone(),
789 },
790 HirExprKind::Index { object, index } => MirExprKind::Index {
791 object: Box::new(self.lower_expr(object)),
792 index: Box::new(self.lower_expr(index)),
793 },
794 HirExprKind::MultiIndex { object, indices } => MirExprKind::MultiIndex {
795 object: Box::new(self.lower_expr(object)),
796 indices: indices.iter().map(|i| self.lower_expr(i)).collect(),
797 },
798 HirExprKind::Assign { target, value } => MirExprKind::Assign {
799 target: Box::new(self.lower_expr(target)),
800 value: Box::new(self.lower_expr(value)),
801 },
802 HirExprKind::Block(block) => MirExprKind::Block(self.lower_block(block)),
803 HirExprKind::StructLit { name, fields } => MirExprKind::StructLit {
804 name: name.clone(),
805 fields: fields
806 .iter()
807 .map(|(n, e)| (n.clone(), self.lower_expr(e)))
808 .collect(),
809 },
810 HirExprKind::ArrayLit(elems) => {
811 MirExprKind::ArrayLit(elems.iter().map(|e| self.lower_expr(e)).collect())
812 }
813 HirExprKind::Col(name) => MirExprKind::Col(name.clone()),
814 HirExprKind::Lambda { params, body } => MirExprKind::Lambda {
815 params: params
816 .iter()
817 .map(|p| MirParam {
818 name: p.name.clone(),
819 ty_name: p.ty_name.clone(),
820 default: p.default.as_ref().map(|d| self.lower_expr(d)),
821 is_variadic: p.is_variadic,
822 })
823 .collect(),
824 body: Box::new(self.lower_expr(body)),
825 },
826 HirExprKind::Closure {
827 params,
828 body,
829 captures,
830 } => {
831 // Lambda-lift: create a top-level function with extra
832 // leading parameters for the captured values.
833 let lifted_name = self.fresh_lambda_name();
834 let lifted_id = self.fresh_fn_id();
835
836 // Build params: captures first, then the original params
837 let mut lifted_params: Vec<MirParam> = captures
838 .iter()
839 .map(|c| MirParam {
840 name: c.name.clone(),
841 ty_name: "any".to_string(), // Type erasure at MIR level
842 default: None,
843 is_variadic: false,
844 })
845 .collect();
846 for p in params {
847 lifted_params.push(MirParam {
848 name: p.name.clone(),
849 ty_name: p.ty_name.clone(),
850 default: p.default.as_ref().map(|d| self.lower_expr(d)),
851 is_variadic: p.is_variadic,
852 });
853 }
854
855 let lifted_body = MirBody {
856 stmts: vec![],
857 result: Some(Box::new(self.lower_expr(body))),
858 };
859
860 self.lifted_functions.push(MirFunction {
861 id: lifted_id,
862 name: lifted_name.clone(),
863 type_params: vec![],
864 params: lifted_params,
865 return_type: None,
866 body: lifted_body,
867 is_nogc: false,
868 cfg_body: None,
869 decorators: vec![],
870 vis: Visibility::Private,
871 });
872
873 // At the call site, emit MakeClosure with the capture
874 // variable references as capture expressions
875 let capture_exprs: Vec<MirExpr> = captures
876 .iter()
877 .map(|c| MirExpr {
878 kind: MirExprKind::Var(c.name.clone()),
879 })
880 .collect();
881
882 MirExprKind::MakeClosure {
883 fn_name: lifted_name,
884 captures: capture_exprs,
885 }
886 }
887 HirExprKind::Match { scrutinee, arms } => {
888 let mir_scrutinee = Box::new(self.lower_expr(scrutinee));
889 let mir_arms = arms
890 .iter()
891 .map(|arm| {
892 let pattern = self.lower_pattern(&arm.pattern);
893 let body = MirBody {
894 stmts: vec![],
895 result: Some(Box::new(self.lower_expr(&arm.body))),
896 };
897 MirMatchArm { pattern, body }
898 })
899 .collect();
900 MirExprKind::Match {
901 scrutinee: mir_scrutinee,
902 arms: mir_arms,
903 }
904 }
905 HirExprKind::TupleLit(elems) => {
906 MirExprKind::TupleLit(elems.iter().map(|e| self.lower_expr(e)).collect())
907 }
908 HirExprKind::VariantLit {
909 enum_name,
910 variant,
911 fields,
912 } => MirExprKind::VariantLit {
913 enum_name: enum_name.clone(),
914 variant: variant.clone(),
915 fields: fields.iter().map(|f| self.lower_expr(f)).collect(),
916 },
917 HirExprKind::If { cond, then_block, else_branch } => {
918 let mir_cond = Box::new(self.lower_expr(cond));
919 let mir_then = self.lower_block(then_block);
920 let mir_else = else_branch.as_ref().map(|eb| match eb {
921 HirElseBranch::ElseIf(elif) => {
922 // Nested else-if: lower as MirStmt::If inside a MirBody
923 let nested = self.lower_if_stmt(elif);
924 MirBody {
925 stmts: vec![nested],
926 result: None,
927 }
928 }
929 HirElseBranch::Else(block) => self.lower_block(block),
930 });
931 MirExprKind::If {
932 cond: mir_cond,
933 then_body: mir_then,
934 else_body: mir_else,
935 }
936 }
937 HirExprKind::Void => MirExprKind::Void,
938 };
939 MirExpr { kind }
940 }
941
942 fn lower_pattern(&self, pat: &HirPattern) -> MirPattern {
943 match &pat.kind {
944 HirPatternKind::Wildcard => MirPattern::Wildcard,
945 HirPatternKind::Binding(name) => MirPattern::Binding(name.clone()),
946 HirPatternKind::LitInt(v) => MirPattern::LitInt(*v),
947 HirPatternKind::LitFloat(v) => MirPattern::LitFloat(*v),
948 HirPatternKind::LitBool(b) => MirPattern::LitBool(*b),
949 HirPatternKind::LitString(s) => MirPattern::LitString(s.clone()),
950 HirPatternKind::Tuple(pats) => {
951 MirPattern::Tuple(pats.iter().map(|p| self.lower_pattern(p)).collect())
952 }
953 HirPatternKind::Struct { name, fields } => MirPattern::Struct {
954 name: name.clone(),
955 fields: fields
956 .iter()
957 .map(|f| (f.name.clone(), self.lower_pattern(&f.pattern)))
958 .collect(),
959 },
960 HirPatternKind::Variant {
961 enum_name,
962 variant,
963 fields,
964 } => MirPattern::Variant {
965 enum_name: enum_name.clone(),
966 variant: variant.clone(),
967 fields: fields.iter().map(|f| self.lower_pattern(f)).collect(),
968 },
969 }
970 }
971}
972
973impl Default for HirToMir {
974 fn default() -> Self {
975 Self::new()
976 }
977}
978
979// ---------------------------------------------------------------------------
980// Tests
981// ---------------------------------------------------------------------------
982
983#[cfg(test)]
984mod tests {
985 use super::*;
986 use cjc_hir::*;
987
988 fn hir_id(n: u32) -> HirId {
989 HirId(n)
990 }
991
992 fn hir_int(v: i64) -> HirExpr {
993 HirExpr {
994 kind: HirExprKind::IntLit(v),
995 hir_id: hir_id(0),
996 }
997 }
998
999 fn hir_var(name: &str) -> HirExpr {
1000 HirExpr {
1001 kind: HirExprKind::Var(name.to_string()),
1002 hir_id: hir_id(0),
1003 }
1004 }
1005
1006 #[test]
1007 fn test_lower_hir_literal() {
1008 let mut lowering = HirToMir::new();
1009 let hir = hir_int(42);
1010 let mir = lowering.lower_expr(&hir);
1011 assert!(matches!(mir.kind, MirExprKind::IntLit(42)));
1012 }
1013
1014 #[test]
1015 fn test_lower_hir_binary() {
1016 let mut lowering = HirToMir::new();
1017 let hir = HirExpr {
1018 kind: HirExprKind::Binary {
1019 op: BinOp::Add,
1020 left: Box::new(hir_int(1)),
1021 right: Box::new(hir_int(2)),
1022 },
1023 hir_id: hir_id(0),
1024 };
1025 let mir = lowering.lower_expr(&hir);
1026 match &mir.kind {
1027 MirExprKind::Binary { op, .. } => assert_eq!(*op, BinOp::Add),
1028 _ => panic!("expected Binary"),
1029 }
1030 }
1031
1032 #[test]
1033 fn test_lower_hir_fn() {
1034 let mut lowering = HirToMir::new();
1035 let hir_fn = HirFn {
1036 name: "add".to_string(),
1037 type_params: vec![],
1038 params: vec![
1039 HirParam {
1040 name: "a".to_string(),
1041 ty_name: "i64".to_string(),
1042 default: None,
1043 is_variadic: false,
1044 hir_id: hir_id(1),
1045 },
1046 HirParam {
1047 name: "b".to_string(),
1048 ty_name: "i64".to_string(),
1049 default: None,
1050 is_variadic: false,
1051 hir_id: hir_id(2),
1052 },
1053 ],
1054 return_type: Some("i64".to_string()),
1055 body: HirBlock {
1056 stmts: vec![],
1057 expr: Some(Box::new(HirExpr {
1058 kind: HirExprKind::Binary {
1059 op: BinOp::Add,
1060 left: Box::new(hir_var("a")),
1061 right: Box::new(hir_var("b")),
1062 },
1063 hir_id: hir_id(3),
1064 })),
1065 hir_id: hir_id(4),
1066 },
1067 is_nogc: false,
1068 hir_id: hir_id(5),
1069 decorators: vec![],
1070 vis: cjc_ast::Visibility::Private,
1071 };
1072 let mir_fn = lowering.lower_fn(&hir_fn);
1073 assert_eq!(mir_fn.name, "add");
1074 assert_eq!(mir_fn.params.len(), 2);
1075 assert!(mir_fn.body.result.is_some());
1076 }
1077
1078 #[test]
1079 fn test_lower_hir_program_entry() {
1080 let mut lowering = HirToMir::new();
1081 let hir = HirProgram {
1082 items: vec![
1083 HirItem::Let(HirLetDecl {
1084 name: "x".to_string(),
1085 mutable: false,
1086 ty_name: None,
1087 init: hir_int(42),
1088 hir_id: hir_id(0),
1089 }),
1090 HirItem::Fn(HirFn {
1091 name: "f".to_string(),
1092 type_params: vec![],
1093 params: vec![],
1094 return_type: None,
1095 body: HirBlock {
1096 stmts: vec![],
1097 expr: Some(Box::new(hir_var("x"))),
1098 hir_id: hir_id(1),
1099 },
1100 is_nogc: false,
1101 hir_id: hir_id(2),
1102 decorators: vec![],
1103 vis: cjc_ast::Visibility::Private,
1104 }),
1105 ],
1106 };
1107 let mir = lowering.lower_program(&hir);
1108 // Should have: function 'f' + synthetic __main
1109 assert_eq!(mir.functions.len(), 2);
1110 let main = mir.functions.iter().find(|f| f.name == "__main").unwrap();
1111 assert_eq!(main.body.stmts.len(), 1); // the let x = 42
1112 assert_eq!(mir.entry, main.id);
1113 }
1114
1115 #[test]
1116 fn test_lower_hir_if_stmt() {
1117 let mut lowering = HirToMir::new();
1118 let hir_if = HirIfExpr {
1119 cond: Box::new(HirExpr {
1120 kind: HirExprKind::BoolLit(true),
1121 hir_id: hir_id(0),
1122 }),
1123 then_block: HirBlock {
1124 stmts: vec![],
1125 expr: Some(Box::new(hir_int(1))),
1126 hir_id: hir_id(1),
1127 },
1128 else_branch: Some(HirElseBranch::Else(HirBlock {
1129 stmts: vec![],
1130 expr: Some(Box::new(hir_int(2))),
1131 hir_id: hir_id(2),
1132 })),
1133 hir_id: hir_id(3),
1134 };
1135 let mir_stmt = lowering.lower_if_stmt(&hir_if);
1136 match &mir_stmt {
1137 MirStmt::If {
1138 then_body,
1139 else_body,
1140 ..
1141 } => {
1142 assert!(then_body.result.is_some());
1143 assert!(else_body.is_some());
1144 }
1145 _ => panic!("expected If"),
1146 }
1147 }
1148
1149 #[test]
1150 fn test_lower_struct_def() {
1151 let mut lowering = HirToMir::new();
1152 let hir = HirProgram {
1153 items: vec![HirItem::Struct(HirStructDef {
1154 name: "Point".to_string(),
1155 fields: vec![
1156 ("x".to_string(), "f64".to_string()),
1157 ("y".to_string(), "f64".to_string()),
1158 ],
1159 hir_id: hir_id(0),
1160 vis: cjc_ast::Visibility::Private,
1161 })],
1162 };
1163 let mir = lowering.lower_program(&hir);
1164 assert_eq!(mir.struct_defs.len(), 1);
1165 assert_eq!(mir.struct_defs[0].name, "Point");
1166 assert_eq!(mir.struct_defs[0].fields.len(), 2);
1167 }
1168}