refactor(mir): Remove VariableContext legacy fields (Phase 2-6/7)

完全移行→削除の安全順序(Option C)に従い、VariableContext の
deprecated フィールドと sync helpers を完全削除。

## Changes
- Migrated all 66+ access sites to variable_ctx.variable_map
- Removed 1 deprecated field (variable_map) from MirBuilder
- Removed 2 sync helpers (sync_variable_ctx_to_legacy, sync_legacy_to_variable_ctx)
- Fixed BoxCompilationContext.variable_map references (kept as-is, different scope)
- Fixed ExitBindingBuilder.variable_map references (kept as-is, local field)
- Updated observer.rs to use variable_map() accessor method

## JoinIR Integration Verified
- CarrierInfo::from_variable_map() works correctly
- ExitLine contract maintained (Phase 132-135)
- NYASH_TRACE_VARMAP debug support preserved
- Pattern 1-5 lowering all functional

## Tests
- cargo test --release --lib: 1033 passed, 0 failed
- phase135_trim_mir_verify.sh: PASS (MIR SSA/ValueId OK)
- cargo build --release: SUCCESS
- Deprecation warnings: reduced (86 remaining, from other contexts)

## Statistics
- 27 files changed
- 146 insertions(+), 174 deletions(-)
- Net: -28 lines

Phase 2 Progress: 6/7 contexts complete (86%)
-  MetadataContext
-  CoreContext
-  TypeContext
-  ScopeContext
-  BindingContext
-  VariableContext (this commit)
-  CompilationContext (Phase 2-7 next)

🤖 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-16 03:48:44 +09:00
parent 44b20bfe28
commit 9170f0a85d
27 changed files with 146 additions and 174 deletions

View File

@ -25,6 +25,6 @@ impl MirBuilder {
/// Enable with NYASH_TRACE_VARMAP=1
#[allow(dead_code)]
pub(in crate::mir::builder) fn trace_varmap(&self, context: &str) {
super::joinir::trace::trace().varmap(context, &self.variable_map);
super::joinir::trace::trace().varmap(context, &self.variable_ctx.variable_map);
}
}

View File

