Files
hakorune/docs/development/current/main/phases/phase-100/README.md
nyash-codex bad8ee1571 docs: Phase 100 P3 string accumulator contract
- Add P3 section to phases/phase-100/README.md
- Document allowed form: out = out + ch (Variable RHS only)
- List Fail-Fast cases (Literal RHS, MethodCall RHS, non-string-ish)
- Add example with length-based output

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-17 16:33:03 +09:00

5.6 KiB
Raw Blame History

Phase 100: Pinned ReadOnly Captures設計メモ

目的

JoinIR lowering のスコープ解決(ConditionEnv → LoopBodyLocalEnv → CapturedEnv)の前提を崩さずに、 「ループ外で定義した local動的式でも可」をループ内で receiver として参照できるようにする。

例(問題の形):

local s = "a" + "b"   // 条件式とは無関係
loop(i < n) {
  local ch = s.substring(i, i + 1)  // receiver 解決が必要
}

方針SSOT

  • “Pinned local” は 新しい Pattern ではなく、ループに入ってくる readonly 入力として扱う。
  • SSOT は増やさず CapturedEnv に統合し、CapturedKind で区別する:
    • Explicit: 従来の capture
    • Pinned: ループ内参照のために必要な readonly local

FailFast 契約

  • 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 は「自由度を上げるほどバグが出やすい領域」なので、段階投入で進める。

  • P1readonly: ループ外 local を Pinned として CapturedEnv に取り込み、receiver 解決を通す。
    • FailFast: loop body 内で再代入される変数は pinned 禁止(理由付き)
  • P2mutable: loop body 内で再代入される外側変数は Pinned では扱わず、LoopStatecarrier/envに昇格して運ぶ。
    • 初期は “更新形を限定” して shape guard を作り、未対応は FailFast曖昧に通さない
  • P3hoist: ループ内の不変式の巻き上げは 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):

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):

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

P3: String Accumulator (out = out + ch, Variable RHS only)

Constraint: String accumulator は最小形のみサポート。

Allowed form:

  • out = out + ch where ch ∈ {Variable (string-ish)}
  • RHS must be Variable (not Literal, not MethodCall)

Fail-Fast (未対応):

  • Literal RHS: out = out + "x" (P3.1 で解禁予定)
  • MethodCall RHS: out = out + substring(...) (P3.2 で解禁予定)
  • Non-string-ish RHS: 型判定は既存の box facts に委譲

Example (now works):

local out = ""
local s = "abc"
loop(i < 3) {
    local ch = s.substring(i, i+1)    # body-local string 1文字
    out = out + ch                    # String accumulator
    print(out.length())               # Output: 1, 2, 3
    i = i + 1
}

Implementation:

  • P3-1: AccumulatorKind::{Int, String} 追加(型判定は委譲)
  • P3-2: StringAccumulatorEmitter 専用箱JoinIR lowering
  • P3-3: Pattern2 wiringstring carrier 昇格、emitter 分岐)
  • P3-4: Fixture + smokesVM + LLVM EXE