Files
hakorune/docs/development/current/main/phase77-expansion-completion.md
nyash-codex 4ab6c4e6b3 docs(joinir): Phase 77 - BindingId expansion plan
Phase 77 design finalizes BindingId infrastructure migration with comprehensive
implementation roadmap. Three design documents outline expansion of promoted_bindings
tracking to Pattern3/4 and legacy code deprecation strategy.

Documents:
- phase77-expansion-completion.md: Architecture overview + 5 implementation tasks
- PHASE_77_IMPLEMENTATION_GUIDE.md: Step-by-step code changes with exact locations
- PHASE_77_EXECUTIVE_SUMMARY.md: Impact analysis and success metrics

Key decisions:
- DigitPosPromoter/TrimLoopHelper populate promoted_bindings
- Pattern3/4 use BindingId priority in variable resolution
- 3-phase deletion strategy (Deprecate→Require→Delete)
- Dev-only feature gate preserves zero production impact

Phase 77 implementation: ~2-3 hours (5 tasks: promoters + patterns + legacy + tests)

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 05:35:35 +09:00

15 KiB

Phase 77: Expansion - Pattern2→3→4 BindingId Migration Complete

Status: ANALYSIS COMPLETE - Ready for Implementation Date: 2025-12-13 Estimated Duration: 2-3 hours Build Baseline: 958/958 tests PASS (without normalized_dev), lib tests stable


Executive Summary

Phase 77 completes the BindingId migration by:

  1. Populating promoted_bindings in DigitPos/Trim promoters
  2. Extending BindingId lookup to Pattern3/4
  3. Deleting legacy name-based code (~40 lines of string hacks)
  4. Achieving type-safe promotion across all JoinIR patterns

Dependencies

  • Phase 74: BindingId infrastructure (MirBuilder.binding_map)
  • Phase 75: BindingId priority lookup (ConditionEnv)
  • Phase 76: promoted_bindings data structure

Success Metrics

  • DigitPos/Trim promoters populate promoted_bindings
  • Pattern3/4 use BindingId priority lookup
  • Legacy name-based code deleted (~40 lines)
  • No new by-name dependencies introduced
  • 958/958 tests PASS (regression-free)

Architecture Overview

Phase 74-77 Evolution

Phase 74: Infrastructure    → binding_map added to MirBuilder
Phase 75: Pilot             → BindingId priority lookup in ConditionEnv
Phase 76: Promotion Infra   → promoted_bindings map in CarrierInfo
Phase 77: Expansion (THIS)  → Populate map + Delete legacy code

Current State (After Phase 76)

Infrastructure Complete:

  • MirBuilder.binding_map: BTreeMap<String, BindingId>
  • CarrierInfo.promoted_bindings: BTreeMap<BindingId, BindingId>
  • ConditionEnv.resolve_var_with_binding() (3-tier lookup)
  • Pattern2ScopeManager.lookup_with_binding() (BindingId priority)

Missing Pieces (Phase 77 scope):

  • Promoters don't call record_promoted_binding() yet
  • Pattern3/4 don't use BindingId lookup yet
  • Legacy name-based code still active

Implementation Plan

Task 1: DigitPosPromoter Integration (45 min)

Goal: Populate promoted_bindings when DigitPos promotion succeeds

File: src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs

Current Code (lines 225-239):

// Phase 229: Record promoted variable (no need for condition_aliases)
// Dynamic resolution uses promoted_loopbodylocals + naming convention
carrier_info
    .promoted_loopbodylocals
    .push(var_in_cond.clone());

eprintln!(
    "[digitpos_promoter] Phase 247-EX: A-4 DigitPos pattern promoted: {}{} (bool) + {} (i64)",
    var_in_cond, bool_carrier_name, int_carrier_name
);
eprintln!(
    "[digitpos_promoter] Phase 229: Recorded promoted variable '{}' (carriers: '{}', '{}')",
    var_in_cond, bool_carrier_name, int_carrier_name
);

return DigitPosPromotionResult::Promoted {
    carrier_info,
    promoted_var: var_in_cond,
    carrier_name: bool_carrier_name,
};

Problem:

  • No access to binding_map from MirBuilder
  • carrier_info is created locally, but binding_map lives in MirBuilder
  • Need to pass binding_map reference through call chain

