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:
Michael Schimmel
2026-02-18 13:23:08 +01:00
parent 3630cb3c6c
commit 73ddd644c1
+24 -9
View File
@@ -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;
self.locals.insert(name.to_string(), LocalInfo { slot, ty });
self.slot_count += 1;
slot
Ok(slot)
}
fn resolve(&self, name: &str) -> Option<LocalInfo> {
@@ -135,16 +138,15 @@ impl Binder {
// 1. Pre-declare name to support recursion
let slot_or_idx = if self.functions.len() == 1 {
let mut globals = self.globals.borrow_mut();
if let Some((idx, _)) = globals.get(name.as_ref()) {
*idx
} else {
if globals.contains_key(name.as_ref()) {
return Err(format!("Global variable '{}' is already defined.", name));
}
let idx = globals.len() as u32;
globals.insert(name.to_string(), (idx, StaticType::Any));
idx
}
} else {
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)
@@ -206,8 +208,8 @@ impl Binder {
{
let current_fn = self.functions.last_mut().unwrap();
for param in params {
// For now, lambda params are Any unless we have a way to specify them
current_fn.scope.define(param, StaticType::Any);
// Lambda params must also be unique in their scope
current_fn.scope.define(param, StaticType::Any)?;
param_types.push(StaticType::Any);
}
}
@@ -411,4 +413,17 @@ mod tests {
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"));
}
}