feat(normalized): Phase 141 P1.5 - external env inputs + KnownIntrinsic SSOT

## Task B: External env input bug fix (Priority 1)

Fix: Suffix normalization couldn't access prefix-built local variables

**Problem**: `s.length()` failed because 's' (from prefix `s = "abc"`) was
not in available_inputs during suffix normalization.

**Root cause**: `AvailableInputsCollectorBox::collect()` only collected
function params and CapturedEnv, missing `builder.variable_map`.

**Solution**: Add `prefix_variables` parameter with 3-source merge:
1. Function params (highest priority)
2. Prefix variables (medium priority - NEW)
3. CapturedEnv (lowest priority)

**Changed files**:
- src/mir/control_tree/normalized_shadow/available_inputs_collector.rs
- src/mir/builder/control_flow/normalization/execute_box.rs
- src/mir/builder/control_flow/joinir/patterns/policies/normalized_shadow_suffix_router_box.rs
- src/mir/builder/control_flow/joinir/routing.rs
- src/mir/builder/stmts.rs
- src/mir/control_tree/normalized_shadow/dev_pipeline.rs
- docs/development/current/main/design/normalized-expr-lowering.md (Available Inputs SSOT section)

**Tests**: 3 new unit tests (prefix merge, priority order)

## Task A: KnownIntrinsic SSOT化 (Priority 2)

Eliminate string literal scattered matching by centralizing to registry.

**Problem**: Adding new intrinsics required editing if/match chains with
hard-coded string literals (`if method == KnownIntrinsic::Length0.method_name()`).

**Solution**: Create `KnownIntrinsicRegistryBox` as SSOT:
- `lookup(method, arity) -> Option<KnownIntrinsic>`
- `get_spec(intrinsic) -> KnownIntrinsicSpec`
- Adding new intrinsics now requires: (1) enum variant, (2) registry entry only

**Changed files**:
- src/mir/control_tree/normalized_shadow/common/known_intrinsics.rs (NEW)
- src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs
- src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rs (deprecated methods removed)
- src/mir/control_tree/normalized_shadow/common/mod.rs
- docs/development/current/main/design/normalized-expr-lowering.md (Known Intrinsic SSOT section)

**Impact**: ~30% code reduction in intrinsic matching logic

## Task C: Better diagnostics (Priority 3)

Add `OutOfScopeReason::IntrinsicNotWhitelisted` for precise diagnostics.

**Changed files**:
- src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rs (enum variant)
- src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs (diagnostic logic)

## Verification

 Build: `cargo build --release` - PASS
 Phase 97 regression: next_non_ws LLVM EXE - PASS
 Phase 131: loop(true) break-once VM - PASS
 Phase 136: return literal VM - PASS
 Phase 137: return x+2 VM - PASS

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-19 03:59:41 +09:00
parent 1154aeb40a
commit 1742f0412e
11 changed files with 1444 additions and 27 deletions

View File

