diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 42f43ac3..7257508e 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -25,12 +25,17 @@ ## 1. 最近完了した重要タスク -### 1-00. Phase 21.7 — Static Box Methodization(完了 2025-11-21) +### 1-00. Phase 21.7 — Static Box Methodization(完了 2025-11-21, 既定ON に移行) **目的** - static box 内の呼び出しを NamingBox/Method まわりで一貫して扱えるようにする。 - Global("BoxName.method/arity") を Method{receiver = static singleton} に寄せる(トグル制御)。 +**🎯 Phase 21.7++ 計画** +- **NamingBox SSOT 統一化チェックリスト**: [phase-21.7-naming-ssot-checklist.md](docs/development/current/main/phase-21.7-naming-ssot-checklist.md) +- StringUtils using 解決バグ修正(2025-11-22, commit f4ae1445)を踏まえた改善計画 +- Phase 0(観測ライン)→ Phase 1(基盤)→ Phase 2(VM統一)の段階実装 + **実装内容** 1. **NamingBox decode 関数追加** (Step 1: commit a13f14ce) - `decode_static_method(func_name: &str) -> Option<(&str, &str, usize)>` @@ -55,12 +60,12 @@ - RC=0 両モード動作確認済み: `apps/tests/phase217_methodize_test.hako` **環境変数** -- `HAKO_MIR_BUILDER_METHODIZE=1`: methodization 有効化(デフォルトOFF) +- `HAKO_MIR_BUILDER_METHODIZE=0/1`: methodization 制御。既定ON(未設定 or "1")、"0" のときのみ無効化。 - `NYASH_METHODIZE_TRACE=1`: Global→Method 変換ログ出力 -**次タスク** -- methodization を段階的に有効化し、static box 呼び出しの統一的な扱いを進める。 -- VM 側の static box singleton 管理との整合性検証。 +**次タスク(Phase 21.8x 優先案件)** +- NamingBox/UnifiedCallEmitter/VM の 3 点で「名前と arity の SSOT」をさらに明示化する(docs + 小さなリファイン)。 +- Global 名の直接操作は NamingBox 内に閉じ込め、それ以外の層は `StaticMethodId` 相当の構造を介して扱うように寄せる。 ### 1-0. Phase 25.3 — FuncScanner / Stage‑B defs 安定化(完了) diff --git a/docs/development/current/main/phase-21.7-naming-ssot-checklist.md b/docs/development/current/main/phase-21.7-naming-ssot-checklist.md new file mode 100644 index 00000000..66d2107d --- /dev/null +++ b/docs/development/current/main/phase-21.7-naming-ssot-checklist.md @@ -0,0 +1,547 @@ +# Phase 21.7++ NamingBox SSOT 統一化チェックリスト + +**作成日**: 2025-11-22 +**目的**: 関数名と arity の扱いを SSOT(NamingBox)に寄せ、Global/Method/VM 呼び出しを統一する + +--- + +## 📋 前提条件(既に実装済み) + +- [x] NamingBox.encode_static_method / decode_static_method / normalize_static_global_name 実装済み +- [x] HAKO_MIR_BUILDER_METHODIZE 既定ON(未設定 or "1")、"0" のときだけ無効 +- [x] unified_emitter 側の Hotfix 7 は static/instance に配慮済み +- [x] VM 側の Global ルックアップは "Box.method" → "Box.method/arity" に補完する修正完了(commit f4ae1445) + +--- + +## 🔥 Phase 0: 観測ライン緊急構築(最優先!) + +**工数**: 2-3時間 +**効果**: Silent Failure 根絶、開発者体験劇的向上 +**リスク**: ゼロ + +### タスク + +- [ ] **0.1: populate_from_toml エラー即座表示** + - ファイル: `src/runner/pipeline.rs:36-55` + - 実装: + ```rust + if let Err(e) = &toml_result { + eprintln!("⚠️ [using/workspace] Failed to load TOML modules:"); + eprintln!(" Error: {}", e); + eprintln!(" → All 'using' aliases will be unavailable"); + eprintln!(" → Fix TOML syntax errors in workspace modules"); + eprintln!(); + eprintln!(" 💡 Debug: NYASH_DEBUG_USING=1 for detailed logs"); + } + ``` + - 検証: TOML エラー時に警告が表示されることを確認 + +- [ ] **0.2: VM 関数ルックアップ常時提案** + - ファイル: `src/backend/mir_interpreter/handlers/calls/global.rs:179-183` + - 実装: + ```rust + if !self.functions.contains_key(&canonical) { + let prefix = if let Some(idx) = canonical.find('.') { + &canonical[..idx] + } else { + &canonical + }; + + let similar: Vec<_> = self.functions.keys() + .filter(|k| k.starts_with(prefix)) + .take(5) + .collect(); + + let mut err_msg = format!("Function not found: {}", func_name); + + if !similar.is_empty() { + err_msg.push_str("\n\n💡 Did you mean:"); + for s in similar { + err_msg.push_str(&format!("\n - {}", s)); + } + } + + err_msg.push_str("\n\n🔍 Debug: NYASH_DEBUG_FUNCTION_LOOKUP=1 for full lookup trace"); + + return Err(self.err_with_context("global function", &err_msg)); + } + ``` + - 検証: 存在しない関数呼び出し時に提案が表示されることを確認 + +- [ ] **0.3: using not found 詳細化** + - ファイル: using resolver の該当箇所(要特定) + - 実装: + ```rust + if !aliases.contains_key(name) { + let similar: Vec<_> = aliases.keys() + .filter(|k| { + k.to_lowercase().contains(&name.to_lowercase()) || + name.to_lowercase().contains(&k.to_lowercase()) + }) + .take(3) + .collect(); + + eprintln!("❌ [using] Module not found: '{}'", name); + + if !similar.is_empty() { + eprintln!(" 💡 Did you mean:"); + for s in similar { + eprintln!(" - {}", s); + } + } + + if aliases.is_empty() { + eprintln!(" ⚠️ No aliases loaded (check TOML parse errors above)"); + } else { + eprintln!(" Available modules: {} total", aliases.len()); + eprintln!(" Run with NYASH_DEBUG_USING=1 to see all aliases"); + } + + return Err(...); + } + ``` + - 検証: 存在しないモジュール使用時に提案が表示されることを確認 + +- [ ] **0.4: テスト実行** + - 既存テスト全通過確認: `cargo test --release --lib` + - StringUtils テスト: `cargo test --release --lib json_lint_stringutils_min_vm` + +--- + +## ⭐ Phase 1: 基盤整備 + +**工数**: 4-6時間 +**効果**: SSOT 確立、テストで安全性保証 +**リスク**: 低(純粋な追加、既存機能に影響なし) + +### タスク + +- [ ] **1.1: StaticMethodId 構造体導入** + - ファイル: `src/mir/naming.rs` + - 実装: + ```rust + /// Global 関数名の構造化表現 + /// 例: "StringUtils.starts_with/2" → + /// { box_name: "StringUtils", method: "starts_with", arity: Some(2) } + pub struct StaticMethodId { + pub box_name: String, + pub method: String, + pub arity: Option, // None = arity 未定(後で補完) + } + ``` + +- [ ] **1.2: ヘルパー関数追加** + - ファイル: `src/mir/naming.rs` + - 実装: + ```rust + impl StaticMethodId { + /// "Box.method/N" or "Box.method" をパース + pub fn parse(name: &str) -> Option { + // 1. arity 分離: "Box.method/2" → ("Box.method", Some(2)) + let (base, arity) = if let Some(idx) = name.rfind('/') { + let (b, a) = name.split_at(idx); + let arity_num = a[1..].parse::().ok()?; + (b, Some(arity_num)) + } else { + (name, None) + }; + + // 2. box_name/method 分離 + let dot_idx = base.rfind('.')?; + let box_name = base[..dot_idx].to_string(); + let method = base[dot_idx + 1..].to_string(); + + // 3. box_name を normalize(main → Main など) + let normalized_box = NamingBox::normalize_box_name(&box_name); + + Some(Self { + box_name: normalized_box, + method, + arity, + }) + } + + /// "Box.method/N" 形式で出力(arity が None なら /N なし) + pub fn format(&self) -> String { + match self.arity { + Some(n) => format!("{}.{}/{}", self.box_name, self.method, n), + None => format!("{}.{}", self.box_name, self.method), + } + } + + /// arity を補完して新しい StaticMethodId を返す + pub fn with_arity(&self, arity: usize) -> Self { + Self { + box_name: self.box_name.clone(), + method: self.method.clone(), + arity: Some(arity), + } + } + } + + // 既存関数のエイリアス(互換性維持) + pub fn parse_global_name(name: &str) -> Option { + StaticMethodId::parse(name) + } + + pub fn format_global_name(id: &StaticMethodId) -> String { + id.format() + } + ``` + +- [ ] **1.3: ミニテスト追加** + - ファイル: `src/tests/namingbox_static_method_id.rs` (新規) + - 実装: + ```rust + use crate::mir::naming::StaticMethodId; + + #[test] + fn test_parse_with_arity() { + let id = StaticMethodId::parse("Main._nop/0").unwrap(); + assert_eq!(id.box_name, "Main"); + assert_eq!(id.method, "_nop"); + assert_eq!(id.arity, Some(0)); + } + + #[test] + fn test_parse_without_arity() { + let id = StaticMethodId::parse("StringUtils.starts_with").unwrap(); + assert_eq!(id.box_name, "StringUtils"); + assert_eq!(id.method, "starts_with"); + assert_eq!(id.arity, None); + } + + #[test] + fn test_normalize_box_name() { + let id = StaticMethodId::parse("main._nop/0").unwrap(); + assert_eq!(id.box_name, "Main"); // main → Main に normalize + } + + #[test] + fn test_format_with_arity() { + let id = StaticMethodId { + box_name: "StringUtils".to_string(), + method: "starts_with".to_string(), + arity: Some(2), + }; + assert_eq!(id.format(), "StringUtils.starts_with/2"); + } + + #[test] + fn test_format_without_arity() { + let id = StaticMethodId { + box_name: "StringUtils".to_string(), + method: "starts_with".to_string(), + arity: None, + }; + assert_eq!(id.format(), "StringUtils.starts_with"); + } + + #[test] + fn test_with_arity() { + let id = StaticMethodId::parse("StringUtils.starts_with").unwrap(); + let with_arity = id.with_arity(2); + assert_eq!(with_arity.arity, Some(2)); + assert_eq!(with_arity.format(), "StringUtils.starts_with/2"); + } + + #[test] + fn test_round_trip() { + let cases = vec![ + "Main._nop/0", + "StringUtils.starts_with/2", + "Console.log/1", + ]; + + for case in cases { + let id = StaticMethodId::parse(case).unwrap(); + let formatted = id.format(); + assert_eq!(formatted, case, "Round-trip failed for: {}", case); + } + } + ``` + +- [ ] **1.4: テスト登録** + - ファイル: `src/tests/mod.rs` + - 追加: `pub mod namingbox_static_method_id;` + +- [ ] **1.5: テスト実行** + - `cargo test --release --lib namingbox_static_method_id` + - 全テスト通過確認 + +--- + +## 🔴 Phase 2: VM 統一 + +**工数**: 3-4時間 +**効果**: arity バグ根治、VM の名前解決が SSOT 準拠 +**リスク**: 中(既存テストで検証、段階的ロールアウト可能) + +### タスク + +- [ ] **2.1: global.rs を NamingBox ベース化** + - ファイル: `src/backend/mir_interpreter/handlers/calls/global.rs:9-16` + - 現状(hotfix): + ```rust + let mut canonical = crate::mir::naming::normalize_static_global_name(func_name); + if !canonical.contains('/') { + canonical = format!("{}/{}", canonical, args.len()); + } + ``` + - Phase 2(正式実装): + ```rust + use crate::mir::naming::StaticMethodId; + + // 1. Parse + let mut id = StaticMethodId::parse(func_name) + .ok_or_else(|| self.err_invalid(&format!("Invalid function name: {}", func_name)))?; + + // 2. arity 補完 + if id.arity.is_none() { + id = id.with_arity(args.len()); + } + + // 3. 正規化された名前で lookup + let canonical = id.format(); + ``` + +- [ ] **2.2: デバッグログ更新** + - Phase 0.2 のエラーメッセージに StaticMethodId 情報を追加: + ```rust + eprintln!("[DEBUG/vm] Parsed: box='{}', method='{}', arity={:?}", + id.box_name, id.method, id.arity); + ``` + +- [ ] **2.3: VM テスト拡張** + - ファイル: `src/tests/json_lint_stringutils_min_vm.rs` + - 両方の呼び方でテスト: + ```rust + #[test] + fn test_vm_arity_both_forms() { + // ... setup ... + + // Test 1: arity 無し呼び出し + let src1 = r#" + static box Main { + main() { + return StringUtils.starts_with("abc", "a") + } + } + "#; + // ... compile & execute ... + + // Test 2: arity 有り呼び出し(明示) + let src2 = r#" + static box Main { + main() { + // MIR で明示的に /2 を付けたケース + // (実際には MIR builder が付けるので、この書き方はできないが、 + // MIR JSON を直接作成してテストする) + } + } + "#; + // ... compile & execute ... + } + ``` + +- [ ] **2.4: テスト実行** + - `cargo test --release --lib json_lint_stringutils_min_vm` + - 既存の全テスト通過確認: `cargo test --release --lib` + +--- + +## 💡 Phase 3: 全体統一(Phase 22 候補) + +**工数**: 10-15時間 +**効果**: 完全統一達成、コード品質向上 +**リスク**: 中(広範囲の変更) + +### タスク + +- [ ] **3.1: 素手 split 置き換え調査** + - コマンド: `rg '"\."' --type rust src/mir/builder/` + - コマンド: `rg '\.split\("\.\"\)' --type rust src/` + - リスト化: 置き換え対象箇所を列挙 + +- [ ] **3.2: builder/calls/unified_emitter.rs 統一** + - ファイル: `src/mir/builder/calls/unified_emitter.rs` + - CalleeResolver で StaticMethodId 使用: + ```rust + use crate::mir::naming::StaticMethodId; + + // methodization ブロック + if let Callee::Global(name) = callee { + if let Some(id) = StaticMethodId::parse(&name) { + // TypeRegistry で static box method か確認 + if self.is_static_box_method(&id) { + // Method 化 + callee = Callee::Method { ... }; + } + } + } + ``` + +- [ ] **3.3: LLVM executor 統一** + - ファイル: LLVM 関連の executor(要特定) + - VM と同じルールで名前解決 + +- [ ] **3.4: Hako 側の統一** + - ファイル: `lang/src/mir/builder/func_lowering.hako` など + - 素手 split を NamingBox 相当の処理に置き換え + +- [ ] **3.5: テスト実行** + - 全テスト通過確認: `cargo test --release` + - スモークテスト: `tools/smokes/v2/run.sh --profile quick` + +--- + +## 📚 Phase 4: ドキュメント整備(Phase 23+ 候補) + +**工数**: 2-3時間 +**効果**: 再発防止、開発者体験向上 +**リスク**: ゼロ + +### タスク + +- [ ] **4.1: Phase 21.7 README 更新** + - ファイル: `docs/development/roadmap/phases/phase-21.7-normalization/README.md` + - 追記内容: + ```markdown + ## Global 名の SSOT ルール + + ### 原則 + - Global 関数名は **`Box.method/N`** が SSOT + - VM/LLVM で `Box.method` を受け取ったら、arity は `args.len()` から補完 + - すべての名前解決は `NamingBox::StaticMethodId` 経由 + + ### 実装 + - **NamingBox**: `src/mir/naming.rs` + - `StaticMethodId::parse()`: 名前のパース + - `StaticMethodId::format()`: 正規化された名前生成 + - `StaticMethodId::with_arity()`: arity 補完 + + - **VM**: `src/backend/mir_interpreter/handlers/calls/global.rs` + - `StaticMethodId` で名前解決 + - arity 無し → `args.len()` で補完 + + - **UnifiedCallEmitter**: `src/mir/builder/calls/unified_emitter.rs` + - Methodization で `StaticMethodId` 使用 + - TypeRegistry と連携して static box method 判定 + + ### デバッグ + - `NYASH_DEBUG_FUNCTION_LOOKUP=1`: VM 関数ルックアップ詳細 + - `NYASH_DEBUG_USING=1`: using 解決詳細 + ``` + +- [ ] **4.2: トラブルシューティングガイド作成** + - ファイル: `docs/development/troubleshooting/using-resolution.md` (新規) + - 内容: + ```markdown + # Using 解決トラブルシューティング + + ## エラーパターン別対処法 + + ### 1. `[using] Module not found: 'ModuleName'` + + **原因**: + - nyash.toml に alias が定義されていない + - TOML parse エラーで alias が読み込めていない + - タイポ + + **対処法**: + 1. TOML parse エラーを確認(上に警告が出ているはず) + 2. `NYASH_DEBUG_USING=1` で利用可能な alias を確認 + 3. nyash.toml の [using.aliases] セクションを確認 + + ### 2. `Function not found: Box.method` + + **原因**: + - 関数が存在しない + - arity が合っていない(method vs method/2) + - using で読み込んだモジュールがコンパイルされていない + + **対処法**: + 1. エラーメッセージの「Did you mean:」を確認 + 2. `NYASH_DEBUG_FUNCTION_LOOKUP=1` で関数テーブルを確認 + 3. arity が必要な場合、`Box.method/N` の形式で呼ぶ + + ### 3. `TOML parse error` + + **原因**: + - TOML 構文エラー + - キー衝突(scalar と table) + + **対処法**: + 1. エラーメッセージの行番号を確認 + 2. 同じキーが scalar と table で定義されていないか確認 + 3. TOML validator で検証 + ``` + +- [ ] **4.3: 関数ルックアップガイド作成** + - ファイル: `docs/development/troubleshooting/function-lookup.md` (新規) + - 内容: + ```markdown + # 関数ルックアップガイド + + ## 名前解決の流れ + + 1. **パース**: `StaticMethodId::parse("Box.method/2")` + → `{ box_name: "Box", method: "method", arity: Some(2) }` + + 2. **正規化**: box_name を normalize(main → Main) + + 3. **arity 補完**: arity が None なら args.len() から補完 + + 4. **フォーマット**: `id.format()` → "Box.method/2" + + 5. **ルックアップ**: 関数テーブルで検索 + + ## デバッグ方法 + + ```bash + # 詳細ログ有効化 + NYASH_DEBUG_FUNCTION_LOOKUP=1 ./target/release/hakorune program.hako + + # 出力例 + [DEBUG/vm] Looking up function: 'StringUtils.starts_with' + [DEBUG/vm] Parsed: box='StringUtils', method='starts_with', arity=None + [DEBUG/vm] After arity補完: 'StringUtils.starts_with/2' + [DEBUG/vm] ✅ 'StringUtils.starts_with/2' found + ``` + ``` + +--- + +## ✅ 完了確認 + +### Phase 0 完了条件 +- [ ] すべてのエラーメッセージが親切(Did you mean 提案付き) +- [ ] Silent Failure がゼロ +- [ ] デバッグ方法が明示されている + +### Phase 1 完了条件 +- [ ] StaticMethodId のテストが全通過 +- [ ] Round-trip テストが成功 +- [ ] 既存機能に影響なし + +### Phase 2 完了条件 +- [ ] VM が StaticMethodId ベースで動作 +- [ ] arity 有り/無し両方で正しく動作 +- [ ] 既存テスト全通過 + +### Phase 3 完了条件 +- [ ] 全箇所が NamingBox 経由 +- [ ] 素手 split がゼロ +- [ ] スモークテスト全通過 + +### Phase 4 完了条件 +- [ ] ドキュメントが充実 +- [ ] 次回のバグで即原因特定可能 + +--- + +## 📝 メモ・気づき + + + diff --git a/docs/development/normalization/ownership.md b/docs/development/normalization/ownership.md index f3233e19..67ba0862 100644 --- a/docs/development/normalization/ownership.md +++ b/docs/development/normalization/ownership.md @@ -16,6 +16,7 @@ Ownership - Legacy JSON v0 → minimal bridging(json_v0_bridge 内での Callee 補完など)。 - 互換/安全弁: 未定義受信の構造的回復(同一BB直近 NewBox)など、dev ガード付きの最小範囲。 - Optimizer は構造・副作用ベースの最適化に限定(意味論の再書換えはしない)。 + - Global 呼び出し名の canonical 化(例: `"Box.method"` → `"Box.method/N"`)は NamingBox を通じて行い、VM/LLVM/Interpreter は arity 付き名を SSOT として扱う。 Guards and Toggles - Hako(dev 推奨セット) @@ -40,4 +41,3 @@ Testing - tools/dev/phase217_methodize_json_canary.sh(schema_version + mir_call present、Method優先) - tools/dev/phase216_chain_canary_call.sh(rc=5) - 失敗時は Hako 側(methodize)→ Rust 側(構造) の順で原因を特定する。 - diff --git a/lang/src/mir/builder/README.md b/lang/src/mir/builder/README.md index 3d2ace96..a921b74a 100644 --- a/lang/src/mir/builder/README.md +++ b/lang/src/mir/builder/README.md @@ -25,7 +25,7 @@ Toggles - `HAKO_MIR_BUILDER_DELEGATE=1` — use Runner/extern provider (`env.mirbuilder.emit`) 経由で Program→MIR - `HAKO_SELFHOST_NO_DELEGATE=1` — selfhost-first 時に delegate 経路を完全無効化し、internal lowers のみで成否を判定する - `HAKO_MIR_BUILDER_FUNCS=1` — enable defs lowering via `FuncLoweringBox.lower_func_defs` -- `HAKO_MIR_BUILDER_METHODIZE=1` — enable call→mir_call(Method) rewrite after MIR 生成 +- `HAKO_MIR_BUILDER_METHODIZE=0/1` — call→mir_call(Method) rewrite。既定ON(未設定または"1")、"0" のときのみ無効化。 - `HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE=1` — apply JsonFrag normalizer to selfhost/provider output - `HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG=1` — dev‑only: minimal loop MIR を強制生成(テスト用) - `HAKO_MIR_BUILDER_REQUIRE_MAIN=1` — inject_funcs で `"name":"main"` を持たない MIR に defs を追加するのを禁止(既定=0)