Refactor: Pass global names to Binder

The `Binder` struct now accepts a `Rc<RefCell<HashMap<Symbol,
(GlobalIdx, Identity)>>>` for global names. This allows the binder to
directly access and manage global symbols during the binding process,
simplifying the logic and improving efficiency.

Additionally, `CompilerScope` now implements `Default`, and a type alias
`BindingResult` has been introduced for clarity. The `bind_root`
function signature has been updated to accept the `globals` argument.
This commit is contained in:
Michael Schimmel
2026-03-12 16:21:20 +01:00
parent a220815bd6
commit dcb7685d29
4 changed files with 86 additions and 28 deletions
+56 -14
View File
@@ -21,6 +21,12 @@ pub struct CompilerScope {
pub locals: HashMap<Symbol, LocalInfo>,
}
impl Default for CompilerScope {
fn default() -> Self {
Self::new()
}
}
impl CompilerScope {
pub fn new() -> Self {
Self {
@@ -108,8 +114,16 @@ pub enum ExprContext {
Statement,
}
pub type BindingResult = (
BoundNode,
HashMap<Identity, Vec<Identity>>,
Vec<CompilerScope>,
u32,
);
pub struct Binder {
functions: Vec<FunctionCompiler>,
globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
capture_map: HashMap<Identity, std::collections::HashSet<Identity>>,
fixed_scope_idx: i32,
}
@@ -119,9 +133,11 @@ impl Binder {
initial_scopes: Vec<CompilerScope>,
initial_slot_count: u32,
fixed_scope_idx: i32,
globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
) -> Self {
let mut binder = Self {
functions: Vec::new(),
globals,
capture_map: HashMap::new(),
fixed_scope_idx,
};
@@ -140,10 +156,11 @@ impl Binder {
initial_scopes: Vec<CompilerScope>,
initial_slot_count: u32,
fixed_scope_idx: i32,
globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
node: &Node<UntypedKind>,
diagnostics: &mut Diagnostics,
) -> Result<(BoundNode, HashMap<Identity, Vec<Identity>>, Vec<CompilerScope>, u32), String> {
let mut binder = Self::new(initial_scopes, initial_slot_count, fixed_scope_idx);
) -> Result<BindingResult, String> {
let mut binder = Self::new(initial_scopes, initial_slot_count, fixed_scope_idx, globals);
let bound = binder.bind(node, ExprContext::Expression, diagnostics);
let final_captures = binder
@@ -531,14 +548,25 @@ impl Binder {
// 2. Try enclosing scopes (capture chain)
for i in (0..current_fn_idx).rev() {
if let Some((info, _)) = self.functions[i].resolve_local(sym) {
// If the resolved address is already Global, we don't need to capture it as an upvalue
if let Some((info, scope_idx)) = self.functions[i].resolve_local(sym) {
// If it's in the root script and within a frozen scope, translate to Global
let is_frozen_root = i == 0 && (scope_idx as i32) <= self.fixed_scope_idx;
if let Address::Global(_) = info.addr {
return Some(info.addr);
}
let mut addr = info.addr;
if is_frozen_root
&& let Address::Local(slot) = addr {
addr = Address::Global(GlobalIdx(slot.0));
}
if let Address::Global(_) = addr {
return Some(addr);
}
// Record the capture for each lambda level in between
for k in (i + 1)..=current_fn_idx {
let lambda_id = self.functions[k].identity.clone();
@@ -552,7 +580,13 @@ impl Binder {
}
}
// 3. Global Fallback (search in root level with context removed)
// 3. Global Fallback
let globals = self.globals.borrow();
if let Some((idx, _)) = globals.get(sym) {
return Some(Address::Global(*idx));
}
// 4. Global Fallback (search in root level with context removed)
if sym.context.is_some() {
let fallback_sym = Symbol {
name: sym.name.clone(),
@@ -560,12 +594,15 @@ impl Binder {
};
// Search again in all functions, primarily we care about Root Scopes
for i in (0..=current_fn_idx).rev() {
if let Some((info, _)) = self.functions[i].resolve_local(&fallback_sym) {
if let Address::Global(_) = info.addr {
return Some(info.addr);
}
if let Some((info, _)) = self.functions[i].resolve_local(&fallback_sym)
&& let Address::Global(_) = info.addr
{
return Some(info.addr);
}
}
if let Some((idx, _)) = globals.get(&fallback_sym) {
return Some(Address::Global(*idx));
}
}
diag.push_error(
@@ -682,8 +719,9 @@ mod tests {
let mut parser = Parser::new(source);
let untyped = parser.parse_expression();
let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diagnostics = Diagnostics::new();
let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics).unwrap();
let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, globals, &untyped, &mut diagnostics).unwrap();
if let BoundKind::Lambda { body, .. } = &bound.kind {
if let BoundKind::Block { exprs } = &body.kind {
@@ -711,8 +749,9 @@ mod tests {
let mut parser = Parser::new(source);
let untyped = parser.parse_expression();
let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diagnostics = Diagnostics::new();
let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics).unwrap();
let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, globals, &untyped, &mut diagnostics).unwrap();
if let BoundKind::Lambda { body, .. } = &bound.kind {
if let BoundKind::Block { exprs } = &body.kind {
@@ -740,8 +779,9 @@ mod tests {
let mut parser = Parser::new(source);
let untyped = parser.parse_expression();
let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diagnostics = Diagnostics::new();
let _ = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics);
let _ = Binder::bind_root(vec![], 0, 0, globals, &untyped, &mut diagnostics);
assert!(diagnostics.has_errors());
assert!(diagnostics.items.iter().any(|i| i.message.contains("already defined")));
@@ -749,16 +789,18 @@ mod tests {
#[test]
fn test_repro_global_redefinition() {
let globals = Rc::new(RefCell::new(HashMap::new()));
let source1 = "(def x 1) 1";
let untyped1 = Parser::new(source1).parse_expression();
let mut diagnostics = Diagnostics::new();
let (_, _, scopes1, slots1) = Binder::bind_root(vec![], 0, -1, &untyped1, &mut diagnostics).unwrap();
let (_, _, scopes1, slots1) = Binder::bind_root(vec![], 0, -1, globals.clone(), &untyped1, &mut diagnostics).unwrap();
let source2 = "(def x 2) 2";
let untyped2 = Parser::new(source2).parse_expression();
let mut diagnostics2 = Diagnostics::new();
// Here we simulate frozen scope by passing fixed_scope_idx = 0
let _ = Binder::bind_root(scopes1, slots1, 0, &untyped2, &mut diagnostics2);
let _ = Binder::bind_root(scopes1, slots1, 0, globals.clone(), &untyped2, &mut diagnostics2);
assert!(diagnostics2.has_errors());
assert!(diagnostics2.items.iter().any(|i| i.message.contains("frozen/immutable")));
+3 -3
View File
@@ -783,7 +783,7 @@ mod tests {
let mut diag = crate::ast::diagnostics::Diagnostics::new();
// fixed_scope_idx = 0 means scope 0 is frozen (Global)
let result = Binder::bind_root(initial_scopes, 1, 0, &expanded, &mut diag);
let result = Binder::bind_root(initial_scopes, 1, 0, Rc::new(RefCell::new(HashMap::new())), &expanded, &mut diag);
assert!(
result.is_ok(),
"Should find global '*' Error: {:?}",
@@ -807,7 +807,7 @@ mod tests {
let expanded = expander.expand(untyped).unwrap();
let mut diag = crate::ast::diagnostics::Diagnostics::new();
let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag);
let result = Binder::bind_root(vec![], 0, 0, Rc::new(RefCell::new(HashMap::new())), &expanded, &mut diag);
assert!(result.is_ok(), "Hygiene failed: {:?}", result.err());
}
@@ -827,7 +827,7 @@ mod tests {
let expanded = expander.expand(untyped).unwrap();
let mut diag = crate::ast::diagnostics::Diagnostics::new();
let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag);
let result = Binder::bind_root(vec![], 0, 0, Rc::new(RefCell::new(HashMap::new())), &expanded, &mut diag);
assert!(
result.is_ok(),
"Explicit Hygiene with Backticks failed: {:?}",
+1 -1
View File
@@ -717,7 +717,7 @@ mod tests {
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
"Statement 'def' cannot be used as an expression.\nCannot define 'x': Scope 0 is frozen/immutable.\nCannot destructure type int as a tuple/vector"
"Statement 'def' cannot be used as an expression.\nCannot destructure type int as a tuple/vector"
);
}