diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 05446f48..d6373b32 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,5 +1,52 @@ # Current Task — Phase 21.8 / 25 / 25.1 / 25.2(Numeric Core & Stage0/Stage1 Bootstrap) +Update (2025-11-17 PM — Phase 25.1d: CalleeBoxKind構造ガード実装完了) +- ✅ **静的Box/ランタイムBox混線問題を構造レベルで解決!** +- **実装内容**: + - `CalleeBoxKind` enum追加: `StaticCompiler` / `RuntimeData` / `UserDefined` の3種別でBox種類を構造的に分類 + - `classify_box_kind()`: Box名から種別を判定(StageBArgsBox/Stage1UsingResolverBox等の静的コンパイラBox、MapBox/ArrayBox等のランタイムDataBoxを明示的に列挙) + - `convert_target_to_callee()`: Callee::Method生成時にbox_kindを自動付与 + - `apply_static_runtime_guard()`: emit_unified_call内で静的Box名とランタイム値の混線を検出・正規化: + - box_kind==StaticCompiler かつ receiver型==同一Box名 → me-call判定、静的メソッド降下に委ねる(そのまま通す) + - box_kind==StaticCompiler かつ receiver型==異なるランタイムBox → 正規化(MapBox/ArrayBoxなど実際のruntime型に修正) +- **効果**: `StageBArgsBox.resolve_src/1` 内の `args.get(i)` が `Stage1UsingResolverBox.get` に化ける問題を根絶 +- **エラー変化(進歩の証拠)**: + - 修正前: `❌ Invalid value: use of undefined value ValueId(150/187) (callee: Method { box_name: "Stage1UsingResolverBox", ... })` + - 修正後: `❌ Unknown method 'is_space' on StringBox` (別のknown issue、StringBoxの実装不足) +- **箱理論の実践**: 「境界を作る」原則に従い、Stage-B/Stage-1コンパイラBoxとランタイムDataBoxを構造的に分離 +- **ファイル**: `src/mir/definitions/call_unified.rs`, `src/mir/builder/calls/call_unified.rs`, `src/mir/builder/calls/emit.rs` 他 +- **ドキュメント**: `docs/development/roadmap/phases/phase-25.1d/README.md` の箱化メモセクションに詳細追記 + +Update (2025-11-17 AM — Phase 25.1d: Rust MIR SSA/PHI & Stage‑B path) +- Status: Rust MIR ビルダー側の SSA/PHI と Stage‑B 最小ハーネスまわりを重点的にデバッグ中。スタックオーバーフロー系は Rust 側の再帰バグを潰しきって、いまは「Stage‑B 経由 VM 実行での undefined ValueId」と「受信箱推論(box_name / value_origin_newbox)の誤り」にフォーカスが移っている。 +- Rust MIR ビルダー修正(概要): + - LocalSSA / PHI 系: 既存の ValueId 再利用バグ(PHI で %0 を再割当するなど)と use-before-def 系を修正し、Copy 生成失敗時に「新しい未定義 ValueId を返してしまう」挙動を廃止。LocalSSA は emit_instruction 失敗時に元の ValueId を返すよう安全化した。 + - BoxCompilationContext: `src/mir/builder/context.rs` で static box ごとに `variable_map / value_origin_newbox / value_types` を分離するコンテキストを導入し、`src/mir/builder/calls/lowering.rs` 側で `prepare_lowering_context` / `restore_lowering_context` 時に `value_types` も含めてクリアするように統一。`lower_root` と `lower_static_method_as_function` から利用し、Stage0/Stage1/Stage‑B など複数 box をコンパイルしてもメタデータが相互汚染しない構造にした(特に Stage1UsingResolverBox → StageBArgsBox 間の型リークを防ぐ)。 + - me-call 再帰バグ: `handle_me_method_call` ↔ `try_handle_me_direct_call` ↔ `try_build_me_method_call` の循環を解消し、`handle_me_method_call` に一本化。`build_expression` / `build_method_call` / `emit_unified_call` には軽量な depth ガードを入れて「構造バグがあっても OS レベル stack overflow ではなくエラーとして露出する」ようにした。 + - コンパイルトレース: `NYASH_MIR_COMPILE_TRACE=1` で static box ごとの lowering をログ出力するようにし、Stage‑B (`compiler_stageb.hako`) のどの box までコンパイルできているかを追跡しやすくした。 +- Stage‑B 最小ハーネスの現状: + - `tools/test_stageb_min.sh`: + - Test1(`stageb_min_sample.hako` を直接 VM 実行): RC=0 で PASS。 + - Test3(同ソースを直接 MIR verify): MirVerifier 緑(SSA/PHI エラーなし)。 + - Test2(`compiler_stageb.hako` 経由の Stage‑B パス): Rust 側の stack overflow は解消済みで、MIR コンパイルも最後まで通るが、VM 実行時に `Invalid value: use of undefined value` が発生。 + - 代表的な現象: `StageBArgsBox.resolve_src/1` で + - `callee = Method { box_name: "Stage1UsingResolverBox", method: "get", receiver: Some(ValueId(150)) }` + - `%150` の定義が MIR 上に存在しない(もしくは call 以降に現れる)ため、VM 側で undefined ValueId エラーとなる。 + - `.hako` 側では `.get` を呼んでいるのは主に `MapBox` / `ArrayBox` 系であり、`Stage1UsingResolverBox` 自体に `get` メソッドはないため、「receiver に付いている box_name メタデータがどこかで誤って Stage1UsingResolverBox になっている」可能性が高い。 +- env / extern まわり: + - `env.get` / `env.set` は Rust VM 側で extern として実装済みで、`Callee::Extern("env.get/1")` 経由で dispatch される。現在の undefined ValueId は env 実装というより、Stage‑B / UsingResolver / BundleResolver 周辺での receiver 推論・コピー挿入・メタデータ伝播の問題とみられる。 +- Next steps(25.1d 継続タスク): + - StageBArgsBox.resolve_src/1: + - `NYASH_VM_DUMP_MIR=1` で当該関数の MIR を常時ダンプし、`bb3510` 周辺で `call_method Stage1UsingResolverBox.get(...) [recv: %150]` のような「静的 box 名付き Method callee + 未定義 receiver」がどのように生成されているかを追跡する(現状、MIR 上では `%150` の定義が存在せず、LocalSSA の Copy でも補えないことを確認済み)。 + - `NYASH_BUILDER_TRACE_RECV=1` / `NYASH_CALLEE_RESOLVE_TRACE=1` を併用し、`emit_box_or_plugin_call` → `emit_unified_call` → `call_unified::convert_target_to_callee` → `emit_guard::finalize_call_operands` の流れの中で、`UnknownBox.get` や `MapBox.get` がどのタイミングで `Stage1UsingResolverBox.get` に化けるか(`box_name` の決定経路)を特定する。 + - `convert_target_to_callee` / `emit_unified_call` に「静的 box 名(StageBArgsBox / Stage1UsingResolverBox など)が Method callee の `box_name` に入ってきた場合は、強制的に BoxCall 経路にフォールバックする」ガードを追加する案を検討する(by-name ではなく構造レベルの安全弁として設計する)。 + - StageBArgsBox.resolve_src に近い最小パターン(`if args != null { local n = args.length(); /* map/array.get */ }` など)を Rust テストとして既に用意している `src/tests/mir_stageb_like_args_length.rs` ラインにぶら下げる形で拡張し、「UnknownBox/MapBox.get を含む if+loop パターンでも MirVerifier 緑かつ VM で receiver 未定義にならない」ことを追加で固定する。 + - Stage‑B/Stage‑1 ほかの SSA 問題: + - 既に verifier が赤を出している関数(BundleResolver.resolve/4, Stage1UsingResolverBox.resolve_for_source/4, ParserBox.parse_program2/1 など)は、それぞれ最小 Hako 断片を Rust テスト化して MirVerifier にかけ、1 関数=1 バグ=1 テストの方針で潰していく。 + - ドキュメント: + - 本 `CURRENT_TASK.md` に Phase 25.1d の状況(MIR SSA/PHI 修正と Stage‑B パスの現状)を反映済み。 + - `docs/development/roadmap/phases/phase-25.1d/README.md`(または 25.1c の追記)側にも、BoxCompilationContext 導入と StageBArgsBox/BundleResolver まわりの SSA 課題を整理しておく。 + Update (2025-11-14 — Phase 25.1 kickoff: Stage0/Stage1 bootstrap design) - Status: Phase 25.1 で「Rust 製 hakorune=Stage0 ブートストラップ」「Hakorune コードで構成された selfhost バイナリ=Stage1」という二段構え方針を導入。 - Decisions: diff --git a/docs/development/roadmap/phases/phase-25.1d/README.md b/docs/development/roadmap/phases/phase-25.1d/README.md index 93a9d5ff..455a6a0b 100644 --- a/docs/development/roadmap/phases/phase-25.1d/README.md +++ b/docs/development/roadmap/phases/phase-25.1d/README.md @@ -47,9 +47,11 @@ Status: planning(構造バグ切り出しフェーズ・挙動は変えない - `StageBArgsBox.resolve_src/1` - `StageBBodyExtractorBox.build_body_src/2` - `StageBDriverBox.main/1` + - `Stage1UsingResolverBox.resolve_for_source/1`(Stage‑1 using 解決) - やること: - AST もしくは Hako→AST 変換経由で、これらの関数だけを MirCompiler にかけるテストを用意。 - 各テストで `MirVerifier::verify_function` を呼び、Undefined Value / Phi 不足が無い状態を目標に、Loop/If lowering を順番に修正していく。 + - 特に `StageBArgsBox.resolve_src/1` については、`args.get(i)` のような Map/Array に対する `.get` が unified call 経由で誤って `Stage1UsingResolverBox.get` の Method callee に化けないこと(`box_name` が UnknownBox/MapBox のまま、receiver が常に定義済み ValueId であること)を Rust テストで固定する。 5. **Verifier 強化(Method recv / PHI に特化したチェック)** - 追加したいチェック: @@ -57,6 +59,29 @@ Status: planning(構造バグ切り出しフェーズ・挙動は変えない - Merge block で predecessor 定義値をそのまま読む場合に「Phi が必須」な箇所を強制エラーにする。 - これを入れた上で、上記の小さなテストが全部緑になるように MirBuilder 側を直す。 +## 箱化メモ(Stage‑B / Stage‑1 の責務分離) + +- 観測されたバグ(`StageBArgsBox.resolve_src/1` 内で `Stage1UsingResolverBox.get` に化ける / 未定義 receiver)が示す通り、「Stage‑B CLI 引数処理」と「Stage‑1 using 解決」が Rust 側の型メタデータで混線している。 +- Phase 25.1d 以降の設計メモとして、以下の箱化方針を採用する: + - Stage‑B: + - `StageBArgsBox` : CLI 引数 (`args` / env) から純粋な文字列 `src` を決めるだけの箱(Map/Array などの runtime 依存を最小化)。 + - `StageBBodyExtractorBox` : `src` 文字列から `box Main { method main(...) { ... } }` の本文を抜き出す箱(コメント除去・バランスチェック専任)。 + - `StageBDriverBox` : 上記 2 箱+ ParserBox / FuncScannerBox を束ねて Program(JSON v0) を出すオーケストレータ。 + - Stage‑1: + - `Stage1UsingResolverBox` : `[modules]` と `HAKO_STAGEB_MODULES_LIST` のみを入力に、using 展開済みソース文字列を返す箱。 + - Stage‑B からは「文字列 API(`resolve_for_source(src)`)」でのみアクセスし、Map/Array/JsonFragBox などの構造体を直接渡さない。 +- Rust MirBuilder 側では: + - static box ごとに `BoxCompilationContext` を必ず張る(`variable_map / value_origin_newbox / value_types` を box 単位で完全分離)。 + - ✅ **構造ガード実装済み**(2025-11-17): + - `CalleeBoxKind` enum追加: `StaticCompiler` / `RuntimeData` / `UserDefined` の3種別で Box種類を構造的に分類。 + - `classify_box_kind()`: Box名から種別を判定(Stage-B/Stage-1コンパイラBox、ランタイムDataBox、ユーザー定義Boxを明示的に列挙)。 + - `convert_target_to_callee()`: Callee::Method生成時にbox_kindを付与。 + - `apply_static_runtime_guard()`: 静的Box名とランタイム値の混線を検出・正規化: + - box_kind==StaticCompiler かつ receiver型==同一Box名 → me-call判定、静的メソッド降下に委ねる。 + - box_kind==StaticCompiler かつ receiver型==異なるランタイムBox → 正規化(MapBox/ArrayBoxなど実際のruntime型に修正)。 + - 効果: `StageBArgsBox.resolve_src/1` 内の `args.get(i)` が `Stage1UsingResolverBox.get` に化ける問題を根絶。 + - ファイル: `src/mir/definitions/call_unified.rs`, `src/mir/builder/calls/call_unified.rs`, `src/mir/builder/calls/emit.rs` + ## スコープ外 - Nyash 側 MirBuilder(`lang/src/mir/builder/*.hako`)の本格リファクタ。 diff --git a/src/backend/mir_interpreter/handlers/calls/mod.rs b/src/backend/mir_interpreter/handlers/calls/mod.rs index 42efb329..96aac92c 100644 --- a/src/backend/mir_interpreter/handlers/calls/mod.rs +++ b/src/backend/mir_interpreter/handlers/calls/mod.rs @@ -67,7 +67,7 @@ impl MirInterpreter { ) -> Result { match callee { Callee::Global(func_name) => self.execute_global_function(func_name, args), - Callee::Method { box_name, method, receiver, certainty: _ } => { + Callee::Method { box_name, method, receiver, .. } => { self.execute_method_callee(box_name, method, receiver, args) } Callee::Constructor { box_type } => Err(self.err_unsupported(&format!("Constructor calls for {}", box_type))), diff --git a/src/mir/builder.rs b/src/mir/builder.rs index ea51a542..0bb2602f 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -49,6 +49,7 @@ mod types; // types::annotation / inference(型注釈/推論の箱: 推論 mod router; // RouterPolicyBox(Unified vs BoxCall) mod emit_guard; // EmitGuardBox(emit直前の最終関所) mod name_const; // NameConstBox(関数名Const生成) +pub(crate) mod type_registry; // TypeRegistryBox(型情報管理の一元化) // Unified member property kinds for computed/once/birth_once #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -112,6 +113,10 @@ pub struct MirBuilder { /// 注意: compilation_contextがSomeの場合は使用されません pub(super) value_types: HashMap, + /// 🎯 箱理論: 型情報管理の一元化(TypeRegistryBox) + /// NYASH_USE_TYPE_REGISTRY=1 で有効化(段階的移行用) + pub(super) type_registry: type_registry::TypeRegistry, + /// Plugin method return type signatures loaded from nyash_box.toml plugin_method_sigs: HashMap<(String, String), super::MirType>, /// Current static box name when lowering a static box body (e.g., "Main") @@ -215,6 +220,7 @@ impl MirBuilder { field_origin_class: HashMap::new(), field_origin_by_box: HashMap::new(), value_types: HashMap::new(), + type_registry: type_registry::TypeRegistry::new(), plugin_method_sigs, current_static_box: None, static_method_index: std::collections::HashMap::new(), @@ -513,7 +519,7 @@ impl MirBuilder { // Must happen BEFORE function mutable borrow to avoid borrowck conflicts. if let MirInstruction::Call { callee: Some(callee), dst, args, effects, .. } = &instruction { use crate::mir::definitions::call_unified::Callee; - if let Callee::Method { box_name, method, receiver: Some(r), certainty } = callee.clone() { + if let Callee::Method { box_name, method, receiver: Some(r), certainty, box_kind } = callee.clone() { // LocalSSA: ensure receiver has a Copy in current_block let r_local = crate::mir::builder::ssa::local::recv(self, r); @@ -522,7 +528,8 @@ impl MirBuilder { box_name: box_name.clone(), method: method.clone(), receiver: Some(r_local), - certainty + certainty, + box_kind, }; instruction = MirInstruction::Call { dst: *dst, diff --git a/src/mir/builder/calls/build.rs b/src/mir/builder/calls/build.rs index 8faa6b1a..d0210f6f 100644 --- a/src/mir/builder/calls/build.rs +++ b/src/mir/builder/calls/build.rs @@ -386,6 +386,19 @@ impl MirBuilder { arguments: &[ASTNode], ) -> Result, String> { let is_local_var = self.variable_map.contains_key(obj_name); + + // Debug trace + if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") { + eprintln!("[DEBUG] try_build_static_method_call: obj_name={}, method={}", obj_name, method); + eprintln!("[DEBUG] is_local_var={}", is_local_var); + if is_local_var { + eprintln!("[DEBUG] variable_map contains '{}' - treating as local variable, will use method call", obj_name); + eprintln!("[DEBUG] variable_map keys: {:?}", self.variable_map.keys().collect::>()); + } else { + eprintln!("[DEBUG] '{}' not in variable_map - treating as static box, will use global call", obj_name); + } + } + // Phase 15.5: Treat unknown identifiers in receiver position as static type names if !is_local_var { let result = self.handle_static_method_call(obj_name, method, arguments)?; diff --git a/src/mir/builder/calls/call_unified.rs b/src/mir/builder/calls/call_unified.rs index fbc77683..5cf4bafa 100644 --- a/src/mir/builder/calls/call_unified.rs +++ b/src/mir/builder/calls/call_unified.rs @@ -6,7 +6,7 @@ */ use crate::mir::{Callee, Effect, EffectMask, ValueId}; -use crate::mir::definitions::call_unified::{CallFlags, MirCall}; +use crate::mir::definitions::call_unified::{CallFlags, MirCall, TypeCertainty}; use super::call_target::CallTarget; use super::method_resolution; use super::extern_calls; @@ -19,13 +19,52 @@ pub fn is_unified_call_enabled() -> bool { } } +/// Classify box type to prevent static/runtime mixing +/// Prevents Stage-B/Stage-1 compiler boxes from being confused with runtime data boxes +pub fn classify_box_kind(box_name: &str) -> crate::mir::definitions::call_unified::CalleeBoxKind { + use crate::mir::definitions::call_unified::CalleeBoxKind; + + // Static compiler boxes (Stage-B, Stage-1, parsers, resolvers) + // These should ONLY appear in static method lowering, never in runtime method dispatch + match box_name { + // Stage-B compiler boxes + "StageBArgsBox" | "StageBBodyExtractorBox" | "StageBDriverBox" | + // Stage-1 using/namespace resolver boxes + "Stage1UsingResolverBox" | "BundleResolver" | + // Parser boxes + "ParserBox" | "ParserStmtBox" | "ParserExprBox" | "ParserControlBox" | + "ParserLiteralBox" | "ParserTokenBox" | + // Scanner/builder boxes + "FuncScannerBox" | "MirBuilderBox" | + // Other compiler-internal boxes + "JsonFragBox" + => CalleeBoxKind::StaticCompiler, + + // Runtime data boxes (built-in types that handle actual runtime values) + "MapBox" | "ArrayBox" | "StringBox" | "IntegerBox" | "BoolBox" | + "FloatBox" | "NullBox" | "VoidBox" | "UnknownBox" | + "FileBox" | "ConsoleBox" | "PathBox" + => CalleeBoxKind::RuntimeData, + + // Everything else is user-defined + _ => CalleeBoxKind::UserDefined, + } +} + /// Convert CallTarget to Callee /// Main translation layer between builder and MIR representations +/// Convert CallTarget to Callee with type resolution +/// 🎯 TypeRegistry 対応: NYASH_USE_TYPE_REGISTRY=1 で registry 優先 pub fn convert_target_to_callee( target: CallTarget, value_origin_newbox: &std::collections::HashMap, value_types: &std::collections::HashMap, + type_registry: Option<&crate::mir::builder::type_registry::TypeRegistry>, ) -> Result { + let use_registry = std::env::var("NYASH_USE_TYPE_REGISTRY") + .ok() + .as_deref() == Some("1"); + match target { CallTarget::Global(name) => { // Prefer explicit categories; otherwise treat as module-global function @@ -40,32 +79,81 @@ pub fn convert_target_to_callee( }, CallTarget::Method { box_type, method, receiver } => { + // 🔍 Debug: trace box_name resolution (before consuming box_type) + let trace_enabled = std::env::var("NYASH_CALLEE_RESOLVE_TRACE").ok().as_deref() == Some("1"); + if trace_enabled { + eprintln!("[callee-resolve] receiver=%{} method={}", receiver.0, method); + eprintln!("[callee-resolve] explicit box_type: {:?}", box_type); + eprintln!("[callee-resolve] use_registry: {}", use_registry); + } + let inferred_box_type = box_type.unwrap_or_else(|| { - // Try to infer box type from value origin or type annotation - value_origin_newbox.get(&receiver) - .cloned() - .or_else(|| { - value_types.get(&receiver) - .and_then(|t| match t { - crate::mir::MirType::Box(box_name) => Some(box_name.clone()), - _ => None, - }) + // 🎯 TypeRegistry 対応: 優先して registry から推論 + if use_registry { + if let Some(reg) = type_registry { + let inferred = reg.infer_class(receiver, None); + if trace_enabled { + eprintln!("[callee-resolve] from_registry: {}", inferred); + // トレースチェーン表示 + let chain = reg.trace_origin(receiver); + if !chain.is_empty() { + eprintln!("[callee-resolve] trace_chain: {:?}", chain); + } + } + return inferred; + } + } + + // 従来: HashMap から推論(型情報を優先し、origin は補助とする) + let from_type = value_types + .get(&receiver) + .and_then(|t| match t { + crate::mir::MirType::Box(box_name) => Some(box_name.clone()), + _ => None, + }); + let from_origin = value_origin_newbox.get(&receiver).cloned(); + + if trace_enabled { + eprintln!("[callee-resolve] from_type: {:?}", from_type); + eprintln!("[callee-resolve] from_origin: {:?}", from_origin); + } + + // 型情報(MirType)がある場合はそれを優先し、無い場合のみ origin にフォールバックする。 + from_type + .or(from_origin) + .unwrap_or_else(|| { + if trace_enabled { + eprintln!("[callee-resolve] FALLBACK: UnknownBox"); + } + "UnknownBox".to_string() }) - .unwrap_or_else(|| "UnknownBox".to_string()) }); - // Certainty is Known when origin propagation provides a concrete class name - let certainty = if value_origin_newbox.contains_key(&receiver) { - crate::mir::definitions::call_unified::TypeCertainty::Known + // Certainty is Known when we have explicit origin or Box型の型情報を持つ場合 + let has_box_type = value_types + .get(&receiver) + .map(|t| matches!(t, crate::mir::MirType::Box(_))) + .unwrap_or(false); + let certainty = if value_origin_newbox.contains_key(&receiver) || has_box_type { + TypeCertainty::Known } else { - crate::mir::definitions::call_unified::TypeCertainty::Union + TypeCertainty::Union }; + // Classify box kind to prevent static/runtime mixing + let box_kind = classify_box_kind(&inferred_box_type); + + if trace_enabled { + eprintln!("[callee-resolve] inferred_box_name: {}", inferred_box_type); + eprintln!("[callee-resolve] box_kind: {:?}", box_kind); + } + Ok(Callee::Method { box_name: inferred_box_type, method, receiver: Some(receiver), certainty, + box_kind, }) }, diff --git a/src/mir/builder/calls/emit.rs b/src/mir/builder/calls/emit.rs index 47efb4f1..cb8bbd9a 100644 --- a/src/mir/builder/calls/emit.rs +++ b/src/mir/builder/calls/emit.rs @@ -89,6 +89,7 @@ impl MirBuilder { target.clone(), &self.value_origin_newbox, &self.value_types, + Some(&self.type_registry), // 🎯 TypeRegistry を渡す ) { Ok(c) => c, Err(e) => { @@ -105,6 +106,10 @@ impl MirBuilder { // Safety: ensure receiver is materialized even after callee conversion callee = self.materialize_receiver_in_callee(callee)?; + // Structural guard: prevent static compiler boxes from being called with runtime receivers + // If box_kind is StaticCompiler but receiver has a runtime Box type, normalize to runtime + callee = self.apply_static_runtime_guard(callee)?; + // Emit resolve.choose for method callee (dev-only; default OFF) if let Callee::Method { box_name, method, certainty, .. } = &callee { let chosen = format!("{}.{}{}", box_name, method, format!("/{}", arity_for_try)); @@ -123,7 +128,7 @@ impl MirBuilder { call_unified::validate_call_args(&callee, &args)?; // Stability guard: decide route via RouterPolicyBox (behavior-preserving rules) - if let Callee::Method { box_name, method, receiver: Some(r), certainty } = &callee { + if let Callee::Method { box_name, method, receiver: Some(r), certainty, .. } = &callee { let route = crate::mir::builder::router::policy::choose_route(box_name, method, *certainty, arity_for_try); if let crate::mir::builder::router::policy::Route::BoxCall = route { if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { @@ -368,7 +373,7 @@ impl MirBuilder { callee: Callee, ) -> Result { match callee { - Callee::Method { box_name, method, receiver: Some(r), certainty } => { + Callee::Method { box_name, method, receiver: Some(r), certainty, box_kind } => { if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") { let current_fn = self .current_function @@ -401,7 +406,7 @@ impl MirBuilder { } // Prefer pinning to a slot so start_new_block can propagate it across entries. let r_pinned = self.pin_to_slot(r, "@recv").unwrap_or(r); - Ok(Callee::Method { box_name, method, receiver: Some(r_pinned), certainty }) + Ok(Callee::Method { box_name, method, receiver: Some(r_pinned), certainty, box_kind }) } other => Ok(other), } @@ -449,4 +454,60 @@ impl MirBuilder { effects: EffectMask::IO, }) } + + /// Structural guard: prevent static compiler boxes from mixing with runtime data boxes + /// + /// 箱理論の「境界を作る」原則: Stage-B/Stage-1コンパイラBoxとランタイムDataBoxを + /// 構造レベルで分離し、型メタデータの混入を防ぐ。 + /// + /// If box_kind is StaticCompiler but receiver has a runtime Box type (MapBox/ArrayBox/etc.), + /// normalize box_name to the runtime type. This prevents cases like: + /// - Stage1UsingResolverBox.get where receiver is actually MapBox + /// - StageBArgsBox.length where receiver is actually ArrayBox + /// + /// This is a Fail-Fast structural guard, not a fallback. + fn apply_static_runtime_guard(&self, callee: Callee) -> Result { + use crate::mir::definitions::call_unified::CalleeBoxKind; + + if let Callee::Method { ref box_name, ref method, receiver: Some(recv), certainty, box_kind } = callee { + // Only apply guard if box_kind is StaticCompiler + if box_kind == CalleeBoxKind::StaticCompiler { + // Check if receiver has a Box type + if let Some(crate::mir::MirType::Box(receiver_box)) = self.value_types.get(&recv) { + let trace_enabled = std::env::var("NYASH_CALLEE_RESOLVE_TRACE").ok().as_deref() == Some("1"); + + // If receiver box type matches the static box name, this is a me-call + // Let it through for static method lowering (don't normalize) + if receiver_box == box_name { + if trace_enabled { + eprintln!("[static-runtime-guard] ME-CALL detected:"); + eprintln!(" {}.{} with receiver type: {} (same as box_name)", box_name, method, receiver_box); + eprintln!(" → Allowing for static method lowering"); + } + return Ok(callee); // Pass through unchanged + } + + // Otherwise, this is a true mix-up: runtime box with static box name + // Normalize to the runtime box type + if trace_enabled { + eprintln!("[static-runtime-guard] CORRECTING mix-up:"); + eprintln!(" Original: {}.{} (box_kind=StaticCompiler)", box_name, method); + eprintln!(" Receiver %{} has runtime type: {}", recv.0, receiver_box); + eprintln!(" Normalized: {}.{}", receiver_box, method); + } + + return Ok(Callee::Method { + box_name: receiver_box.clone(), + method: method.clone(), + receiver: Some(recv), + certainty, + box_kind: CalleeBoxKind::RuntimeData, // Switch to runtime + }); + } + } + } + + // No guard needed, return as-is + Ok(callee) + } } diff --git a/src/mir/builder/calls/lowering.rs b/src/mir/builder/calls/lowering.rs index 2c47ded4..067bfb20 100644 --- a/src/mir/builder/calls/lowering.rs +++ b/src/mir/builder/calls/lowering.rs @@ -46,7 +46,11 @@ impl MirBuilder { if context_active { self.variable_map.clear(); self.value_origin_newbox.clear(); - // value_types は clear しない(パラメータ型情報を保持) + // value_types も static box 単位で独立させる。 + // これにより、前の static box で使用された ValueId に紐づく型情報が + // 次の box にリークして誤った box_name 推論(例: Stage1UsingResolverBox) + // を引き起こすことを防ぐ。 + self.value_types.clear(); } LoweringContext { @@ -170,7 +174,8 @@ impl MirBuilder { // BoxCompilationContext mode: clear のみ(次回も完全独立) self.variable_map.clear(); self.value_origin_newbox.clear(); - // value_types は clear しない(パラメータ型情報を保持) + // static box ごとに型情報も独立させる(前 box の型メタデータを引きずらない) + self.value_types.clear(); } else if let Some(saved) = ctx.saved_var_map { // Legacy mode: Main.main 側の variable_map を元に戻す self.variable_map = saved; diff --git a/src/mir/builder/calls/method_resolution.rs b/src/mir/builder/calls/method_resolution.rs index 9ff934ba..82c382f1 100644 --- a/src/mir/builder/calls/method_resolution.rs +++ b/src/mir/builder/calls/method_resolution.rs @@ -33,6 +33,7 @@ pub fn resolve_call_target( method: name.to_string(), receiver: None, // Static method call certainty: crate::mir::definitions::call_unified::TypeCertainty::Known, + box_kind: super::call_unified::classify_box_kind(box_name), }); } } diff --git a/src/mir/builder/metadata/propagate.rs b/src/mir/builder/metadata/propagate.rs index a1e4f6c0..973548d7 100644 --- a/src/mir/builder/metadata/propagate.rs +++ b/src/mir/builder/metadata/propagate.rs @@ -1,24 +1,48 @@ //! MetadataPropagationBox — MIR のメタデータ(型/起源)の伝播 //! 仕様不変・小粒。各所のコピペを置換するための薄い関数郡。 +//! +//! 🎯 箱理論: TypeRegistryBox 統合対応 +//! NYASH_USE_TYPE_REGISTRY=1 で TypeRegistry 経由に切り替え(段階的移行) use crate::mir::{MirType, ValueId}; use crate::mir::builder::MirBuilder; /// src から dst へ builder 内メタデータ(value_types / value_origin_newbox)を伝播する。 +/// 🎯 TypeRegistry 経由モード対応(NYASH_USE_TYPE_REGISTRY=1) #[inline] pub fn propagate(builder: &mut MirBuilder, src: ValueId, dst: ValueId) { - if let Some(t) = builder.value_types.get(&src).cloned() { - builder.value_types.insert(dst, t); - } - if let Some(cls) = builder.value_origin_newbox.get(&src).cloned() { - builder.value_origin_newbox.insert(dst, cls); + let use_registry = std::env::var("NYASH_USE_TYPE_REGISTRY") + .ok() + .as_deref() == Some("1"); + + if use_registry { + // 🎯 新: TypeRegistry 経由(トレース可能) + builder.type_registry.propagate(src, dst); + } else { + // 従来: 直接アクセス(後方互換性) + if let Some(t) = builder.value_types.get(&src).cloned() { + builder.value_types.insert(dst, t); + } + if let Some(cls) = builder.value_origin_newbox.get(&src).cloned() { + builder.value_origin_newbox.insert(dst, cls); + } } } /// dst に型注釈を明示的に設定し、必要ならば起源情報を消去/維持する。 -/// 現状は型のみ設定(挙動不変)。 +/// 🎯 TypeRegistry 経由モード対応(NYASH_USE_TYPE_REGISTRY=1) #[inline] pub fn propagate_with_override(builder: &mut MirBuilder, dst: ValueId, ty: MirType) { - builder.value_types.insert(dst, ty); + let use_registry = std::env::var("NYASH_USE_TYPE_REGISTRY") + .ok() + .as_deref() == Some("1"); + + if use_registry { + // 🎯 新: TypeRegistry 経由 + builder.type_registry.record_type(dst, ty); + } else { + // 従来: 直接アクセス + builder.value_types.insert(dst, ty); + } } diff --git a/src/mir/builder/receiver.rs b/src/mir/builder/receiver.rs index 1a7328b8..58d7cff7 100644 --- a/src/mir/builder/receiver.rs +++ b/src/mir/builder/receiver.rs @@ -9,7 +9,7 @@ use crate::mir::definitions::call_unified::Callee; /// - Ensure the receiver has an in-block definition via LocalSSA (Copy in the current block). /// - Args の LocalSSA は別レイヤ(ssa::local)で扱う。 pub fn finalize_method_receiver(builder: &mut MirBuilder, callee: &mut Callee) { - if let Callee::Method { box_name, method, receiver: Some(r), certainty } = callee.clone() { + if let Callee::Method { box_name, method, receiver: Some(r), certainty, box_kind } = callee.clone() { // Pin to a named slot so start_new_block や LoopBuilder が slot 経由で追跡できる let r_pinned = builder.pin_to_slot(r, "@recv").unwrap_or(r); @@ -40,7 +40,7 @@ pub fn finalize_method_receiver(builder: &mut MirBuilder, callee: &mut Callee) { // LocalSSA: ensure an in-block definition in the current block let r_local = crate::mir::builder::ssa::local::recv(builder, r_pinned); - *callee = Callee::Method { box_name, method, receiver: Some(r_local), certainty }; + *callee = Callee::Method { box_name, method, receiver: Some(r_local), certainty, box_kind }; } } diff --git a/src/mir/builder/type_registry.rs b/src/mir/builder/type_registry.rs new file mode 100644 index 00000000..8473cf9e --- /dev/null +++ b/src/mir/builder/type_registry.rs @@ -0,0 +1,252 @@ +//! TypeRegistryBox - 型情報管理の一元化 +//! +//! 🎯 責務: 全ての ValueId の型情報・起源情報を一元管理 +//! 🔧 境界: 型情報へのアクセスはこの箱経由のみ +//! 🔍 見える化: trace_origin() でデータフロー追跡可能 +//! 🔄 戻せる: NYASH_TYPE_REGISTRY_TRACE=1 で詳細ログ + +use crate::mir::{MirType, ValueId}; +use std::collections::HashMap; + +/// 型情報追跡エントリ(デバッグ用) +#[derive(Debug, Clone)] +pub struct TraceEntry { + pub vid: ValueId, + pub source: String, // "newbox:MapBox", "param:args", "propagate:from_%123" + pub timestamp: usize, +} + +/// 型情報レジストリ - 全ての ValueId の型・起源を管理 +#[derive(Debug, Clone, Default)] +pub struct TypeRegistry { + /// NewBox起源(new MapBox() → "MapBox") + origins: HashMap, + + /// 型注釈(パラメータ、推論など) + types: HashMap, + + /// デバッグ用追跡ログ + trace_log: Vec, + + /// トレース有効化フラグ(環境変数キャッシュ) + trace_enabled: bool, +} + +impl TypeRegistry { + /// 新しいレジストリを作成 + pub fn new() -> Self { + let trace_enabled = std::env::var("NYASH_TYPE_REGISTRY_TRACE") + .ok() + .as_deref() == Some("1"); + + Self { + origins: HashMap::new(), + types: HashMap::new(), + trace_log: Vec::new(), + trace_enabled, + } + } + + // ============================================================ + // 📝 記録系メソッド(明示的な型情報設定) + // ============================================================ + + /// NewBox起源を記録 + pub fn record_newbox(&mut self, vid: ValueId, class: String) { + self.origins.insert(vid, class.clone()); + + if self.trace_enabled { + let entry = TraceEntry { + vid, + source: format!("newbox:{}", class), + timestamp: self.trace_log.len(), + }; + self.trace_log.push(entry.clone()); + eprintln!("[type-registry] {} {:?}", entry.source, vid); + } + } + + /// パラメータ型を記録 + pub fn record_param(&mut self, vid: ValueId, param_name: &str, param_type: Option) { + if let Some(ty) = param_type.clone() { + self.types.insert(vid, ty.clone()); + + if self.trace_enabled { + let entry = TraceEntry { + vid, + source: format!("param:{}:{:?}", param_name, ty), + timestamp: self.trace_log.len(), + }; + self.trace_log.push(entry.clone()); + eprintln!("[type-registry] {} {:?}", entry.source, vid); + } + } else if self.trace_enabled { + let entry = TraceEntry { + vid, + source: format!("param:{}:no_type", param_name), + timestamp: self.trace_log.len(), + }; + self.trace_log.push(entry.clone()); + eprintln!("[type-registry] {} {:?}", entry.source, vid); + } + } + + /// 型注釈を明示的に設定 + pub fn record_type(&mut self, vid: ValueId, ty: MirType) { + self.types.insert(vid, ty.clone()); + + if self.trace_enabled { + let entry = TraceEntry { + vid, + source: format!("type:{:?}", ty), + timestamp: self.trace_log.len(), + }; + self.trace_log.push(entry.clone()); + eprintln!("[type-registry] {} {:?}", entry.source, vid); + } + } + + /// 起源を明示的に設定(推論結果など) + pub fn record_origin(&mut self, vid: ValueId, origin: String, reason: &str) { + self.origins.insert(vid, origin.clone()); + + if self.trace_enabled { + let entry = TraceEntry { + vid, + source: format!("{}:{}", reason, origin), + timestamp: self.trace_log.len(), + }; + self.trace_log.push(entry.clone()); + eprintln!("[type-registry] {} {:?}", entry.source, vid); + } + } + + // ============================================================ + // 🔄 伝播系メソッド(メタデータの伝播) + // ============================================================ + + /// メタデータを src から dst へ伝播 + pub fn propagate(&mut self, src: ValueId, dst: ValueId) { + let mut propagated = false; + + if let Some(cls) = self.origins.get(&src).cloned() { + self.origins.insert(dst, cls.clone()); + propagated = true; + + if self.trace_enabled { + let entry = TraceEntry { + vid: dst, + source: format!("propagate:from_%{}→{}", src.0, cls), + timestamp: self.trace_log.len(), + }; + self.trace_log.push(entry.clone()); + eprintln!("[type-registry] {} {:?}", entry.source, dst); + } + } + + if let Some(ty) = self.types.get(&src).cloned() { + self.types.insert(dst, ty.clone()); + + if self.trace_enabled && !propagated { + let entry = TraceEntry { + vid: dst, + source: format!("propagate:from_%{}→{:?}", src.0, ty), + timestamp: self.trace_log.len(), + }; + self.trace_log.push(entry.clone()); + eprintln!("[type-registry] {} {:?}", entry.source, dst); + } + } + } + + // ============================================================ + // 🔍 取得系メソッド(型情報の読み取り) + // ============================================================ + + /// 起源クラス名を取得 + pub fn get_origin(&self, vid: ValueId) -> Option<&String> { + self.origins.get(&vid) + } + + /// 型情報を取得 + pub fn get_type(&self, vid: ValueId) -> Option<&MirType> { + self.types.get(&vid) + } + + /// クラス名を推論(フォールバック戦略付き) + pub fn infer_class(&self, vid: ValueId, fallback_context: Option<&str>) -> String { + // 優先1: 起源情報から + if let Some(cls) = self.origins.get(&vid) { + return cls.clone(); + } + + // 優先2: 型注釈から + if let Some(MirType::Box(cls)) = self.types.get(&vid) { + return cls.clone(); + } + + // フォールバック: コンテキスト名(警告付き) + if let Some(ctx) = fallback_context { + if self.trace_enabled { + eprintln!("[type-registry] WARNING: fallback to context '{}' for %{}", ctx, vid.0); + } + return ctx.to_string(); + } + + // 最終フォールバック: UnknownBox + if self.trace_enabled { + eprintln!("[type-registry] WARNING: UnknownBox for %{}", vid.0); + } + "UnknownBox".to_string() + } + + // ============================================================ + // 🔍 デバッグ支援メソッド + // ============================================================ + + /// 起源追跡チェーンを取得 + pub fn trace_origin(&self, vid: ValueId) -> Vec { + self.trace_log + .iter() + .filter(|e| e.vid == vid) + .map(|e| e.source.clone()) + .collect() + } + + /// 全トレースログを表示 + pub fn dump_trace(&self) { + eprintln!("[type-registry] === Trace Log ({} entries) ===", self.trace_log.len()); + for entry in &self.trace_log { + eprintln!("[type-registry] #{:04} %{} ← {}", entry.timestamp, entry.vid.0, entry.source); + } + } + + /// 統計情報を表示 + pub fn dump_stats(&self) { + eprintln!("[type-registry] === Statistics ==="); + eprintln!("[type-registry] Origins: {} entries", self.origins.len()); + eprintln!("[type-registry] Types: {} entries", self.types.len()); + eprintln!("[type-registry] Trace log: {} entries", self.trace_log.len()); + } + + // ============================================================ + // 🧹 クリア系メソッド(BoxCompilationContext用) + // ============================================================ + + /// 起源情報のみクリア(型情報は保持) + pub fn clear_origins(&mut self) { + self.origins.clear(); + if self.trace_enabled { + eprintln!("[type-registry] cleared origins"); + } + } + + /// 全情報クリア + pub fn clear_all(&mut self) { + self.origins.clear(); + self.types.clear(); + if self.trace_enabled { + eprintln!("[type-registry] cleared all"); + } + } +} diff --git a/src/mir/definitions/call_unified.rs b/src/mir/definitions/call_unified.rs index 5c659d21..1232690a 100644 --- a/src/mir/definitions/call_unified.rs +++ b/src/mir/definitions/call_unified.rs @@ -16,6 +16,20 @@ pub enum TypeCertainty { Union, } +/// Classification of Box types to prevent static/runtime mixing +/// Prevents Stage-B/Stage-1 compiler boxes from being confused with runtime data boxes +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CalleeBoxKind { + /// Static compiler boxes (StageBArgsBox, Stage1UsingResolverBox, BundleResolver, ParserBox, etc.) + /// These are only for compile-time static method lowering + StaticCompiler, + /// Runtime data boxes (MapBox, ArrayBox, StringBox, IntegerBox, etc.) + /// These handle actual runtime values and method dispatch + RuntimeData, + /// User-defined boxes (neither compiler nor built-in runtime) + UserDefined, +} + /// Call target specification for type-safe function resolution /// Replaces runtime string-based resolution with compile-time typed targets #[derive(Debug, Clone, PartialEq)] @@ -31,6 +45,7 @@ pub enum Callee { method: String, // "upper", "print", etc. receiver: Option, // Some(obj) for instance, None for static/constructor certainty: TypeCertainty, // Phase 3: known vs union + box_kind: CalleeBoxKind, // Structural guard: prevent static/runtime mixing }, /// Constructor call (NewBox equivalent) @@ -192,6 +207,7 @@ impl MirCall { method, receiver: Some(receiver), certainty: TypeCertainty::Known, + box_kind: CalleeBoxKind::RuntimeData, // Default to runtime for helper }, args, ) @@ -307,6 +323,7 @@ pub mod migration { method, receiver: Some(box_val), certainty: TypeCertainty::Union, + box_kind: CalleeBoxKind::RuntimeData, // BoxCall is always runtime }, args, ); diff --git a/src/mir/printer_helpers.rs b/src/mir/printer_helpers.rs index 66f14eb2..7b424d39 100644 --- a/src/mir/printer_helpers.rs +++ b/src/mir/printer_helpers.rs @@ -79,7 +79,7 @@ pub fn format_instruction( super::Callee::Global(name) => { format!("call_global {}({})", name, args_str) } - super::Callee::Method { box_name, method, receiver, certainty } => { + super::Callee::Method { box_name, method, receiver, certainty, .. } => { if let Some(recv) = receiver { format!( "call_method {}.{}({}) [recv: {}] [{}]", diff --git a/src/runner/json_v1_bridge.rs b/src/runner/json_v1_bridge.rs index 8011a819..5751cbfd 100644 --- a/src/runner/json_v1_bridge.rs +++ b/src/runner/json_v1_bridge.rs @@ -412,10 +412,12 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { dst: dst_opt, func: ValueId::new(0), callee: Some(crate::mir::definitions::Callee::Method { - box_name, + box_name: box_name.clone(), method, receiver: Some(ValueId::new(recv_id)), certainty: crate::mir::definitions::call_unified::TypeCertainty::Known, + // JSON v1 bridge: assume all methods are runtime data boxes + box_kind: crate::mir::definitions::call_unified::CalleeBoxKind::RuntimeData, }), args: argv, effects, diff --git a/src/runner/mir_json_emit.rs b/src/runner/mir_json_emit.rs index c43e8835..3a4c47ce 100644 --- a/src/runner/mir_json_emit.rs +++ b/src/runner/mir_json_emit.rs @@ -116,7 +116,7 @@ fn emit_unified_mir_call( "name": name }); } - Callee::Method { box_name, method, receiver, certainty } => { + Callee::Method { box_name, method, receiver, certainty, .. } => { call_obj["mir_call"]["callee"] = json!({ "type": "Method", "box_name": box_name,