## 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
    }