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:
nyash-codex
2025-12-15 20:40:23 +09:00
parent 3127ebb73d
commit 1adf57ec54
9 changed files with 598 additions and 22 deletions

View File

@ -18,6 +18,7 @@ use std::collections::{BTreeMap, HashMap};
mod builder_calls;
mod call_resolution; // ChatGPT5 Pro: Type-safe call resolution utilities
mod calls; // Call system modules (refactored from builder_calls)
mod binding_context; // Phase 136 follow-up (Step 4/7): BindingContext extraction
mod context; // BoxCompilationContext - 箱理論による静的Boxコンパイル時のコンテキスト分離
mod core_context; // Phase 136 follow-up (Step 2/7): CoreContext extraction
mod decls; // declarations lowering split
@ -113,6 +114,11 @@ pub struct MirBuilder {
/// Direct field access for backward compatibility (migration in progress).
pub(super) scope_ctx: scope_context::ScopeContext,
/// Phase 136 follow-up (Step 4/7): Binding context
/// Consolidates binding_map (String -> BindingId mapping).
/// Direct field access for backward compatibility (migration in progress).
pub(super) binding_ctx: binding_context::BindingContext,
/// Variable name to ValueId mapping (for SSA conversion)
/// 注意: compilation_contextがSomeの場合は使用されません
/// Phase 25.1: HashMap → BTreeMapPHI生成の決定性確保
@ -210,11 +216,9 @@ pub struct MirBuilder {
#[deprecated(note = "Use core_ctx.next_binding_id instead")]
pub next_binding_id: u32,
/// 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 `pop_lexical_scope()`).
/// BTreeMap for deterministic iteration (Phase 25.1 consistency).
/// [DEPRECATED] Phase 74: BindingId mapping for lexical variable bindings
/// Phase 136: Moved to binding_ctx.binding_map (backward compat wrapper)
#[deprecated(note = "Use binding_ctx.binding_map instead")]
pub binding_map: BTreeMap<String, super::BindingId>,
// include guards removed
@ -335,6 +339,7 @@ impl MirBuilder {
compilation_context: None, // 箱理論: デフォルトは従来モード
type_ctx: type_context::TypeContext::new(), // Phase 136: Type context
scope_ctx: scope_context::ScopeContext::new(), // Phase 136 Step 3/7: Scope context
binding_ctx: binding_context::BindingContext::new(), // Phase 136 Step 4/7: Binding context
variable_map: BTreeMap::new(), // Phase 25.1: 決定性確保
lexical_scope_stack: Vec::new(),
pending_phis: Vec::new(),
@ -436,6 +441,19 @@ impl MirBuilder {
self.scope_ctx.debug_scope_stack = self.debug_scope_stack.clone();
}
// ---- Phase 136 Step 4/7: BindingContext synchronization helpers ----
/// Sync binding_ctx changes back to legacy fields (backward compatibility)
#[allow(deprecated)]
fn sync_binding_ctx_to_legacy(&mut self) {
self.binding_map = self.binding_ctx.binding_map.clone();
}
/// Sync legacy field changes to binding_ctx (backward compatibility)
#[allow(deprecated)]
fn sync_legacy_to_binding_ctx(&mut self) {
self.binding_ctx.binding_map = self.binding_map.clone();
}
/// Push/pop helpers for If merge context (best-effort; optional usage)
#[allow(deprecated)]
pub(super) fn push_if_merge(&mut self, bb: BasicBlockId) {
@ -1223,7 +1241,8 @@ use crate::mir::loop_pattern_detection::BindingMapProvider;
impl BindingMapProvider for MirBuilder {
#[cfg(feature = "normalized_dev")]
fn get_binding_map(&self) -> Option<&std::collections::BTreeMap<String, crate::mir::BindingId>> {
Some(&self.binding_map)
// Phase 136 Step 4/7: Use binding_ctx (SSOT)
Some(self.binding_ctx.binding_map())
}
#[cfg(not(feature = "normalized_dev"))]
@ -1237,9 +1256,12 @@ mod binding_id_tests {
use super::*;
#[test]
#[allow(deprecated)]
fn test_binding_map_initialization() {
let builder = MirBuilder::new();
assert_eq!(builder.next_binding_id, 0);
// Phase 136 Step 4/7: Check both binding_ctx (SSOT) and legacy field
assert!(builder.binding_ctx.is_empty());
assert!(builder.binding_map.is_empty());
}
@ -1257,6 +1279,7 @@ mod binding_id_tests {
}
#[test]
#[allow(deprecated)]
fn test_shadowing_binding_restore() {
let mut builder = MirBuilder::new();
@ -1269,8 +1292,11 @@ mod binding_id_tests {
builder
.declare_local_in_current_scope("x", outer_vid)
.unwrap();
let outer_bid = *builder.binding_map.get("x").unwrap();
// Phase 136 Step 4/7: Check binding_ctx (SSOT)
let outer_bid = builder.binding_ctx.lookup("x").unwrap();
assert_eq!(outer_bid.raw(), 0);
// Also verify legacy field is synced
assert_eq!(*builder.binding_map.get("x").unwrap(), outer_bid);
// Enter inner scope and shadow x
builder.push_lexical_scope();
@ -1279,14 +1305,20 @@ mod binding_id_tests {
builder
.declare_local_in_current_scope("x", inner_vid)
.unwrap();
let inner_bid = *builder.binding_map.get("x").unwrap();
// Phase 136 Step 4/7: Check binding_ctx (SSOT)
let inner_bid = builder.binding_ctx.lookup("x").unwrap();
assert_eq!(inner_bid.raw(), 1);
// Also verify legacy field is synced
assert_eq!(*builder.binding_map.get("x").unwrap(), inner_bid);
// Exit inner scope - should restore outer binding
builder.pop_lexical_scope();
let restored_bid = *builder.binding_map.get("x").unwrap();
// Phase 136 Step 4/7: Check binding_ctx (SSOT)
let restored_bid = builder.binding_ctx.lookup("x").unwrap();
assert_eq!(restored_bid, outer_bid);
assert_eq!(restored_bid.raw(), 0);
// Also verify legacy field is synced
assert_eq!(*builder.binding_map.get("x").unwrap(), restored_bid);
// Cleanup
builder.pop_lexical_scope();

View 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)));
}
}

View File

@ -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,

View File

@ -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({})",

View File

@ -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(),

View File

@ -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);

View File

@ -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();