Refactor scope handling and immutability
Introduces `fixed_scope_idx` to `Binder` and `Environment` to track immutable scopes. This allows enforcing that definitions (`def`) cannot occur in scopes that are considered fixed or immutable, such as the global scope or the initial bootstrap scope. The `FunctionCompiler` is updated to use `fixed_scope_idx` and `is_root` to determine scope immutability.
This commit is contained in:
+40
-34
@@ -31,28 +31,24 @@ impl CompilerScope {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ScopeKind {
|
||||
Root,
|
||||
Local,
|
||||
}
|
||||
|
||||
struct FunctionCompiler {
|
||||
identity: Identity,
|
||||
scopes: Vec<CompilerScope>,
|
||||
slot_count: u32,
|
||||
upvalues: Vec<Address>,
|
||||
kind: ScopeKind,
|
||||
fixed_scope_idx: i32,
|
||||
is_root: bool,
|
||||
}
|
||||
|
||||
impl FunctionCompiler {
|
||||
fn new(kind: ScopeKind, identity: Identity) -> Self {
|
||||
fn new(fixed_scope_idx: i32, identity: Identity, is_root: bool) -> Self {
|
||||
Self {
|
||||
identity,
|
||||
scopes: vec![CompilerScope::new()],
|
||||
slot_count: 0,
|
||||
upvalues: Vec::new(),
|
||||
kind,
|
||||
fixed_scope_idx,
|
||||
is_root,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,15 +66,26 @@ impl FunctionCompiler {
|
||||
identity: Identity,
|
||||
globals: &Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
|
||||
) -> Result<Address, String> {
|
||||
if self.kind == ScopeKind::Root && self.scopes.len() == 1 {
|
||||
let current_scope = self.scopes.last_mut().unwrap();
|
||||
if current_scope.locals.contains_key(name) {
|
||||
return Err(format!(
|
||||
"Variable '{}' is already defined in this scope level.",
|
||||
name.name
|
||||
));
|
||||
}
|
||||
let current_scope_idx = self.scopes.len() as i32 - 1;
|
||||
|
||||
// 1. Check if the current scope is immutable (e.g. Scope 0 after bootstrapping)
|
||||
if current_scope_idx <= self.fixed_scope_idx {
|
||||
return Err(format!(
|
||||
"Cannot define '{}': Scope {} is frozen/immutable.",
|
||||
name.name, current_scope_idx
|
||||
));
|
||||
}
|
||||
|
||||
let current_scope = self.scopes.last_mut().unwrap();
|
||||
if current_scope.locals.contains_key(name) {
|
||||
return Err(format!(
|
||||
"Variable '{}' is already defined in this scope level.",
|
||||
name.name
|
||||
));
|
||||
}
|
||||
|
||||
// 2. Only Scope 0 of the ROOT function is the Global scope in Myc
|
||||
if self.is_root && current_scope_idx == 0 {
|
||||
let mut globals_map = globals.borrow_mut();
|
||||
let addr = if let Some((idx, existing_id)) = globals_map.get(name) {
|
||||
if *existing_id != identity {
|
||||
@@ -101,13 +108,7 @@ impl FunctionCompiler {
|
||||
);
|
||||
Ok(addr)
|
||||
} else {
|
||||
let current_scope = self.scopes.last_mut().unwrap();
|
||||
if current_scope.locals.contains_key(name) {
|
||||
return Err(format!(
|
||||
"Variable '{}' is already defined in this scope level.",
|
||||
name.name
|
||||
));
|
||||
}
|
||||
// Local scope (Block or Lambda)
|
||||
let slot = LocalSlot(self.slot_count);
|
||||
current_scope.locals.insert(
|
||||
name.clone(),
|
||||
@@ -160,28 +161,33 @@ pub struct Binder {
|
||||
}
|
||||
|
||||
impl Binder {
|
||||
pub fn new(globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>) -> Self {
|
||||
pub fn new(
|
||||
globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
|
||||
fixed_scope_idx: i32,
|
||||
) -> Self {
|
||||
let mut binder = Self {
|
||||
functions: Vec::new(),
|
||||
globals,
|
||||
capture_map: HashMap::new(),
|
||||
};
|
||||
binder.functions.push(FunctionCompiler::new(
|
||||
ScopeKind::Root,
|
||||
fixed_scope_idx,
|
||||
crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation {
|
||||
line: 0,
|
||||
col: 0,
|
||||
}),
|
||||
true, // is_root = true for the script itself
|
||||
));
|
||||
binder
|
||||
}
|
||||
|
||||
pub fn bind_root(
|
||||
globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
|
||||
fixed_scope_idx: i32,
|
||||
node: &Node<UntypedKind>,
|
||||
diagnostics: &mut Diagnostics,
|
||||
) -> Result<(BoundNode, HashMap<Identity, Vec<Identity>>), String> {
|
||||
let mut binder = Self::new(globals);
|
||||
let mut binder = Self::new(globals, fixed_scope_idx);
|
||||
let bound = binder.bind(node, ExprContext::Expression, diagnostics);
|
||||
|
||||
// Convert HashSet to sorted Vec
|
||||
@@ -358,7 +364,7 @@ impl Binder {
|
||||
UntypedKind::Lambda { params, body } => {
|
||||
let identity = node.identity.clone();
|
||||
self.functions
|
||||
.push(FunctionCompiler::new(ScopeKind::Local, identity.clone()));
|
||||
.push(FunctionCompiler::new(-1, identity.clone(), false));
|
||||
|
||||
// 1. Bind the parameter pattern/tuple
|
||||
let params_bound = self.bind_pattern(params, DeclarationKind::Parameter, diag);
|
||||
@@ -696,7 +702,7 @@ mod tests {
|
||||
|
||||
let globals = Rc::new(RefCell::new(HashMap::new()));
|
||||
let mut diagnostics = Diagnostics::new();
|
||||
let (bound, captures) = Binder::bind_root(globals, &untyped, &mut diagnostics).unwrap();
|
||||
let (bound, captures) = Binder::bind_root(globals, 0, &untyped, &mut diagnostics).unwrap();
|
||||
|
||||
// Structure: Lambda -> Block -> [ Define(x), Define(f), Get(x) ]
|
||||
if let BoundKind::Lambda { body, .. } = &bound.kind {
|
||||
@@ -730,7 +736,7 @@ mod tests {
|
||||
|
||||
let globals = Rc::new(RefCell::new(HashMap::new()));
|
||||
let mut diagnostics = Diagnostics::new();
|
||||
let (bound, captures) = Binder::bind_root(globals, &untyped, &mut diagnostics).unwrap();
|
||||
let (bound, captures) = Binder::bind_root(globals, 0, &untyped, &mut diagnostics).unwrap();
|
||||
|
||||
if let BoundKind::Lambda { body, .. } = &bound.kind {
|
||||
if let BoundKind::Block { exprs } = &body.kind {
|
||||
@@ -760,7 +766,7 @@ mod tests {
|
||||
|
||||
let globals = Rc::new(RefCell::new(HashMap::new()));
|
||||
let mut diagnostics = Diagnostics::new();
|
||||
let _ = Binder::bind_root(globals, &untyped, &mut diagnostics);
|
||||
let _ = Binder::bind_root(globals, 0, &untyped, &mut diagnostics);
|
||||
|
||||
assert!(diagnostics.has_errors());
|
||||
assert!(diagnostics.items.iter().any(|i| i.message.contains("already defined")));
|
||||
@@ -774,15 +780,15 @@ mod tests {
|
||||
let source1 = "(def x 1) 1";
|
||||
let untyped1 = Parser::new(source1).parse_expression();
|
||||
let mut diagnostics = Diagnostics::new();
|
||||
assert!(Binder::bind_root(globals.clone(), &untyped1, &mut diagnostics).is_ok());
|
||||
assert!(Binder::bind_root(globals.clone(), 0, &untyped1, &mut diagnostics).is_ok());
|
||||
|
||||
// Second run: attempts to redefine 'x' in the same global environment
|
||||
let source2 = "(def x 2) 2";
|
||||
let untyped2 = Parser::new(source2).parse_expression();
|
||||
let mut diagnostics2 = Diagnostics::new();
|
||||
let _ = Binder::bind_root(globals.clone(), &untyped2, &mut diagnostics2);
|
||||
let _ = Binder::bind_root(globals.clone(), 0, &untyped2, &mut diagnostics2);
|
||||
|
||||
assert!(diagnostics2.has_errors());
|
||||
assert!(diagnostics2.items.iter().any(|i| i.message.contains("already defined")));
|
||||
assert!(diagnostics2.items.iter().any(|i| i.message.contains("frozen/immutable")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -778,7 +778,7 @@ mod tests {
|
||||
let globals = Rc::new(RefCell::new(global_names));
|
||||
|
||||
let mut diag = crate::ast::diagnostics::Diagnostics::new();
|
||||
let result = Binder::bind_root(globals, &expanded, &mut diag);
|
||||
let result = Binder::bind_root(globals, 0, &expanded, &mut diag);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Should find global '*' Error: {:?}",
|
||||
@@ -803,7 +803,7 @@ mod tests {
|
||||
|
||||
let globals = Rc::new(RefCell::new(HashMap::new()));
|
||||
let mut diag = crate::ast::diagnostics::Diagnostics::new();
|
||||
let result = Binder::bind_root(globals, &expanded, &mut diag);
|
||||
let result = Binder::bind_root(globals, 0, &expanded, &mut diag);
|
||||
assert!(result.is_ok(), "Hygiene failed: {:?}", result.err());
|
||||
}
|
||||
|
||||
@@ -824,7 +824,7 @@ mod tests {
|
||||
|
||||
let globals = Rc::new(RefCell::new(HashMap::new()));
|
||||
let mut diag = crate::ast::diagnostics::Diagnostics::new();
|
||||
let result = Binder::bind_root(globals, &expanded, &mut diag);
|
||||
let result = Binder::bind_root(globals, 0, &expanded, &mut diag);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Explicit Hygiene with Backticks failed: {:?}",
|
||||
|
||||
@@ -717,7 +717,7 @@ mod tests {
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err(),
|
||||
"Statement 'def' cannot be used as an expression.\nCannot destructure type int as a tuple/vector"
|
||||
"Statement 'def' cannot be used as an expression.\nCannot define 'x': Scope 0 is frozen/immutable.\nCannot destructure type int as a tuple/vector"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ pub struct Environment {
|
||||
pub global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>,
|
||||
pub global_purity: Rc<RefCell<HashMap<GlobalIdx, Purity>>>,
|
||||
pub global_values: Rc<RefCell<Vec<Value>>>,
|
||||
pub fixed_scope_idx: i32,
|
||||
pub function_registry: Rc<RefCell<GlobalFunctionRegistry>>,
|
||||
pub typed_function_registry: Rc<RefCell<GlobalAnalyzedRegistry>>,
|
||||
pub monomorph_cache: Rc<RefCell<MonoCache>>,
|
||||
@@ -112,6 +113,7 @@ struct RuntimeMacroEvaluator {
|
||||
global_names: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
|
||||
global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>,
|
||||
global_values: Rc<RefCell<Vec<Value>>>,
|
||||
fixed_scope_idx: i32,
|
||||
}
|
||||
|
||||
impl MacroEvaluator for RuntimeMacroEvaluator {
|
||||
@@ -127,7 +129,7 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
|
||||
}
|
||||
|
||||
let mut diag = Diagnostics::new();
|
||||
let (bound_ast, captures) = Binder::bind_root(self.global_names.clone(), node, &mut diag)?;
|
||||
let (bound_ast, captures) = Binder::bind_root(self.global_names.clone(), self.fixed_scope_idx, node, &mut diag)?;
|
||||
let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures);
|
||||
let checker = TypeChecker::new(self.global_types.clone());
|
||||
let typed_ast = checker.check(&bound_ast, &[], &mut diag);
|
||||
@@ -161,6 +163,7 @@ impl Environment {
|
||||
global_types: Rc::new(RefCell::new(HashMap::new())),
|
||||
global_purity: Rc::new(RefCell::new(HashMap::new())),
|
||||
global_values: Rc::new(RefCell::new(Vec::new())),
|
||||
fixed_scope_idx: -1,
|
||||
function_registry: Rc::new(RefCell::new(HashMap::new())),
|
||||
typed_function_registry: Rc::new(RefCell::new(HashMap::new())),
|
||||
monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
|
||||
@@ -172,6 +175,9 @@ impl Environment {
|
||||
loaded_modules: Rc::new(RefCell::new(HashSet::new())),
|
||||
};
|
||||
crate::ast::rtl::register(&env);
|
||||
|
||||
let mut env = env;
|
||||
env.fixed_scope_idx = 0;
|
||||
|
||||
// Automatically add standard search paths (CWD and CWD/rtl)
|
||||
if let Ok(cwd) = std::env::current_dir() {
|
||||
@@ -212,6 +218,7 @@ impl Environment {
|
||||
global_names: self.global_names.clone(),
|
||||
global_types: self.global_types.clone(),
|
||||
global_values: self.global_values.clone(),
|
||||
fixed_scope_idx: self.fixed_scope_idx,
|
||||
};
|
||||
MacroExpander::new(self.macro_registry.borrow().clone(), evaluator)
|
||||
}
|
||||
@@ -423,7 +430,7 @@ impl Environment {
|
||||
};
|
||||
|
||||
let (bound_ast, captures) =
|
||||
match Binder::bind_root(self.global_names.clone(), &expanded_ast, diagnostics) {
|
||||
match Binder::bind_root(self.global_names.clone(), self.fixed_scope_idx, &expanded_ast, diagnostics) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
diagnostics.push_error(e, None);
|
||||
|
||||
Reference in New Issue
Block a user