Files
hakorune/docs/development/current/main/phase-21.7-naming-ssot-checklist.md
nyash-codex ce7517dc21 docs(phase-21.7): NamingBox SSOT 統一化チェックリスト作成
Phase 21.7++ の詳細実装計画を文書化

## 作成ファイル
- docs/development/current/main/phase-21.7-naming-ssot-checklist.md
  - Phase 0-4 の詳細タスクリスト(チェックボックス付き)
  - 各タスクの具体的な実装コード例
  - テストケース
  - 工数見積もり・優先順位
  - 完了条件

## CURRENT_TASK.md 更新
- Phase 21.7 セクションにチェックリストへのリンク追加

## 実装優先順位
1. Phase 0: 観測ライン緊急構築(最優先、2-3時間)
2. Phase 1: 基盤整備(4-6時間)
3. Phase 2: VM 統一(3-4時間)
4. Phase 3-4: 全体統一・ドキュメント(Phase 22+)

次のステップ: Phase 0 実装開始
2025-11-22 01:59:27 +09:00

17 KiB
Raw Blame History

Phase 21.7++ NamingBox SSOT 統一化チェックリスト

作成日: 2025-11-22 目的: 関数名と arity の扱いを SSOTNamingBoxに寄せ、Global/Method/VM 呼び出しを統一する


📋 前提条件(既に実装済み)

  • NamingBox.encode_static_method / decode_static_method / normalize_static_global_name 実装済み
  • HAKO_MIR_BUILDER_METHODIZE 既定ON未設定 or "1")、"0" のときだけ無効
  • unified_emitter 側の Hotfix 7 は static/instance に配慮済み
  • 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
    • 実装:
      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
    • 実装:
      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 の該当箇所(要特定)
    • 実装:
      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
    • 実装:
      /// 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<usize>,  // None = arity 未定(後で補完)
      }
      
  • 1.2: ヘルパー関数追加

    • ファイル: src/mir/naming.rs
    • 実装:
      impl StaticMethodId {
          /// "Box.method/N" or "Box.method" をパース
          pub fn parse(name: &str) -> Option<Self> {
              // 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::<usize>().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 を normalizemain → 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> {
          StaticMethodId::parse(name)
      }
      
      pub fn format_global_name(id: &StaticMethodId) -> String {
          id.format()
      }
      
  • 1.3: ミニテスト追加

    • ファイル: src/tests/namingbox_static_method_id.rs (新規)
    • 実装:
      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:
      let mut canonical = crate::mir::naming::normalize_static_global_name(func_name);
      if !canonical.contains('/') {
          canonical = format!("{}/{}", canonical, args.len());
      }
      
    • Phase 2正式実装:
      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 情報を追加:
      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
    • 両方の呼び方でテスト:
      #[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 使用:
      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
    • 追記内容:
      ## 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 (新規)
    • 内容:
      # 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 (新規)
    • 内容:
      # 関数ルックアップガイド
      
      ## 名前解決の流れ
      
      1. **パース**: `StaticMethodId::parse("Box.method/2")``{ box_name: "Box", method: "method", arity: Some(2) }`
      
      2. **正規化**: box_name を normalizemain → 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 完了条件

  • ドキュメントが充実
  • 次回のバグで即原因特定可能

📝 メモ・気づき