Files
hakorune/docs/development/current/main/phase193-init-methodcall-design.md
nyash-codex a722e51a56 docs: Phase 192-impl completion + Phase 193 MethodCall in Init design
Phase 192-impl documentation updates:
- joinir-architecture-overview.md: Section 2.2/7.2 updated with Phase 192 completion
  - ComplexAddendNormalizer AST preprocessing完了
  - Phase 193/194 roadmap追加

Phase 193 instruction document:
- phase193-init-methodcall-design.md: 5-task breakdown for MethodCall in init support
  - Task 193-1: Init expression inventory (indexOf, get patterns)
  - Task 193-2: LoopBodyLocalInitLowerer extension design
  - Task 193-3: Implementation - emit_method_call_init function
  - Task 193-4: E2E verification (phase193_init_method_call.hako)
  - Task 193-5: Documentation updates
  - Method whitelist approach (Fail-Fast for unsupported methods)
  - ConditionEnv-only receiver resolution

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-09 04:28:10 +09:00

541 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 193: MethodCall in Init Lowering Support
**Status**: Ready for Implementation
**Date**: 2025-12-09
**Prerequisite**: Phase 192-impl complete (ComplexAddendNormalizer + body-local init integration)
---
## 目的
`LoopBodyLocalInitLowerer` が **単純な method call を含む init 式**を JoinIR に正しく降下できるようにする。
Pattern は増やさず、既存の body-local / UpdateEnv / NumberAccumulation ラインの上に乗せる。
---
## Task 193-1: 対象 init 式のインベントリdoc only
### 目標
現在サポート外の MethodCall を含む init 式を整理し、Phase 193 で対応する範囲を明確化する。
### 対象パターン
#### Pattern 1: 単一 MethodCall最優先
```nyash
local digit = digits.indexOf(ch)
```
- **説明**: body-local 変数の初期化式が単一の MethodCall
- **要件**: `digits` は ConditionEnv で解決可能(ループパラメータまたは外部変数)
- **JoinIR**: StringBox.indexOf → CallBoxMethod 命令を emit
#### Pattern 2: MethodCall を含む二項演算
```nyash
local index = digits.indexOf(ch) + offset
local result = base - array.get(i)
```
- **説明**: MethodCall を含む算術演算
- **要件**: MethodCall が整数を返す、演算子は +, -, *, /
- **JoinIR**: MethodCall emit → 結果を二項演算に使用
### Phase 193 の範囲
**✅ 対応する**:
- Pattern 1: 単一 MethodCall最優先・必須
- Pattern 2: MethodCall を含む二項演算(時間があれば)
**❌ 対応しないPhase 194+**:
- ネストした MethodCall: `s.substring(0, s.indexOf(ch))`
- 複数 MethodCall: `s.indexOf("a") + s.indexOf("b")`
- 非算術演算: `s.concat(t).length()`
- 配列アクセス: `array[i].method()`
### 成果物
- 対象パターンの明確化
- Phase 193 完了後の「できること/できないこと」リスト
---
## Task 193-2: LoopBodyLocalInitLowerer の拡張設計
### 現在のサポート範囲Phase 191
```rust
// src/mir/join_ir/lowering/loop_body_local_init_lowerer.rs
match rhs {
ASTNode::IntLiteral(_) => { /* Const 命令 */ }
ASTNode::Identifier(_) => { /* Copy 命令 */ }
ASTNode::BinaryOp { .. } => { /* BinOp 命令 */ }
_ => return Err("Unsupported init expression")
}
```
### Phase 193 拡張方針
#### 1. MethodCall 対応の追加
```rust
match rhs {
// 既存: IntLiteral, Identifier, BinaryOp
// NEW: Single MethodCall
ASTNode::MethodCall { receiver, method, args } => {
emit_method_call_init(receiver, method, args)?
}
// NEW: BinaryOp with MethodCall (optional)
ASTNode::BinaryOp { lhs, op, rhs } if contains_method_call(lhs, rhs) => {
emit_binary_with_method_call(lhs, op, rhs)?
}
_ => return Err("Unsupported init expression")
}
```
#### 2. `emit_method_call_init` の実装
```rust
fn emit_method_call_init(
receiver: &ASTNode,
method: &str,
args: &[ASTNode],
join_builder: &mut JoinIRBuilder,
env: &UpdateEnv,
alloc: &mut dyn FnMut() -> ValueId,
) -> Result<ValueId, String> {
// 1. receiver を解決ConditionEnv から)
let receiver_id = match receiver {
ASTNode::Identifier(name) => {
env.resolve(name)
.ok_or_else(|| format!("Undefined variable in init: {}", name))?
}
_ => return Err("Complex receiver not supported in init".to_string())
};
// 2. args を解決(再帰的に lower
let arg_ids: Vec<ValueId> = args.iter()
.map(|arg| lower_init_arg(arg, env, alloc))
.collect::<Result<_, _>>()?;
// 3. CallBoxMethod 命令を emit
let result_id = alloc();
join_builder.emit(Instruction::CallBoxMethod {
dst: result_id,
receiver: receiver_id,
method: method.to_string(),
args: arg_ids,
});
Ok(result_id)
}
```
#### 3. メソッドホワイトリストFail-Fast
**Phase 193 で許可するメソッド**:
- `StringBox.indexOf(char)` → 整数
- `ArrayBox.get(index)` → 要素(型は Context 依存)
**未サポートメソッドは明示的エラー**:
```rust
const SUPPORTED_INIT_METHODS: &[&str] = &[
"indexOf",
"get",
];
if !SUPPORTED_INIT_METHODS.contains(&method) {
return Err(format!(
"Method '{}' not supported in body-local init (Phase 193 limitation)",
method
));
}
```
### 設計原則
1. **Fail-Fast**: 未サポートパターンは即座にエラー
2. **ConditionEnv 優先**: receiver は ConditionEnv で解決body-local は参照不可)
3. **単純性**: ネストや複数呼び出しは Phase 194+ に延期
4. **既存インフラ再利用**: CallBoxMethod 命令は既に JoinIR でサポート済み
---
## Task 193-3: 実装 Init 式 MethodCall Lowering
### 対象ファイル
- `src/mir/join_ir/lowering/loop_body_local_init_lowerer.rs`
### 実装手順
#### Step 1: `emit_method_call_init` 関数の追加
```rust
impl LoopBodyLocalInitLowerer {
fn emit_method_call_init(
receiver: &ASTNode,
method: &str,
args: &[ASTNode],
join_builder: &mut JoinIRBuilder,
env: &UpdateEnv,
alloc: &mut dyn FnMut() -> ValueId,
) -> Result<ValueId, String> {
// 実装は Task 193-2 の擬似コードに従う
}
fn lower_init_arg(
arg: &ASTNode,
env: &UpdateEnv,
alloc: &mut dyn FnMut() -> ValueId,
) -> Result<ValueId, String> {
match arg {
ASTNode::IntLiteral(n) => {
let id = alloc();
// Const 命令 emit
Ok(id)
}
ASTNode::Identifier(name) => {
env.resolve(name)
.ok_or_else(|| format!("Undefined arg: {}", name))
}
_ => Err("Complex args not supported".to_string())
}
}
}
```
#### Step 2: `lower_init` の MethodCall 分岐追加
```rust
pub fn lower_init(
body_ast: &[ASTNode],
join_builder: &mut JoinIRBuilder,
env: &UpdateEnv,
alloc: &mut dyn FnMut() -> ValueId,
) -> Result<LoopBodyLocalEnv, String> {
let mut body_local_env = LoopBodyLocalEnv::new();
for node in body_ast {
match node {
ASTNode::LocalVar { name, init } => {
let value_id = match init {
// 既存パターン
ASTNode::IntLiteral(_) => { /* ... */ }
ASTNode::Identifier(_) => { /* ... */ }
ASTNode::BinaryOp { .. } => { /* ... */ }
// NEW: MethodCall
ASTNode::MethodCall { receiver, method, args } => {
Self::emit_method_call_init(
receiver, method, args,
join_builder, env, alloc
)?
}
_ => return Err(format!(
"Unsupported init expression for '{}' in body-local",
name
))
};
body_local_env.register(name, value_id);
}
_ => {} // Skip non-local nodes
}
}
Ok(body_local_env)
}
```
#### Step 3: ユニットテスト追加
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_method_call_init_index_of() {
// UpdateEnv with "digits" → ValueId(5)
let mut condition_env = ConditionEnv::new();
condition_env.register("digits", ValueId(5));
let update_env = UpdateEnv::new(condition_env, LoopBodyLocalEnv::new());
// AST: local digit = digits.indexOf("x")
let init_ast = ASTNode::MethodCall {
receiver: Box::new(ASTNode::Identifier("digits".to_string())),
method: "indexOf".to_string(),
args: vec![ASTNode::StringLiteral("x".to_string())],
};
let mut builder = JoinIRBuilder::new();
let mut value_id_counter = 10;
let mut alloc = || { value_id_counter += 1; ValueId(value_id_counter) };
let result_id = LoopBodyLocalInitLowerer::emit_method_call_init(
&init_ast.receiver(),
&init_ast.method(),
&init_ast.args(),
&mut builder,
&update_env,
&mut alloc,
).unwrap();
// Verify CallBoxMethod instruction was emitted
assert_eq!(result_id, ValueId(11));
// ... verify builder contains CallBoxMethod instruction
}
#[test]
fn test_unsupported_method_fails() {
// AST: local x = obj.unsupportedMethod()
let init_ast = ASTNode::MethodCall {
receiver: Box::new(ASTNode::Identifier("obj".to_string())),
method: "unsupportedMethod".to_string(),
args: vec![],
};
let result = LoopBodyLocalInitLowerer::emit_method_call_init(
// ...
);
assert!(result.is_err());
assert!(result.unwrap_err().contains("not supported in body-local init"));
}
}
```
---
## Task 193-4: E2E 検証
### テストケース 1: 単一 MethodCall init
#### ファイル: `apps/tests/phase193_init_method_call.hako`
```nyash
static box Main {
main() {
local digits = "0123456789"
local result = 0
local i = 0
loop(i < 3) {
local ch = "0" // 簡略化: 実際は配列から取得
local digit = digits.indexOf(ch) // ← Phase 193 target
result = result * 10 + digit
i = i + 1
}
print(result) // Expected: 0 (indexOf("0") = 0)
return 0
}
}
```
#### 実行手順
```bash
# 1. ビルド
cargo build --release
# 2. E2E 実行
./target/release/hakorune apps/tests/phase193_init_method_call.hako
# Expected output: 0
# 3. デバッグ(必要に応じて)
NYASH_TRACE_VARMAP=1 ./target/release/hakorune apps/tests/phase193_init_method_call.hako 2>&1 | grep digit
```
### テストケース 2: MethodCall を含む二項演算(オプション)
#### ファイル: `apps/tests/phase193_init_method_binop.hako`
```nyash
static box Main {
main() {
local digits = "0123456789"
local result = 0
local i = 0
loop(i < 2) {
local ch = "1"
local digit = digits.indexOf(ch) + 1 // ← indexOf + offset
result = result * 10 + digit
i = i + 1
}
print(result) // Expected: 22 (indexOf("1") = 1, +1 = 2)
return 0
}
}
```
### 退行チェック
既存のテストが引き続き動作すること:
```bash
# Phase 191 body-local tests
./target/release/hakorune apps/tests/phase191_body_local_atoi.hako
# Expected: 123
# Phase 192 complex addend tests
./target/release/hakorune apps/tests/phase192_normalization_demo.hako
# Expected: 123
# Phase 190 NumberAccumulation tests
./target/release/hakorune apps/tests/phase190_atoi_impl.hako
# Expected: 12
```
---
## Task 193-5: ドキュメント更新
### 更新対象
#### 1. `phase193-init-methodcall-design.md`(本ファイル)
実装完了後、以下のセクションを追記:
```markdown
## Implementation Status
**完了日**: 2025-12-XX
### 実装サマリ
- **対応パターン**:
- [x] 単一 MethodCall (`local digit = digits.indexOf(ch)`)
- [ ] MethodCall を含む二項演算(オプション、時間により延期可)
- **サポートメソッド**:
- `StringBox.indexOf(char)` → 整数
- `ArrayBox.get(index)` → 要素
### JoinIR Emission 例
入力 AST:
```nyash
local digit = digits.indexOf(ch)
```
生成される JoinIR:
```
%10 = Copy { src: %5 } // digits (from ConditionEnv)
%11 = Copy { src: %6 } // ch (from ConditionEnv)
%12 = CallBoxMethod { receiver: %10, method: "indexOf", args: [%11] }
// LoopBodyLocalEnv: digit → %12
```
### 技術的発見
- **ConditionEnv 優先**: receiver は必ず ConditionEnv で解決body-local は相互参照不可)
- **Fail-Fast 効果**: 未サポートメソッドは明示的エラーで早期検出
- **既存インフラ再利用**: CallBoxMethod は JoinIR で既存、MIR merge も問題なし
### 制限事項
Phase 193 では以下をサポートしないFail-Fast でエラー):
- ネストした MethodCall: `s.substring(0, s.indexOf(ch))`
- 複数 MethodCall: `a.get(i) + b.get(j)`
- 配列アクセス: `array[i].method()`
これらは Phase 194+ で段階的に対応予定。
```
#### 2. `joinir-architecture-overview.md`
Section 2.2 "Update Lowering Infrastructure" に追記:
```markdown
- **Phase 193完了**: LoopBodyLocalInitLowerer が MethodCall を含む init 式に対応。
- 対応メソッド: `StringBox.indexOf`, `ArrayBox.get` (ホワイトリスト方式)
- receiver は ConditionEnv で解決(ループパラメータ・外部変数のみ)
- Fail-Fast: 未サポートメソッドは明示的エラー
- 制約: ネスト・複数呼び出しは Phase 194+ で対応
```
Section 7.2 "残タスク" を更新:
```markdown
- [x] **Phase 193**: MethodCall を含む body-local init 式の対応
- `local digit = digits.indexOf(ch)` パターンが動作
- 既存インフラCallBoxMethod再利用で実装完了
- [ ] **Phase 194**: 複雑な MethodCall パターン(ネスト・複数呼び出し)
- [ ] **Phase 195**: Pattern 3 (if-in-loop) への body-local 統合
```
#### 3. `CURRENT_TASK.md`
Phase 193 完了マークを追加:
```markdown
## Phase 193-impl: MethodCall in Init Lowering (完了: 2025-12-XX)
**目標**: body-local 変数の初期化式で MethodCall をサポート
**実装内容**:
- `loop_body_local_init_lowerer.rs`: `emit_method_call_init` 関数追加(~80行
- ホワイトリスト方式: `indexOf`, `get` のみ許可
- ConditionEnv 優先解決: receiver は必ずループパラメータ/外部変数
**テスト結果**:
- phase193_init_method_call.hako → 0 ✅
- 既存テスト退行なし ✅
**技術的成果**:
- CallBoxMethod 命令の再利用(新規 Pattern 不要)
- Fail-Fast でサポート範囲を明確化
- ConditionEnv vs LoopBodyLocalEnv の責務分離確認
**制限事項**:
- ネスト・複数呼び出しは Phase 194+ で対応
- receiver は単一変数のみ(複雑な式は未サポート)
**次のステップ**: Phase 194複雑な MethodCall パターン)または Phase 195Pattern 3 統合)
```
---
## 成功基準
- [x] 代表テスト(`phase193_init_method_call.hako`)が JoinIR only で期待値を返す
- [x] 既存テストphase191, phase192, phase190が退行しない
- [x] LoopBodyLocalInitLowerer が MethodCall 式を CallBoxMethod 命令に変換できる
- [x] 未サポートメソッドは明示的エラーで Fail-Fast する
- [x] ドキュメントが更新されている
---
## 関連ファイル
### インフラPhase 191 で実装済み)
- `src/mir/join_ir/lowering/loop_body_local_init_lowerer.rs`Phase 193 で拡張)
- `src/mir/join_ir/lowering/loop_body_local_env.rs`
- `src/mir/join_ir/lowering/update_env.rs`
### 統合対象
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`変更不要、Phase 191 インフラを再利用)
### テストファイル
- `apps/tests/phase193_init_method_call.hako`(新規作成)
- `apps/tests/phase193_init_method_binop.hako`(新規作成・オプション)
- `apps/tests/phase191_body_local_atoi.hako`(退行確認)
- `apps/tests/phase192_normalization_demo.hako`(退行確認)
---
## 次の Phase への接続
### Phase 194: 複雑な MethodCall パターン(候補)
- ネストした MethodCall: `s.substring(0, s.indexOf(ch))`
- 複数 MethodCall: `a.get(i) + b.get(j)`
- 配列アクセス: `array[i].method()`
### Phase 195: Pattern 3 への body-local 統合(候補)
- if-in-loop での body-local 変数
- 条件分岐を跨ぐ PHI 接続
Phase 193 完了後、ユーザーと相談して次の優先順位を決定する。