Files
hakorune/docs/development/current/main/phase-21.7-naming-ssot-checklist.md

548 lines
17 KiB
Markdown
Raw Normal View History

# Phase 21.7++ NamingBox SSOT 統一化チェックリスト
**作成日**: 2025-11-22
**目的**: 関数名と arity の扱いを SSOTNamingBoxに寄せ、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<usize>, // None = arity 未定(後で補完)
}
```
- [ ] **1.2: ヘルパー関数追加**
- ファイル: `src/mir/naming.rs`
- 実装:
```rust
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` (新規)
- 実装:
```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 を 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 完了条件
- [ ] ドキュメントが充実
- [ ] 次回のバグで即原因特定可能
---
## 📝 メモ・気づき
<!-- ここに実装中の気づきや問題点をメモ -->