feat(naming): Phase 21.7++ Phase 1 完全達成 - StaticMethodId SSOT 基盤確立

## 🎉 成果概要
**Phase 1: 基盤整備** - NamingBox SSOT の構造化された基盤完成!

###  実装完了項目(全5タスク)
1. **StaticMethodId 構造体導入** (src/mir/naming.rs:86-248)
   - 構造化された関数名表現: box_name, method, arity
   - Optional arity でパース柔軟性確保

2. **ヘルパー関数追加**
   - `parse()`: "Box.method/N" or "Box.method" をパース
   - `format()`: 構造化 ID → 文字列変換
   - `with_arity()`: arity 補完用ビルダー
   - 互換性エイリアス: parse_global_name(), format_global_name()

3. **包括的テスト追加** (src/tests/namingbox_static_method_id.rs)
   - 13 テストケース全通過 
   - arity 有り/無し、normalize、format、round-trip、エラー処理
   - エッジケース: 複数ドット、大きな arity、不正入力

4. **テスト登録** (src/tests/mod.rs:21)
   - Phase 21.7++ コメント付きで登録

5. **動作確認**
   - `cargo test --release --lib namingbox_static_method_id`
   - 全 13 テスト PASS (0.00s)

### 📊 技術的効果
- **SSOT 確立**: 関数名パース/フォーマットを 1 箇所に集約
- **型安全**: 構造化表現で誤用防止
- **テスト保証**: 13 ケースで安全性確保
- **後方互換**: エイリアス関数で段階移行可能

### 🎯 Phase 2 への準備完了
- VM 側の関数ルックアップを StaticMethodId ベース化
- arity バグ根治への基盤確立

---

**Phase 0**:  完了 (Silent Failure 根絶)
**Phase 1**:  完了 (SSOT 基盤確立)
**Phase 2**: 次のタスク (VM 統一)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-22 02:25:22 +09:00
parent 63012932eb
commit 96c1345ec0
4 changed files with 296 additions and 9 deletions

View File

@ -83,3 +83,166 @@ pub fn is_static_method_name(func_name: &str) -> bool {
decode_static_method(func_name).is_some()
}
// =========================================================================
// Phase 1: StaticMethodId - 構造化された関数名表現
// =========================================================================
/// Global 関数名の構造化表現
///
/// MIR の Global 関数名("Box.method/N")をパース・生成するための型。
///
/// # Examples
///
/// ```
/// use hakorune_selfhost::mir::naming::StaticMethodId;
///
/// // パースarity 有り)
/// let id = StaticMethodId::parse("StringUtils.starts_with/2").unwrap();
/// assert_eq!(id.box_name, "StringUtils");
/// assert_eq!(id.method, "starts_with");
/// assert_eq!(id.arity, Some(2));
///
/// // パースarity 無し)
/// let id = StaticMethodId::parse("StringUtils.starts_with").unwrap();
/// assert_eq!(id.arity, None);
///
/// // フォーマット
/// let id = StaticMethodId {
/// box_name: "Main".to_string(),
/// method: "main".to_string(),
/// arity: Some(0),
/// };
/// assert_eq!(id.format(), "Main.main/0");
///
/// // arity 補完
/// let with_arity = id.with_arity(2);
/// assert_eq!(with_arity.format(), "Main.main/2");
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StaticMethodId {
pub box_name: String,
pub method: String,
pub arity: Option<usize>, // None = arity 未定(後で補完)
}
impl StaticMethodId {
/// "Box.method/N" or "Box.method" をパース
///
/// # Arguments
/// * `name` - 関数名("BoxName.method/arity" または "BoxName.method"
///
/// # Returns
/// * `Some(StaticMethodId)` - パース成功
/// * `None` - パース失敗(不正なフォーマット)
///
/// # Examples
///
/// ```
/// use hakorune_selfhost::mir::naming::StaticMethodId;
///
/// // 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));
///
/// // 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);
///
/// // main → Main に normalize
/// let id = StaticMethodId::parse("main._nop/0").unwrap();
/// assert_eq!(id.box_name, "Main");
/// ```
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 = canonical_box_name(&box_name);
Some(Self {
box_name: normalized_box,
method,
arity,
})
}
/// "Box.method/N" 形式で出力arity が None なら /N なし)
///
/// # Examples
///
/// ```
/// use hakorune_selfhost::mir::naming::StaticMethodId;
///
/// let id = StaticMethodId {
/// box_name: "StringUtils".to_string(),
/// method: "starts_with".to_string(),
/// arity: Some(2),
/// };
/// assert_eq!(id.format(), "StringUtils.starts_with/2");
///
/// let no_arity = StaticMethodId {
/// box_name: "StringUtils".to_string(),
/// method: "starts_with".to_string(),
/// arity: None,
/// };
/// assert_eq!(no_arity.format(), "StringUtils.starts_with");
/// ```
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 を返す
///
/// # Examples
///
/// ```
/// use hakorune_selfhost::mir::naming::StaticMethodId;
///
/// 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");
/// ```
pub fn with_arity(&self, arity: usize) -> Self {
Self {
box_name: self.box_name.clone(),
method: self.method.clone(),
arity: Some(arity),
}
}
}
// 既存関数のエイリアス(互換性維持)
/// `StaticMethodId::parse()` のエイリアス
///
/// 互換性のために提供。新しいコードでは `StaticMethodId::parse()` を使用してください。
pub fn parse_global_name(name: &str) -> Option<StaticMethodId> {
StaticMethodId::parse(name)
}
/// `StaticMethodId::format()` のエイリアス
///
/// 互換性のために提供。新しいコードでは `StaticMethodId::format()` を使用してください。
pub fn format_global_name(id: &StaticMethodId) -> String {
id.format()
}