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:
@ -22,7 +22,7 @@
|
||||
|
||||
### タスク
|
||||
|
||||
- [ ] **0.1: populate_from_toml エラー即座表示**
|
||||
- [x] **0.1: populate_from_toml エラー即座表示** ✅ 完了 (2025-11-22)
|
||||
- ファイル: `src/runner/pipeline.rs:36-55`
|
||||
- 実装:
|
||||
```rust
|
||||
@ -37,7 +37,7 @@
|
||||
```
|
||||
- 検証: TOML エラー時に警告が表示されることを確認
|
||||
|
||||
- [ ] **0.2: VM 関数ルックアップ常時提案**
|
||||
- [x] **0.2: VM 関数ルックアップ常時提案** ✅ 完了 (2025-11-22)
|
||||
- ファイル: `src/backend/mir_interpreter/handlers/calls/global.rs:179-183`
|
||||
- 実装:
|
||||
```rust
|
||||
@ -69,7 +69,7 @@
|
||||
```
|
||||
- 検証: 存在しない関数呼び出し時に提案が表示されることを確認
|
||||
|
||||
- [ ] **0.3: using not found 詳細化**
|
||||
- [x] **0.3: using not found 詳細化** ✅ 完了 (2025-11-22)
|
||||
- ファイル: using resolver の該当箇所(要特定)
|
||||
- 実装:
|
||||
```rust
|
||||
@ -103,7 +103,7 @@
|
||||
```
|
||||
- 検証: 存在しないモジュール使用時に提案が表示されることを確認
|
||||
|
||||
- [ ] **0.4: テスト実行**
|
||||
- [x] **0.4: テスト実行** ✅ 完了 (2025-11-22)
|
||||
- 既存テスト全通過確認: `cargo test --release --lib`
|
||||
- StringUtils テスト: `cargo test --release --lib json_lint_stringutils_min_vm`
|
||||
|
||||
@ -117,7 +117,7 @@
|
||||
|
||||
### タスク
|
||||
|
||||
- [ ] **1.1: StaticMethodId 構造体導入**
|
||||
- [x] **1.1: StaticMethodId 構造体導入** ✅ 完了 (2025-11-22)
|
||||
- ファイル: `src/mir/naming.rs`
|
||||
- 実装:
|
||||
```rust
|
||||
@ -131,7 +131,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **1.2: ヘルパー関数追加**
|
||||
- [x] **1.2: ヘルパー関数追加** ✅ 完了 (2025-11-22)
|
||||
- ファイル: `src/mir/naming.rs`
|
||||
- 実装:
|
||||
```rust
|
||||
@ -190,7 +190,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **1.3: ミニテスト追加**
|
||||
- [x] **1.3: ミニテスト追加** ✅ 完了 (2025-11-22)
|
||||
- ファイル: `src/tests/namingbox_static_method_id.rs` (新規)
|
||||
- 実装:
|
||||
```rust
|
||||
@ -262,11 +262,11 @@
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **1.4: テスト登録**
|
||||
- [x] **1.4: テスト登録** ✅ 完了 (2025-11-22)
|
||||
- ファイル: `src/tests/mod.rs`
|
||||
- 追加: `pub mod namingbox_static_method_id;`
|
||||
|
||||
- [ ] **1.5: テスト実行**
|
||||
- [x] **1.5: テスト実行** ✅ 完了 (2025-11-22)
|
||||
- `cargo test --release --lib namingbox_static_method_id`
|
||||
- 全テスト通過確認
|
||||
|
||||
|
||||
@ -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 を normalize(main → 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()
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ pub mod mir_stage1_cli_emit_program_min;
|
||||
pub mod mir_stage1_staticcompiler_receiver; // Phase 25.1: StaticCompiler receiver型推論バグ回帰防止
|
||||
pub mod mir_stage1_using_resolver_verify;
|
||||
pub mod json_lint_stringutils_min_vm; // Phase 21.7++: using StringUtils alias resolution fix
|
||||
pub mod namingbox_static_method_id; // Phase 21.7++ Phase 1: StaticMethodId structure tests
|
||||
pub mod stage1_cli_entry_ssa_smoke;
|
||||
pub mod mir_stageb_like_args_length;
|
||||
pub mod mir_stageb_loop_break_continue;
|
||||
|
||||
123
src/tests/namingbox_static_method_id.rs
Normal file
123
src/tests/namingbox_static_method_id.rs
Normal file
@ -0,0 +1,123 @@
|
||||
//! Tests for StaticMethodId structure (Phase 21.7++ Phase 1)
|
||||
//!
|
||||
//! 責務: NamingBox SSOT の基盤となる StaticMethodId のパース・フォーマットを検証
|
||||
|
||||
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() {
|
||||
// main → Main に normalize されることを確認
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_no_dot() {
|
||||
// ドットがない場合は None
|
||||
assert!(StaticMethodId::parse("print").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_arity() {
|
||||
// arity が数値でない場合は None
|
||||
assert!(StaticMethodId::parse("Main.method/invalid").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_multiple_dots() {
|
||||
// 最後のドットで分離されることを確認
|
||||
let id = StaticMethodId::parse("namespace.Box.method/2").unwrap();
|
||||
assert_eq!(id.box_name, "namespace.Box");
|
||||
assert_eq!(id.method, "method");
|
||||
assert_eq!(id.arity, Some(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arity_zero() {
|
||||
// arity 0 を正しく処理
|
||||
let id = StaticMethodId::parse("Main._nop/0").unwrap();
|
||||
assert_eq!(id.arity, Some(0));
|
||||
assert_eq!(id.format(), "Main._nop/0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arity_large_number() {
|
||||
// 大きな arity も処理可能
|
||||
let id = StaticMethodId::parse("Utils.variadic/99").unwrap();
|
||||
assert_eq!(id.arity, Some(99));
|
||||
assert_eq!(id.format(), "Utils.variadic/99");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_arity_override() {
|
||||
// 既存の arity を上書き
|
||||
let id = StaticMethodId {
|
||||
box_name: "Box".to_string(),
|
||||
method: "method".to_string(),
|
||||
arity: Some(1),
|
||||
};
|
||||
let overridden = id.with_arity(3);
|
||||
assert_eq!(overridden.arity, Some(3));
|
||||
assert_eq!(overridden.format(), "Box.method/3");
|
||||
}
|
||||
Reference in New Issue
Block a user