Add redefinition checks for variables
Ensure that variables are not redefined within the same scope, both for global and function-local variables. This commit modifies the `define` method in `CompilerScope` to return a `Result` and includes checks for existing definitions. The `Binder`'s logic for handling global variables and function parameters is also updated to incorporate these new checks and return appropriate errors.
This commit is contained in:
+26
-11
@@ -25,11 +25,14 @@ impl CompilerScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn define(&mut self, name: &str, ty: StaticType) -> u32 {
|
fn define(&mut self, name: &str, ty: StaticType) -> Result<u32, String> {
|
||||||
|
if self.locals.contains_key(name) {
|
||||||
|
return Err(format!("Variable '{}' is already defined in this scope level.", name));
|
||||||
|
}
|
||||||
let slot = self.slot_count;
|
let slot = self.slot_count;
|
||||||
self.locals.insert(name.to_string(), LocalInfo { slot, ty });
|
self.locals.insert(name.to_string(), LocalInfo { slot, ty });
|
||||||
self.slot_count += 1;
|
self.slot_count += 1;
|
||||||
slot
|
Ok(slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&self, name: &str) -> Option<LocalInfo> {
|
fn resolve(&self, name: &str) -> Option<LocalInfo> {
|
||||||
@@ -135,16 +138,15 @@ impl Binder {
|
|||||||
// 1. Pre-declare name to support recursion
|
// 1. Pre-declare name to support recursion
|
||||||
let slot_or_idx = if self.functions.len() == 1 {
|
let slot_or_idx = if self.functions.len() == 1 {
|
||||||
let mut globals = self.globals.borrow_mut();
|
let mut globals = self.globals.borrow_mut();
|
||||||
if let Some((idx, _)) = globals.get(name.as_ref()) {
|
if globals.contains_key(name.as_ref()) {
|
||||||
*idx
|
return Err(format!("Global variable '{}' is already defined.", name));
|
||||||
} else {
|
|
||||||
let idx = globals.len() as u32;
|
|
||||||
globals.insert(name.to_string(), (idx, StaticType::Any));
|
|
||||||
idx
|
|
||||||
}
|
}
|
||||||
|
let idx = globals.len() as u32;
|
||||||
|
globals.insert(name.to_string(), (idx, StaticType::Any));
|
||||||
|
idx
|
||||||
} else {
|
} else {
|
||||||
let current_fn = self.functions.last_mut().unwrap();
|
let current_fn = self.functions.last_mut().unwrap();
|
||||||
current_fn.scope.define(name, StaticType::Any)
|
current_fn.scope.define(name, StaticType::Any)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. Bind Value (now 'name' is visible)
|
// 2. Bind Value (now 'name' is visible)
|
||||||
@@ -206,8 +208,8 @@ impl Binder {
|
|||||||
{
|
{
|
||||||
let current_fn = self.functions.last_mut().unwrap();
|
let current_fn = self.functions.last_mut().unwrap();
|
||||||
for param in params {
|
for param in params {
|
||||||
// For now, lambda params are Any unless we have a way to specify them
|
// Lambda params must also be unique in their scope
|
||||||
current_fn.scope.define(param, StaticType::Any);
|
current_fn.scope.define(param, StaticType::Any)?;
|
||||||
param_types.push(StaticType::Any);
|
param_types.push(StaticType::Any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,4 +413,17 @@ mod tests {
|
|||||||
panic!("Root should be a Lambda");
|
panic!("Root should be a Lambda");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_redefinition_error() {
|
||||||
|
let source = "(do (def x 1) (def x 2))";
|
||||||
|
let mut parser = Parser::new(source).unwrap();
|
||||||
|
let untyped = parser.parse_expression().unwrap();
|
||||||
|
|
||||||
|
let globals = Rc::new(RefCell::new(HashMap::new()));
|
||||||
|
let result = Binder::bind_root(globals, &untyped);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result.unwrap_err().contains("already defined"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user