Skip to content

Commit

Permalink
Merge pull request #10 from notJoon/support_nested_scope_in_borrow_ch…
Browse files Browse the repository at this point in the history
…ecker

merge PR #10
  • Loading branch information
notJoon authored Jun 7, 2023
2 parents be2cc12 + 9673750 commit c82d857
Show file tree
Hide file tree
Showing 6 changed files with 526 additions and 61 deletions.
169 changes: 126 additions & 43 deletions src/borrow_checker.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::collections::HashMap;

use crate::ast::{Expression, Statement};
use crate::{
ast::{Expression, Statement},
errors::BorrowError,
};

type BorrowResult = Result<(), String>;
type BorrowResult = Result<(), BorrowError>;

/// The `BorrowChecker` struct is used to keep track of the state of borrows.
pub struct BorrowChecker<'a> {
Expand Down Expand Up @@ -71,13 +74,11 @@ impl<'a> BorrowChecker<'a> {
if let Some(state) = self.get_borrow(ident) {
match state {
BorrowState::Borrowed | BorrowState::ImmutBorrowed => {
return Err(format!(
"Cannot borrow {ident} as it is currently being mutably borrowed"
));
return Err(BorrowError::BorrowedMutable(ident.to_string()));
}
BorrowState::Uninitialized => {
return Err(format!(
"Variable {ident} is declared without an initial value"
return Err(BorrowError::DeclaredWithoutInitialValue(
ident.to_string(),
));
}
_ => {}
Expand All @@ -89,18 +90,16 @@ impl<'a> BorrowChecker<'a> {
return Ok(());
}

Err(format!("Cannot borrow {ident} because it is not defined"))
Err(BorrowError::VariableNotDefined(ident.to_string()))
}
(true, _) => Err(format!("Variable {name} is not initialized")),
(true, _) => Err(BorrowError::VariableNotInitialized(name.to_string())),
(false, Some(expr)) => {
self.check_expression(expr)?;
self.insert_borrow(name, BorrowState::Initialized);

Ok(())
}
(false, None) => Err(format!(
"Variable {name} is declared without an initial value"
)),
(false, None) => Err(BorrowError::DeclaredWithoutInitialValue(name.to_string())),
}
}

Expand All @@ -112,19 +111,17 @@ impl<'a> BorrowChecker<'a> {
if let Some(Expression::Ident(ref ident)) = value {
if let Some(state) = self.get_borrow(ident) {
if state == &BorrowState::Borrowed {
return Err(format!(
"Error: Cannot borrow {ident} as it is currently being mutably borrowed"
));
return Err(BorrowError::InvalidBorrowMutablyBorrowed(ident.to_string()));
}

self.insert_borrow(name, BorrowState::ImmutBorrowed);
return Ok(());
}

return Err(format!("Error: Variable {ident} is not initialized"));
return Err(BorrowError::VariableNotDefined(ident.to_string()));
}

Err(format!("Invalid borrow of {name}"))
Err(BorrowError::InvalidBorrow(name.to_string()))
}

fn check_value_expr(&mut self, value: &'a Option<Expression>) -> BorrowResult {
Expand Down Expand Up @@ -171,17 +168,15 @@ impl<'a> BorrowChecker<'a> {
fn declare(&mut self, var: &'a str) -> BorrowResult {
if let Some(scope) = self.borrows.last_mut() {
if scope.contains_key(var) {
return Err(format!("Variable {var} is already declared"));
return Err(BorrowError::VariableDeclaredDuplicate(var.to_string()));
}

scope.insert(var, BorrowState::Uninitialized);

return Ok(());
}

Err(format!(
"No scope available for declaration of variable for {var}"
))
Err(BorrowError::NoScopeAvailable(var.to_string()))
}
/// `check_function_call` checks a function call.
///
Expand All @@ -193,9 +188,7 @@ impl<'a> BorrowChecker<'a> {

if let Expression::Ident(ident) = arg {
if let Some(BorrowState::Borrowed) = self.get_borrow(ident) {
return Err(format!(
"Cannot borrow {ident} as it is currently being mutably borrowed"
));
return Err(BorrowError::BorrowedMutable(ident.to_string()));
}
}
}
Expand Down Expand Up @@ -231,9 +224,7 @@ impl<'a> BorrowChecker<'a> {
let borrow = self.get_borrow(var);

if let Some(BorrowState::Borrowed) = borrow {
return Err(format!(
"Cannot borrow {var} as it is currently being mutably borrowed"
));
return Err(BorrowError::BorrowedMutable(var.to_string()));
}
}

Expand All @@ -246,7 +237,7 @@ impl<'a> BorrowChecker<'a> {
// if the expression is an identifier, check if the variable is already borrowed
Expression::Ident(ident) => {
if self.get_borrow(ident).is_none() {
return Err(format!("Variable {ident} is not initialized"));
return Err(BorrowError::VariableNotInitialized(ident.to_string()));
}
}

Expand Down Expand Up @@ -274,7 +265,7 @@ impl<'a> BorrowChecker<'a> {
return Ok(());
}

Err(format!("Cannot borrow {name} as mutable"))
Err(BorrowError::CannotBorrowMutable(name.to_string()))
}
/// `borrow_imm` method should handle the logic of mutably borrowing a variable.
///
Expand All @@ -287,7 +278,7 @@ impl<'a> BorrowChecker<'a> {
return Ok(());
}

Err(format!("Cannot borrow {name} as immutable"))
Err(BorrowError::CannotBorrowImmutable(name.to_string()))
}
/// `free` method should handle the logic of releasing a borrow
/// when a variable goes out of scope.
Expand Down Expand Up @@ -351,7 +342,7 @@ mod borrow_tests {

assert_eq!(
result,
Err("Cannot borrow b because it is not defined".to_string())
Err(BorrowError::VariableNotDefined("b".to_string()))
);
}

Expand Down Expand Up @@ -395,7 +386,7 @@ mod borrow_tests {

assert_eq!(
result,
Err("Variable a is declared without an initial value".to_string())
Err(BorrowError::DeclaredWithoutInitialValue("a".to_string()))
);
}

Expand All @@ -416,23 +407,115 @@ mod borrow_tests {
}

#[test]
#[ignore = "todo"]
fn test_nested_scope() {
fn test_nested_scope_borrow_checking() {
let mut checker = BorrowChecker::new();

// let a = 10;
// {
// let b = &a;
// }
// let c = &a;
let stmts = vec![
Statement::VariableDecl {
name: "a".to_string(),
value: Some(Expression::Number(10)),
is_borrowed: false,
},
Statement::Scope(vec![Statement::VariableDecl {
name: "b".to_string(),
value: Some(Expression::Reference("a".to_string())),
is_borrowed: true,
}]),
Statement::VariableDecl {
name: "c".to_string(),
value: Some(Expression::Reference("a".to_string())),
is_borrowed: true,
},
];

assert_eq!(checker.check(&stmts), Ok(()));
}

#[test]
fn test_nested_scope_with_multiple_borrows_throw_error() {
let mut checker = BorrowChecker::new();

let input = r#"
function foo(x) {
let a = x;
{
let b = &a;
}
let c = &a;
let x = 5;
{
let y = &x;
let z = &x;
}
"#;

let mut checker = BorrowChecker::new();

let result = setup(input);
let result = checker.check(&result);

assert_eq!(result, Ok(()));
let stmts = vec![
Statement::VariableDecl {
name: "x".to_string(),
value: Some(Expression::Number(5)),
is_borrowed: false,
},
Statement::Scope(vec![
Statement::VariableDecl {
name: "y".to_string(),
value: Some(Expression::Reference("x".to_string())),
is_borrowed: true,
},
Statement::VariableDecl {
name: "z".to_string(),
value: Some(Expression::Reference("x".to_string())),
is_borrowed: true,
},
]),
];

assert_eq!(result, Err(BorrowError::BorrowedMutable("x".to_string())));
}

#[test]
fn test_nested_scope_with_mut_borrow_and_use_in_same_scope() {
let mut checker = BorrowChecker::new();
let stmts = vec![
Statement::VariableDecl {
name: "x".to_string(),
value: Some(Expression::Number(5)),
is_borrowed: false,
},
Statement::Scope(vec![
Statement::VariableDecl {
name: "y".to_string(),
value: Some(Expression::Reference("x".to_string())),
is_borrowed: true,
},
Statement::Expr(Expression::Ident("x".to_string())),
]),
];
assert_eq!(
checker.check(&stmts),
Err(BorrowError::InvalidBorrowMutablyBorrowed("x".to_string())),
);
}

#[test]
fn test_nested_scope_with_uninitialized_variable() {
let mut checker = BorrowChecker::new();
let stmts = vec![
Statement::VariableDecl {
name: "x".to_string(),
value: None,
is_borrowed: false,
},
Statement::Scope(vec![Statement::VariableDecl {
name: "y".to_string(),
value: Some(Expression::Reference("x".to_string())),
is_borrowed: true,
}]),
];
assert_eq!(
checker.check(&stmts),
Err(BorrowError::DeclaredWithoutInitialValue("x".to_string())),
);
}
}
Loading

0 comments on commit c82d857

Please sign in to comment.