Coverage Report

Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/middleend/mir/lower.rs
Line
Count
Source
1
//! AST to MIR lowering
2
3
use super::builder::MirBuilder;
4
use super::types::{
5
    BinOp, BlockId, Constant, Mutability, Operand, Place, Program, Rvalue, Type, UnOp,
6
};
7
use crate::frontend::ast::{
8
    BinaryOp as AstBinOp, Expr, ExprKind, Literal, Param, Type as AstType, UnaryOp as AstUnOp,
9
};
10
use anyhow::{anyhow, Result};
11
use std::collections::HashMap;
12
13
/// Context for lowering AST to MIR
14
pub struct LoweringContext {
15
    /// MIR builder
16
    builder: MirBuilder,
17
    /// Type environment for expressions
18
    #[allow(dead_code)]
19
    type_env: HashMap<String, Type>,
20
    /// Current block being built
21
    current_block: Option<BlockId>,
22
}
23
24
impl LoweringContext {
25
    /// Create a new lowering context
26
    #[must_use]
27
0
    pub fn new() -> Self {
28
0
        Self {
29
0
            builder: MirBuilder::new(),
30
0
            type_env: HashMap::new(),
31
0
            current_block: None,
32
0
        }
33
0
    }
34
35
    /// Lower an expression to MIR
36
    ///
37
    /// # Errors
38
    ///
39
    /// Returns an error if the expression cannot be lowered to MIR
40
    /// # Errors
41
    ///
42
    /// Returns an error if the operation fails
43
0
    pub fn lower_expr(&mut self, expr: &Expr) -> Result<Program> {
44
0
        match &expr.kind {
45
            ExprKind::Function {
46
0
                name,
47
0
                params,
48
0
                return_type,
49
0
                body,
50
                ..
51
0
            } => self.lower_function(name, params, return_type.as_ref(), body),
52
            _ => {
53
                // For non-function expressions, create a main function
54
0
                self.lower_main_expr(expr)
55
            }
56
        }
57
0
    }
58
59
    /// Lower a function expression
60
0
    fn lower_function(
61
0
        &mut self,
62
0
        name: &str,
63
0
        params: &[Param],
64
0
        return_type: Option<&AstType>,
65
0
        body: &Expr,
66
0
    ) -> Result<Program> {
67
0
        let func_name = name.to_string();
68
0
        let ret_ty = return_type.map_or(Type::Unit, |t| self.ast_to_mir_type(t));
69
70
0
        self.builder.start_function(func_name.clone(), ret_ty);
71
72
        // Add parameters
73
0
        for param in params {
74
0
            let ty = self.ast_to_mir_type(&param.ty);
75
0
            self.builder.add_param(param.name(), ty);
76
0
        }
77
78
        // Create entry block
79
0
        let entry = self.builder.new_block();
80
0
        self.current_block = Some(entry);
81
82
        // Lower function body
83
0
        let result = self.lower_expr_to_operand(body)?;
84
85
        // Return the result
86
0
        self.builder.return_(entry, Some(result));
87
88
0
        let function = self
89
0
            .builder
90
0
            .finish_function()
91
0
            .ok_or_else(|| anyhow!("Failed to finish function"))?;
92
93
0
        let mut functions = HashMap::new();
94
0
        functions.insert(func_name.clone(), function);
95
96
0
        Ok(Program {
97
0
            functions,
98
0
            entry: func_name,
99
0
        })
100
0
    }
101
102
    /// Lower a main expression (wrap in main function)
103
0
    fn lower_main_expr(&mut self, expr: &Expr) -> Result<Program> {
104
0
        self.builder.start_function("main".to_string(), Type::Unit);
105
106
0
        let entry = self.builder.new_block();
107
0
        self.current_block = Some(entry);
108
109
        // Lower the expression
110
0
        let _result = self.lower_expr_to_operand(expr)?;
111
112
        // Main function returns unit
113
0
        self.builder
114
0
            .return_(entry, Some(Operand::Constant(Constant::Unit)));
115
116
0
        let function = self
117
0
            .builder
118
0
            .finish_function()
119
0
            .ok_or_else(|| anyhow!("Failed to finish main function"))?;
120
121
0
        let mut functions = HashMap::new();
122
0
        functions.insert("main".to_string(), function);
123
124
0
        Ok(Program {
125
0
            functions,
126
0
            entry: "main".to_string(),
127
0
        })
128
0
    }
129
130
    /// Lower an expression to an operand
131
0
    fn lower_expr_to_operand(&mut self, expr: &Expr) -> Result<Operand> {
132
0
        let block = self
133
0
            .current_block
134
0
            .ok_or_else(|| anyhow!("No current block"))?;
135
136
0
        match &expr.kind {
137
0
            ExprKind::Literal(lit) => Ok(Operand::Constant(Self::lower_literal(lit))),
138
0
            ExprKind::Identifier(name) => {
139
0
                if let Some(local) = self.builder.get_local(name) {
140
0
                    Ok(Operand::Copy(Place::Local(local)))
141
                } else {
142
0
                    Err(anyhow!("Unbound variable: {}", name))
143
                }
144
            }
145
0
            ExprKind::Binary { op, left, right } => {
146
0
                let left_op = self.lower_expr_to_operand(left)?;
147
0
                let right_op = self.lower_expr_to_operand(right)?;
148
0
                let mir_op = Self::lower_binary_op(*op);
149
150
                // Create a variable for the result
151
0
                let result_ty = Self::infer_binary_result_type(*op);
152
0
                let temp = self.builder.alloc_local(result_ty, false, None);
153
154
0
                self.builder
155
0
                    .binary_op(block, temp, mir_op, left_op, right_op);
156
0
                Ok(Operand::Move(Place::Local(temp)))
157
            }
158
0
            ExprKind::Unary { op, operand } => {
159
0
                let operand_mir = self.lower_expr_to_operand(operand)?;
160
0
                let mir_op = Self::lower_unary_op(*op);
161
162
0
                let result_ty = Self::infer_unary_result_type(*op);
163
0
                let temp = self.builder.alloc_local(result_ty, false, None);
164
165
0
                self.builder.unary_op(block, temp, mir_op, operand_mir);
166
0
                Ok(Operand::Move(Place::Local(temp)))
167
            }
168
            ExprKind::Let {
169
0
                name, value, body, ..
170
            } => {
171
                // Lower the value
172
0
                let value_op = self.lower_expr_to_operand(value)?;
173
174
                // Create a local for the binding
175
0
                let local_ty = Type::I32; // Default type inference
176
0
                let local = self
177
0
                    .builder
178
0
                    .alloc_local(local_ty, false, Some(name.clone()));
179
180
                // Assign the value
181
0
                self.builder
182
0
                    .assign(block, Place::Local(local), Rvalue::Use(value_op));
183
184
                // Lower the body with the binding in scope
185
0
                self.lower_expr_to_operand(body)
186
            }
187
            ExprKind::If {
188
0
                condition,
189
0
                then_branch,
190
0
                else_branch,
191
            } => {
192
0
                let cond_op = self.lower_expr_to_operand(condition)?;
193
194
0
                let then_block = self.builder.new_block();
195
0
                let else_block = self.builder.new_block();
196
0
                let merge_block = self.builder.new_block();
197
198
                // Branch based on condition
199
0
                self.builder.branch(block, cond_op, then_block, else_block);
200
201
                // Lower then branch
202
0
                self.current_block = Some(then_block);
203
0
                let then_result = self.lower_expr_to_operand(then_branch)?;
204
0
                self.builder.goto(then_block, merge_block);
205
206
                // Lower else branch
207
0
                self.current_block = Some(else_block);
208
0
                let _else_result = if let Some(else_expr) = else_branch {
209
0
                    self.lower_expr_to_operand(else_expr)?
210
                } else {
211
0
                    Operand::Constant(Constant::Unit)
212
                };
213
0
                self.builder.goto(else_block, merge_block);
214
215
                // Create a variable for the result
216
0
                let result_ty = Type::I32; // Type inference would determine this
217
0
                let result_temp = self.builder.alloc_local(result_ty, false, None);
218
219
                // In merge block, we'd need phi nodes, but for simplicity we'll just use the then result
220
0
                self.current_block = Some(merge_block);
221
0
                self.builder.assign(
222
0
                    merge_block,
223
0
                    Place::Local(result_temp),
224
0
                    Rvalue::Use(then_result),
225
                );
226
227
0
                Ok(Operand::Move(Place::Local(result_temp)))
228
            }
229
0
            ExprKind::Call { func, args } => {
230
0
                let func_op = self.lower_expr_to_operand(func)?;
231
0
                let mut arg_ops = Vec::new();
232
233
0
                for arg in args {
234
0
                    arg_ops.push(self.lower_expr_to_operand(arg)?);
235
                }
236
237
                // Create a variable for the result
238
0
                let result_ty = Type::I32; // Type inference would determine this
239
0
                let result_temp = self.builder.alloc_local(result_ty, false, None);
240
241
                // Create call terminator
242
0
                let next_block = self.builder.call(block, result_temp, func_op, arg_ops);
243
0
                self.current_block = Some(next_block);
244
245
0
                Ok(Operand::Move(Place::Local(result_temp)))
246
            }
247
0
            ExprKind::Block(exprs) => {
248
0
                if exprs.is_empty() {
249
0
                    Ok(Operand::Constant(Constant::Unit))
250
                } else {
251
                    // Lower all expressions, return the last one
252
0
                    let mut result = Operand::Constant(Constant::Unit);
253
0
                    for expr in exprs {
254
0
                        result = self.lower_expr_to_operand(expr)?;
255
                    }
256
0
                    Ok(result)
257
                }
258
            }
259
            _ => {
260
                // For unsupported expressions, return unit for now
261
0
                Ok(Operand::Constant(Constant::Unit))
262
            }
263
        }
264
0
    }
265
266
    /// Lower a literal to a constant
267
0
    fn lower_literal(lit: &Literal) -> Constant {
268
0
        match lit {
269
0
            Literal::Integer(i) => Constant::Int(i128::from(*i), Type::I32),
270
0
            Literal::Float(f) => Constant::Float(*f, Type::F64),
271
0
            Literal::String(s) => Constant::String(s.clone()),
272
0
            Literal::Bool(b) => Constant::Bool(*b),
273
0
            Literal::Char(c) => Constant::Char(*c),
274
0
            Literal::Unit => Constant::Unit,
275
        }
276
0
    }
277
278
    /// Lower binary operator
279
0
    fn lower_binary_op(op: AstBinOp) -> BinOp {
280
0
        match op {
281
0
            AstBinOp::Add => BinOp::Add,
282
0
            AstBinOp::Subtract => BinOp::Sub,
283
0
            AstBinOp::Multiply => BinOp::Mul,
284
0
            AstBinOp::Divide => BinOp::Div,
285
0
            AstBinOp::Modulo => BinOp::Rem,
286
0
            AstBinOp::Power => BinOp::Pow,
287
0
            AstBinOp::Equal => BinOp::Eq,
288
0
            AstBinOp::NotEqual => BinOp::Ne,
289
0
            AstBinOp::Less => BinOp::Lt,
290
0
            AstBinOp::LessEqual => BinOp::Le,
291
0
            AstBinOp::Greater => BinOp::Gt,
292
0
            AstBinOp::GreaterEqual => BinOp::Ge,
293
0
            AstBinOp::And => BinOp::And,
294
0
            AstBinOp::Or => BinOp::Or,
295
0
            AstBinOp::NullCoalesce => BinOp::NullCoalesce,
296
0
            AstBinOp::BitwiseAnd => BinOp::BitAnd,
297
0
            AstBinOp::BitwiseOr => BinOp::BitOr,
298
0
            AstBinOp::BitwiseXor => BinOp::BitXor,
299
0
            AstBinOp::LeftShift => BinOp::Shl,
300
        }
301
0
    }
302
303
    /// Lower unary operator
304
0
    fn lower_unary_op(op: AstUnOp) -> UnOp {
305
0
        match op {
306
0
            AstUnOp::Negate => UnOp::Neg,
307
0
            AstUnOp::Not => UnOp::Not,
308
0
            AstUnOp::BitwiseNot => UnOp::BitNot,
309
0
            AstUnOp::Reference => UnOp::Ref,
310
        }
311
0
    }
312
313
    /// Convert AST Type to MIR Type
314
    #[allow(clippy::only_used_in_recursion)]
315
0
    fn ast_to_mir_type(&self, ast_ty: &AstType) -> Type {
316
        use crate::frontend::ast::TypeKind;
317
0
        match &ast_ty.kind {
318
0
            TypeKind::Named(name) => match name.as_str() {
319
0
                "bool" => Type::Bool,
320
0
                "i8" => Type::I8,
321
0
                "i16" => Type::I16,
322
0
                "i32" => Type::I32,
323
0
                "i64" => Type::I64,
324
0
                "i128" => Type::I128,
325
0
                "u8" => Type::U8,
326
0
                "u16" => Type::U16,
327
0
                "u32" => Type::U32,
328
0
                "u64" => Type::U64,
329
0
                "u128" => Type::U128,
330
0
                "f32" => Type::F32,
331
0
                "f64" => Type::F64,
332
0
                "String" => Type::String,
333
0
                "()" => Type::Unit,
334
0
                _ => Type::UserType(name.clone()),
335
            },
336
0
            TypeKind::Generic { base, params } => {
337
0
                match base.as_str() {
338
0
                    "Vec" if params.len() == 1 => {
339
0
                        Type::Vec(Box::new(self.ast_to_mir_type(&params[0])))
340
                    }
341
0
                    "Array" if params.len() == 1 => {
342
                        // For simplicity, treat arrays as vectors for now
343
0
                        Type::Vec(Box::new(self.ast_to_mir_type(&params[0])))
344
                    }
345
0
                    _ => Type::UserType(base.clone()),
346
                }
347
            }
348
0
            TypeKind::Optional(inner) => {
349
                // For simplicity, treat optionals as user types for now
350
0
                Type::UserType(format!("Option<{:?}>", inner.kind))
351
            }
352
0
            TypeKind::Function { params, ret } => {
353
0
                let param_types = params.iter().map(|p| self.ast_to_mir_type(p)).collect();
354
0
                Type::FnPtr(param_types, Box::new(self.ast_to_mir_type(ret)))
355
            }
356
0
            TypeKind::List(inner) => Type::Vec(Box::new(self.ast_to_mir_type(inner))),
357
            TypeKind::DataFrame { .. } => {
358
                // Map DataFrames to a user type for now
359
0
                Type::UserType("DataFrame".to_string())
360
            }
361
            TypeKind::Series { .. } => {
362
                // Map Series to a user type for now
363
0
                Type::UserType("Series".to_string())
364
            }
365
0
            TypeKind::Tuple(types) => {
366
0
                let mir_types: Vec<_> = types.iter().map(|t| self.ast_to_mir_type(t)).collect();
367
0
                Type::Tuple(mir_types)
368
            }
369
0
            TypeKind::Reference { inner, .. } => {
370
                // For MIR, treat references as the inner type for now
371
0
                self.ast_to_mir_type(inner)
372
            }
373
        }
374
0
    }
375
376
    /// Infer result type for binary operations
377
0
    fn infer_binary_result_type(op: AstBinOp) -> Type {
378
0
        match op {
379
            AstBinOp::Add
380
            | AstBinOp::Subtract
381
            | AstBinOp::Multiply
382
            | AstBinOp::Divide
383
            | AstBinOp::Modulo
384
            | AstBinOp::Power
385
            | AstBinOp::BitwiseAnd
386
            | AstBinOp::BitwiseOr
387
            | AstBinOp::BitwiseXor
388
0
            | AstBinOp::LeftShift => Type::I32,
389
            AstBinOp::Equal
390
            | AstBinOp::NotEqual
391
            | AstBinOp::Less
392
            | AstBinOp::LessEqual
393
            | AstBinOp::Greater
394
            | AstBinOp::GreaterEqual
395
            | AstBinOp::And
396
0
            | AstBinOp::Or => Type::Bool,
397
0
            AstBinOp::NullCoalesce => Type::I32, // For now, assume Int (could be improved)
398
        }
399
0
    }
400
401
    /// Infer result type for unary operations
402
0
    fn infer_unary_result_type(op: AstUnOp) -> Type {
403
0
        match op {
404
0
            AstUnOp::Negate | AstUnOp::BitwiseNot => Type::I32,
405
0
            AstUnOp::Not => Type::Bool,
406
0
            AstUnOp::Reference => Type::Ref(Box::new(Type::I32), Mutability::Immutable), // Reference creates an immutable reference
407
        }
408
0
    }
409
}
410
411
impl Default for LoweringContext {
412
0
    fn default() -> Self {
413
0
        Self::new()
414
0
    }
415
}
416
417
#[cfg(test)]
418
#[allow(clippy::unwrap_used, clippy::panic)]
419
mod tests {
420
    use super::*;
421
    use crate::frontend::Parser;
422
423
    #[test]
424
    fn test_lower_literal() -> Result<()> {
425
        let mut parser = Parser::new("42");
426
        let ast = parser.parse()?;
427
428
        let mut ctx = LoweringContext::new();
429
        let program = ctx.lower_expr(&ast)?;
430
431
        assert_eq!(program.entry, "main");
432
        assert!(program.functions.contains_key("main"));
433
434
        Ok(())
435
    }
436
437
    #[test]
438
    fn test_lower_binary_expr() -> Result<()> {
439
        let mut parser = Parser::new("1 + 2");
440
        let ast = parser.parse()?;
441
442
        let mut ctx = LoweringContext::new();
443
        let program = ctx.lower_expr(&ast)?;
444
445
        let main_func = &program.functions["main"];
446
        assert!(!main_func.blocks.is_empty());
447
448
        Ok(())
449
    }
450
451
    #[test]
452
    fn test_lower_function() -> Result<()> {
453
        let mut parser = Parser::new("fun add(x: i32, y: i32) -> i32 { x + y }");
454
        let ast = parser.parse()?;
455
456
        let mut ctx = LoweringContext::new();
457
        let program = ctx.lower_expr(&ast)?;
458
459
        assert!(program.functions.contains_key("add"));
460
        let func = &program.functions["add"];
461
        assert_eq!(func.params.len(), 2);
462
463
        Ok(())
464
    }
465
466
    #[test]
467
    fn test_lower_if_expr() -> Result<()> {
468
        let mut parser = Parser::new("if true { 1 } else { 2 }");
469
        let ast = parser.parse()?;
470
471
        let mut ctx = LoweringContext::new();
472
        let program = ctx.lower_expr(&ast)?;
473
474
        let main_func = &program.functions["main"];
475
        // Should have multiple blocks for if/else
476
        assert!(main_func.blocks.len() > 1);
477
478
        Ok(())
479
    }
480
}