Files
hakorune/docs/development/current/main/phase80-bindingid-p3p4-plan.md
nyash-codex 9e32807a96 refactor(joinir): Phase 82-83 - Debug flag SSOT + Fallback verification
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)
2025-12-13 19:01:14 +09:00

10 KiB
Raw Blame History

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: PASSPhase 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 E2EDigitPos/Trimuntil ExitLine contract is stabilized

Phase 80 の主目的は Pattern3/4 の BindingId 配線なので、Pattern2 の E2E は Phase 81+ に回し、 さらに “promoted carriersDigitPos/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. ⚠️ Pattern2DigitPos/TrimE2E は保留 - promoted carriers を含む ExitLine 契約の安定化後に固定する
  5. Pre-existing smoke test failure - json_lint_vm fails (unrelated)

Classification:

  • Phase 80 blockers: NONE
  • Phase 80 out-of-scope:
    • Pattern2DigitPos/Trimpromoted 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 を先に進め、E2E80-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: ConditionEnvBuilder 生成直後(lower_if_sum_pattern() に渡す前)

BindingId registration points:

  1. Loop var registrationdev-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);
    }
    
  2. Condition bindings registrationdev-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 登録はしない
  • ここでの目的は「条件 loweringlookup_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:

  1. Caller sidedev-only: binding_map を lowerer に渡す

  2. Lowerer sidedev-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:

  1. Run tests with HAKO_JOINIR_DEBUG=1 (or legacy 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

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

  1. Task 80-A: Design doc (this section)
  2. Task 80-B: Pattern3 wiringloop var + condition bindings
  3. Task 80-C: Pattern4 wiringlowerer 側で登録)
  4. Task 80-D: E2E testsP3 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 が PASSproduction 影響なし)
  • 追加コードは dev-onlynormalized_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] タグ活用)
  • cargo test --release --lib PASS退行なし