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 index 66d2107d..c67c1c2b 100644 --- a/docs/development/current/main/phase-21.7-naming-ssot-checklist.md +++ b/docs/development/current/main/phase-21.7-naming-ssot-checklist.md @@ -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` - 全テスト通過確認 diff --git a/src/mir/naming.rs b/src/mir/naming.rs index c408d526..13facdbf 100644 --- a/src/mir/naming.rs +++ b/src/mir/naming.rs @@ -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, // 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 { + // 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 = 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::parse(name) +} + +/// `StaticMethodId::format()` のエイリアス +/// +/// 互換性のために提供。新しいコードでは `StaticMethodId::format()` を使用してください。 +pub fn format_global_name(id: &StaticMethodId) -> String { + id.format() +} + diff --git a/src/tests/mod.rs b/src/tests/mod.rs index a9988e9d..311dc217 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -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; diff --git a/src/tests/namingbox_static_method_id.rs b/src/tests/namingbox_static_method_id.rs new file mode 100644 index 00000000..6e28ccf7 --- /dev/null +++ b/src/tests/namingbox_static_method_id.rs @@ -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"); +}