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 Stmt::Escape { .. } => {}
272
273 _ => {}
275 }
276 Ok(())
277 }
278
279 fn check_not_moved(&self, expr: &Expr<'_>) -> Result<(), OwnershipError> {
281 match expr {
282 Expr::Identifier(sym) => {
283 match self.state.get(sym).copied() {
284 Some(VarState::Moved) => {
285 Err(OwnershipError {
286 kind: OwnershipErrorKind::UseAfterMove {
287 variable: self.interner.resolve(*sym).to_string(),
288 },
289 span: Span::default(),
290 })
291 }
292 Some(VarState::MaybeMoved) => {
293 Err(OwnershipError {
294 kind: OwnershipErrorKind::UseAfterMaybeMove {
295 variable: self.interner.resolve(*sym).to_string(),
296 branch: "a conditional branch".to_string(),
297 },
298 span: Span::default(),
299 })
300 }
301 _ => Ok(())
302 }
303 }
304 Expr::BinaryOp { left, right, .. } => {
305 self.check_not_moved(left)?;
306 self.check_not_moved(right)?;
307 Ok(())
308 }
309 Expr::FieldAccess { object, .. } => {
310 self.check_not_moved(object)
311 }
312 Expr::Index { collection, index } => {
313 self.check_not_moved(collection)?;
314 self.check_not_moved(index)?;
315 Ok(())
316 }
317 Expr::Slice { collection, start, end } => {
318 self.check_not_moved(collection)?;
319 self.check_not_moved(start)?;
320 self.check_not_moved(end)?;
321 Ok(())
322 }
323 Expr::Call { args, .. } => {
324 for arg in args {
325 self.check_not_moved(arg)?;
326 }
327 Ok(())
328 }
329 Expr::List(items) | Expr::Tuple(items) => {
330 for item in items {
331 self.check_not_moved(item)?;
332 }
333 Ok(())
334 }
335 Expr::Range { start, end } => {
336 self.check_not_moved(start)?;
337 self.check_not_moved(end)?;
338 Ok(())
339 }
340 Expr::New { init_fields, .. } => {
341 for (_, field_expr) in init_fields {
342 self.check_not_moved(field_expr)?;
343 }
344 Ok(())
345 }
346 Expr::NewVariant { fields, .. } => {
347 for (_, field_expr) in fields {
348 self.check_not_moved(field_expr)?;
349 }
350 Ok(())
351 }
352 Expr::Copy { expr } | Expr::Give { value: expr } | Expr::Length { collection: expr } => {
353 self.check_not_moved(expr)
354 }
355 Expr::ManifestOf { zone } => {
356 self.check_not_moved(zone)
357 }
358 Expr::ChunkAt { index, zone } => {
359 self.check_not_moved(index)?;
360 self.check_not_moved(zone)
361 }
362 Expr::Contains { collection, value } => {
363 self.check_not_moved(collection)?;
364 self.check_not_moved(value)
365 }
366 Expr::Union { left, right } | Expr::Intersection { left, right } => {
367 self.check_not_moved(left)?;
368 self.check_not_moved(right)
369 }
370 Expr::Escape { .. } => Ok(()),
372
373 Expr::Literal(_) => Ok(()),
375 }
376 }
377
378 fn merge_states(
380 &self,
381 state_a: &HashMap<Symbol, VarState>,
382 state_b: &HashMap<Symbol, VarState>,
383 ) -> HashMap<Symbol, VarState> {
384 let mut merged = state_a.clone();
385
386 for (sym, state_b_val) in state_b {
388 let state_a_val = state_a.get(sym).copied().unwrap_or(VarState::Owned);
389
390 let merged_val = match (state_a_val, *state_b_val) {
391 (VarState::Moved, VarState::Moved) => VarState::Moved,
393 (VarState::Moved, _) | (_, VarState::Moved) => VarState::MaybeMoved,
395 (VarState::MaybeMoved, _) | (_, VarState::MaybeMoved) => VarState::MaybeMoved,
397 (VarState::Borrowed, VarState::Borrowed) => VarState::Borrowed,
399 (VarState::Borrowed, _) | (_, VarState::Borrowed) => VarState::Borrowed,
401 (VarState::Owned, VarState::Owned) => VarState::Owned,
403 };
404
405 merged.insert(*sym, merged_val);
406 }
407
408 for sym in state_a.keys() {
410 if !state_b.contains_key(sym) {
411 }
414 }
415
416 merged
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn test_ownership_checker_basic() {
426 let interner = Interner::new();
427 let checker = OwnershipChecker::new(&interner);
428 assert!(checker.state.is_empty());
429 }
430}