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 var_states(&self) -> &HashMap<Symbol, VarState> {
122 &self.state
123 }
124
125 pub fn check_program(&mut self, stmts: &[Stmt<'_>]) -> Result<(), OwnershipError> {
127 self.check_block(stmts)
128 }
129
130 fn check_block(&mut self, stmts: &[Stmt<'_>]) -> Result<(), OwnershipError> {
131 for stmt in stmts {
132 self.check_stmt(stmt)?;
133 }
134 Ok(())
135 }
136
137 fn check_stmt(&mut self, stmt: &Stmt<'_>) -> Result<(), OwnershipError> {
138 match stmt {
139 Stmt::Let { var, value, .. } => {
140 self.check_not_moved(value)?;
142 self.state.insert(*var, VarState::Owned);
144 }
145
146 Stmt::Give { object, .. } => {
147 if let Expr::Identifier(sym) = object {
149 let current = self.state.get(sym).copied().unwrap_or(VarState::Owned);
150 match current {
151 VarState::Moved => {
152 return Err(OwnershipError {
153 kind: OwnershipErrorKind::DoubleMoved {
154 variable: self.interner.resolve(*sym).to_string(),
155 },
156 span: Span::default(),
157 });
158 }
159 VarState::MaybeMoved => {
160 return Err(OwnershipError {
161 kind: OwnershipErrorKind::UseAfterMaybeMove {
162 variable: self.interner.resolve(*sym).to_string(),
163 branch: "a previous branch".to_string(),
164 },
165 span: Span::default(),
166 });
167 }
168 _ => {
169 self.state.insert(*sym, VarState::Moved);
170 }
171 }
172 } else {
173 self.check_not_moved(object)?;
175 }
176 }
177
178 Stmt::Show { object, .. } => {
179 self.check_not_moved(object)?;
181 if let Expr::Identifier(sym) = object {
183 let current = self.state.get(sym).copied();
184 if current == Some(VarState::Owned) || current.is_none() {
185 self.state.insert(*sym, VarState::Borrowed);
186 }
187 }
188 }
189
190 Stmt::If { then_block, else_block, .. } => {
191 let state_before = self.state.clone();
193
194 self.check_block(then_block)?;
196 let state_after_then = self.state.clone();
197
198 let state_after_else = if let Some(else_b) = else_block {
200 self.state = state_before.clone();
201 self.check_block(else_b)?;
202 self.state.clone()
203 } else {
204 state_before.clone()
205 };
206
207 self.state = self.merge_states(&state_after_then, &state_after_else);
209 }
210
211 Stmt::While { body, .. } => {
212 let state_before = self.state.clone();
214
215 self.check_block(body)?;
217 let state_after_body = self.state.clone();
218
219 self.state = self.merge_states(&state_before, &state_after_body);
222 }
223
224 Stmt::Repeat { body, .. } => {
225 self.check_block(body)?;
227 }
228
229 Stmt::Zone { body, .. } => {
230 self.check_block(body)?;
231 }
232
233 Stmt::Inspect { arms, .. } => {
234 if arms.is_empty() {
235 return Ok(());
236 }
237
238 let state_before = self.state.clone();
240 let mut branch_states = Vec::new();
241
242 for arm in arms {
243 self.state = state_before.clone();
244 self.check_block(arm.body)?;
245 branch_states.push(self.state.clone());
246 }
247
248 if let Some(first) = branch_states.first() {
250 let mut merged = first.clone();
251 for state in branch_states.iter().skip(1) {
252 merged = self.merge_states(&merged, state);
253 }
254 self.state = merged;
255 }
256 }
257
258 Stmt::Return { value: Some(expr) } => {
259 self.check_not_moved(expr)?;
260 }
261
262 Stmt::Return { value: None } => {}
263
264 Stmt::Set { value, .. } => {
265 self.check_not_moved(value)?;
266 }
267
268 Stmt::Call { args, .. } => {
269 for arg in args {
270 self.check_not_moved(arg)?;
271 }
272 }
273
274 Stmt::Escape { .. } => {}
277
278 _ => {}
280 }
281 Ok(())
282 }
283
284 fn check_not_moved(&self, expr: &Expr<'_>) -> Result<(), OwnershipError> {
286 match expr {
287 Expr::Identifier(sym) => {
288 match self.state.get(sym).copied() {
289 Some(VarState::Moved) => {
290 Err(OwnershipError {
291 kind: OwnershipErrorKind::UseAfterMove {
292 variable: self.interner.resolve(*sym).to_string(),
293 },
294 span: Span::default(),
295 })
296 }
297 Some(VarState::MaybeMoved) => {
298 Err(OwnershipError {
299 kind: OwnershipErrorKind::UseAfterMaybeMove {
300 variable: self.interner.resolve(*sym).to_string(),
301 branch: "a conditional branch".to_string(),
302 },
303 span: Span::default(),
304 })
305 }
306 _ => Ok(())
307 }
308 }
309 Expr::BinaryOp { left, right, .. } => {
310 self.check_not_moved(left)?;
311 self.check_not_moved(right)?;
312 Ok(())
313 }
314 Expr::FieldAccess { object, .. } => {
315 self.check_not_moved(object)
316 }
317 Expr::Index { collection, index } => {
318 self.check_not_moved(collection)?;
319 self.check_not_moved(index)?;
320 Ok(())
321 }
322 Expr::Slice { collection, start, end } => {
323 self.check_not_moved(collection)?;
324 self.check_not_moved(start)?;
325 self.check_not_moved(end)?;
326 Ok(())
327 }
328 Expr::Call { args, .. } => {
329 for arg in args {
330 self.check_not_moved(arg)?;
331 }
332 Ok(())
333 }
334 Expr::List(items) | Expr::Tuple(items) => {
335 for item in items {
336 self.check_not_moved(item)?;
337 }
338 Ok(())
339 }
340 Expr::Range { start, end } => {
341 self.check_not_moved(start)?;
342 self.check_not_moved(end)?;
343 Ok(())
344 }
345 Expr::New { init_fields, .. } => {
346 for (_, field_expr) in init_fields {
347 self.check_not_moved(field_expr)?;
348 }
349 Ok(())
350 }
351 Expr::NewVariant { fields, .. } => {
352 for (_, field_expr) in fields {
353 self.check_not_moved(field_expr)?;
354 }
355 Ok(())
356 }
357 Expr::Copy { expr } | Expr::Give { value: expr } | Expr::Length { collection: expr } => {
358 self.check_not_moved(expr)
359 }
360 Expr::ManifestOf { zone } => {
361 self.check_not_moved(zone)
362 }
363 Expr::ChunkAt { index, zone } => {
364 self.check_not_moved(index)?;
365 self.check_not_moved(zone)
366 }
367 Expr::Contains { collection, value } => {
368 self.check_not_moved(collection)?;
369 self.check_not_moved(value)
370 }
371 Expr::Union { left, right } | Expr::Intersection { left, right } => {
372 self.check_not_moved(left)?;
373 self.check_not_moved(right)
374 }
375 Expr::WithCapacity { value, capacity } => {
376 self.check_not_moved(value)?;
377 self.check_not_moved(capacity)
378 }
379 Expr::OptionSome { value } => self.check_not_moved(value),
380 Expr::OptionNone => Ok(()),
381
382 Expr::Escape { .. } => Ok(()),
384
385 Expr::Literal(_) => Ok(()),
387 }
388 }
389
390 fn merge_states(
392 &self,
393 state_a: &HashMap<Symbol, VarState>,
394 state_b: &HashMap<Symbol, VarState>,
395 ) -> HashMap<Symbol, VarState> {
396 let mut merged = state_a.clone();
397
398 for (sym, state_b_val) in state_b {
400 let state_a_val = state_a.get(sym).copied().unwrap_or(VarState::Owned);
401
402 let merged_val = match (state_a_val, *state_b_val) {
403 (VarState::Moved, VarState::Moved) => VarState::Moved,
405 (VarState::Moved, _) | (_, VarState::Moved) => VarState::MaybeMoved,
407 (VarState::MaybeMoved, _) | (_, VarState::MaybeMoved) => VarState::MaybeMoved,
409 (VarState::Borrowed, VarState::Borrowed) => VarState::Borrowed,
411 (VarState::Borrowed, _) | (_, VarState::Borrowed) => VarState::Borrowed,
413 (VarState::Owned, VarState::Owned) => VarState::Owned,
415 };
416
417 merged.insert(*sym, merged_val);
418 }
419
420 for sym in state_a.keys() {
422 if !state_b.contains_key(sym) {
423 }
426 }
427
428 merged
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn test_ownership_checker_basic() {
438 let interner = Interner::new();
439 let checker = OwnershipChecker::new(&interner);
440 assert!(checker.state.is_empty());
441 }
442}