1use std::collections::HashMap;
40use crate::ast::stmt::{BinaryOpKind, Literal, Stmt, Expr, TypeExpr};
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 types: HashMap<Symbol, bool>,
110 interner: &'a Interner,
112}
113
114impl<'a> OwnershipChecker<'a> {
115 pub fn new(interner: &'a Interner) -> Self {
116 Self {
117 state: HashMap::new(),
118 types: HashMap::new(),
119 interner,
120 }
121 }
122
123 pub fn var_states(&self) -> &HashMap<Symbol, VarState> {
125 &self.state
126 }
127
128 fn is_copy_sym(&self, sym: Symbol) -> bool {
131 self.types.get(&sym).copied().unwrap_or(true)
132 }
133
134 fn infer_copy_from_expr(&self, expr: &Expr) -> bool {
137 match expr {
138 Expr::Literal(Literal::Number(_)) => true,
139 Expr::Literal(Literal::Float(_)) => true,
140 Expr::Literal(Literal::Boolean(_)) => true,
141 Expr::Literal(Literal::Nothing) => true,
142 Expr::Literal(Literal::Text(_)) => false,
143 Expr::Identifier(sym) => self.is_copy_sym(*sym),
144 Expr::New { .. } => false,
145 Expr::List(_) => false,
146 Expr::InterpolatedString(_) => false,
147 Expr::Copy { .. } => true,
148 Expr::BinaryOp { op: BinaryOpKind::Concat, .. } => false,
149 Expr::BinaryOp { .. } => true,
150 Expr::Contains { .. } => true,
151 Expr::Length { .. } => true,
152 _ => true,
153 }
154 }
155
156 fn mark_moves_in_expr(&mut self, expr: &Expr) {
159 match expr {
160 Expr::Call { args, .. } | Expr::CallExpr { args, .. } => {
161 for arg in args.iter() {
162 if let Expr::Identifier(sym) = arg {
163 if !self.is_copy_sym(*sym) {
164 self.state.insert(*sym, VarState::Moved);
165 }
166 }
167 self.mark_moves_in_expr(arg);
168 }
169 }
170 Expr::BinaryOp { left, right, .. } => {
171 self.mark_moves_in_expr(left);
172 self.mark_moves_in_expr(right);
173 }
174 Expr::Index { collection, index } => {
175 self.mark_moves_in_expr(collection);
176 self.mark_moves_in_expr(index);
177 }
178 Expr::FieldAccess { object, .. } => {
179 self.mark_moves_in_expr(object);
180 }
181 _ => {}
182 }
183 }
184
185 fn infer_copy_from_type_name(&self, ty: &TypeExpr) -> bool {
188 match ty {
189 TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
190 let name = self.interner.resolve(*sym);
191 matches!(name, "Int" | "Nat" | "Float" | "Bool" | "Char" | "Byte")
192 }
193 TypeExpr::Generic { .. } => false,
194 TypeExpr::Function { .. } => true,
195 _ => true,
196 }
197 }
198
199 pub fn check_program(&mut self, stmts: &[Stmt<'_>]) -> Result<(), OwnershipError> {
201 self.check_block(stmts)
202 }
203
204 fn check_block(&mut self, stmts: &[Stmt<'_>]) -> Result<(), OwnershipError> {
205 for stmt in stmts {
206 self.check_stmt(stmt)?;
207 }
208 Ok(())
209 }
210
211 fn check_stmt(&mut self, stmt: &Stmt<'_>) -> Result<(), OwnershipError> {
212 match stmt {
213 Stmt::Let { var, value, .. } => {
214 self.check_not_moved(value)?;
216 if let Expr::Identifier(sym) = value {
218 if !self.is_copy_sym(*sym) {
219 self.state.insert(*sym, VarState::Moved);
220 }
221 }
222 self.mark_moves_in_expr(value);
224 let is_copy = self.infer_copy_from_expr(value);
226 self.state.insert(*var, VarState::Owned);
227 self.types.insert(*var, is_copy);
228 }
229
230 Stmt::Give { object, .. } => {
231 if let Expr::Identifier(sym) = object {
233 let current = self.state.get(sym).copied().unwrap_or(VarState::Owned);
234 match current {
235 VarState::Moved => {
236 return Err(OwnershipError {
237 kind: OwnershipErrorKind::DoubleMoved {
238 variable: self.interner.resolve(*sym).to_string(),
239 },
240 span: Span::default(),
241 });
242 }
243 VarState::MaybeMoved => {
244 return Err(OwnershipError {
245 kind: OwnershipErrorKind::UseAfterMaybeMove {
246 variable: self.interner.resolve(*sym).to_string(),
247 branch: "a previous branch".to_string(),
248 },
249 span: Span::default(),
250 });
251 }
252 _ => {
253 self.state.insert(*sym, VarState::Moved);
254 }
255 }
256 } else {
257 self.check_not_moved(object)?;
259 }
260 }
261
262 Stmt::Show { object, .. } => {
263 self.check_not_moved(object)?;
265 if let Expr::Identifier(sym) = object {
267 let current = self.state.get(sym).copied();
268 if current == Some(VarState::Owned) || current.is_none() {
269 self.state.insert(*sym, VarState::Borrowed);
270 }
271 }
272 }
273
274 Stmt::If { then_block, else_block, .. } => {
275 let state_before = self.state.clone();
277
278 self.check_block(then_block)?;
280 let state_after_then = self.state.clone();
281
282 let state_after_else = if let Some(else_b) = else_block {
284 self.state = state_before.clone();
285 self.check_block(else_b)?;
286 self.state.clone()
287 } else {
288 state_before.clone()
289 };
290
291 self.state = self.merge_states(&state_after_then, &state_after_else);
293 }
294
295 Stmt::While { body, .. } => {
296 let state_before = self.state.clone();
298
299 self.check_block(body)?;
301 let state_after_body = self.state.clone();
302
303 self.state = self.merge_states(&state_before, &state_after_body);
306 }
307
308 Stmt::Repeat { body, .. } => {
309 self.check_block(body)?;
311 }
312
313 Stmt::Zone { body, .. } => {
314 self.check_block(body)?;
315 }
316
317 Stmt::Inspect { arms, .. } => {
318 if arms.is_empty() {
319 return Ok(());
320 }
321
322 let state_before = self.state.clone();
324 let mut branch_states = Vec::new();
325
326 for arm in arms {
327 self.state = state_before.clone();
328 self.check_block(arm.body)?;
329 branch_states.push(self.state.clone());
330 }
331
332 if let Some(first) = branch_states.first() {
334 let mut merged = first.clone();
335 for state in branch_states.iter().skip(1) {
336 merged = self.merge_states(&merged, state);
337 }
338 self.state = merged;
339 }
340 }
341
342 Stmt::Return { value: Some(expr) } => {
343 self.check_not_moved(expr)?;
344 self.mark_moves_in_expr(expr);
345 }
346
347 Stmt::Return { value: None } => {}
348
349 Stmt::Set { value, .. } => {
350 self.check_not_moved(value)?;
351 self.mark_moves_in_expr(value);
353 }
354
355 Stmt::Call { args, .. } => {
356 for arg in args.iter() {
357 self.check_not_moved(arg)?;
358 }
359 for arg in args.iter() {
361 if let Expr::Identifier(sym) = arg {
362 if !self.is_copy_sym(*sym) {
363 self.state.insert(*sym, VarState::Moved);
364 }
365 }
366 }
367 }
368
369 Stmt::FunctionDef { params, body, .. } => {
370 let saved_state = self.state.clone();
372 let saved_types = self.types.clone();
373 for (param_sym, param_type) in params.iter() {
375 self.state.insert(*param_sym, VarState::Owned);
376 let is_copy = self.infer_copy_from_type_name(param_type);
377 self.types.insert(*param_sym, is_copy);
378 }
379 self.check_block(body)?;
380 self.state = saved_state;
381 self.types = saved_types;
382 }
383
384 Stmt::Escape { .. } => {}
387
388 _ => {}
390 }
391 Ok(())
392 }
393
394 fn check_not_moved(&self, expr: &Expr<'_>) -> Result<(), OwnershipError> {
396 match expr {
397 Expr::InterpolatedString(parts) => {
398 for part in parts {
399 if let crate::ast::stmt::StringPart::Expr { value, .. } = part {
400 self.check_not_moved(value)?;
401 }
402 }
403 Ok(())
404 }
405 Expr::Identifier(sym) => {
406 match self.state.get(sym).copied() {
407 Some(VarState::Moved) => {
408 Err(OwnershipError {
409 kind: OwnershipErrorKind::UseAfterMove {
410 variable: self.interner.resolve(*sym).to_string(),
411 },
412 span: Span::default(),
413 })
414 }
415 Some(VarState::MaybeMoved) => {
416 Err(OwnershipError {
417 kind: OwnershipErrorKind::UseAfterMaybeMove {
418 variable: self.interner.resolve(*sym).to_string(),
419 branch: "a conditional branch".to_string(),
420 },
421 span: Span::default(),
422 })
423 }
424 _ => Ok(())
425 }
426 }
427 Expr::BinaryOp { left, right, .. } => {
428 self.check_not_moved(left)?;
429 self.check_not_moved(right)?;
430 Ok(())
431 }
432 Expr::FieldAccess { object, .. } => {
433 self.check_not_moved(object)
434 }
435 Expr::Index { collection, index } => {
436 self.check_not_moved(collection)?;
437 self.check_not_moved(index)?;
438 Ok(())
439 }
440 Expr::Slice { collection, start, end } => {
441 self.check_not_moved(collection)?;
442 self.check_not_moved(start)?;
443 self.check_not_moved(end)?;
444 Ok(())
445 }
446 Expr::Call { args, .. } => {
447 for arg in args {
448 self.check_not_moved(arg)?;
449 }
450 Ok(())
451 }
452 Expr::List(items) | Expr::Tuple(items) => {
453 for item in items {
454 self.check_not_moved(item)?;
455 }
456 Ok(())
457 }
458 Expr::Range { start, end } => {
459 self.check_not_moved(start)?;
460 self.check_not_moved(end)?;
461 Ok(())
462 }
463 Expr::New { init_fields, .. } => {
464 for (_, field_expr) in init_fields {
465 self.check_not_moved(field_expr)?;
466 }
467 Ok(())
468 }
469 Expr::NewVariant { fields, .. } => {
470 for (_, field_expr) in fields {
471 self.check_not_moved(field_expr)?;
472 }
473 Ok(())
474 }
475 Expr::Copy { expr } | Expr::Give { value: expr } | Expr::Length { collection: expr }
476 | Expr::Not { operand: expr } => {
477 self.check_not_moved(expr)
478 }
479 Expr::ManifestOf { zone } => {
480 self.check_not_moved(zone)
481 }
482 Expr::ChunkAt { index, zone } => {
483 self.check_not_moved(index)?;
484 self.check_not_moved(zone)
485 }
486 Expr::Contains { collection, value } => {
487 self.check_not_moved(collection)?;
488 self.check_not_moved(value)
489 }
490 Expr::Union { left, right } | Expr::Intersection { left, right } => {
491 self.check_not_moved(left)?;
492 self.check_not_moved(right)
493 }
494 Expr::WithCapacity { value, capacity } => {
495 self.check_not_moved(value)?;
496 self.check_not_moved(capacity)
497 }
498 Expr::OptionSome { value } => self.check_not_moved(value),
499 Expr::OptionNone => Ok(()),
500
501 Expr::Escape { .. } => Ok(()),
503
504 Expr::Closure { body, .. } => {
508 match body {
509 crate::ast::stmt::ClosureBody::Expression(expr) => {
510 self.check_not_moved(expr)
511 }
512 crate::ast::stmt::ClosureBody::Block(_) => Ok(()),
513 }
514 }
515
516 Expr::CallExpr { callee, args } => {
517 self.check_not_moved(callee)?;
518 for arg in args {
519 self.check_not_moved(arg)?;
520 }
521 Ok(())
522 }
523
524 Expr::Literal(_) => Ok(()),
526 }
527 }
528
529 fn merge_states(
531 &self,
532 state_a: &HashMap<Symbol, VarState>,
533 state_b: &HashMap<Symbol, VarState>,
534 ) -> HashMap<Symbol, VarState> {
535 let mut merged = state_a.clone();
536
537 for (sym, state_b_val) in state_b {
539 let state_a_val = state_a.get(sym).copied().unwrap_or(VarState::Owned);
540
541 let merged_val = match (state_a_val, *state_b_val) {
542 (VarState::Moved, VarState::Moved) => VarState::Moved,
544 (VarState::Moved, _) | (_, VarState::Moved) => VarState::MaybeMoved,
546 (VarState::MaybeMoved, _) | (_, VarState::MaybeMoved) => VarState::MaybeMoved,
548 (VarState::Borrowed, VarState::Borrowed) => VarState::Borrowed,
550 (VarState::Borrowed, _) | (_, VarState::Borrowed) => VarState::Borrowed,
552 (VarState::Owned, VarState::Owned) => VarState::Owned,
554 };
555
556 merged.insert(*sym, merged_val);
557 }
558
559 for sym in state_a.keys() {
561 if !state_b.contains_key(sym) {
562 }
565 }
566
567 merged
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[test]
576 fn test_ownership_checker_basic() {
577 let interner = Interner::new();
578 let checker = OwnershipChecker::new(&interner);
579 assert!(checker.state.is_empty());
580 }
581}