ruka_hir_lower/
lib.rs

1//! Lowering from frontend AST nodes into the HIR model.
2
3use cranelift_entity::PrimaryMap;
4use std::collections::BTreeMap;
5
6use ruka_frontend::{Block, CallArg, Expr, IntrinsicId, Program, Stmt, TypeRef};
7use ruka_hir::{
8    DuplicateExternFunction, ExprId, FuncId, HirEnumDecl, HirExpr, HirExternFunction, HirFunction,
9    HirMatchArm, HirParam, HirProgram, HirStmt, HirStructDecl, HirType, StmtId,
10};
11
12/// Lower a parsed frontend program into HIR.
13pub fn lower_program(program: &Program) -> HirProgram {
14    let mut lowerer = Lowerer {
15        functions: PrimaryMap::new(),
16        extern_functions: BTreeMap::new(),
17        duplicate_extern_functions: Vec::new(),
18        statements: PrimaryMap::new(),
19        expressions: PrimaryMap::new(),
20        expr_spans: PrimaryMap::new(),
21    };
22
23    for module in &program.extern_modules {
24        for function in &module.functions {
25            let full_name = format!("{}::{}", module.name, function.name);
26            if let Some(existing) = lowerer.extern_functions.get(&full_name) {
27                lowerer
28                    .duplicate_extern_functions
29                    .push(DuplicateExternFunction {
30                        name: full_name,
31                        first_span: existing.span,
32                        duplicate_span: function.span,
33                    });
34                continue;
35            }
36
37            lowerer.extern_functions.insert(
38                full_name,
39                HirExternFunction {
40                    symbol: format!("{}::{}", module.name, function.symbol),
41                    params: function
42                        .params
43                        .iter()
44                        .map(|param| HirParam {
45                            name: param.name.clone(),
46                            name_span: param.name_span,
47                            ty: lower_type_ref(&param.ty),
48                        })
49                        .collect(),
50                    return_type: lower_type_ref(&function.return_type),
51                    span: function.span,
52                },
53            );
54        }
55    }
56
57    for function in &program.functions {
58        let body = lowerer.lower_block(&function.body);
59        let params = function
60            .params
61            .iter()
62            .map(|param| HirParam {
63                name: param.name.clone(),
64                name_span: param.name_span,
65                ty: lower_type_ref(&param.ty),
66            })
67            .collect();
68        let return_type = lower_type_ref(&function.return_type);
69
70        let _func_id: FuncId = lowerer.functions.push(HirFunction {
71            name: function.name.clone(),
72            params,
73            return_type,
74            body,
75        });
76    }
77
78    HirProgram {
79        functions: lowerer.functions,
80        extern_functions: lowerer.extern_functions,
81        duplicate_extern_functions: lowerer.duplicate_extern_functions,
82        structs: program
83            .structs
84            .iter()
85            .map(|decl| HirStructDecl {
86                name: decl.name.clone(),
87                type_params: decl.type_params.clone(),
88                fields: decl.fields.clone(),
89            })
90            .collect(),
91        enums: program
92            .enums
93            .iter()
94            .map(|decl| HirEnumDecl {
95                name: decl.name.clone(),
96                type_params: decl.type_params.clone(),
97                variants: decl.variants.clone(),
98            })
99            .collect(),
100        statements: lowerer.statements,
101        expressions: lowerer.expressions,
102        expr_spans: lowerer.expr_spans,
103    }
104}
105
106fn lower_type_ref(ty: &TypeRef) -> HirType {
107    HirType {
108        mode: ty.mode,
109        ty: ty.ty.clone(),
110    }
111}
112
113struct Lowerer {
114    functions: PrimaryMap<FuncId, HirFunction>,
115    extern_functions: BTreeMap<String, HirExternFunction>,
116    duplicate_extern_functions: Vec<DuplicateExternFunction>,
117    statements: PrimaryMap<StmtId, HirStmt>,
118    expressions: PrimaryMap<ExprId, HirExpr>,
119    expr_spans: PrimaryMap<ExprId, Option<ruka_frontend::SourceSpan>>,
120}
121
122impl Lowerer {
123    fn lower_block(&mut self, block: &Block) -> Vec<StmtId> {
124        block
125            .statements
126            .iter()
127            .map(|stmt| self.lower_stmt(stmt))
128            .collect()
129    }
130
131    fn lower_stmt(&mut self, stmt: &Stmt) -> StmtId {
132        let lowered = match stmt {
133            Stmt::Let {
134                name,
135                name_span,
136                ownership,
137                mode,
138                value,
139            } => {
140                let value = self.lower_expr(value);
141                HirStmt::Let {
142                    name: name.clone(),
143                    name_span: *name_span,
144                    ownership: *ownership,
145                    mode: *mode,
146                    value,
147                }
148            }
149            Stmt::Assign {
150                target,
151                mode,
152                value,
153            } => {
154                let value = self.lower_expr(value);
155                HirStmt::Assign {
156                    target: target.clone(),
157                    mode: *mode,
158                    value,
159                }
160            }
161            Stmt::If {
162                condition,
163                then_block,
164                else_block,
165            } => {
166                let condition = self.lower_expr(condition);
167                let then_body = self.lower_block(then_block);
168                let else_body = else_block.as_ref().map(|block| self.lower_block(block));
169                HirStmt::If {
170                    condition,
171                    then_body,
172                    else_body,
173                }
174            }
175            Stmt::For {
176                binding,
177                iterable,
178                body,
179            } => {
180                let iterable = self.lower_expr(iterable);
181                let body = self.lower_block(body);
182                HirStmt::For {
183                    binding_name: binding.name.clone(),
184                    binding_name_span: binding.span,
185                    binding_is_mut_borrow: binding.is_mut_borrow,
186                    iterable,
187                    body,
188                }
189            }
190            Stmt::While { condition, body } => {
191                let condition = self.lower_expr(condition);
192                let body = self.lower_block(body);
193                HirStmt::While { condition, body }
194            }
195            Stmt::Match { value, arms } => {
196                let value = self.lower_expr(value);
197                let arms = arms
198                    .iter()
199                    .map(|arm| HirMatchArm {
200                        pattern: arm.pattern.clone(),
201                        body: self.lower_block(&arm.body),
202                    })
203                    .collect();
204                HirStmt::Match { value, arms }
205            }
206            Stmt::Meta { body } => HirStmt::Meta {
207                body_len: body.statements.len(),
208            },
209            Stmt::Expr { expr, has_semi } => {
210                let expr = self.lower_expr(expr);
211                HirStmt::Expr {
212                    expr,
213                    has_semi: *has_semi,
214                }
215            }
216        };
217        self.statements.push(lowered)
218    }
219
220    fn lower_expr(&mut self, expr: &Expr) -> ExprId {
221        let span = expr_source_span(expr);
222        let lowered = match expr {
223            Expr::Ident { name, span } => HirExpr::Ident {
224                name: name.clone(),
225                span: *span,
226            },
227            Expr::Int(value) => HirExpr::Number(ruka_frontend::NumberLiteral {
228                raw: value.to_string(),
229            }),
230            Expr::Number(value) => HirExpr::Number(value.clone()),
231            Expr::String(value) => HirExpr::String(value.clone()),
232            Expr::Bool(value) => HirExpr::Bool(*value),
233            Expr::Array { items, .. } => {
234                let item_ids = items.iter().map(|item| self.lower_expr(item)).collect();
235                HirExpr::Array(item_ids)
236            }
237            Expr::Tuple { items, .. } => {
238                let item_ids = items.iter().map(|item| self.lower_expr(item)).collect();
239                HirExpr::Tuple(item_ids)
240            }
241            Expr::StructLit { name, fields, .. } => {
242                let lowered_fields = fields
243                    .iter()
244                    .map(|field| (field.name.clone(), self.lower_expr(&field.value)))
245                    .collect();
246                HirExpr::StructLit {
247                    name: name.clone(),
248                    fields: lowered_fields,
249                }
250            }
251            Expr::Call { callee, args, .. } => {
252                let callee = self.lower_expr(callee);
253                let args = args
254                    .iter()
255                    .map(|arg| match arg {
256                        CallArg::Expr(expr) => self.lower_expr(expr),
257                        CallArg::Spread(_) => {
258                            panic!("tuple spread should be resolved during elaboration")
259                        }
260                        CallArg::Type(_) => {
261                            panic!("type call arguments should be resolved during elaboration")
262                        }
263                    })
264                    .collect();
265                HirExpr::Call { callee, args }
266            }
267            Expr::IntrinsicCall { name, args, .. } => {
268                let mut lowered_args = Vec::new();
269                let mut type_args = Vec::new();
270                for arg in args {
271                    match arg {
272                        CallArg::Expr(expr) => lowered_args.push(self.lower_expr(expr)),
273                        CallArg::Spread(_) => {
274                            panic!("tuple spread should be resolved during elaboration")
275                        }
276                        CallArg::Type(ty) => type_args.push(ty.clone()),
277                    }
278                }
279                HirExpr::IntrinsicCall {
280                    intrinsic: IntrinsicId::parse(name),
281                    type_args,
282                    args: lowered_args,
283                }
284            }
285            Expr::Field { base, field } => {
286                let base = self.lower_expr(base);
287                HirExpr::Field {
288                    base,
289                    field: field.clone(),
290                }
291            }
292            Expr::Index { base, index } => {
293                let base = self.lower_expr(base);
294                let index = self.lower_expr(index);
295                HirExpr::Index { base, index }
296            }
297            Expr::SliceRange { base, start, end } => {
298                let base = self.lower_expr(base);
299                let start = start.as_ref().map(|expr| self.lower_expr(expr));
300                let end = end.as_ref().map(|expr| self.lower_expr(expr));
301                HirExpr::SliceRange { base, start, end }
302            }
303            Expr::Prefix { op, value } => {
304                let value = self.lower_expr(value);
305                HirExpr::Prefix { op: *op, value }
306            }
307            Expr::Binary { op, lhs, rhs } => {
308                let lhs = self.lower_expr(lhs);
309                let rhs = self.lower_expr(rhs);
310                HirExpr::Binary { op: *op, lhs, rhs }
311            }
312            Expr::Relational { op, lhs, rhs } => {
313                let lhs = self.lower_expr(lhs);
314                let rhs = self.lower_expr(rhs);
315                HirExpr::Relational { op: *op, lhs, rhs }
316            }
317            Expr::Block(block) => {
318                let body = self.lower_block(block);
319                HirExpr::Block { body }
320            }
321            Expr::Splice(value) => HirExpr::SpliceMeta {
322                value: format!("{value:?}"),
323            },
324        };
325        let expr_id = self.expressions.push(lowered);
326        let span_slot = self.expr_spans.push(span);
327        assert_eq!(
328            expr_id, span_slot,
329            "expression and span arenas should stay aligned"
330        );
331        expr_id
332    }
333}
334
335/// Return the direct source span attached to one frontend expression when available.
336fn expr_source_span(expr: &Expr) -> Option<ruka_frontend::SourceSpan> {
337    match expr {
338        Expr::Ident { span, .. } => Some(*span),
339        Expr::Array { span, .. } => Some(*span),
340        Expr::Tuple { span, .. } => Some(*span),
341        Expr::StructLit { span, .. } => Some(*span),
342        Expr::Call { span, .. } => Some(*span),
343        Expr::IntrinsicCall { span, .. } => Some(*span),
344        _ => None,
345    }
346}