use std::collections::HashMap;
use crate::ast::stmt::{Stmt, Expr};
use crate::intern::{Interner, Symbol};
use crate::token::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VarState {
Owned,
Moved,
MaybeMoved,
Borrowed,
}
#[derive(Debug, Clone)]
pub struct OwnershipError {
pub kind: OwnershipErrorKind,
pub span: Span,
}
#[derive(Debug, Clone)]
pub enum OwnershipErrorKind {
UseAfterMove { variable: String },
UseAfterMaybeMove { variable: String, branch: String },
DoubleMoved { variable: String },
}
impl std::fmt::Display for OwnershipError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.kind {
OwnershipErrorKind::UseAfterMove { variable } => {
write!(f, "Cannot use '{}' after giving it away.\n\n\
You transferred ownership of '{}' with Give.\n\
Once given, you cannot use it anymore.\n\n\
Tip: Use Show instead to lend without giving up ownership.",
variable, variable)
}
OwnershipErrorKind::UseAfterMaybeMove { variable, branch } => {
write!(f, "Cannot use '{}' - it might have been given away in {}.\n\n\
If the {} branch executes, '{}' will be moved.\n\
Using it afterward is not safe.\n\n\
Tip: Move the usage inside the branch, or restructure to ensure ownership.",
variable, branch, branch, variable)
}
OwnershipErrorKind::DoubleMoved { variable } => {
write!(f, "Cannot give '{}' twice.\n\n\
You already transferred ownership of '{}' with Give.\n\
You cannot give it again.\n\n\
Tip: Consider using Copy to duplicate the value.",
variable, variable)
}
}
}
}
impl std::error::Error for OwnershipError {}
pub struct OwnershipChecker<'a> {
state: HashMap<Symbol, VarState>,
interner: &'a Interner,
}
impl<'a> OwnershipChecker<'a> {
pub fn new(interner: &'a Interner) -> Self {
Self {
state: HashMap::new(),
interner,
}
}
pub fn var_states(&self) -> &HashMap<Symbol, VarState> {
&self.state
}
pub fn check_program(&mut self, stmts: &[Stmt<'_>]) -> Result<(), OwnershipError> {
self.check_block(stmts)
}
fn check_block(&mut self, stmts: &[Stmt<'_>]) -> Result<(), OwnershipError> {
for stmt in stmts {
self.check_stmt(stmt)?;
}
Ok(())
}
fn check_stmt(&mut self, stmt: &Stmt<'_>) -> Result<(), OwnershipError> {
match stmt {
Stmt::Let { var, value, .. } => {
self.check_not_moved(value)?;
self.state.insert(*var, VarState::Owned);
}
Stmt::Give { object, .. } => {
if let Expr::Identifier(sym) = object {
let current = self.state.get(sym).copied().unwrap_or(VarState::Owned);
match current {
VarState::Moved => {
return Err(OwnershipError {
kind: OwnershipErrorKind::DoubleMoved {
variable: self.interner.resolve(*sym).to_string(),
},
span: Span::default(),
});
}
VarState::MaybeMoved => {
return Err(OwnershipError {
kind: OwnershipErrorKind::UseAfterMaybeMove {
variable: self.interner.resolve(*sym).to_string(),
branch: "a previous branch".to_string(),
},
span: Span::default(),
});
}
_ => {
self.state.insert(*sym, VarState::Moved);
}
}
} else {
self.check_not_moved(object)?;
}
}
Stmt::Show { object, .. } => {
self.check_not_moved(object)?;
if let Expr::Identifier(sym) = object {
let current = self.state.get(sym).copied();
if current == Some(VarState::Owned) || current.is_none() {
self.state.insert(*sym, VarState::Borrowed);
}
}
}
Stmt::If { then_block, else_block, .. } => {
let state_before = self.state.clone();
self.check_block(then_block)?;
let state_after_then = self.state.clone();
let state_after_else = if let Some(else_b) = else_block {
self.state = state_before.clone();
self.check_block(else_b)?;
self.state.clone()
} else {
state_before.clone()
};
self.state = self.merge_states(&state_after_then, &state_after_else);
}
Stmt::While { body, .. } => {
let state_before = self.state.clone();
self.check_block(body)?;
let state_after_body = self.state.clone();
self.state = self.merge_states(&state_before, &state_after_body);
}
Stmt::Repeat { body, .. } => {
self.check_block(body)?;
}
Stmt::Zone { body, .. } => {
self.check_block(body)?;
}
Stmt::Inspect { arms, .. } => {
if arms.is_empty() {
return Ok(());
}
let state_before = self.state.clone();
let mut branch_states = Vec::new();
for arm in arms {
self.state = state_before.clone();
self.check_block(arm.body)?;
branch_states.push(self.state.clone());
}
if let Some(first) = branch_states.first() {
let mut merged = first.clone();
for state in branch_states.iter().skip(1) {
merged = self.merge_states(&merged, state);
}
self.state = merged;
}
}
Stmt::Return { value: Some(expr) } => {
self.check_not_moved(expr)?;
}
Stmt::Return { value: None } => {}
Stmt::Set { value, .. } => {
self.check_not_moved(value)?;
}
Stmt::Call { args, .. } => {
for arg in args {
self.check_not_moved(arg)?;
}
}
Stmt::Escape { .. } => {}
_ => {}
}
Ok(())
}
fn check_not_moved(&self, expr: &Expr<'_>) -> Result<(), OwnershipError> {
match expr {
Expr::Identifier(sym) => {
match self.state.get(sym).copied() {
Some(VarState::Moved) => {
Err(OwnershipError {
kind: OwnershipErrorKind::UseAfterMove {
variable: self.interner.resolve(*sym).to_string(),
},
span: Span::default(),
})
}
Some(VarState::MaybeMoved) => {
Err(OwnershipError {
kind: OwnershipErrorKind::UseAfterMaybeMove {
variable: self.interner.resolve(*sym).to_string(),
branch: "a conditional branch".to_string(),
},
span: Span::default(),
})
}
_ => Ok(())
}
}
Expr::BinaryOp { left, right, .. } => {
self.check_not_moved(left)?;
self.check_not_moved(right)?;
Ok(())
}
Expr::FieldAccess { object, .. } => {
self.check_not_moved(object)
}
Expr::Index { collection, index } => {
self.check_not_moved(collection)?;
self.check_not_moved(index)?;
Ok(())
}
Expr::Slice { collection, start, end } => {
self.check_not_moved(collection)?;
self.check_not_moved(start)?;
self.check_not_moved(end)?;
Ok(())
}
Expr::Call { args, .. } => {
for arg in args {
self.check_not_moved(arg)?;
}
Ok(())
}
Expr::List(items) | Expr::Tuple(items) => {
for item in items {
self.check_not_moved(item)?;
}
Ok(())
}
Expr::Range { start, end } => {
self.check_not_moved(start)?;
self.check_not_moved(end)?;
Ok(())
}
Expr::New { init_fields, .. } => {
for (_, field_expr) in init_fields {
self.check_not_moved(field_expr)?;
}
Ok(())
}
Expr::NewVariant { fields, .. } => {
for (_, field_expr) in fields {
self.check_not_moved(field_expr)?;
}
Ok(())
}
Expr::Copy { expr } | Expr::Give { value: expr } | Expr::Length { collection: expr } => {
self.check_not_moved(expr)
}
Expr::ManifestOf { zone } => {
self.check_not_moved(zone)
}
Expr::ChunkAt { index, zone } => {
self.check_not_moved(index)?;
self.check_not_moved(zone)
}
Expr::Contains { collection, value } => {
self.check_not_moved(collection)?;
self.check_not_moved(value)
}
Expr::Union { left, right } | Expr::Intersection { left, right } => {
self.check_not_moved(left)?;
self.check_not_moved(right)
}
Expr::WithCapacity { value, capacity } => {
self.check_not_moved(value)?;
self.check_not_moved(capacity)
}
Expr::OptionSome { value } => self.check_not_moved(value),
Expr::OptionNone => Ok(()),
Expr::Escape { .. } => Ok(()),
Expr::Closure { body, .. } => {
match body {
crate::ast::stmt::ClosureBody::Expression(expr) => {
self.check_not_moved(expr)
}
crate::ast::stmt::ClosureBody::Block(_) => Ok(()),
}
}
Expr::CallExpr { callee, args } => {
self.check_not_moved(callee)?;
for arg in args {
self.check_not_moved(arg)?;
}
Ok(())
}
Expr::Literal(_) => Ok(()),
}
}
fn merge_states(
&self,
state_a: &HashMap<Symbol, VarState>,
state_b: &HashMap<Symbol, VarState>,
) -> HashMap<Symbol, VarState> {
let mut merged = state_a.clone();
for (sym, state_b_val) in state_b {
let state_a_val = state_a.get(sym).copied().unwrap_or(VarState::Owned);
let merged_val = match (state_a_val, *state_b_val) {
(VarState::Moved, VarState::Moved) => VarState::Moved,
(VarState::Moved, _) | (_, VarState::Moved) => VarState::MaybeMoved,
(VarState::MaybeMoved, _) | (_, VarState::MaybeMoved) => VarState::MaybeMoved,
(VarState::Borrowed, VarState::Borrowed) => VarState::Borrowed,
(VarState::Borrowed, _) | (_, VarState::Borrowed) => VarState::Borrowed,
(VarState::Owned, VarState::Owned) => VarState::Owned,
};
merged.insert(*sym, merged_val);
}
for sym in state_a.keys() {
if !state_b.contains_key(sym) {
}
}
merged
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ownership_checker_basic() {
let interner = Interner::new();
let checker = OwnershipChecker::new(&interner);
assert!(checker.state.is_empty());
}
}