Solution Approach:

Option A (Recommended): Add optional binding_map parameter to DigitPosPromotionRequest

pub struct DigitPosPromotionRequest<'a> {
    // ... existing fields ...

    /// Phase 77: Optional BindingId map for type-safe promotion tracking (dev-only)
    #[cfg(feature = "normalized_dev")]
    pub binding_map: Option<&'a BTreeMap<String, BindingId>>,
}

Changes Required:

  1. Add binding_map field to DigitPosPromotionRequest (dev-only)
  2. In try_promote(), after successful promotion:
    #[cfg(feature = "normalized_dev")]
    if let Some(binding_map) = req.binding_map {
        if let (Some(original_bid), Some(promoted_bool_bid)) = (
            binding_map.get(var_in_cond),
            binding_map.get(&bool_carrier_name),
        ) {
            carrier_info.record_promoted_binding(*original_bid, *promoted_bool_bid);
            eprintln!(
                "[digitpos_promoter/phase77] Recorded promoted binding: {} (BindingId({:?})) → {} (BindingId({:?}))",
                var_in_cond, original_bid, bool_carrier_name, promoted_bool_bid
            );
        }
    }
    

Call Site Updates:

  • src/mir/loop_pattern_detection/loop_body_cond_promoter.rs:203-210
    • Add binding_map: Some(&req.binding_map) to DigitPosPromotionRequest construction
    • Requires binding_map in ConditionPromotionRequest

Dependency Chain:

MirBuilder.binding_map
  ↓ (needs to flow through)
ConditionPromotionRequest.binding_map (NEW)
  ↓
DigitPosPromotionRequest.binding_map (NEW)
  ↓
try_promote() → record_promoted_binding()

Task 2: TrimLoopHelper Integration (30 min)

Goal: Populate promoted_bindings when Trim promotion succeeds

File: src/mir/loop_pattern_detection/trim_loop_helper.rs

Similar Approach:

  1. Add optional binding_map to TrimPromotionRequest
  2. In TrimLoopHelper::try_promote(), record promoted binding
  3. Update call sites to pass binding_map

Implementation:

#[cfg(feature = "normalized_dev")]
if let Some(binding_map) = req.binding_map {
    if let (Some(original_bid), Some(promoted_bid)) = (
        binding_map.get(&req.var_name),
        binding_map.get(&carrier_name),
    ) {
        carrier_info.record_promoted_binding(*original_bid, *promoted_bid);
        eprintln!(
            "[trim_lowerer/phase77] Recorded promoted binding: {}{}",
            req.var_name, carrier_name
        );
    }
}

Task 3: Pattern3/4 BindingId Lookup Integration (45 min)

Goal: Use BindingId priority lookup in Pattern3/4 condition resolution

Files:

  • src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs
  • src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs

Current State: Pattern3/4 use name-based lookup only

Target State: Use ConditionEnv.resolve_var_with_binding() like Pattern2 does

Changes:

  1. Pattern3 ConditionEnv Builder (dev-only parameter):

    #[cfg(feature = "normalized_dev")]
    fn build_condition_env_with_binding_map(
        &mut self,
        binding_map: &BTreeMap<String, BindingId>,
    ) -> ConditionEnv {
        // Use BindingId priority resolution
    }
    
  2. Pattern4 Similar Integration:

    • Add with_binding_map() variant for dev-only BindingId lookup
    • Fallback to name-based for production (dual-path)

Note: Full production integration deferred to Phase 78+ (requires broader refactoring)

Task 4: Legacy Code Deletion (~40 lines)

Goal: Remove fragile name-based promotion hacks

4.1 CarrierInfo::resolve_promoted_join_id (carrier_info.rs:440-505)

Current Code:

let candidates = [
    format!("is_{}", original_name),       // DigitPos pattern
    format!("is_{}_match", original_name), // Trim pattern
];

for carrier_name in &candidates {
    if let Some(carrier) = self.carriers.iter().find(|c| c.name == *carrier_name) {
        return carrier.join_id;
    }
}

Deletion Strategy:

