Phase 82: Centralized JoinIR debug flag reading - Added is_joinir_debug() SSOT function in joinir_flags.rs - Replaced 16 direct env::var() calls across 8 files - Updated docs to recommend HAKO_JOINIR_DEBUG (NYASH_ deprecated) - Backward compat: Both env vars work Phase 83: Verified promoted carrier fallback behavior - Confirmed NO fallback to name-based lookup for DigitPos/Trim - Documented fallback expectations in Phase 80/81 docs - Added verification commands and expected output Changes: - src/config/env/joinir_flags.rs: +187 lines (new SSOT module) - 8 files: env var reads → is_joinir_debug() calls - 3 docs: HAKO_JOINIR_DEBUG examples + fallback sections - 1 summary doc: phase82-83-debug-flag-ssot-summary.md Tests: 970/970 lib PASS, 58/58 normalized_dev PASS Impact: Dev-only (zero production changes)
10 KiB
Phase 80: BindingId P3/P4 Expansion Plan
Status: Completed (commit 84129a7e)
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 (dev-only)
- ✅ Task 80-C: Pattern4 BindingId Wiring (dev-only)
- ✅ Task 80-D: E2E Tests (P3/P4, dev-only)
Task 80-0: Current Status Verification
Test Results
1. Full lib test suite
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
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
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 81+ に回し、 さらに “promoted carriers(DigitPos/Trim)を含む Pattern2 の ExitLine 接続” が安定してから固定する。
Task 80-0: Summary
Status: ✅ COMPLETE
Key Findings:
- ✅ Production code stable - 970/970 lib tests PASS
- ✅ Phase 79 changes production-safe - No regressions in lib tests
- ✅ Core VM functionality stable - Smoke tests pass
- ⚠️ Pattern2(DigitPos/Trim)E2E は保留 - promoted carriers を含む ExitLine 契約の安定化後に固定する
- ❌ Pre-existing smoke test failure -
json_lint_vmfails (unrelated)
Classification:
- Phase 80 blockers: NONE
- Phase 80 out-of-scope:
- Pattern2(DigitPos/Trim)promoted carriers の ExitLine 契約安定化(Phase 81+)
- 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:
- Pattern3 (if-sum) BindingId registration at ValueId determination point
- Pattern4 (continue/Trim) BindingId registration at ValueId determination point
- E2E verification tests (2 tests: P3 + P4)
- 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)
- Dev-only: All code
#[cfg(feature = "normalized_dev")]or#[cfg(debug_assertions)] - Dual-path maintained: BindingId priority + name fallback (no removal yet)
- Structural detection only: NO by-name rule branching
- Fail-Fast: Explicit errors with tags, no silent fallbacks
- 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: ConditionEnvBuilder 生成直後(lower_if_sum_pattern() に渡す前)
BindingId registration points:
-
Loop var registration(dev-only):
#[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); } -
Condition bindings registration(dev-only):
#[cfg(feature = "normalized_dev")] for binding in &inline_boundary.condition_bindings { if let Some(bid) = builder.binding_map.get(&binding.name).copied() { cond_env.register_condition_binding(bid, binding.join_value); eprintln!( "[phase80/p3] Registered condition binding '{}' BindingId({}) -> ValueId({})", binding.name, bid.0, binding.join_value.0 ); } }
Timing: BEFORE condition lowering
Note (重要):
- Pattern3/4 は lowering の後段(ExitMeta / merge 側)で carrier join_id が確定するため、Phase 80 では carriers の BindingId 登録はしない。
- ここでの目的は「条件 lowering(lookup_with_binding)」の経路を先に BindingId 化して、shadowing の破綻を防ぐこと。
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: lower_loop_with_continue_minimal() 内
BindingId registration points:
-
Caller side(dev-only):
binding_mapを lowerer に渡す -
Lowerer side(dev-only): join_id が確定したタイミングで
- loop var / condition bindings / carriers を BindingId 登録する
- ログは
[phase80/p4]タグで可視化する
Special note: Trim patterns (skip_whitespace) use promoted carriers, BindingId comes from promoted_bindings map via CarrierBindingAssigner
Timing: ValueId allocation の直後(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:
- Run tests with
HAKO_JOINIR_DEBUG=1(or legacyNYASH_JOINIR_DEBUG=1) - Check for
[binding_pilot/hit]tags (BindingId path success) - Check for NO
[binding_pilot/fallback]tags (name fallback NOT used) - If fallback occurs → test fails with diagnostic
Note (Phase 82): Both HAKO_JOINIR_DEBUG and NYASH_JOINIR_DEBUG are supported.
Recommended: Use HAKO_JOINIR_DEBUG=1 (NYASH_ variant is deprecated but still works)
Acceptance criteria (Task 80-D):
- P3 test: BindingId hit, NO fallback
- P4 test: BindingId hit, NO fallback
Fallback Behavior Documentation (Phase 83):
Expected: Promoted carriers should ALWAYS use BindingId path, never fallback
- DigitPos carriers (
is_digit_pos): ✅ BindingId hit only - Trim carriers (
is_ch_match): ✅ BindingId hit only - Loop variables in P3/P4: ✅ BindingId hit only
Verification:
# Phase 80 tests verify BindingId resolution works (no runtime errors)
cargo test --features normalized_dev --test normalized_joinir_min test_phase80_p3_bindingid_lookup_works
cargo test --features normalized_dev --test normalized_joinir_min test_phase80_p4_bindingid_lookup_works
# Phase 81 tests verify ExitLine contract (promoted carriers handled correctly)
cargo test --features normalized_dev --test normalized_joinir_min test_phase81_digitpos_exitline_contract
cargo test --features normalized_dev --test normalized_joinir_min test_phase81_trim_exitline_contract
Debug Tags (dev-only, during MIR compilation):
[binding_pilot/hit]: BindingId lookup succeeded ✅ (expected)[binding_pilot/fallback]: Name-based fallback occurred ❌ (should NOT appear for promoted carriers)[binding_pilot/legacy]: No BindingId provided, using name (legacy code paths only)
Status (Phase 83): All Phase 80/81 tests PASS, indicating NO fallback to name-based lookup for promoted carriers.
Implementation Order
- ✅ Task 80-A: Design doc (this section)
- ✅ Task 80-B: Pattern3 wiring(loop var + condition bindings)
- ✅ Task 80-C: Pattern4 wiring(lowerer 側で登録)
- ✅ Task 80-D: E2E tests(P3 1本 / P4 1本,
tests/normalized_joinir_min.rs)
Success Metrics
- P3: 条件 lowering が BindingId 経路で解決できる(E2Eテストで固定)
- P4: 条件 lowering が BindingId 経路で解決できる(E2Eテストで固定)
- Fallback 検知(
[binding_pilot/fallback]をテストで検知できる) cargo test --release --libが PASS(production 影響なし)- 追加コードは dev-only(
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)
- Pattern3 (if-sum) BindingId 登録配線 ✅
- Pattern4 (continue) BindingId 登録配線 ✅
- E2E tests (P3 1本 / P4 1本) ✅
Success Metrics
- P3/P4 代表ケースで lookup_with_binding() 経路がヒット(ログ or テスト)
- Fallback 検知可能(既存
[binding_pilot/fallback]タグ活用) cargo test --release --libPASS(退行なし)