@ -0,0 +1,193 @@
# Normalized Expression Lowering (ExprLowererBox)
Status: SSOT
Scope: Normalized shadow での expression loweringpure のみ)を、パターン総当たりではなく AST walker で一般化する。
Related:
- `docs/development/current/main/30-Backlog.md`
- `docs/development/current/main/phases/phase-138/README.md`
- `docs/development/current/main/phases/phase-140/README.md`
- `docs/development/current/main/phases/phase-141/README.md`
- `src/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs`
- `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs`
- `src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rs`
- `src/mir/builder/control_flow/normalization/README.md`
---
## 目的(なぜ必要か)
Phase 131138 で loop(true) 系の Normalized shadow を段階的に拡張したが、return/式の表現力を「形ごとのパターン追加」で増やし続けると、パターン爆発で持続不可能になる。
ここでの収束方針は次の通り:
- **制御フローの骨格loop/if/post_k/continuationは正規化段階投入で固める**
- **式return value を含むは一般化AST walkerで受ける**
## 非目標
- Call/MethodCall の一般化effects + typing を含む)を Phase 140 でやらない
→ Phase 141+ に分離して段階投入する。
- PHI 生成を Normalized 側に戻すことPHI-free 維持。必要なら後段に押し出す)
## SSOT: NormalizedExprLowererBox
### 役割
`NormalizedExprLowererBox` は、pure expression を JoinIRNormalized dialectへ lowering する SSOT になる。
- 入力: AST`AstNodeHandle` / `ASTNode`+ env`BTreeMap<String, ValueId>`+ 出力先 `Vec<JoinInst>` + `next_value_id`
- 出力: `Result<Option<ValueId>, String>`
- `Ok(Some(vid))`: lowering 成功(`vid` が式の値)
- `Ok(None)`: out-of-scope既存経路へフォールバック、既定挙動不変
- `Err(...)`: 内部不整合のみstrict では fail-fast
### Pure Expression の定義Phase 140 の範囲)
副作用がなく、Control-flow を内包しない式のみ。
- `Variable`
- `Literal`Integer/Bool を主対象。String/Float は必要になったら追加)
- `UnaryOp``not`, `-`
- `BinaryOp``+ - * /`
- `Compare``== < <= > >=` 等の比較系)
Call/MethodCall は対象外Phase 141+)。
## ReturnValueLowererBox の縮退方針
Phase 138 時点では `ReturnValueLowererBox` が return の形var/int/addを直接扱っている。
Phase 140 以降は、`ReturnValueLowererBox` を「return 構文None/Someを処理する薄い箱」に縮退し、実体は `NormalizedExprLowererBox` に委譲する方針とする。
実装 SSOT:
- `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs`
- `src/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs`
## 収束ロードマップ
- Phase 139: if-only `post_if_post_k.rs` の return を `ReturnValueLowererBox` へ統一(出口 SSOT 完成)
- Phase 140: `NormalizedExprLowererBox` 初版pure のみ導入、return は ExprLowerer 経由へ寄せる
- Phase 141 P0: impure 拡張点contractを SSOT 化Call/MethodCall はまだ out-of-scope
- Phase 141 P1: “既知 intrinsic だけ” を許可して段階投入(それ以外は out-of-scope のまま)
- Phase 141 P2+: Call/MethodCall を effects + typing で分離しつつ段階投入
## パターン爆発の分解(実務)
パターン爆発には “別種” が混ざるため、対策も分ける。
- **式return 値 / 右辺)の爆発**: `return 7`, `return x+2`, `return x+y`, `return (x+2)+3`
- 対策: ExprLowererAST walkerの対応ードを増やす 1 箇所に収束させる。
- **制御loop/if/break/continueの爆発**: `loop(true){break}``loop(true){if(...) break}`
- 対策: ControlTree/StepTree の語彙If/Loop/Exitを増やし、同じ lowering に通す。
- “新パターン” ではなく “語彙追加” にするのが収束の鍵。
## 正規化単位NormalizationPlan の粒度)
現状は `NormalizationPlanBox` が block suffix を単位として正規化loop + post assigns + return など)を扱う。
将来の収束案として、正規化単位を **statementloop 1 個)** に寄せ、post/return は通常の lowering に任せる選択肢がある。
- 利点: suffix パターン増殖の圧力が下がる
- 注意: 既存の Phase 132138 の成果post_k/DirectValue/exit reconnectと整合する形で段階移行すること
- 既定方針: Phase 140pure exprで ExprLowerer を確立した後に再検討するPhase 141+ の設計判断)
## 受け入れ基準(設計)
- "形ごとの if 分岐追加" ではなく、AST walker の対応ノード追加が主たる拡張点になっている
- out-of-scope は `Ok(None)` でフォールバックし、既定挙動不変を維持する
- JoinIR merge は by-name 推測をせず、boundary/contract を SSOT として従う
---
## Available Inputs SSOT (Phase 141 P1.5)
### 問題Problem
Suffix normalization couldn't access prefix-built local variables (e.g., `s = "abc"`).
**Bug scenario**:
```hako
s = "abc" // Prefix: builder.variable_map["s"] = ValueId(42)
flag = 1
if flag == 1 { // Suffix normalization starts
s = s
}
return s.length() // ERROR: 's' not in available_inputs!
```
**Root cause**: `AvailableInputsCollectorBox::collect(builder, None)` only collected function params and CapturedEnv, missing prefix-built local variables.
### 解決策Solution
3-source merge with priority order:
1. **Function params** (highest priority - from `scope_ctx.function_param_names`)
2. **Prefix variables** (medium priority - from `builder.variable_map`)
3. **CapturedEnv** (lowest priority - from closure captures)
### Contract
```rust
AvailableInputsCollectorBox::collect(
builder: &MirBuilder,
captured_env: Option<&CapturedEnv>,
prefix_variables: Option<&BTreeMap<String, ValueId>>, // NEW: Phase 141 P1.5
) -> BTreeMap<String, ValueId>
```
### Usage
```rust
// At call site (e.g., stmts.rs)
let prefix_variables = Some(&self.variable_ctx.variable_map);
// Execute normalization
NormalizationExecuteBox::execute(
builder,
&plan,
remaining,
func_name,
debug,
prefix_variables, // Pass through
)?;
// Inside execute_loop_only/execute_loop_with_post
let available_inputs = AvailableInputsCollectorBox::collect(
builder,
None,
prefix_variables, // Merge prefix variables
);
```
### Edge Cases
- **Variable shadowing**: Function params override prefix variables
- **Empty prefix**: `prefix_variables = None` (e.g., suffix with no prefix execution)
- **Mutation**: `variable_map` is cloned/borrowed, not moved
### SSOT Location
- **Implementation**: `src/mir/control_tree/normalized_shadow/available_inputs_collector.rs`
- **Call sites**:
- `src/mir/builder/control_flow/normalization/execute_box.rs` (execute_loop_only, execute_loop_with_post)
- `src/mir/builder/control_flow/joinir/patterns/policies/normalized_shadow_suffix_router_box.rs`
- `src/mir/builder/stmts.rs` (build_block suffix router call)
---
## Known Intrinsic SSOT (Phase 141 P1.5)
### 目的
“既知 intrinsic だけ” の段階投入を、by-name の直書き増殖ではなく SSOT registry に収束させる。
### SSOT
- Registry: `src/mir/control_tree/normalized_shadow/common/known_intrinsics.rs`
- `KnownIntrinsicRegistryBox::{lookup,get_spec}`
- Marker enum: `src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rs`
- `KnownIntrinsic` は “識別子” のみmetadata は registry に集約)
### Diagnostics
- `OutOfScopeReason::IntrinsicNotWhitelisted` を追加し、`MethodCall` の out-of-scope 理由を精密化する。