@ -3,7 +3,7 @@
//! Modularizes the exit_bindings collection logic from Pattern lowerers
//! into a focused, testable Box.
//!
//! **Responsibility**: Construct exit_bindings from ExitMeta + variable_map lookup
//! **Responsibility**: Construct exit_bindings from ExitMeta + variable_ctx.variable_map lookup
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::ExitMeta;
@ -16,10 +16,10 @@ use crate::mir::ValueId; // Phase 228-8: For ConditionOnly placeholder
///
/// ## Pure Function Philosophy
/// ExitMetaCollector::collect() is a **pure function**:
/// - Input: builder (read-only for variable_map lookup)
/// - Input: builder (read-only for variable_ctx.variable_map lookup)
/// - Input: exit_meta (data structure)
/// - Output: Vec<LoopExitBinding> (new data)
/// - No side effects (except reading builder.variable_map)
/// - No side effects (except reading builder.variable_ctx.variable_map)
///
/// ## Why Pure Functions?
/// - Easy to test (no mocks needed)
@ -40,7 +40,7 @@ use crate::mir::ValueId; // Phase 228-8: For ConditionOnly placeholder
///
/// **Input**:
/// - ExitMeta with exit_values (carrier_name → join_exit_value mappings)
/// - MirBuilder with variable_map for host ValueId lookup
/// - MirBuilder with variable_ctx.variable_map for host ValueId lookup
///
/// **Effect**:
/// - Creates LoopExitBinding vector (pure function, no side effects)
@ -50,25 +50,25 @@ use crate::mir::ValueId; // Phase 228-8: For ConditionOnly placeholder
pub struct ExitMetaCollector;
impl ExitMetaCollector {
/// Build exit_bindings from ExitMeta and variable_map
/// Build exit_bindings from ExitMeta and variable_ctx.variable_map
///
/// # Algorithm
///
/// For each entry in exit_meta.exit_values:
/// 1. Look up the carrier's host ValueId from builder.variable_map
/// 1. Look up the carrier's host ValueId from builder.variable_ctx.variable_map
/// 2. Create LoopExitBinding with carrier_name, join_exit_value, host_slot
/// 3. Collect into Vec<LoopExitBinding>
///
/// # Phase 228-8: ConditionOnly carrier handling
///
/// ConditionOnly carriers are included in exit_bindings even if they're not
/// in variable_map, because they need latch incoming values for header PHI.
/// in variable_ctx.variable_map, because they need latch incoming values for header PHI.
/// The host_slot is set to ValueId(0) as a placeholder since ConditionOnly
/// carriers don't participate in exit PHI.
///
/// # Skipped carriers
///
/// Carriers not found in variable_map AND not in carrier_info are silently skipped.
/// Carriers not found in variable_ctx.variable_map AND not in carrier_info are silently skipped.
/// This is intentional: some carriers may not be relevant to the current pattern.
///
/// # Logging
@ -96,13 +96,13 @@ impl ExitMetaCollector {
for (carrier_name, join_exit_value) in &exit_meta.exit_values {
if verbose {
eprintln!(
"[joinir/exit-line] checking carrier '{}' in variable_map",
"[joinir/exit-line] checking carrier '{}' in variable_ctx.variable_map",
carrier_name
);
}
// Look up host slot from variable_map
if let Some(&host_slot) = builder.variable_map.get(carrier_name) {
// Look up host slot from variable_ctx.variable_map
if let Some(&host_slot) = builder.variable_ctx.variable_map.get(carrier_name) {
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
// Phase 228-8: Look up role from carrier_info if available
@ -157,7 +157,7 @@ impl ExitMetaCollector {
if verbose {
eprintln!(
"[joinir/exit-line] collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_map)",
"[joinir/exit-line] collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_ctx.variable_map)",
carrier_name, join_exit_value
);
}
@ -166,7 +166,7 @@ impl ExitMetaCollector {
}
Some((CarrierRole::LoopState, CarrierInit::FromHost)) => {
// Phase 247-EX: Include FromHost carrier in exit_bindings
// (needed for latch incoming, not for exit PHI or variable_map)
// (needed for latch incoming, not for exit PHI or variable_ctx.variable_map)
let binding = LoopExitBinding {
carrier_name: carrier_name.clone(),
join_exit_value: *join_exit_value,
@ -176,7 +176,7 @@ impl ExitMetaCollector {
if verbose {
eprintln!(
"[joinir/exit-line] collected FromHost carrier '{}' JoinIR {:?} (not in variable_map)",
"[joinir/exit-line] collected FromHost carrier '{}' JoinIR {:?} (not in variable_ctx.variable_map)",
carrier_name, join_exit_value
);
}
@ -203,7 +203,7 @@ impl ExitMetaCollector {
}
_ => {
let msg = format!(
"[joinir/exit-line] carrier '{}' not in variable_map and not ConditionOnly/FromHost (skip)",
"[joinir/exit-line] carrier '{}' not in variable_ctx.variable_map and not ConditionOnly/FromHost (skip)",
carrier_name
);
if strict {
@ -243,7 +243,7 @@ mod tests {
fn test_missing_carrier_in_variable_map() {
// This test would require full MirBuilder setup
// Placeholder for future detailed testing
// When carrier not in variable_map, should be silently skipped
// When carrier not in variable_ctx.variable_map, should be silently skipped
assert!(true);
}
}

View File

@ -3,7 +3,7 @@
//! Modularizes the exit line reconnection logic (Phase 6 from merge/mod.rs)
//! into a focused, testable Box.
//!
//! **Responsibility**: Update host variable_map with PHI dst values from exit block
//! **Responsibility**: Update host variable_ctx.variable_map with PHI dst values from exit block
//!
//! # Phase 33-13 Architecture Change
//!
@ -23,15 +23,15 @@ use std::collections::BTreeMap;
/// # Design Notes
///
/// ## Why Separate Box?
/// - Responsibility: Update host variable_map with PHI dst values
/// - Responsibility: Update host variable_ctx.variable_map with PHI dst values
/// - Input: JoinInlineBoundary.exit_bindings + carrier_phis map
/// - Output: Updated MirBuilder.variable_map
/// - No side effects beyond variable_map updates
/// - Output: Updated MirBuilder.variable_ctx.variable_map
/// - No side effects beyond variable_ctx.variable_map updates
///
/// ## Phase 33-13 Carrier PHI Integration
/// This Box now uses carrier_phis from exit_phi_builder:
/// - Before: `variable_map[carrier] = remapper.get(join_exit)` (SSA-incorrect!)
/// - After: `variable_map[carrier] = carrier_phis[carrier]` (SSA-correct!)
/// - Before: `variable_ctx.variable_map[carrier] = remapper.get(join_exit)` (SSA-incorrect!)
/// - After: `variable_ctx.variable_map[carrier] = carrier_phis[carrier]` (SSA-correct!)
///
/// The key insight is that remapped exit values are PHI INPUTS, not OUTPUTS.
/// Only the PHI dst ValueId is defined in the exit block.
@ -40,7 +40,7 @@ use std::collections::BTreeMap;
/// Can be tested independently:
/// 1. Create mock boundary with exit_bindings
/// 2. Create mock carrier_phis map
/// 3. Call reconnect() and verify variable_map updates
/// 3. Call reconnect() and verify variable_ctx.variable_map updates
/// 4. No need to construct full merge/mod.rs machinery
///
/// # Box Contract
@ -50,14 +50,14 @@ use std::collections::BTreeMap;
/// - carrier_phis: Map from carrier name to PHI dst ValueId
///
/// **Effect**:
/// - Updates builder.variable_map entries for each carrier with PHI dst values
/// - Updates builder.variable_ctx.variable_map entries for each carrier with PHI dst values
///
/// **Output**:
/// - Result<(), String> (side effect on builder)
pub struct ExitLineReconnector;
impl ExitLineReconnector {
/// Reconnect exit values to host variable_map using carrier PHI dst values
/// Reconnect exit values to host variable_ctx.variable_map using carrier PHI dst values
///
/// # Phase 33-13: Carrier PHI Integration
///
@ -71,7 +71,7 @@ impl ExitLineReconnector {
///
/// For each exit_binding:
/// 1. Look up the PHI dst for this carrier in carrier_phis
/// 2. Update variable_map[binding.carrier_name] with PHI dst
/// 2. Update variable_ctx.variable_map[binding.carrier_name] with PHI dst
/// 3. Log each update (if debug enabled)
pub fn reconnect(
builder: &mut MirBuilder,
@ -119,12 +119,12 @@ impl ExitLineReconnector {
// Process each exit binding
for binding in &boundary.exit_bindings {
// Phase 228-8: Skip ConditionOnly carriers (no variable_map update needed)
// Phase 228-8: Skip ConditionOnly carriers (no variable_ctx.variable_map update needed)
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
if binding.role == CarrierRole::ConditionOnly {
if verbose {
eprintln!(
"[joinir/exit-line] skip ConditionOnly carrier '{}' (no variable_map update)",
"[joinir/exit-line] skip ConditionOnly carrier '{}' (no variable_ctx.variable_map update)",
binding.carrier_name
);
}
@ -141,25 +141,25 @@ impl ExitLineReconnector {
);
}
// Update variable_map with PHI dst
// Update variable_ctx.variable_map with PHI dst
if let Some(&phi_value) = phi_dst {
if let Some(var_vid) = builder.variable_map.get_mut(&binding.carrier_name) {
if let Some(var_vid) = builder.variable_ctx.variable_map.get_mut(&binding.carrier_name) {
// Phase 177-STRUCT: Always log for debugging
if verbose {
eprintln!(
"[joinir/exit-line] variable_map['{}'] {:?}{:?}",
"[joinir/exit-line] variable_ctx.variable_map['{}'] {:?}{:?}",
binding.carrier_name, *var_vid, phi_value
);
}
*var_vid = phi_value;
} else if verbose {
eprintln!(
"[joinir/exit-line] warning: carrier '{}' not found in variable_map",
"[joinir/exit-line] warning: carrier '{}' not found in variable_ctx.variable_map",
binding.carrier_name
);
} else if strict {
return Err(format!(
"[joinir/exit-line] missing variable_map entry for carrier '{}' (exit reconnection)",
"[joinir/exit-line] missing variable_ctx.variable_map entry for carrier '{}' (exit reconnection)",
binding.carrier_name
));
}
@ -188,9 +188,9 @@ impl ExitLineReconnector {
}
// Phase 190-impl-D-3: Contract verification (debug build only)
// Ensures all exit_bindings have corresponding entries in carrier_phis and variable_map
// Ensures all exit_bindings have corresponding entries in carrier_phis and variable_ctx.variable_map
#[cfg(debug_assertions)]
Self::verify_exit_line_contract(boundary, carrier_phis, &builder.variable_map);
Self::verify_exit_line_contract(boundary, carrier_phis, &builder.variable_ctx.variable_map);
Ok(())
}
@ -200,14 +200,14 @@ impl ExitLineReconnector {
/// # Contract Requirements
///
/// 1. Every exit_binding must have a corresponding entry in carrier_phis
/// 2. Every exit_binding's carrier must exist in variable_map after reconnect
/// 3. The variable_map entry must point to the PHI dst (not the original host value)
/// 2. Every exit_binding's carrier must exist in variable_ctx.variable_map after reconnect
/// 3. The variable_ctx.variable_map entry must point to the PHI dst (not the original host value)
///
/// # Panics
///
/// Panics if any contract violation is detected. This helps catch bugs where:
/// - PHI is missing for a carrier (Phase 190-impl-D root cause)
/// - variable_map update was skipped
/// - variable_ctx.variable_map update was skipped
/// - ValueId collision occurred
#[cfg(debug_assertions)]
fn verify_exit_line_contract(
@ -218,10 +218,10 @@ impl ExitLineReconnector {
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
for binding in &boundary.exit_bindings {
// Phase 228-8: Skip ConditionOnly carriers (not in variable_map by design)
// Phase 228-8: Skip ConditionOnly carriers (not in variable_ctx.variable_map by design)
if binding.role == CarrierRole::ConditionOnly {
eprintln!(
"[JoinIR/ExitLine/Contract] Phase 228-8: Skipping ConditionOnly carrier '{}' (not in variable_map)",
"[JoinIR/ExitLine/Contract] Phase 228-8: Skipping ConditionOnly carrier '{}' (not in variable_ctx.variable_map)",
binding.carrier_name
);
continue;
@ -240,20 +240,20 @@ impl ExitLineReconnector {
// Future: Distinguish loop_var from carriers in exit_bindings
}
// Contract 2: variable_map must contain this carrier after reconnect
// Contract 2: variable_ctx.variable_map must contain this carrier after reconnect
let var_value = variable_map.get(&binding.carrier_name);
if var_value.is_none() {
panic!(
"[JoinIR/ExitLine/Contract] VIOLATION: Carrier '{}' missing from variable_map after reconnect",
"[JoinIR/ExitLine/Contract] VIOLATION: Carrier '{}' missing from variable_ctx.variable_map after reconnect",
binding.carrier_name
);
}
// Contract 3: variable_map entry should point to PHI dst (if PHI exists)
// Contract 3: variable_ctx.variable_map entry should point to PHI dst (if PHI exists)
if let (Some(&phi), Some(&var)) = (phi_dst, var_value) {
if phi != var {
panic!(
"[JoinIR/ExitLine/Contract] VIOLATION: Carrier '{}' variable_map={:?} but PHI dst={:?} (mismatch!)",
"[JoinIR/ExitLine/Contract] VIOLATION: Carrier '{}' variable_ctx.variable_map={:?} but PHI dst={:?} (mismatch!)",
binding.carrier_name, var, phi
);
}

View File

@ -54,7 +54,7 @@ impl MirBuilder {
let ctx = build_pattern_context(self, condition, body, PatternVariant::Pattern1)?;
// Phase 195: Use unified trace
trace::trace().varmap("pattern1_start", &self.variable_map);
trace::trace().varmap("pattern1_start", &self.variable_ctx.variable_map);
// Phase 202-A: Create JoinValueSpace for unified ValueId allocation
// Pattern 1 uses Param region for boundary input slots (loop var) and Local region for temps.

View File

@ -108,7 +108,7 @@ fn prepare_pattern2_inputs(
ConditionEnvBuilder::build_for_break_condition_v2(
condition,
&loop_var_name,
&builder.variable_map,
&builder.variable_ctx.variable_map,
loop_var_id,
&mut join_value_space,
)?;
@ -140,7 +140,7 @@ fn prepare_pattern2_inputs(
// Add captured vars
for var in &captured_env.vars {
if let Some(&host_id) = builder.variable_map.get(&var.name) {
if let Some(&host_id) = builder.variable_ctx.variable_map.get(&var.name) {
let join_id = join_value_space.alloc_param();
env.insert(var.name.clone(), join_id);
condition_bindings.push(ConditionBinding {
@ -676,7 +676,7 @@ pub(crate) fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternCo
CommonPatternInitializer::check_carrier_updates_allowed(
ctx.body,
&loop_var_name,
&builder.variable_map,
&builder.variable_ctx.variable_map,
)
}
@ -745,7 +745,7 @@ impl MirBuilder {
use super::pattern_pipeline::{build_pattern_context, PatternVariant};
let ctx = build_pattern_context(self, condition, _body, PatternVariant::Pattern2)?;
trace::trace().varmap("pattern2_start", &self.variable_map);
trace::trace().varmap("pattern2_start", &self.variable_ctx.variable_map);
let mut inputs =
prepare_pattern2_inputs(self, condition, _body, fn_body, &ctx, verbose)?;
@ -977,11 +977,11 @@ mod tests {
use crate::mir::ValueId;
let mut builder = MirBuilder::new();
builder.variable_map.insert("i".to_string(), ValueId(1));
builder.variable_map.insert("len".to_string(), ValueId(2));
builder.variable_map.insert("s".to_string(), ValueId(3));
builder.variable_map.insert("digits".to_string(), ValueId(4));
builder.variable_map.insert("result".to_string(), ValueId(5));
builder.variable_ctx.variable_map.insert("i".to_string(), ValueId(1));
builder.variable_ctx.variable_map.insert("len".to_string(), ValueId(2));
builder.variable_ctx.variable_map.insert("s".to_string(), ValueId(3));
builder.variable_ctx.variable_map.insert("digits".to_string(), ValueId(4));
builder.variable_ctx.variable_map.insert("result".to_string(), ValueId(5));
let condition = bin(BinaryOperator::Less, var("i"), var("len"));

View File

@ -124,7 +124,7 @@ impl MirBuilder {
ConditionEnvBuilder::build_for_break_condition_v2(
condition,
&loop_var_name,
&self.variable_map,
&self.variable_ctx.variable_map,
loop_var_id,
&mut join_value_space,
)?;
@ -183,7 +183,7 @@ impl MirBuilder {
// Collect parent-defined variables from function scope
// For now, use all variables in variable_map except loop_var
let parent_defined: Vec<String> = self
.variable_map
.variable_ctx.variable_map
.keys()
.filter(|name| *name != &loop_var_name)
.cloned()

View File

@ -81,7 +81,7 @@ pub(crate) fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternCo
CommonPatternInitializer::check_carrier_updates_allowed(
ctx.body,
&loop_var_name,
&builder.variable_map,
&builder.variable_ctx.variable_map,
)
}
@ -335,7 +335,7 @@ fn lower_pattern4_joinir(
use crate::mir::join_ir::lowering::loop_with_continue_minimal::lower_loop_with_continue_minimal;
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
trace::trace().varmap("pattern4_start", &builder.variable_map);
trace::trace().varmap("pattern4_start", &builder.variable_ctx.variable_map);
let mut join_value_space = JoinValueSpace::new();

View File

@ -316,9 +316,9 @@ pub(crate) fn lower(
);
}
// Step 2: Get counter ValueId from variable_map
let counter_id = builder.variable_map.get(&counter_name).copied().ok_or_else(|| {
format!("Counter variable '{}' not found in variable_map", counter_name)
// Step 2: Get counter ValueId from variable_ctx.variable_map
let counter_id = builder.variable_ctx.variable_map.get(&counter_name).copied().ok_or_else(|| {
format!("Counter variable '{}' not found in variable_ctx.variable_map", counter_name)
})?;
if debug {

View File

@ -263,7 +263,7 @@ pub(crate) fn build_pattern_context(
let (loop_var_name, loop_var_id, carrier_info) = CommonPatternInitializer::initialize_pattern(
builder,
condition,
&builder.variable_map,
&builder.variable_ctx.variable_map,
None, // No exclusions for now (Pattern 2/4 will filter carriers later)
)?;

View File

@ -280,7 +280,7 @@ impl TrimLoopLowerer {
Self::generate_carrier_initialization(builder, body, trim_helper)?;
eprintln!(
"[TrimLoopLowerer] Registered carrier '{}' in variable_map",
"[TrimLoopLowerer] Registered carrier '{}' in variable_ctx.variable_map",
trim_helper.carrier_name
);
@ -328,7 +328,7 @@ impl TrimLoopLowerer {
/// Generates:
/// 1. ch0 = s.substring(start, start+1)
/// 2. is_ch_match0 = (ch0 == " " || ch0 == "\t" || ...)
/// 3. Registers carrier in variable_map
/// 3. Registers carrier in variable_ctx.variable_map
fn generate_carrier_initialization(
builder: &mut MirBuilder,
body: &[ASTNode],
@ -353,7 +353,7 @@ impl TrimLoopLowerer {
// Get ValueIds for string and start
let s_id =
builder.variable_map.get(&s_name).copied().ok_or_else(|| {
builder.variable_ctx.variable_map.get(&s_name).copied().ok_or_else(|| {
format!("[TrimLoopLowerer] String variable '{}' not found", s_name)
})?;
@ -401,9 +401,9 @@ impl TrimLoopLowerer {
is_ch_match0
);
// Register carrier in variable_map
// Register carrier in variable_ctx.variable_map
builder
.variable_map
.variable_ctx.variable_map
.insert(trim_helper.carrier_name.clone(), is_ch_match0);
Ok(())
@ -438,7 +438,7 @@ impl TrimLoopLowerer {
let mut bindings = Vec::new();
// Add carrier to ConditionEnv
let get_value = |name: &str| builder.variable_map.get(name).copied();
let get_value = |name: &str| builder.variable_ctx.variable_map.get(name).copied();
let mut env_temp = std::collections::HashMap::new(); // Temporary env for closure
let binding = TrimPatternLowerer::add_to_condition_env(