ruka_check/
lib.rs

1//! Semantic checking for HIR programs.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use cranelift_entity::EntityRef;
6use ruka_frontend::{
7    AssignMode, AssignTarget, BinaryOp, IntrinsicId, OwnershipMode, PrefixOp, RelationalOp,
8    SourceSpan, TypeExpr,
9};
10use ruka_hir::{
11    ExprId, HirEnumDecl, HirExpr, HirExternFunction, HirFunction, HirMatchArm, HirProgram, HirStmt,
12    HirStructDecl, StmtId,
13};
14pub use ruka_types::Ty;
15
16use ruka_types::{can_safely_coerce_numeric, is_truncation_cast};
17
18mod checker_calls;
19mod checker_entry;
20mod checker_expr;
21mod checker_state;
22mod checker_stmt;
23mod decls;
24mod errors;
25mod utils;
26
27use decls::{
28    EnumDef, StructDef, build_enum_defs, build_struct_defs, populate_enum_defs,
29    resolve_extern_signature, resolve_signature, resolve_type_expr, split_variant_path,
30};
31pub use errors::CheckError;
32use utils::*;
33
34/// Checked parameter signature used for call validation and lowering.
35#[derive(Debug, Clone)]
36pub struct ParamSig {
37    /// Parameter name in source order.
38    pub name: String,
39    /// Ownership mode declared for the parameter.
40    pub mode: OwnershipMode,
41    /// Resolved semantic type of the parameter.
42    pub ty: Ty,
43    /// Whether the parameter is mutated by the function body.
44    pub mutated_in_body: bool,
45}
46
47/// Checked function signature used during semantic analysis.
48#[derive(Debug, Clone)]
49pub struct FunctionSig {
50    /// Checked parameter list.
51    pub params: Vec<ParamSig>,
52    /// Checked function return type.
53    pub return_ty: Ty,
54}
55
56/// Fully checked program annotations used by later compiler stages.
57#[derive(Debug, Clone)]
58pub struct CheckedProgram {
59    /// Checked signatures keyed by function name.
60    pub signatures: BTreeMap<String, FunctionSig>,
61    /// Names of extern functions declared in the program.
62    pub extern_function_names: BTreeSet<String>,
63    expr_types: Vec<Ty>,
64    call_signatures: Vec<Option<FunctionSig>>,
65    local_symbols: Vec<LocalSymbol>,
66    local_symbol_occurrences: Vec<LocalSymbolOccurrence>,
67    expr_local_symbols: Vec<Option<usize>>,
68}
69
70/// Semantic metadata for one local binding symbol.
71#[derive(Debug, Clone)]
72pub struct LocalSymbol {
73    /// User-visible binding name.
74    pub name: String,
75    /// Inferred semantic type for the binding.
76    pub ty: Ty,
77}
78
79/// One local-symbol occurrence discovered during checking.
80#[derive(Debug, Clone)]
81pub struct LocalSymbolOccurrence {
82    /// Stable index into `CheckedProgram::local_symbols`.
83    pub symbol_id: usize,
84    /// User-visible binding name.
85    pub name: String,
86    /// Whether this occurrence is the binding declaration.
87    pub is_declaration: bool,
88    /// Source span covering the identifier token.
89    pub span: SourceSpan,
90}
91
92impl CheckedProgram {
93    /// Return the semantic type assigned to an expression.
94    pub fn expr_ty(&self, expr_id: ExprId) -> &Ty {
95        &self.expr_types[expr_id.index()]
96    }
97
98    /// Return the resolved call signature for a call expression, if any.
99    pub fn call_signature(&self, expr_id: ExprId) -> Option<&FunctionSig> {
100        self.call_signatures[expr_id.index()].as_ref()
101    }
102
103    /// Return all local symbols discovered during checking.
104    pub fn local_symbols(&self) -> &[LocalSymbol] {
105        &self.local_symbols
106    }
107
108    /// Return semantic local-symbol occurrences in deterministic source order.
109    pub fn local_symbol_occurrences(&self) -> &[LocalSymbolOccurrence] {
110        &self.local_symbol_occurrences
111    }
112
113    /// Return the local symbol id resolved for one identifier expression, if any.
114    pub fn expr_local_symbol(&self, expr_id: ExprId) -> Option<usize> {
115        self.expr_local_symbols[expr_id.index()]
116    }
117}
118
119/// Check a HIR program and produce semantic annotations for later stages.
120pub fn check_program(program: &HirProgram) -> Result<CheckedProgram, CheckError> {
121    if let Some(duplicate) = program.duplicate_extern_functions.first() {
122        return Err(CheckError::DuplicateExternFunction {
123            name: duplicate.name.clone(),
124            first_decl: duplicate.first_span,
125            redecl: duplicate.duplicate_span,
126        });
127    }
128
129    let mut enum_defs = build_enum_defs(&program.enums)?;
130    let struct_defs = build_struct_defs(&program.structs, &enum_defs)?;
131    populate_enum_defs(&mut enum_defs, &program.enums, &struct_defs)?;
132    utils::validate_finite_value_types(&struct_defs, &enum_defs)?;
133
134    let mut signatures = BTreeMap::new();
135    let mut extern_function_names = BTreeSet::new();
136    for (name, function) in &program.extern_functions {
137        signatures.insert(
138            name.clone(),
139            resolve_extern_signature(function, &struct_defs, &enum_defs)?,
140        );
141        let _ = extern_function_names.insert(name.clone());
142    }
143    for (_, function) in program.functions.iter() {
144        signatures.insert(
145            function.name.clone(),
146            resolve_signature(function, &struct_defs, &enum_defs)?,
147        );
148    }
149
150    let mut checker = Checker {
151        program,
152        signatures,
153        expr_types: vec![Ty::Unit; program.expressions.len()],
154        call_signatures: vec![None; program.expressions.len()],
155        local_symbols: Vec::new(),
156        local_symbol_occurrences: Vec::new(),
157        expr_local_symbols: vec![None; program.expressions.len()],
158        scopes: Vec::new(),
159        loan_scopes: Vec::new(),
160        active_loans: Vec::new(),
161        struct_defs,
162        enum_defs,
163    };
164
165    for (_, function) in program.functions.iter() {
166        checker.check_function(function)?;
167    }
168
169    Ok(CheckedProgram {
170        signatures: checker.signatures,
171        extern_function_names,
172        expr_types: checker.expr_types,
173        call_signatures: checker.call_signatures,
174        local_symbols: checker.local_symbols,
175        local_symbol_occurrences: checker.local_symbol_occurrences,
176        expr_local_symbols: checker.expr_local_symbols,
177    })
178}
179
180struct Checker<'a> {
181    program: &'a HirProgram,
182    signatures: BTreeMap<String, FunctionSig>,
183    expr_types: Vec<Ty>,
184    call_signatures: Vec<Option<FunctionSig>>,
185    local_symbols: Vec<LocalSymbol>,
186    local_symbol_occurrences: Vec<LocalSymbolOccurrence>,
187    expr_local_symbols: Vec<Option<usize>>,
188    scopes: Vec<BTreeMap<String, LocalBinding>>,
189    loan_scopes: Vec<Vec<usize>>,
190    active_loans: Vec<Loan>,
191    struct_defs: BTreeMap<String, StructDef>,
192    enum_defs: BTreeMap<String, EnumDef>,
193}
194
195#[derive(Debug, Clone)]
196struct LocalBinding {
197    ty: Ty,
198    binding_kind: BindingKind,
199    place: Option<Place>,
200    symbol_id: usize,
201    moved: bool,
202    was_mutated: bool,
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206enum LoanKind {
207    Shared,
208    Mutable,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
212enum Projection {
213    Field(String),
214    IndexLike,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
218struct Place {
219    root: String,
220    projections: Vec<Projection>,
221}
222
223#[derive(Debug, Clone)]
224struct Loan {
225    place: Place,
226    kind: LoanKind,
227    owner: String,
228    active: bool,
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232enum BindingKind {
233    ReadOnlyAlias,
234    ReadOnlyOwned,
235    Owned,
236    RefMut,
237}
238
239impl BindingKind {
240    /// Return whether assignment through the binding is allowed.
241    fn allows_assignment(self) -> bool {
242        matches!(self, Self::Owned | Self::RefMut)
243    }
244
245    /// Return whether mutable borrowing from the binding is allowed.
246    fn allows_mut_borrow(self) -> bool {
247        matches!(self, Self::Owned | Self::RefMut)
248    }
249
250    /// Return whether ownership may be moved out of the binding.
251    fn allows_move(self) -> bool {
252        matches!(self, Self::Owned)
253    }
254}