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:
Michael Schimmel
2026-03-12 11:22:40 +01:00
parent bb77caf1af
commit be2bef373f
4 changed files with 53 additions and 40 deletions
+40 -34
View File
@@ -31,28 +31,24 @@ impl CompilerScope {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ScopeKind {
Root,
Local,
}
struct FunctionCompiler { struct FunctionCompiler {
identity: Identity, identity: Identity,
scopes: Vec<CompilerScope>, scopes: Vec<CompilerScope>,
slot_count: u32, slot_count: u32,
upvalues: Vec<Address>, upvalues: Vec<Address>,
kind: ScopeKind, fixed_scope_idx: i32,
is_root: bool,
} }
impl FunctionCompiler { impl FunctionCompiler {
fn new(kind: ScopeKind, identity: Identity) -> Self { fn new(fixed_scope_idx: i32, identity: Identity, is_root: bool) -> Self {
Self { Self {
identity, identity,
scopes: vec![CompilerScope::new()], scopes: vec![CompilerScope::new()],
slot_count: 0, slot_count: 0,
upvalues: Vec::new(), upvalues: Vec::new(),
kind, fixed_scope_idx,
is_root,
} }
} }
@@ -70,15 +66,26 @@ impl FunctionCompiler {
identity: Identity, identity: Identity,
globals: &Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>, globals: &Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
) -> Result<Address, String> { ) -> Result<Address, String> {
if self.kind == ScopeKind::Root && self.scopes.len() == 1 { let current_scope_idx = self.scopes.len() as i32 - 1;
let current_scope = self.scopes.last_mut().unwrap();
if current_scope.locals.contains_key(name) { // 1. Check if the current scope is immutable (e.g. Scope 0 after bootstrapping)
return Err(format!( if current_scope_idx <= self.fixed_scope_idx {
"Variable '{}' is already defined in this scope level.", return Err(format!(
name.name "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 mut globals_map = globals.borrow_mut();
let addr = if let Some((idx, existing_id)) = globals_map.get(name) { let addr = if let Some((idx, existing_id)) = globals_map.get(name) {
if *existing_id != identity { if *existing_id != identity {
@@ -101,13 +108,7 @@ impl FunctionCompiler {
); );
Ok(addr) Ok(addr)
} else { } else {
let current_scope = self.scopes.last_mut().unwrap(); // Local scope (Block or Lambda)
if current_scope.locals.contains_key(name) {
return Err(format!(
"Variable '{}' is already defined in this scope level.",
name.name
));
}
let slot = LocalSlot(self.slot_count); let slot = LocalSlot(self.slot_count);
current_scope.locals.insert( current_scope.locals.insert(
name.clone(), name.clone(),
@@ -160,28 +161,33 @@ pub struct Binder {
} }
impl 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 { let mut binder = Self {
functions: Vec::new(), functions: Vec::new(),
globals, globals,
capture_map: HashMap::new(), capture_map: HashMap::new(),
}; };
binder.functions.push(FunctionCompiler::new( binder.functions.push(FunctionCompiler::new(
ScopeKind::Root, fixed_scope_idx,
crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation { crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation {
line: 0, line: 0,
col: 0, col: 0,
}), }),
true, // is_root = true for the script itself
)); ));
binder binder
} }
pub fn bind_root( pub fn bind_root(
globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>, globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
fixed_scope_idx: i32,
node: &Node<UntypedKind>, node: &Node<UntypedKind>,
diagnostics: &mut Diagnostics, diagnostics: &mut Diagnostics,
) -> Result<(BoundNode, HashMap<Identity, Vec<Identity>>), String> { ) -> 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); let bound = binder.bind(node, ExprContext::Expression, diagnostics);
// Convert HashSet to sorted Vec // Convert HashSet to sorted Vec
@@ -358,7 +364,7 @@ impl Binder {
UntypedKind::Lambda { params, body } => { UntypedKind::Lambda { params, body } => {
let identity = node.identity.clone(); let identity = node.identity.clone();
self.functions self.functions
.push(FunctionCompiler::new(ScopeKind::Local, identity.clone())); .push(FunctionCompiler::new(-1, identity.clone(), false));
// 1. Bind the parameter pattern/tuple // 1. Bind the parameter pattern/tuple
let params_bound = self.bind_pattern(params, DeclarationKind::Parameter, diag); 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 globals = Rc::new(RefCell::new(HashMap::new()));
let mut diagnostics = Diagnostics::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) ] // Structure: Lambda -> Block -> [ Define(x), Define(f), Get(x) ]
if let BoundKind::Lambda { body, .. } = &bound.kind { if let BoundKind::Lambda { body, .. } = &bound.kind {
@@ -730,7 +736,7 @@ mod tests {
let globals = Rc::new(RefCell::new(HashMap::new())); let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diagnostics = Diagnostics::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::Lambda { body, .. } = &bound.kind {
if let BoundKind::Block { exprs } = &body.kind { if let BoundKind::Block { exprs } = &body.kind {
@@ -760,7 +766,7 @@ mod tests {
let globals = Rc::new(RefCell::new(HashMap::new())); let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diagnostics = Diagnostics::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.has_errors());
assert!(diagnostics.items.iter().any(|i| i.message.contains("already defined"))); assert!(diagnostics.items.iter().any(|i| i.message.contains("already defined")));
@@ -774,15 +780,15 @@ mod tests {
let source1 = "(def x 1) 1"; let source1 = "(def x 1) 1";
let untyped1 = Parser::new(source1).parse_expression(); let untyped1 = Parser::new(source1).parse_expression();
let mut diagnostics = Diagnostics::new(); 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 // Second run: attempts to redefine 'x' in the same global environment
let source2 = "(def x 2) 2"; let source2 = "(def x 2) 2";
let untyped2 = Parser::new(source2).parse_expression(); let untyped2 = Parser::new(source2).parse_expression();
let mut diagnostics2 = Diagnostics::new(); 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.has_errors());
assert!(diagnostics2.items.iter().any(|i| i.message.contains("already defined"))); assert!(diagnostics2.items.iter().any(|i| i.message.contains("frozen/immutable")));
} }
} }
+3 -3
View File
@@ -778,7 +778,7 @@ mod tests {
let globals = Rc::new(RefCell::new(global_names)); let globals = Rc::new(RefCell::new(global_names));
let mut diag = crate::ast::diagnostics::Diagnostics::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!( assert!(
result.is_ok(), result.is_ok(),
"Should find global '*' Error: {:?}", "Should find global '*' Error: {:?}",
@@ -803,7 +803,7 @@ mod tests {
let globals = Rc::new(RefCell::new(HashMap::new())); let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diag = crate::ast::diagnostics::Diagnostics::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()); assert!(result.is_ok(), "Hygiene failed: {:?}", result.err());
} }
@@ -824,7 +824,7 @@ mod tests {
let globals = Rc::new(RefCell::new(HashMap::new())); let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diag = crate::ast::diagnostics::Diagnostics::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!( assert!(
result.is_ok(), result.is_ok(),
"Explicit Hygiene with Backticks failed: {:?}", "Explicit Hygiene with Backticks failed: {:?}",
+1 -1
View File
@@ -717,7 +717,7 @@ mod tests {
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.unwrap_err(), 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"
); );
} }
+9 -2
View File
@@ -75,6 +75,7 @@ pub struct Environment {
pub global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>, pub global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>,
pub global_purity: Rc<RefCell<HashMap<GlobalIdx, Purity>>>, pub global_purity: Rc<RefCell<HashMap<GlobalIdx, Purity>>>,
pub global_values: Rc<RefCell<Vec<Value>>>, pub global_values: Rc<RefCell<Vec<Value>>>,
pub fixed_scope_idx: i32,
pub function_registry: Rc<RefCell<GlobalFunctionRegistry>>, pub function_registry: Rc<RefCell<GlobalFunctionRegistry>>,
pub typed_function_registry: Rc<RefCell<GlobalAnalyzedRegistry>>, pub typed_function_registry: Rc<RefCell<GlobalAnalyzedRegistry>>,
pub monomorph_cache: Rc<RefCell<MonoCache>>, pub monomorph_cache: Rc<RefCell<MonoCache>>,
@@ -112,6 +113,7 @@ struct RuntimeMacroEvaluator {
global_names: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>, global_names: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>, global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>,
global_values: Rc<RefCell<Vec<Value>>>, global_values: Rc<RefCell<Vec<Value>>>,
fixed_scope_idx: i32,
} }
impl MacroEvaluator for RuntimeMacroEvaluator { impl MacroEvaluator for RuntimeMacroEvaluator {
@@ -127,7 +129,7 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
} }
let mut diag = Diagnostics::new(); 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 bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures);
let checker = TypeChecker::new(self.global_types.clone()); let checker = TypeChecker::new(self.global_types.clone());
let typed_ast = checker.check(&bound_ast, &[], &mut diag); let typed_ast = checker.check(&bound_ast, &[], &mut diag);
@@ -161,6 +163,7 @@ impl Environment {
global_types: Rc::new(RefCell::new(HashMap::new())), global_types: Rc::new(RefCell::new(HashMap::new())),
global_purity: Rc::new(RefCell::new(HashMap::new())), global_purity: Rc::new(RefCell::new(HashMap::new())),
global_values: Rc::new(RefCell::new(Vec::new())), global_values: Rc::new(RefCell::new(Vec::new())),
fixed_scope_idx: -1,
function_registry: Rc::new(RefCell::new(HashMap::new())), function_registry: Rc::new(RefCell::new(HashMap::new())),
typed_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())), monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
@@ -172,6 +175,9 @@ impl Environment {
loaded_modules: Rc::new(RefCell::new(HashSet::new())), loaded_modules: Rc::new(RefCell::new(HashSet::new())),
}; };
crate::ast::rtl::register(&env); crate::ast::rtl::register(&env);
let mut env = env;
env.fixed_scope_idx = 0;
// Automatically add standard search paths (CWD and CWD/rtl) // Automatically add standard search paths (CWD and CWD/rtl)
if let Ok(cwd) = std::env::current_dir() { if let Ok(cwd) = std::env::current_dir() {
@@ -212,6 +218,7 @@ impl Environment {
global_names: self.global_names.clone(), global_names: self.global_names.clone(),
global_types: self.global_types.clone(), global_types: self.global_types.clone(),
global_values: self.global_values.clone(), global_values: self.global_values.clone(),
fixed_scope_idx: self.fixed_scope_idx,
}; };
MacroExpander::new(self.macro_registry.borrow().clone(), evaluator) MacroExpander::new(self.macro_registry.borrow().clone(), evaluator)
} }
@@ -423,7 +430,7 @@ impl Environment {
}; };
let (bound_ast, captures) = 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, Ok(res) => res,
Err(e) => { Err(e) => {
diagnostics.push_error(e, None); diagnostics.push_error(e, None);