refactor(mir): Extract BindingContext from MirBuilder (Phase 136 follow-up 4/7)
## Summary Extracted binding management into dedicated BindingContext struct, completing step 4 of 7 in the Context Box refactoring plan. ## Changes - NEW: src/mir/builder/binding_context.rs (BindingContext struct + helpers) - Modified 7 files to use binding_ctx (SSOT pattern with legacy sync) - Added comprehensive unit tests for BindingContext ## Extracted Fields - binding_map: BTreeMap<String, BindingId> → binding_ctx.binding_map ## Benefits - Clear separation: BindingId mapping isolated from MirBuilder - Better testability: BindingContext can be tested independently - Consistent pattern: Same SSOT + legacy sync approach as previous steps ## Tests - cargo test --release --lib: 1008/1008 passed - phase135_trim_mir_verify.sh: PASS - Backward compatibility: 100% maintained (deprecated fields synced) ## Progress Phase 136 Context Extraction: 4/7 complete (57%) - ✅ Step 1: TypeContext - ✅ Step 2: CoreContext - ✅ Step 3: ScopeContext - ✅ Step 4: BindingContext (this commit) - ⏳ Step 5: VariableContext - ⏳ Step 6: MetadataContext - ⏳ Step 7: RegionContext 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
137
src/mir/builder/binding_context.rs
Normal file
137
src/mir/builder/binding_context.rs
Normal file
@ -0,0 +1,137 @@
|
||||
//! Phase 136 follow-up (Step 4/7): BindingContext extraction
|
||||
//!
|
||||
//! Consolidates variable binding management:
|
||||
//! - binding_map: String -> BindingId mapping (parallel to variable_map)
|
||||
//! - BindingId allocation (via CoreContext.next_binding())
|
||||
//! - Scope restoration logic (stored in ScopeContext frames)
|
||||
//!
|
||||
//! ## Relationship with other contexts:
|
||||
//! - **CoreContext**: Allocates BindingId via next_binding()
|
||||
//! - **ScopeContext**: Manages lexical scope frames with restore_binding data
|
||||
//! - **TypeContext**: Independent (type tracking vs binding tracking)
|
||||
//!
|
||||
//! ## Design:
|
||||
//! - BindingId tracks variable binding identity (survives SSA renaming)
|
||||
//! - Parallel to ValueId (variable_map), but for binding semantics
|
||||
//! - Restored on scope exit (see LexicalScopeFrame.restore_binding)
|
||||
//!
|
||||
//! Phase 74: BindingId system introduction
|
||||
//! Phase 136 Step 4/7: Extraction into dedicated context
|
||||
|
||||
use crate::mir::BindingId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Phase 136 Step 4/7: Binding context for variable binding management
|
||||
///
|
||||
/// Manages the mapping from variable names to their BindingId.
|
||||
/// Parallel to `variable_map` (String -> ValueId), but tracks binding identity.
|
||||
///
|
||||
/// ## Key responsibilities:
|
||||
/// - Maintain current binding_map (String -> BindingId)
|
||||
/// - Provide lookup/insertion/removal operations
|
||||
/// - Work with ScopeContext for scope-based restoration
|
||||
///
|
||||
/// ## Implementation note:
|
||||
/// - Uses BTreeMap for deterministic iteration (Phase 25.1 consistency)
|
||||
/// - BindingId allocation is delegated to CoreContext.next_binding()
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BindingContext {
|
||||
/// Phase 74: BindingId mapping for lexical variable bindings
|
||||
/// Maps variable names to their current BindingId.
|
||||
/// Parallel to `variable_map` (String -> ValueId), but tracks binding identity.
|
||||
/// Restored on lexical scope exit (see ScopeContext restore_binding).
|
||||
pub(super) binding_map: BTreeMap<String, BindingId>,
|
||||
}
|
||||
|
||||
impl Default for BindingContext {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl BindingContext {
|
||||
/// Create a new BindingContext
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
binding_map: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup a variable's BindingId
|
||||
pub fn lookup(&self, name: &str) -> Option<BindingId> {
|
||||
self.binding_map.get(name).copied()
|
||||
}
|
||||
|
||||
/// Insert a variable binding
|
||||
pub fn insert(&mut self, name: String, binding_id: BindingId) {
|
||||
self.binding_map.insert(name, binding_id);
|
||||
}
|
||||
|
||||
/// Remove a variable binding
|
||||
pub fn remove(&mut self, name: &str) -> Option<BindingId> {
|
||||
self.binding_map.remove(name)
|
||||
}
|
||||
|
||||
/// Get immutable reference to the binding map (for BindingMapProvider)
|
||||
pub fn binding_map(&self) -> &BTreeMap<String, BindingId> {
|
||||
&self.binding_map
|
||||
}
|
||||
|
||||
/// Check if a variable has a binding
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
self.binding_map.contains_key(name)
|
||||
}
|
||||
|
||||
/// Get the number of bindings
|
||||
pub fn len(&self) -> usize {
|
||||
self.binding_map.len()
|
||||
}
|
||||
|
||||
/// Check if there are no bindings
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.binding_map.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_binding_context_basic() {
|
||||
let mut ctx = BindingContext::new();
|
||||
assert!(ctx.is_empty());
|
||||
assert_eq!(ctx.len(), 0);
|
||||
|
||||
let bid = BindingId::new(0);
|
||||
ctx.insert("x".to_string(), bid);
|
||||
assert_eq!(ctx.lookup("x"), Some(bid));
|
||||
assert_eq!(ctx.len(), 1);
|
||||
assert!(!ctx.is_empty());
|
||||
|
||||
ctx.remove("x");
|
||||
assert_eq!(ctx.lookup("x"), None);
|
||||
assert!(ctx.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binding_context_contains() {
|
||||
let mut ctx = BindingContext::new();
|
||||
assert!(!ctx.contains("x"));
|
||||
|
||||
ctx.insert("x".to_string(), BindingId::new(0));
|
||||
assert!(ctx.contains("x"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binding_map_access() {
|
||||
let mut ctx = BindingContext::new();
|
||||
ctx.insert("a".to_string(), BindingId::new(1));
|
||||
ctx.insert("b".to_string(), BindingId::new(2));
|
||||
|
||||
let map = ctx.binding_map();
|
||||
assert_eq!(map.len(), 2);
|
||||
assert_eq!(map.get("a"), Some(&BindingId::new(1)));
|
||||
assert_eq!(map.get("b"), Some(&BindingId::new(2)));
|
||||
}
|
||||
}
|
||||
@ -115,7 +115,8 @@ fn prepare_pattern2_inputs(
|
||||
|
||||
// Phase 79-2: Register loop variable BindingId (dev-only)
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
if let Some(loop_var_bid) = builder.binding_map.get(&loop_var_name).copied() {
|
||||
// Phase 136 Step 4/7: Use binding_ctx for lookup
|
||||
if let Some(loop_var_bid) = builder.binding_ctx.lookup(&loop_var_name) {
|
||||
env.register_loop_var_binding(loop_var_bid, _loop_var_join_id);
|
||||
log_pattern2(
|
||||
verbose,
|
||||
|
||||
@ -133,8 +133,9 @@ impl MirBuilder {
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
{
|
||||
// Register loop variable BindingId
|
||||
if let Some(bid) = self.binding_map.get(&loop_var_name) {
|
||||
cond_env.register_loop_var_binding(*bid, _loop_var_join_id);
|
||||
// Phase 136 Step 4/7: Use binding_ctx for lookup
|
||||
if let Some(bid) = self.binding_ctx.lookup(&loop_var_name) {
|
||||
cond_env.register_loop_var_binding(bid, _loop_var_join_id);
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[phase80/p3] Registered loop var '{}' BindingId({}) -> ValueId({})",
|
||||
@ -147,8 +148,9 @@ impl MirBuilder {
|
||||
// These are variables from the condition expression (e.g., "len" in "i < len")
|
||||
// May include ConditionOnly carriers if they appear in the condition
|
||||
for binding in &condition_bindings {
|
||||
if let Some(bid) = self.binding_map.get(&binding.name) {
|
||||
cond_env.register_condition_binding(*bid, binding.join_value);
|
||||
// Phase 136 Step 4/7: Use binding_ctx for lookup
|
||||
if let Some(bid) = self.binding_ctx.lookup(&binding.name) {
|
||||
cond_env.register_condition_binding(bid, binding.join_value);
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[phase80/p3] Registered condition binding '{}' BindingId({}) -> ValueId({})",
|
||||
|
||||
@ -240,7 +240,8 @@ fn prepare_pattern4_context(
|
||||
continue_cond,
|
||||
loop_body: &normalized_body,
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
binding_map: Some(&builder.binding_map),
|
||||
// Phase 136 Step 4/7: Use binding_ctx for binding_map reference
|
||||
binding_map: Some(builder.binding_ctx.binding_map()),
|
||||
};
|
||||
|
||||
match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) {
|
||||
@ -339,7 +340,8 @@ fn lower_pattern4_joinir(
|
||||
let mut join_value_space = JoinValueSpace::new();
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
let binding_map_clone = builder.binding_map.clone();
|
||||
// Phase 136 Step 4/7: Use binding_ctx for binding_map clone
|
||||
let binding_map_clone = builder.binding_ctx.binding_map().clone();
|
||||
|
||||
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(
|
||||
prepared.loop_scope.clone(),
|
||||
|
||||
@ -227,7 +227,8 @@ impl TrimLoopLowerer {
|
||||
break_cond: Some(break_cond),
|
||||
loop_body: body,
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
binding_map: Some(&builder.binding_map),
|
||||
// Phase 136 Step 4/7: Use binding_ctx for binding_map reference
|
||||
binding_map: Some(builder.binding_ctx.binding_map()),
|
||||
};
|
||||
|
||||
match LoopBodyCarrierPromoter::try_promote(&request) {
|
||||
@ -239,7 +240,8 @@ impl TrimLoopLowerer {
|
||||
|
||||
// Step 3: Convert to CarrierInfo and merge
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
let promoted_carrier = trim_info.to_carrier_info(Some(&builder.binding_map));
|
||||
// Phase 136 Step 4/7: Use binding_ctx for binding_map reference
|
||||
let promoted_carrier = trim_info.to_carrier_info(Some(builder.binding_ctx.binding_map()));
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
let promoted_carrier = trim_info.to_carrier_info();
|
||||
carrier_info.merge_from(&promoted_carrier);
|
||||
|
||||
@ -69,16 +69,18 @@ impl super::super::MirBuilder {
|
||||
}
|
||||
|
||||
// Phase 74: Restore BindingId mappings in parallel
|
||||
// Phase 136 Step 4/7: Update binding_ctx (SSOT) then sync to legacy field
|
||||
for (name, previous_binding) in frame.restore_binding {
|
||||
match previous_binding {
|
||||
Some(prev_bid) => {
|
||||
self.binding_map.insert(name, prev_bid);
|
||||
self.binding_ctx.insert(name.clone(), prev_bid);
|
||||
}
|
||||
None => {
|
||||
self.binding_map.remove(&name);
|
||||
self.binding_ctx.remove(&name);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.sync_binding_ctx_to_legacy();
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
@ -98,7 +100,8 @@ impl super::super::MirBuilder {
|
||||
frame.restore.insert(name.to_string(), previous);
|
||||
|
||||
// Phase 74: Capture previous BindingId for parallel restoration
|
||||
let previous_binding = self.binding_map.get(name).copied();
|
||||
// Phase 136 Step 4/7: Use binding_ctx for lookup
|
||||
let previous_binding = self.binding_ctx.lookup(name);
|
||||
frame
|
||||
.restore_binding
|
||||
.insert(name.to_string(), previous_binding);
|
||||
@ -109,7 +112,9 @@ impl super::super::MirBuilder {
|
||||
|
||||
// Phase 74: Allocate and register new BindingId for this binding
|
||||
let binding_id = self.allocate_binding_id();
|
||||
self.binding_map.insert(name.to_string(), binding_id);
|
||||
// Phase 136 Step 4/7: Update binding_ctx (SSOT) then sync to legacy field
|
||||
self.binding_ctx.insert(name.to_string(), binding_id);
|
||||
self.sync_binding_ctx_to_legacy();
|
||||
|
||||
// Sync to legacy field
|
||||
self.sync_scope_ctx_to_legacy();
|
||||
|
||||
Reference in New Issue
Block a user