/// Phase 77: Type-safe promoted binding resolution
///
/// DEPRECATED: Use `resolve_promoted_with_binding()` for BindingId-based lookup.
/// This method remains for legacy fallback only (dev-only name guard).
#[cfg(feature = "normalized_dev")]
#[deprecated(since = "phase77", note = "Use resolve_promoted_with_binding() instead")]
pub fn resolve_promoted_join_id(&self, original_name: &str) -> Option<ValueId> {
    // Try BindingId-based lookup first (requires binding context)
    // Fallback to name-based (legacy, for non-migrated call sites)

    eprintln!(
        "[phase77/legacy/carrier_info] WARNING: Using deprecated name-based promoted lookup for '{}'",
        original_name
    );

    let candidates = [
        format!("is_{}", original_name),
        format!("is_{}_match", original_name),
    ];
    // ... rest of legacy code ...
}

Full Deletion Criteria (Phase 78+):

  • All call sites migrated to BindingId
  • No None binding_map in production paths
  • Dev-only name guard deprecated warnings addressed

4.2 Pattern2ScopeManager Fallback Path (scope_manager.rs)

Current Code (lines 140-160):

// Step 3: Legacy name-based fallback
self.lookup(name)

Phase 77 Update:

// Step 3: Legacy name-based fallback (dev-only warning)
#[cfg(feature = "normalized_dev")]
if binding_id.is_some() {
    eprintln!(
        "[phase77/fallback/scope_manager] BindingId provided but not resolved: '{}' (BindingId({:?}))",
        name, binding_id
    );
}

self.lookup(name)

Full Deletion (Phase 78+): Remove fallback entirely, make BindingId required

Task 5: E2E Verification Tests (30 min)

Goal: Verify end-to-end BindingId promotion flow

File: tests/normalized_joinir_min.rs (or new tests/phase77_binding_promotion.rs)

Test 1: DigitPos End-to-End

#[cfg(feature = "normalized_dev")]
#[test]
fn test_phase77_digitpos_end_to_end_binding_resolution() {
    // Fixture with digit_pos pattern
    // Verify:
    // 1. DigitPosPromoter records promoted_bindings
    // 2. ScopeManager resolves via BindingId (not name)
    // 3. No fallback warnings in debug output
}

Test 2: Trim End-to-End

#[cfg(feature = "normalized_dev")]
#[test]
fn test_phase77_trim_end_to_end_binding_resolution() {
    // Fixture with trim pattern (ch → is_ch_match)
    // Verify promoted_bindings population
}

Test 3: Pattern3 BindingId Lookup

#[cfg(feature = "normalized_dev")]
#[test]
fn test_phase77_pattern3_binding_lookup() {
    // P3 if-sum with promoted carrier
    // Verify BindingId priority lookup works
}

Test 4: Pattern4 BindingId Lookup

#[cfg(feature = "normalized_dev")]
#[test]
fn test_phase77_pattern4_binding_lookup() {
    // P4 continue with promoted carrier
    // Verify BindingId resolution
}

Migration Path Analysis

Phase 77 Scope (THIS PHASE)

WILL DO:

  • Populate promoted_bindings in promoters (DigitPos/Trim)
  • Add binding_map parameter to promotion requests (dev-only)
  • Extend Pattern3/4 with BindingId lookup (dev-only variants)
  • Deprecate legacy name-based code (not delete yet)
  • Add E2E verification tests

WILL NOT DO (deferred to Phase 78+):

  • Remove legacy code entirely (keep dual-path for safety)
  • Make BindingId required in production paths
  • Remove promoted_loopbodylocals field (still used as fallback)

Phase 78+ Future Work

Deletion Candidates (~40 lines):

  1. CarrierInfo::resolve_promoted_join_id() body (25 lines)
  2. Pattern2ScopeManager name-based fallback (10 lines)
  3. promoted_loopbodylocals field + related code (5 lines)

Preconditions for Deletion:

  • All promoters populate promoted_bindings (Phase 77)
  • All patterns use BindingId lookup (Phase 77 dev-only, Phase 78 production)
  • No None binding_map in call chain (Phase 78 refactoring)

Implementation Sequence

