fix(mir): Phase 25.1 StaticCompiler receiver型推論バグ根治
## 問題 Stage-1ブリッジで `StringHelpers.skip_ws/2` 呼び出し時に: - ParserBox.length(receiver=ValueId未定義) でVM落ち - receiver が捏造される(型情報なし) ## 根本原因 1. CalleeResolver: receiver型なし→捏造ValueId生成 2. 順序問題: materialization → guard(逆であるべき) 3. 型注釈不足: String定数・BinOp結果に型情報なし ## 解決策(3 Phase) ### Phase 1: guard.rs Global フォールバック (lines 100-146) - receiver型なし→Global変換で捏造ValueId防止 - Phase 3-C: 文字列メソッド→StringBox正規化追加 ### Phase 2: unified_emitter.rs 順序反転 (lines 176-188) - guard FIRST → materialization (Method のみ) - Global 呼び出しの receiver 実体化を回避 ### Phase 3-A: emit_string 型注釈 (constant.rs:46-49) - String定数に value_types + value_origin_newbox 登録 ### Phase 3-B: BinOp(Add) 型注釈強化 (ops.rs:70-86,145-166,178-198) - value_origin_newbox もチェック(value_types のみ→両方) - 結果も両方のマップに登録 ### Phase 3-C: StaticCompiler 文字列メソッド正規化 (guard.rs:106-129) - length/substring等→StringBox.method に統一 - ParserBox.length → StringBox.length ## 検証 ✅ RC=0 達成(apps/tests/stage1_skip_ws_repro.hako) ✅ 正規化トレース確認(ParserBox→StringBox) ✅ receiver捏造完全防止 Co-Authored-By: ChatGPT5 <chatgpt@openai.com>
This commit is contained in:
@ -99,18 +99,50 @@ impl<'a> CalleeGuardBox<'a> {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// StaticCompiler Method but receiver has NO Box type
|
// StaticCompiler Method but receiver has NO Box type
|
||||||
// → Pass through as ME-CALL (let downstream handle it)
|
// 🎯 Phase 3-C: Normalize StaticCompiler string methods to StringBox
|
||||||
if trace_enabled {
|
//
|
||||||
eprintln!("[static-runtime-guard] StaticCompiler receiver has NO Box type:");
|
// Common string methods that should be routed to StringBox:
|
||||||
eprintln!(
|
// length, substring, charAt, indexOf, etc.
|
||||||
" {}.{} receiver %{} has no type info",
|
let is_string_method = matches!(
|
||||||
box_name, method, recv.0
|
method.as_str(),
|
||||||
);
|
"length" | "substring" | "charAt" | "indexOf" | "lastIndexOf"
|
||||||
eprintln!(" → Passing through as ME-CALL (downstream will resolve)");
|
| "toUpperCase" | "toLowerCase" | "trim" | "split"
|
||||||
}
|
);
|
||||||
|
|
||||||
// Pass through unchanged - let finalize_method_receiver() handle it
|
if is_string_method {
|
||||||
return Ok(callee);
|
if trace_enabled {
|
||||||
|
eprintln!("[static-runtime-guard] StaticCompiler string method normalization:");
|
||||||
|
eprintln!(
|
||||||
|
" {}.{} receiver %{} has no type info",
|
||||||
|
box_name, method, recv.0
|
||||||
|
);
|
||||||
|
eprintln!(" → Normalize: StringBox.{}", method);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize to StringBox method call
|
||||||
|
return Ok(Callee::Method {
|
||||||
|
box_name: "StringBox".to_string(),
|
||||||
|
method: method.clone(),
|
||||||
|
receiver: Some(recv),
|
||||||
|
certainty,
|
||||||
|
box_kind: CalleeBoxKind::RuntimeData,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Non-string methods: fallback to Global call
|
||||||
|
if trace_enabled {
|
||||||
|
eprintln!("[static-runtime-guard] StaticCompiler receiver has NO Box type:");
|
||||||
|
eprintln!(
|
||||||
|
" {}.{} receiver %{} has no type info",
|
||||||
|
box_name, method, recv.0
|
||||||
|
);
|
||||||
|
eprintln!(" → Fallback: Global({}.{})", box_name, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
// receiver 型情報がない場合は Global 呼び出しに降格
|
||||||
|
// 例: StageBArgsBox.get(receiver=%123) で receiver 型不明
|
||||||
|
// → "StageBArgsBox.get" (receiver なし) へ変換
|
||||||
|
return Ok(Callee::Global(format!("{}.{}", box_name, method)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -173,16 +173,20 @@ impl UnifiedCallEmitterBox {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Safety: ensure receiver is materialized even after callee conversion (via CallMaterializerBox)
|
// Structural guard FIRST: prevent static compiler boxes from being called with runtime receivers
|
||||||
callee = super::materializer::CallMaterializerBox::materialize_receiver_in_callee(
|
|
||||||
builder, callee,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Structural guard: prevent static compiler boxes from being called with runtime receivers
|
|
||||||
// 箱理論: CalleeGuardBox による構造的分離
|
// 箱理論: CalleeGuardBox による構造的分離
|
||||||
|
// (Guard may convert Method → Global, so we check BEFORE materializing receiver)
|
||||||
let guard = super::guard::CalleeGuardBox::new(&builder.value_types);
|
let guard = super::guard::CalleeGuardBox::new(&builder.value_types);
|
||||||
callee = guard.apply_static_runtime_guard(callee)?;
|
callee = guard.apply_static_runtime_guard(callee)?;
|
||||||
|
|
||||||
|
// Safety: ensure receiver is materialized ONLY for Method calls
|
||||||
|
// (Global calls don't have receivers, so skip materialization)
|
||||||
|
if matches!(callee, Callee::Method { .. }) {
|
||||||
|
callee = super::materializer::CallMaterializerBox::materialize_receiver_in_callee(
|
||||||
|
builder, callee,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Emit resolve.choose for method callee (dev-only; default OFF)
|
// Emit resolve.choose for method callee (dev-only; default OFF)
|
||||||
if let Callee::Method {
|
if let Callee::Method {
|
||||||
box_name,
|
box_name,
|
||||||
|
|||||||
@ -43,6 +43,10 @@ pub fn emit_string<S: Into<String>>(b: &mut MirBuilder, s: S) -> ValueId {
|
|||||||
dst,
|
dst,
|
||||||
value: ConstValue::String(s.into()),
|
value: ConstValue::String(s.into()),
|
||||||
});
|
});
|
||||||
|
// 🎯 Phase 3-A: String constant type annotation
|
||||||
|
// Ensures string constants have proper Box type for method resolution
|
||||||
|
b.value_types.insert(dst, crate::mir::MirType::Box("StringBox".to_string()));
|
||||||
|
b.value_origin_newbox.insert(dst, "StringBox".to_string());
|
||||||
dst
|
dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -67,19 +67,20 @@ impl super::MirBuilder {
|
|||||||
super::builder_calls::CallTarget::Global(name),
|
super::builder_calls::CallTarget::Global(name),
|
||||||
vec![lhs, rhs],
|
vec![lhs, rhs],
|
||||||
)?;
|
)?;
|
||||||
// 型注釈(従来と同等)
|
// 型注釈(Phase 3-B: value_origin_newbox もチェック&両方登録)
|
||||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||||
Some(MirType::String) => true,
|
Some(MirType::String) => true,
|
||||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||||
_ => false,
|
_ => self.value_origin_newbox.get(&lhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||||
};
|
};
|
||||||
let rhs_is_str = match self.value_types.get(&rhs) {
|
let rhs_is_str = match self.value_types.get(&rhs) {
|
||||||
Some(MirType::String) => true,
|
Some(MirType::String) => true,
|
||||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||||
_ => false,
|
_ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||||
};
|
};
|
||||||
if lhs_is_str || rhs_is_str {
|
if lhs_is_str || rhs_is_str {
|
||||||
self.value_types.insert(dst, MirType::String);
|
self.value_types.insert(dst, MirType::Box("StringBox".to_string()));
|
||||||
|
self.value_origin_newbox.insert(dst, "StringBox".to_string());
|
||||||
} else {
|
} else {
|
||||||
self.value_types.insert(dst, MirType::Integer);
|
self.value_types.insert(dst, MirType::Integer);
|
||||||
}
|
}
|
||||||
@ -142,18 +143,20 @@ impl super::MirBuilder {
|
|||||||
self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?;
|
self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?;
|
||||||
}
|
}
|
||||||
if matches!(op, crate::mir::BinaryOp::Add) {
|
if matches!(op, crate::mir::BinaryOp::Add) {
|
||||||
|
// Phase 3-B: value_origin_newbox もチェック&両方登録
|
||||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||||
Some(MirType::String) => true,
|
Some(MirType::String) => true,
|
||||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||||
_ => false,
|
_ => self.value_origin_newbox.get(&lhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||||
};
|
};
|
||||||
let rhs_is_str = match self.value_types.get(&rhs) {
|
let rhs_is_str = match self.value_types.get(&rhs) {
|
||||||
Some(MirType::String) => true,
|
Some(MirType::String) => true,
|
||||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||||
_ => false,
|
_ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||||
};
|
};
|
||||||
if lhs_is_str || rhs_is_str {
|
if lhs_is_str || rhs_is_str {
|
||||||
self.value_types.insert(dst, MirType::String);
|
self.value_types.insert(dst, MirType::Box("StringBox".to_string()));
|
||||||
|
self.value_origin_newbox.insert(dst, "StringBox".to_string());
|
||||||
} else {
|
} else {
|
||||||
self.value_types.insert(dst, MirType::Integer);
|
self.value_types.insert(dst, MirType::Integer);
|
||||||
}
|
}
|
||||||
@ -173,18 +176,20 @@ impl super::MirBuilder {
|
|||||||
self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?;
|
self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?;
|
||||||
}
|
}
|
||||||
if matches!(op, crate::mir::BinaryOp::Add) {
|
if matches!(op, crate::mir::BinaryOp::Add) {
|
||||||
|
// Phase 3-B: value_origin_newbox もチェック&両方登録
|
||||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||||
Some(MirType::String) => true,
|
Some(MirType::String) => true,
|
||||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||||
_ => false,
|
_ => self.value_origin_newbox.get(&lhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||||
};
|
};
|
||||||
let rhs_is_str = match self.value_types.get(&rhs) {
|
let rhs_is_str = match self.value_types.get(&rhs) {
|
||||||
Some(MirType::String) => true,
|
Some(MirType::String) => true,
|
||||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||||
_ => false,
|
_ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||||
};
|
};
|
||||||
if lhs_is_str || rhs_is_str {
|
if lhs_is_str || rhs_is_str {
|
||||||
self.value_types.insert(dst, MirType::String);
|
self.value_types.insert(dst, MirType::Box("StringBox".to_string()));
|
||||||
|
self.value_origin_newbox.insert(dst, "StringBox".to_string());
|
||||||
} else {
|
} else {
|
||||||
self.value_types.insert(dst, MirType::Integer);
|
self.value_types.insert(dst, MirType::Integer);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user