logicaffeine_compile/analysis/
ownership.rs1use std::collections::HashMap;
40use crate::ast::stmt::{Stmt, Expr};
41use crate::intern::{Interner, Symbol};
42use crate::token::Span;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum VarState {
47 Owned,
49 Moved,
51 MaybeMoved,
53 Borrowed,
55}
56
57#[derive(Debug, Clone)]
59pub struct OwnershipError {
60 pub kind: OwnershipErrorKind,
61 pub span: Span,
62}
63
64#[derive(Debug, Clone)]
65pub enum OwnershipErrorKind {
66 UseAfterMove { variable: String },
68 UseAfterMaybeMove { variable: String, branch: String },
70 DoubleMoved { variable: String },
72}
73
74impl std::fmt::Display for OwnershipError {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 match &self.kind {
77 OwnershipErrorKind::UseAfterMove { variable } => {
78 write!(f, "Cannot use '{}' after giving it away.\n\n\
79 You transferred ownership of '{}' with Give.\n\
80 Once given, you cannot use it anymore.\n\n\
81 Tip: Use Show instead to lend without giving up ownership.",
82 variable, variable)
83 }
84 OwnershipErrorKind::UseAfterMaybeMove { variable, branch } => {
85 write!(f, "Cannot use '{}' - it might have been given away in {}.\n\n\
86 If the {} branch executes, '{}' will be moved.\n\
87 Using it afterward is not safe.\n\n\
88 Tip: Move the usage inside the branch, or restructure to ensure ownership.",
89 variable, branch, branch, variable)
90 }
91 OwnershipErrorKind::DoubleMoved { variable } => {
92 write!(f, "Cannot give '{}' twice.\n\n\
93 You already transferred ownership of '{}' with Give.\n\
94 You cannot give it again.\n\n\
95 Tip: Consider using Copy to duplicate the value.",
96 variable, variable)
97 }
98 }
99 }
100}
101
102impl std::error::Error for OwnershipError {}
103
104pub struct OwnershipChecker<'a> {
106 state: HashMap<Symbol, VarState>,
108 interner: &'a Interner,
110}
111
112impl<'a> OwnershipChecker<'a> {
113 pub fn new(interner: &'a Interner) -> Self {
114 Self {
115 state: HashMap::new(),
116 interner,
117 }
118 }
119
120 pub fn check_program(&mut self, stmts: &[Stmt<'_>]) -> Result<(), OwnershipError> {
122 self.check_block(stmts)
123 }
124
125 fn check_block(&mut self, stmts: &[Stmt<'_>]) -> Result<(), OwnershipError> {
126 for stmt in stmts {
127 self.check_stmt(stmt)?;
128 }
129 Ok(())
130 }
131
132 fn check_stmt(&mut self, stmt: &Stmt<'_>) -> Result<(), OwnershipError> {
133 match stmt {
134 Stmt::Let { var, value, .. } => {
135 self.check_not_moved(value)?;
137 self.state.insert(*var, VarState::Owned);
139 }
140
141 Stmt::Give { object, .. } => {
142 if let Expr::Identifier(sym) = object {
144 let current = self.state.get(sym).copied().unwrap_or(VarState::Owned);
145 match current {
146 VarState::Moved => {
147 return Err(OwnershipError {
148 kind: OwnershipErrorKind::DoubleMoved {
149 variable: self.interner.resolve(*sym).to_string(),
150 },
151 span: Span::default(),
152 });
153 }
154 VarState::MaybeMoved => {
155 return Err(OwnershipError {
156 kind: OwnershipErrorKind::UseAfterMaybeMove {
157 variable: self.interner.resolve(*sym).to_string(),
158 branch: "a previous branch".to_string(),
159 },
160 span: Span::default(),
161 });
162 }
163 _ => {
164 self.state.insert(*sym, VarState::Moved);
165 }
166 }
167 } else {
168 self.check_not_moved(object)?;
170 }
171 }
172
173 Stmt::Show { object, .. } => {
174 self.check_not_moved(object)?;
176 if let Expr::Identifier(sym) = object {
178 let current = self.state.get(sym).copied();
179 if current == Some(VarState::Owned) || current.is_none() {
180 self.state.insert(*sym, VarState::Borrowed);
181 }
182 }
183 }
184
185 Stmt::If { then_block, else_block, .. } => {
186 let state_before = self.state.clone();
188
189 self.check_block(then_block)?;
191 let state_after_then = self.state.clone();
192
193 let state_after_else = if let Some(else_b) = else_block {
195 self.state = state_before.clone();
196 self.check_block(else_b)?;
197 self.state.clone()
198 } else {
199 state_before.clone()
200 };
201
202 self.state = self.merge_states(&state_after_then, &state_after_else);
204 }
205
206 Stmt::While { body, .. } => {
207 let state_before = self.state.clone();
209
210 self.check_block(body)?;
212 let state_after_body = self.state.clone();
213
214 self.state = self.merge_states(&state_before, &state_after_body);
217 }
218
219 Stmt::Repeat { body, .. } => {
220 self.check_block(body)?;
222 }
223
224 Stmt::Zone { body, .. } => {
225 self.check_block(body)?;
226 }
227
228 Stmt::Inspect { arms, .. } => {
229 if arms.is_empty() {
230 return Ok(());
231 }
232
233 let state_before = self.state.clone();
235 let mut branch_states = Vec::new();
236
237 for arm in arms {
238 self.state = state_before.clone();
239 self.check_block(arm.body)?;
240 branch_states.push(self.state.clone());
241 }
242
243 if let Some(first) = branch_states.first() {
245 let mut merged = first.clone();
246 for state in branch_states.iter().skip(1) {
247 merged = self.merge_states(&merged, state);
248 }
249 self.state = merged;
250 }
251 }
252
253 Stmt::Return { value: Some(expr) } => {
254 self.check_not_moved(expr)?;
255 }
256
257 Stmt::Return { value: None } => {}
258
259 Stmt::Set { value, .. } => {
260 self.check_not_moved(value)?;
261 }
262
263 Stmt::Call { args, .. } => {
264 for arg in args {
265 self.check_not_moved(arg)?;
266 }
267 }
268
269 _ => {}
271 }
272 Ok(())
273 }
274
275 fn check_not_moved(&self, expr: &Expr<'_>) -> Result<(), OwnershipError> {
277 match expr {
278 Expr::Identifier(sym) => {
279 match self.state.get(sym).copied() {
280 Some(VarState::Moved) => {
281 Err(OwnershipError {
282 kind: OwnershipErrorKind::UseAfterMove {
283 variable: self.interner.resolve(*sym).to_string(),
284 },
285 span: Span::default(),
286 })
287 }
288 Some(VarState::MaybeMoved) => {
289 Err(OwnershipError {
290 kind: OwnershipErrorKind::UseAfterMaybeMove {
291 variable: self.interner.resolve(*sym).to_string(),
292 branch: "a conditional branch".to_string(),
293 },
294 span: Span::default(),
295 })
296 }
297 _ => Ok(())
298 }
299 }
300 Expr::BinaryOp { left, right, .. } => {
301 self.check_not_moved(left)?;
302 self.check_not_moved(right)?;
303 Ok(())
304 }
305 Expr::FieldAccess { object, .. } => {
306 self.check_not_moved(object)
307 }
308 Expr::Index { collection, index } => {
309 self.check_not_moved(collection)?;
310 self.check_not_moved(index)?;
311 Ok(())
312 }
313 Expr::Slice { collection, start, end } => {
314 self.check_not_moved(collection)?;
315 self.check_not_moved(start)?;
316 self.check_not_moved(end)?;
317 Ok(())
318 }
319 Expr::Call { args, .. } => {
320 for arg in args {
321 self.check_not_moved(arg)?;
322 }
323 Ok(())
324 }
325 Expr::List(items) | Expr::Tuple(items) => {
326 for item in items {
327 self.check_not_moved(item)?;
328 }
329 Ok(())
330 }
331 Expr::Range { start, end } => {
332 self.check_not_moved(start)?;
333 self.check_not_moved(end)?;
334 Ok(())
335 }
336 Expr::New { init_fields, .. } => {
337 for (_, field_expr) in init_fields {
338 self.check_not_moved(field_expr)?;
339 }
340 Ok(())
341 }
342 Expr::NewVariant { fields, .. } => {
343 for (_, field_expr) in fields {
344 self.check_not_moved(field_expr)?;
345 }
346 Ok(())
347 }
348 Expr::Copy { expr } | Expr::Length { collection: expr } => {
349 self.check_not_moved(expr)
350 }
351 Expr::ManifestOf { zone } => {
352 self.check_not_moved(zone)
353 }
354 Expr::ChunkAt { index, zone } => {
355 self.check_not_moved(index)?;
356 self.check_not_moved(zone)
357 }
358 Expr::Contains { collection, value } => {
359 self.check_not_moved(collection)?;
360 self.check_not_moved(value)
361 }
362 Expr::Union { left, right } | Expr::Intersection { left, right } => {
363 self.check_not_moved(left)?;
364 self.check_not_moved(right)
365 }
366 Expr::Literal(_) => Ok(()),
368 }
369 }
370
371 fn merge_states(
373 &self,
374 state_a: &HashMap<Symbol, VarState>,
375 state_b: &HashMap<Symbol, VarState>,
376 ) -> HashMap<Symbol, VarState> {
377 let mut merged = state_a.clone();
378
379 for (sym, state_b_val) in state_b {
381 let state_a_val = state_a.get(sym).copied().unwrap_or(VarState::Owned);
382
383 let merged_val = match (state_a_val, *state_b_val) {
384 (VarState::Moved, VarState::Moved) => VarState::Moved,
386 (VarState::Moved, _) | (_, VarState::Moved) => VarState::MaybeMoved,
388 (VarState::MaybeMoved, _) | (_, VarState::MaybeMoved) => VarState::MaybeMoved,
390 (VarState::Borrowed, VarState::Borrowed) => VarState::Borrowed,
392 (VarState::Borrowed, _) | (_, VarState::Borrowed) => VarState::Borrowed,
394 (VarState::Owned, VarState::Owned) => VarState::Owned,
396 };
397
398 merged.insert(*sym, merged_val);
399 }
400
401 for sym in state_a.keys() {
403 if !state_b.contains_key(sym) {
404 }
407 }
408
409 merged
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416
417 #[test]
418 fn test_ownership_checker_basic() {
419 let interner = Interner::new();
420 let checker = OwnershipChecker::new(&interner);
421 assert!(checker.state.is_empty());
422 }
423}