Step-by-Step (2-3 hours total)

  1. DigitPosPromoter Integration (45 min)

    • Add binding_map parameter to request struct
    • Implement promoted_bindings recording
    • Update call sites
    • Test: verify BindingId recorded correctly
  2. TrimLoopHelper Integration (30 min)

    • Similar changes to DigitPos
    • Update trim-specific call sites
    • Test: verify trim pattern promotion
  3. Pattern3/4 Lookup Integration (45 min)

    • Add dev-only BindingId lookup variants
    • Wire up binding_map from MirBuilder
    • Test: verify P3/P4 resolve via BindingId
  4. Legacy Code Deprecation (15 min)

    • Add deprecation warnings
    • Add fallback logging
    • Document deletion criteria
  5. E2E Tests (30 min)

    • Write 4 verification tests
    • Verify no regressions in existing tests
    • Document test coverage
  6. Documentation (15 min)

    • This document
    • Update CURRENT_TASK.md
    • Phase 77 completion summary

Acceptance Criteria

Build & Test

  • cargo build --lib --features normalized_dev succeeds
  • cargo test --release --lib passes (958/958)
  • New Phase 77 tests pass (4/4)
  • No regressions in existing tests

Code Quality

  • DigitPos/Trim promoters populate promoted_bindings
  • Pattern3/4 use BindingId lookup (dev-only)
  • Legacy code deprecated (not deleted)
  • Debug logging for fallback paths

Documentation

  • This document complete
  • Migration path clearly defined
  • Phase 78+ deletion criteria documented
  • CURRENT_TASK.md updated

Design Principles

  • Fail-Fast: No silent fallbacks in dev mode
  • Dual-path: Legacy code kept for safety
  • Dev-only: All changes feature-gated
  • Type-safe: BindingId replaces string matching

Design Notes

Q: Why not delete legacy code immediately?

Answer: Gradual migration reduces risk

  • Phase 77: Deprecate + Log (observe fallback usage)
  • Phase 78: Require BindingId in production paths
  • Phase 79: Delete legacy code entirely

This 3-phase deletion ensures:

  • No "big bang" removals
  • Fallback paths for debugging
  • Easy rollback if issues arise

Q: Why dev-only feature gate?

Answer: Zero production impact during migration

  • #[cfg(feature = "normalized_dev")] guards all new code
  • Production builds use existing name-based paths
  • Enables safe experimentation

Q: Where is binding_map created?

Answer: MirBuilder owns the SSOT

  • MirBuilder.binding_map populated during AST lowering (Phase 68-69)
  • Each local declaration allocates a new BindingId
  • Shadowing tracked via LexicalScopeFrame

Q: How to pass binding_map to promoters?

Answer: Thread through call chain (Option A)

MirBuilder.binding_map
  ↓
[NEW] ConditionPromotionRequest.binding_map
  ↓
[NEW] DigitPosPromotionRequest.binding_map
  ↓
try_promote() → record_promoted_binding()

Alternative (Option B): Return promoted names from promoter, caller records

  • Pro: Simpler promoter API
  • Con: Caller needs both binding_map and carrier_info
  • Verdict: Option A preferred (promoter owns CarrierInfo, natural fit)

  • Phase 73: ScopeManager BindingId design (SSOT)
  • Phase 74: BindingId infrastructure (MirBuilder.binding_map)
  • Phase 75: BindingId priority lookup (ConditionEnv)
  • Phase 76: Promoted BindingId mapping (promoted_bindings)
  • Phase 77: Population + Expansion (this phase)
  • Phase 78+: Legacy code deletion (future)

Conclusion

Phase 77 completes the BindingId migration foundation by:

  1. Populating promoted_bindings in all promoters
  2. Extending BindingId lookup to Pattern3/4
  3. Deprecating legacy name-based code (deletion in Phase 78+)
  4. Verifying end-to-end type-safe promotion

Impact: Eliminates ~40 lines of fragile string matching and achieves type-safe, shadowing-aware promotion tracking across all JoinIR patterns.

Next Steps (Phase 78+):

  • Remove deprecated legacy code
  • Make BindingId required in production paths
  • Remove promoted_loopbodylocals field entirely

Risk Level: LOW

  • Dev-only feature-gated changes
  • Dual-path design provides fallback
  • Comprehensive test coverage
  • Gradual migration strategy