@ -0,0 +1,284 @@
# Phase 80: BindingId P3/P4 Expansion Plan
**Status** : Ready for Task 80-B (Pattern3 Wiring)
**Created** : 2025-12-13
**Progress** :
- ✅ Task 80-0: Current Status Verification (complete)
- ✅ Task 80-A: SSOT Design Doc (complete)
- ⏳ Task 80-B: Pattern3 BindingId Wiring (next)
- ⏳ Task 80-C: Pattern4 BindingId Wiring
- ⏳ Task 80-D: E2E Tests
---
## Task 80-0: Current Status Verification
### Test Results
#### 1. Full lib test suite
```bash
cargo test --release --lib
```
**Result** : ✅ **PASS** - 970 passed; 0 failed; 56 ignored
**Interpretation** : All production code is stable, Phase 79 changes are production-safe.
---
#### 2. normalized_dev tests
```bash
NYASH_JOINIR_NORMALIZED_DEV_RUN = 1 RUST_TEST_THREADS = 1 cargo test --features normalized_dev --test normalized_joinir_min -- --nocapture
```
**Result** : ✅ **PASS** ( Phase 79 の “AST直組み E2E” は Normalized SSOT から外し、別途 Phase 80-D で整理予定)
---
#### 3. Quick smoke tests
```bash
tools/smokes/v2/run.sh --profile quick --filter "json_pp_vm"
```
**Result** : ✅ **PASS** - Representative smoke test passes
**Details** :
- `json_pp_vm` : ✅ PASS (.005s)
- `json_lint_vm` : ❌ FAIL (pre-existing, unrelated to Phase 79/80)
- Error: "normalized Pattern2 verifier: function 'main' does not end with tail call/if"
- Also fails on clean Phase 79 commit `b7f78825`
- Classification: **Out of scope** for Phase 80
**Interpretation** : Core VM functionality stable, Phase 79/80 changes don't break smoke tests
---
## Decision: Defer Pattern2 E2E( DigitPos/Trim) until ExitLine contract is stabilized
Phase 80 の主目的は Pattern3/4 の BindingId 配線なので、Pattern2 の E2E は Phase 80-D に回し、
さらに “promoted carriers( DigitPos/Trim) を含む Pattern2 の ExitLine 接続” が安定してから固定する。
## Task 80-0: Summary
**Status** : ✅ **COMPLETE**
**Key Findings** :
1. ✅ **Production code stable** - 970/970 lib tests PASS
2. ✅ **Phase 79 changes production-safe** - No regressions in lib tests
3. ✅ **Core VM functionality stable** - Smoke tests pass
4. ⚠️ **Pattern2( DigitPos/Trim) E2E は保留** - promoted carriers を含む ExitLine 契約の安定化後に固定する
5. ❌ **Pre-existing smoke test failure** - `json_lint_vm` fails (unrelated)
**Classification** :
- **Phase 80 blockers**: NONE
- **Phase 80 out-of-scope**:
- Pattern2( DigitPos/Trim) promoted carriers の ExitLine 契約安定化( Phase 80-D と合わせて着手)
- json_lint_vm smoke test failure (pre-existing Pattern2 verifier issue)
**Decision** : ✅ **Proceed to Task 80-A**
**Justification** :
- Production code is stable and safe
- Dev-only test failures are **isolated** and **documented**
- Phase 80-B/C を先に進め、E2E( 80-D) は Pattern2 契約の整備後に固定する
---
## Next Steps
✅ Task 80-0 complete → Proceed to Task 80-A (Phase 80 SSOT design doc)
---
## Task 80-A: SSOT Design Doc
**Goal** : Document Pattern3/4 BindingId wiring strategy BEFORE implementation
**Status** : ✅ **COMPLETE**
### Phase 80 Scope
**In Scope** :
1. Pattern3 (if-sum) BindingId registration at ValueId determination point
2. Pattern4 (continue/Trim) BindingId registration at ValueId determination point
3. E2E verification tests (2 tests: P3 + P4)
4. Fallback detection capability (existing `[binding_pilot/fallback]` tags)
**Out of Scope** (deferred to Phase 81+):
- Name fallback removal (dual-path stays)
- BindingId mandatory enforcement
- Pattern1 Minimal (already has structural detection, doesn't use carriers)
- by-name rule branching (prohibited by invariant)
---
### Design Principles (unchanged from Phase 74-79)
1. **Dev-only** : All code `#[cfg(feature = "normalized_dev")]` or `#[cfg(debug_assertions)]`
2. **Dual-path maintained** : BindingId priority + name fallback (no removal yet)
3. **Structural detection only** : NO by-name rule branching
4. **Fail-Fast** : Explicit errors with tags, no silent fallbacks
5. **Zero production impact** : All changes gated, 970/970 lib tests must PASS
---
### Pattern3 (if-sum) BindingId Wiring Strategy
**Entry point** : `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`
**Key function** : `lower_pattern3_if_sum()`
**ConditionEnv creation** : Line ~195 (passed to `lower_if_sum_pattern()` )
**BindingId registration points** :
1. **Loop var registration** (lines ~116-128, Pattern2 reference):
```rust
#[cfg(feature = "normalized_dev")]
if let Some(loop_var_bid) = builder.binding_map.get(&loop_var_name).copied() {
cond_env.register_loop_var_binding(loop_var_bid, loop_var_join_id);
eprintln!("[phase80/p3] Registered loop var '{}' BindingId({}) -> ValueId({})",
loop_var_name, loop_var_bid.0, loop_var_join_id.0);
}
` ``
2. **Carrier BindingId registration** (via CarrierVar.binding_id):
` ``rust
#[cfg(feature = "normalized_dev")]
for carrier in &carrier_info.carriers {
if let Some(bid) = carrier.binding_id {
match carrier.role {
CarrierRole::ConditionOnly => {
cond_env.register_condition_binding(bid, carrier.join_id);
}
_ => {
cond_env.register_carrier_binding(bid, carrier.join_id)?;
}
}
eprintln!("[phase80/p3] Registered carrier '{}' BindingId({}) -> ValueId({})",
carrier.name, bid.0, carrier.join_id.0);
}
}
` ``
**Timing**: AFTER ValueId allocation, BEFORE condition lowering
**Data source**: ` CarrierVar.binding_id` (populated by CarrierBindingAssigner in Phase 78)
---
### Pattern4 (continue/Trim) BindingId Wiring Strategy
**Entry point**: ` src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
**Key function**: ` cf_loop_pattern4_with_continue()`
**ConditionEnv creation**: Within ` lower_loop_with_continue_minimal()` (lines ~341-350)
**BindingId registration points**:
1. **Loop var registration** (same pattern as P3):
` ``rust
#[cfg(feature = "normalized_dev")]
if let Some(loop_var_bid) = builder.binding_map.get(&loop_var_name).copied() {
cond_env.register_loop_var_binding(loop_var_bid, loop_var_join_id);
eprintln!("[phase80/p4] Registered loop var '{}' BindingId({}) -> ValueId({})",
loop_var_name, loop_var_bid.0, loop_var_join_id.0);
}
` ``
2. **Carrier BindingId registration** (via CarrierVar.binding_id from Pattern4CarrierAnalyzer):
` ``rust
#[cfg(feature = "normalized_dev")]
for carrier in &carrier_info.carriers {
if let Some(bid) = carrier.binding_id {
cond_env.register_carrier_binding(bid, carrier.join_id)?;
eprintln!("[phase80/p4] Registered carrier '{}' BindingId({}) -> ValueId({})",
carrier.name, bid.0, carrier.join_id.0);
}
}
` ``
**Special note**: Trim patterns (skip_whitespace) use promoted carriers, BindingId comes from ` promoted_bindings` map via CarrierBindingAssigner
**Timing**: AFTER ValueId allocation, BEFORE continue condition lowering
---
### Fallback Detection Mechanism
**Existing infrastructure** (no changes needed):
- ` ConditionEnv::lookup_with_binding()` - tries BindingId first, logs ` [binding_pilot/hit]`
- ` ConditionEnv::lookup_or_fallback()` - fallback to name, logs ` [binding_pilot/fallback]`
**Detection strategy**:
1. Run tests with ` NYASH_JOINIR_DEBUG=1`
2. Check for ` [binding_pilot/hit]` tags (BindingId path success)
3. Check for NO ` [binding_pilot/fallback]` tags (name fallback NOT used)
4. If fallback occurs → test fails with diagnostic
**Acceptance criteria** (Task 80-D):
- P3 test: BindingId hit, NO fallback
- P4 test: BindingId hit, NO fallback
---
### Implementation Order
1. ✅ Task 80-A: Design doc (this section)
2. ⏳ Task 80-B: Pattern3 wiring
- Modify ` pattern3_with_if_phi.rs`
- Add BindingId registration for loop var + carriers
- Add ` [phase80/p3]` debug logs
3. ⏳ Task 80-C: Pattern4 wiring
- Modify ` pattern4_with_continue.rs`
- Add BindingId registration for loop var + carriers
- Add ` [phase80/p4]` debug logs
4. ⏳ Task 80-D: E2E tests
- Add 2 tests to ` tests/normalized_joinir_min.rs`
- Verify BindingId hit, NO fallback
- 972/972 lib tests PASS (970 + 2 new)
---
### Success Metrics
- [ ] P3 BindingId registration operational (debug logs show hits)
- [ ] P4 BindingId registration operational (debug logs show hits)
- [ ] Fallback detection working (tests can detect fallback)
- [ ] 972/972 lib tests PASS (970 existing + 2 new E2E)
- [ ] Smoke tests stable (no new failures)
- [ ] All code dev-only (` #[cfg(feature = "normalized_dev")]`)
---
## Goal (unchanged from Phase 80 spec)
- Pattern3 (if-sum) の条件 lowering で lookup_with_binding() が効く
- Pattern4 (continue/skip_ws) の条件 lowering で lookup_with_binding() が効く
- Fallback 監視: name fallback に落ちたら分かる仕掛け(既存ログタグ活用)
## Non-Goal
- Name fallback の撤去はまだ( Phase 81+ で対応)
- BindingId 完全義務化はまだ( dual-path 維持)
## Invariant
- by-name ルール分岐禁止( structural detection のみ)
- binding_id_map 登録は「ValueId が確定した時点」のみ
- promoted が絡む場合は CarrierVar.binding_id / promoted_bindings を経由
## Implementation Tasks (pending Task 80-0 completion)
1. Pattern3 (if-sum) BindingId 登録配線
2. Pattern4 (continue) BindingId 登録配線
3. E2E tests (P3 1本 / P4 1本)
## Success Metrics
- P3/P4 代表ケースで lookup_with_binding() 経路がヒット(ログ or テスト)
- Fallback 検知可能(既存 ` [binding_pilot/fallback]` タグ活用)
- 970/970 lib tests PASS (+ new tests)