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 moved_at: HashMap<Symbol, usize>,
115 current_top_stmt: usize,
117}
118
119#[derive(Debug)]
123pub struct OwnershipFinding {
124 pub stmt_index: usize,
125 pub cause_stmt_index: Option<usize>,
126 pub error: OwnershipError,
127}
128
129impl<'a> OwnershipChecker<'a> {
130 pub fn new(interner: &'a Interner) -> Self {
131 Self {
132 state: HashMap::new(),
133 types: HashMap::new(),
134 interner,
135 moved_at: HashMap::new(),
136 current_top_stmt: 0,
137 }
138 }
139
140 fn mark_moved(&mut self, sym: Symbol) {
142 self.state.insert(sym, VarState::Moved);
143 self.moved_at.insert(sym, self.current_top_stmt);
144 }
145
146 fn symbol_for(&self, error: &OwnershipError) -> Option<Symbol> {
148 let name = match &error.kind {
149 OwnershipErrorKind::UseAfterMove { variable }
150 | OwnershipErrorKind::UseAfterMaybeMove { variable, .. }
151 | OwnershipErrorKind::DoubleMoved { variable } => variable,
152 };
153 self.state
154 .keys()
155 .copied()
156 .find(|s| self.interner.resolve(*s) == name)
157 }
158
159 pub fn check_program_collect(&mut self, stmts: &[Stmt<'_>]) -> Vec<OwnershipFinding> {
166 let mut findings = Vec::new();
167 for (index, stmt) in stmts.iter().enumerate() {
168 self.current_top_stmt = index;
169 if let Err(error) = self.check_stmt(stmt) {
170 let sym = self.symbol_for(&error);
171 let cause_stmt_index = sym.and_then(|s| self.moved_at.get(&s).copied());
172 if let Some(s) = sym {
173 self.state.insert(s, VarState::Owned);
174 }
175 findings.push(OwnershipFinding {
176 stmt_index: index,
177 cause_stmt_index,
178 error,
179 });
180 }
181 }
182 findings
183 }
184
185 pub fn var_states(&self) -> &HashMap<Symbol, VarState> {
187 &self.state
188 }
189
190 fn is_copy_sym(&self, sym: Symbol) -> bool {
193 self.types.get(&sym).copied().unwrap_or(true)
194 }
195
196 fn infer_copy_from_expr(&self, expr: &Expr) -> bool {
199 match expr {
200 Expr::Literal(Literal::Number(_)) => true,
201 Expr::Literal(Literal::Float(_)) => true,
202 Expr::Literal(Literal::Boolean(_)) => true,
203 Expr::Literal(Literal::Nothing) => true,
204 Expr::Literal(Literal::Text(_)) => false,
205 Expr::Identifier(sym) => self.is_copy_sym(*sym),
206 Expr::New { .. } => false,
207 Expr::List(_) => false,
208 Expr::InterpolatedString(_) => false,
209 Expr::Copy { .. } => true,
210 Expr::BinaryOp { op: BinaryOpKind::Concat, .. } => false,
211 Expr::BinaryOp { .. } => true,
212 Expr::Contains { .. } => true,
213 Expr::Length { .. } => true,
214 _ => true,
215 }
216 }
217
218 fn mark_moves_in_expr(&mut self, expr: &Expr) {
221 match expr {
222 Expr::Call { args, .. } | Expr::CallExpr { args, .. } => {
223 for arg in args.iter() {
224 if let Expr::Identifier(sym) = arg {
225 if !self.is_copy_sym(*sym) {
226 self.mark_moved(*sym);
227 }
228 }
229 self.mark_moves_in_expr(arg);
230 }
231 }
232 Expr::BinaryOp { left, right, .. } => {
233 self.mark_moves_in_expr(left);
234 self.mark_moves_in_expr(right);
235 }
236 Expr::Index { collection, index } => {
237 self.mark_moves_in_expr(collection);
238 self.mark_moves_in_expr(index);
239 }
240 Expr::FieldAccess { object, .. } => {
241 self.mark_moves_in_expr(object);
242 }
243 _ => {}
244 }
245 }
246
247 fn infer_copy_from_type_name(&self, ty: &TypeExpr) -> bool {
250 match ty {
251 TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
252 let name = self.interner.resolve(*sym);
253 matches!(name, "Int" | "Nat" | "Float" | "Bool" | "Char" | "Byte")
254 }
255 TypeExpr::Generic { .. } => false,
256 TypeExpr::Function { .. } => true,
257 _ => true,
258 }
259 }
260
261 pub fn check_program(&mut self, stmts: &[Stmt<'_>]) -> Result<(), OwnershipError> {
263 self.check_block(stmts)
264 }
265
266 fn check_block(&mut self, stmts: &[Stmt<'_>]) -> Result<(), OwnershipError> {
267 for stmt in stmts {
268 self.check_stmt(stmt)?;
269 }
270 Ok(())
271 }
272
273 fn check_stmt(&mut self, stmt: &Stmt<'_>) -> Result<(), OwnershipError> {
274 match stmt {
275 Stmt::Let { var, value, .. } => {
276 self.check_not_moved(value)?;
278 if let Expr::Identifier(sym) = value {
280 if !self.is_copy_sym(*sym) {
281 self.mark_moved(*sym);
282 }
283 }
284 self.mark_moves_in_expr(value);
286 let is_copy = self.infer_copy_from_expr(value);
288 self.state.insert(*var, VarState::Owned);
289 self.types.insert(*var, is_copy);
290 }
291
292 Stmt::Give { object, .. } => {
293 if let Expr::Identifier(sym) = object {
295 let current = self.state.get(sym).copied().unwrap_or(VarState::Owned);
296 match current {
297 VarState::Moved => {
298 return Err(OwnershipError {
299 kind: OwnershipErrorKind::DoubleMoved {
300 variable: self.interner.resolve(*sym).to_string(),
301 },
302 span: Span::default(),
303 });
304 }
305 VarState::MaybeMoved => {
306 return Err(OwnershipError {
307 kind: OwnershipErrorKind::UseAfterMaybeMove {
308 variable: self.interner.resolve(*sym).to_string(),
309 branch: "a previous branch".to_string(),
310 },
311 span: Span::default(),
312 });
313 }
314 _ => {
315 self.mark_moved(*sym);
316 }
317 }
318 } else {
319 self.check_not_moved(object)?;
321 }
322 }
323
324 Stmt::Show { object, .. } => {
325 self.check_not_moved(object)?;
327 if let Expr::Identifier(sym) = object {
329 let current = self.state.get(sym).copied();
330 if current == Some(VarState::Owned) || current.is_none() {
331 self.state.insert(*sym, VarState::Borrowed);
332 }
333 }
334 }
335
336 Stmt::If { then_block, else_block, .. } => {
337 let state_before = self.state.clone();
339
340 self.check_block(then_block)?;
342 let state_after_then = self.state.clone();
343
344 let state_after_else = if let Some(else_b) = else_block {
346 self.state = state_before.clone();
347 self.check_block(else_b)?;
348 self.state.clone()
349 } else {
350 state_before.clone()
351 };
352
353 self.state = self.merge_states(&state_after_then, &state_after_else);
355 }
356
357 Stmt::While { body, .. } => {
358 let state_before = self.state.clone();
360
361 self.check_block(body)?;
363 let state_after_body = self.state.clone();
364
365 self.state = self.merge_states(&state_before, &state_after_body);
368 }
369
370 Stmt::Repeat { body, .. } => {
371 self.check_block(body)?;
373 }
374
375 Stmt::Zone { body, .. } => {
376 self.check_block(body)?;
377 }
378
379 Stmt::Inspect { arms, .. } => {
380 if arms.is_empty() {
381 return Ok(());
382 }
383
384 let state_before = self.state.clone();
386 let mut branch_states = Vec::new();
387
388 for arm in arms {
389 self.state = state_before.clone();
390 self.check_block(arm.body)?;
391 branch_states.push(self.state.clone());
392 }
393
394 if let Some(first) = branch_states.first() {
396 let mut merged = first.clone();
397 for state in branch_states.iter().skip(1) {
398 merged = self.merge_states(&merged, state);
399 }
400 self.state = merged;
401 }
402 }
403
404 Stmt::Return { value: Some(expr) } => {
405 self.check_not_moved(expr)?;
406 self.mark_moves_in_expr(expr);
407 }
408
409 Stmt::Return { value: None } => {}
410
411 Stmt::Set { value, .. } => {
412 self.check_not_moved(value)?;
413 self.mark_moves_in_expr(value);
415 }
416
417 Stmt::Call { args, .. } => {
418 for arg in args.iter() {
419 self.check_not_moved(arg)?;
420 }
421 for arg in args.iter() {
423 if let Expr::Identifier(sym) = arg {
424 if !self.is_copy_sym(*sym) {
425 self.mark_moved(*sym);
426 }
427 }
428 }
429 }
430
431 Stmt::FunctionDef { params, body, .. } => {
432 let saved_state = self.state.clone();
434 let saved_types = self.types.clone();
435 for (param_sym, param_type) in params.iter() {
437 self.state.insert(*param_sym, VarState::Owned);
438 let is_copy = self.infer_copy_from_type_name(param_type);
439 self.types.insert(*param_sym, is_copy);
440 }
441 self.check_block(body)?;
442 self.state = saved_state;
443 self.types = saved_types;
444 }
445
446 Stmt::Escape { .. } => {}
449
450 _ => {}
452 }
453 Ok(())
454 }
455
456 fn check_not_moved(&self, expr: &Expr<'_>) -> Result<(), OwnershipError> {
458 match expr {
459 Expr::InterpolatedString(parts) => {
460 for part in parts {
461 if let crate::ast::stmt::StringPart::Expr { value, .. } = part {
462 self.check_not_moved(value)?;
463 }
464 }
465 Ok(())
466 }
467 Expr::Identifier(sym) => {
468 match self.state.get(sym).copied() {
469 Some(VarState::Moved) => {
470 Err(OwnershipError {
471 kind: OwnershipErrorKind::UseAfterMove {
472 variable: self.interner.resolve(*sym).to_string(),
473 },
474 span: Span::default(),
475 })
476 }
477 Some(VarState::MaybeMoved) => {
478 Err(OwnershipError {
479 kind: OwnershipErrorKind::UseAfterMaybeMove {
480 variable: self.interner.resolve(*sym).to_string(),
481 branch: "a conditional branch".to_string(),
482 },
483 span: Span::default(),
484 })
485 }
486 _ => Ok(())
487 }
488 }
489 Expr::BinaryOp { left, right, .. } => {
490 self.check_not_moved(left)?;
491 self.check_not_moved(right)?;
492 Ok(())
493 }
494 Expr::FieldAccess { object, .. } => {
495 self.check_not_moved(object)
496 }
497 Expr::Index { collection, index } => {
498 self.check_not_moved(collection)?;
499 self.check_not_moved(index)?;
500 Ok(())
501 }
502 Expr::Slice { collection, start, end } => {
503 self.check_not_moved(collection)?;
504 self.check_not_moved(start)?;
505 self.check_not_moved(end)?;
506 Ok(())
507 }
508 Expr::Call { args, .. } => {
509 for arg in args {
510 self.check_not_moved(arg)?;
511 }
512 Ok(())
513 }
514 Expr::List(items) | Expr::Tuple(items) => {
515 for item in items {
516 self.check_not_moved(item)?;
517 }
518 Ok(())
519 }
520 Expr::Range { start, end } => {
521 self.check_not_moved(start)?;
522 self.check_not_moved(end)?;
523 Ok(())
524 }
525 Expr::New { init_fields, .. } => {
526 for (_, field_expr) in init_fields {
527 self.check_not_moved(field_expr)?;
528 }
529 Ok(())
530 }
531 Expr::NewVariant { fields, .. } => {
532 for (_, field_expr) in fields {
533 self.check_not_moved(field_expr)?;
534 }
535 Ok(())
536 }
537 Expr::Copy { expr } | Expr::Give { value: expr } | Expr::Length { collection: expr }
538 | Expr::Not { operand: expr } => {
539 self.check_not_moved(expr)
540 }
541 Expr::ManifestOf { zone } => {
542 self.check_not_moved(zone)
543 }
544 Expr::ChunkAt { index, zone } => {
545 self.check_not_moved(index)?;
546 self.check_not_moved(zone)
547 }
548 Expr::Contains { collection, value } => {
549 self.check_not_moved(collection)?;
550 self.check_not_moved(value)
551 }
552 Expr::Union { left, right } | Expr::Intersection { left, right } => {
553 self.check_not_moved(left)?;
554 self.check_not_moved(right)
555 }
556 Expr::WithCapacity { value, capacity } => {
557 self.check_not_moved(value)?;
558 self.check_not_moved(capacity)
559 }
560 Expr::OptionSome { value } => self.check_not_moved(value),
561 Expr::OptionNone => Ok(()),
562
563 Expr::Escape { .. } => Ok(()),
565
566 Expr::Closure { body, .. } => {
570 match body {
571 crate::ast::stmt::ClosureBody::Expression(expr) => {
572 self.check_not_moved(expr)
573 }
574 crate::ast::stmt::ClosureBody::Block(_) => Ok(()),
575 }
576 }
577
578 Expr::CallExpr { callee, args } => {
579 self.check_not_moved(callee)?;
580 for arg in args {
581 self.check_not_moved(arg)?;
582 }
583 Ok(())
584 }
585
586 Expr::Literal(_) => Ok(()),
588 }
589 }
590
591 fn merge_states(
593 &self,
594 state_a: &HashMap<Symbol, VarState>,
595 state_b: &HashMap<Symbol, VarState>,
596 ) -> HashMap<Symbol, VarState> {
597 let mut merged = state_a.clone();
598
599 for (sym, state_b_val) in state_b {
601 let state_a_val = state_a.get(sym).copied().unwrap_or(VarState::Owned);
602
603 let merged_val = match (state_a_val, *state_b_val) {
604 (VarState::Moved, VarState::Moved) => VarState::Moved,
606 (VarState::Moved, _) | (_, VarState::Moved) => VarState::MaybeMoved,
608 (VarState::MaybeMoved, _) | (_, VarState::MaybeMoved) => VarState::MaybeMoved,
610 (VarState::Borrowed, VarState::Borrowed) => VarState::Borrowed,
612 (VarState::Borrowed, _) | (_, VarState::Borrowed) => VarState::Borrowed,
614 (VarState::Owned, VarState::Owned) => VarState::Owned,
616 };
617
618 merged.insert(*sym, merged_val);
619 }
620
621 for sym in state_a.keys() {
623 if !state_b.contains_key(sym) {
624 }
627 }
628
629 merged
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 #[test]
638 fn test_ownership_checker_basic() {
639 let interner = Interner::new();
640 let checker = OwnershipChecker::new(&interner);
641 assert!(checker.state.is_empty());
642 }
643}