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 {
|
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")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: {:?}",
|
||||||
|
|||||||
@@ -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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user