# Phase 100: Pinned Read‑Only Captures(設計メモ) ## 目的 JoinIR lowering のスコープ解決(`ConditionEnv → LoopBodyLocalEnv → CapturedEnv`)の前提を崩さずに、 「ループ外で定義した local(動的式でも可)」をループ内で receiver として参照できるようにする。 例(問題の形): ```nyash local s = "a" + "b" // 条件式とは無関係 loop(i < n) { local ch = s.substring(i, i + 1) // receiver 解決が必要 } ``` ## 方針(SSOT) - “Pinned local” は **新しい Pattern ではなく**、ループに入ってくる **read‑only 入力**として扱う。 - SSOT は増やさず `CapturedEnv` に統合し、`CapturedKind` で区別する: - `Explicit`: 従来の capture - `Pinned`: ループ内参照のために必要な read‑only local ## Fail‑Fast 契約 - `Pinned` は loop body 内で **再代入されない**(assignment target に現れたら拒否)。 - loop entry 時点で host 側 ValueId が存在する(`variable_map` に無い場合は拒否)。 - 初期化が loop より後にある等、支配関係が曖昧な場合は拒否(理由付き)。 - init 式の副作用有無は問わない(評価はホスト側で1回、Pinned は値を渡すだけ)。 ## Loop Canonicalizer との関係(混ぜない) - Phase 100 は「ループ外の値が loop 内で見えない」問題(capture/wiring)を解く。 - Loop Canonicalizer は「ループ内の不変式を外に出す」AST 変換であり、別ユースケース。 ## 予定(段階投入) この Phase は「自由度を上げるほどバグが出やすい領域」なので、段階投入で進める。 - **P1(read‑only)**: ループ外 local を `Pinned` として `CapturedEnv` に取り込み、receiver 解決を通す。 - Fail‑Fast: loop body 内で再代入される変数は pinned 禁止(理由付き) - **P2(mutable)**: loop body 内で再代入される外側変数は `Pinned` では扱わず、LoopState(carrier/env)に昇格して運ぶ。 - 初期は “更新形を限定” して shape guard を作り、未対応は Fail‑Fast(曖昧に通さない) - **P3(hoist)**: ループ内の不変式の巻き上げは Loop Canonicalizer 側で扱う(別ユースケースとして分離) ## P1: Pinned Local Captures (Explicit/Pinned) - 完了 ✅ Implemented CapturedKind enum to distinguish: - **Explicit**: Traditional captures (condition variables, carriers) - **Pinned**: Phase 100 read-only loop-outer locals used as method receivers **Status**: P1-1 through P1-5 complete - pinned receiver functionality validated with fixture+smoke tests. Example (now works): ```hako local s = "a" + "b" + "c" # Dynamic construction (loop-outer) loop(i < 1) { local ch = s.substring(i, i+1) # Pinned receiver works! print(i) i = i + 1 } ``` **Smoke test**: phase100_pinned_local_receiver_vm.sh ### Implementation Details **Search Order (SSOT)**: 1. ConditionEnv (loop-outer scope) 2. LoopBodyLocalEnv (body-local variables - Phase 226 cascading) 3. CapturedEnv (pinned loop-outer locals) 4. CarrierInfo (mutable carriers) **Key Components**: - `CapturedKind` enum: Distinguishes Explicit vs Pinned captures - `PinnedLocalAnalyzer`: Identifies loop-outer read-only locals - `loop_body_local_init.rs`: Updated receiver resolution with full search order ### Test Coverage - **Fixture**: `apps/tests/phase100_pinned_local_receiver_min.hako` - **Smoke Test**: `tools/smokes/v2/profiles/integration/apps/phase100_pinned_local_receiver_vm.sh` - **Regression**: Phase 96 and Phase 94 smoke tests pass ## P2: Mutable Accumulators (s = s + x form only) **Constraint**: Mutable variables are LIMITED to accumulator pattern. **Allowed form**: - `s = s + x` where x ∈ {Const, BodyLocal, Captured, Pinned, Carrier} **Fail-Fast (未対応)**: - Any other mutation: `s = f(...)` / `s = x + s` / `s += x` / multiple updates / conditional updates - Reason: Shape explosion prevention, safe accumulator support (JSON/CSV building) **Example** (now works): ```hako local out = "" loop(i < 2) { local ch = "a" out = out + ch # Accumulator form → LoopState carrier i = i + 1 } print(out.length()) # Output: 2 ``` **Implementation**: - **P2-1**: MutableAccumulatorAnalyzer (AST shape detection only) - **P2-2**: Pattern2 wiring (ScopeManager delegates read-only check) - **P2-3**: Lowering (carrier update emission) - **P2-4**: Integration test (fixture + smoke, length-based validation) **LLVM EXE parity**: P2 accumulator を LLVM EXE smoke でも固定(phase100_mutable_accumulator_llvm_exe.sh)