feat(mir): Phase 131-11-E - TypeFacts/TypeDemands 分離(SSOT)
## 実装内容
### 1) Rust MIR Builder (ops.rs + lifecycle.rs)
- OperandTypeClass で型分類(String/Integer/Unknown)
- BinOp 型推論: Integer + Unknown → Integer
- lifecycle.rs に repropagate_binop_types() パス追加
- PHI 型解決後に BinOp 型を再計算
### 2) JSON Emission (mir_json_emit.rs)
- 結果ベースの dst_type 発行に変更
- Integer → "i64", String → {kind: handle, box_type: StringBox}
### 3) Python LLVM Backend (binop.py)
- dst_type を確実な情報として使用
- dst_type != None なら優先して処理
## 結果
- ✅ MIR: PHI/BinOp が Integer として正しく型付け
- ✅ VM: `Result: 3` (正しい出力)
- ✅ JSON: `dst_type: "i64"` を発行
- ❓ LLVM: 別の codegen 問題の可能性あり
## SSOT 設計達成
- TypeFacts(事実): 定義命令から推論
- TypeDemands(要求): 使用箇所の coercion で吸収
- 後方伝播なし: Fail-Fast に統一
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -385,8 +385,12 @@ impl super::MirBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 131-11-E: BinOp type re-propagation after PHI resolution
|
||||
// After PHI types are corrected, re-infer BinOp result types
|
||||
self.repropagate_binop_types(&mut function);
|
||||
|
||||
// Phase 131-9: Update function metadata with corrected types
|
||||
// MUST happen after PHI type correction above
|
||||
// MUST happen after PHI type correction above AND BinOp re-propagation
|
||||
function.metadata.value_types = self.value_types.clone();
|
||||
|
||||
// Phase 82-5: lifecycle.rs バグ修正 - terminator の Return のみをチェック
|
||||
@ -595,4 +599,83 @@ impl super::MirBuilder {
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
// Phase 131-11-E: Re-propagate BinOp result types after PHI resolution
|
||||
// This fixes cases where BinOp instructions were created before PHI types were known
|
||||
fn repropagate_binop_types(&mut self, function: &mut super::MirFunction) {
|
||||
use crate::mir::MirInstruction;
|
||||
use crate::mir::MirType;
|
||||
|
||||
let mut binop_updates: Vec<(super::ValueId, MirType)> = Vec::new();
|
||||
|
||||
for (_bid, bb) in function.blocks.iter() {
|
||||
for inst in bb.instructions.iter() {
|
||||
if let MirInstruction::BinOp { dst, op, lhs, rhs } = inst {
|
||||
// Only handle Add operations (string concat vs numeric addition)
|
||||
if matches!(op, crate::mir::BinaryOp::Add) {
|
||||
// Get current lhs/rhs types after PHI resolution
|
||||
let lhs_type = self.value_types.get(lhs);
|
||||
let rhs_type = self.value_types.get(rhs);
|
||||
|
||||
// Classify types
|
||||
let lhs_class = match lhs_type {
|
||||
Some(MirType::String) => OperandTypeClass::String,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => OperandTypeClass::String,
|
||||
Some(MirType::Integer) => OperandTypeClass::Integer,
|
||||
Some(MirType::Bool) => OperandTypeClass::Integer,
|
||||
_ => OperandTypeClass::Unknown,
|
||||
};
|
||||
let rhs_class = match rhs_type {
|
||||
Some(MirType::String) => OperandTypeClass::String,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => OperandTypeClass::String,
|
||||
Some(MirType::Integer) => OperandTypeClass::Integer,
|
||||
Some(MirType::Bool) => OperandTypeClass::Integer,
|
||||
_ => OperandTypeClass::Unknown,
|
||||
};
|
||||
|
||||
use OperandTypeClass::*;
|
||||
let new_type = match (lhs_class, rhs_class) {
|
||||
(String, String) => Some(MirType::Box("StringBox".to_string())),
|
||||
(Integer, Integer) | (Integer, Unknown) | (Unknown, Integer) => {
|
||||
Some(MirType::Integer)
|
||||
}
|
||||
_ => None, // Keep Unknown for mixed/unclear cases
|
||||
};
|
||||
|
||||
if let Some(new_ty) = new_type {
|
||||
// Check if type is missing or different
|
||||
let current_type = self.value_types.get(dst);
|
||||
if current_type.is_none() || current_type != Some(&new_ty) {
|
||||
binop_updates.push((*dst, new_ty));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other arithmetic ops: always Integer
|
||||
if !self.value_types.contains_key(dst) {
|
||||
binop_updates.push((*dst, MirType::Integer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply updates
|
||||
for (dst, ty) in binop_updates {
|
||||
if std::env::var("NYASH_BINOP_REPROP_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[binop-reprop] {} updated {:?} -> {:?}",
|
||||
function.signature.name, dst, ty
|
||||
);
|
||||
}
|
||||
self.value_types.insert(dst, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 131-11-E: OperandTypeClass for BinOp type inference
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum OperandTypeClass {
|
||||
String,
|
||||
Integer,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@ -10,7 +10,40 @@ enum BinaryOpType {
|
||||
Comparison(CompareOp),
|
||||
}
|
||||
|
||||
// Phase 131-11-E: TypeFacts - operand type classification
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum OperandTypeClass {
|
||||
String,
|
||||
Integer,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl super::MirBuilder {
|
||||
// Phase 131-11-E: TypeFacts - classify operand type for BinOp inference
|
||||
fn classify_operand_type(&self, vid: ValueId) -> OperandTypeClass {
|
||||
let result = match self.value_types.get(&vid) {
|
||||
Some(MirType::String) => OperandTypeClass::String,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => OperandTypeClass::String,
|
||||
Some(MirType::Integer) => OperandTypeClass::Integer,
|
||||
Some(MirType::Bool) => OperandTypeClass::Integer, // Bool can be used as integer
|
||||
_ => {
|
||||
// Check value_origin_newbox for StringBox
|
||||
if self
|
||||
.value_origin_newbox
|
||||
.get(&vid)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return OperandTypeClass::String;
|
||||
}
|
||||
OperandTypeClass::Unknown
|
||||
}
|
||||
};
|
||||
if std::env::var("NYASH_TYPEFACTS_DEBUG").is_ok() {
|
||||
eprintln!("[typefacts] classify {:?} -> {:?}", vid, result);
|
||||
}
|
||||
result
|
||||
}
|
||||
// Build a binary operation
|
||||
pub(super) fn build_binary_op(
|
||||
&mut self,
|
||||
@ -68,35 +101,33 @@ impl super::MirBuilder {
|
||||
vec![lhs, rhs],
|
||||
)?;
|
||||
// Phase 196: TypeFacts SSOT - AddOperator call type annotation
|
||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&lhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
let rhs_is_str = match self.value_types.get(&rhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&rhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if lhs_is_str && rhs_is_str {
|
||||
// BOTH are strings: result is string
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
} else if !lhs_is_str && !rhs_is_str {
|
||||
// NEITHER is a string: numeric addition
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
// Phase 131-11-E: TypeFacts - classify operand types
|
||||
let lhs_type = self.classify_operand_type(lhs);
|
||||
let rhs_type = self.classify_operand_type(rhs);
|
||||
|
||||
use OperandTypeClass::*;
|
||||
match (lhs_type, rhs_type) {
|
||||
(String, String) => {
|
||||
// BOTH are strings: result is string
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
}
|
||||
(Integer, Integer) | (Integer, Unknown) | (Unknown, Integer) => {
|
||||
// TypeFact: Integer + anything non-String = Integer
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
(String, Integer) | (Integer, String) => {
|
||||
// Mixed types: leave as Unknown for use-site coercion
|
||||
}
|
||||
(Unknown, Unknown) => {
|
||||
// Both Unknown: cannot infer
|
||||
}
|
||||
(String, Unknown) | (Unknown, String) => {
|
||||
// One side is String, other is Unknown: cannot infer safely
|
||||
}
|
||||
}
|
||||
// else: Mixed - leave Unknown for use-site coercion
|
||||
} else if all_call {
|
||||
// Lower other arithmetic ops to operator boxes under ALL flag
|
||||
let (name, guard_prefix) = match op {
|
||||
@ -158,37 +189,36 @@ impl super::MirBuilder {
|
||||
// Phase 196: TypeFacts SSOT - BinOp type is determined by operands only
|
||||
// String concatenation is handled at use-site in LLVM lowering
|
||||
if matches!(op, crate::mir::BinaryOp::Add) {
|
||||
// Check if BOTH operands are known to be strings (TypeFacts)
|
||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&lhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
let rhs_is_str = match self.value_types.get(&rhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&rhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if lhs_is_str && rhs_is_str {
|
||||
// BOTH are strings: result is definitely a string
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
} else if !lhs_is_str && !rhs_is_str {
|
||||
// NEITHER is a string: numeric addition
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
// Phase 131-11-E: TypeFacts - classify operand types
|
||||
let lhs_type = self.classify_operand_type(lhs);
|
||||
let rhs_type = self.classify_operand_type(rhs);
|
||||
|
||||
use OperandTypeClass::*;
|
||||
match (lhs_type, rhs_type) {
|
||||
(String, String) => {
|
||||
// BOTH are strings: result is definitely a string
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
}
|
||||
(Integer, Integer) | (Integer, Unknown) | (Unknown, Integer) => {
|
||||
// TypeFact: Integer + anything non-String = Integer
|
||||
// This handles `counter + 1` where counter might be Unknown
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
(String, Integer) | (Integer, String) => {
|
||||
// Mixed types: leave as Unknown for use-site coercion
|
||||
// LLVM backend will handle string concatenation
|
||||
}
|
||||
(Unknown, Unknown) => {
|
||||
// Both Unknown: cannot infer, leave as Unknown
|
||||
}
|
||||
(String, Unknown) | (Unknown, String) => {
|
||||
// One side is String, other is Unknown: cannot infer safely
|
||||
// Leave as Unknown
|
||||
}
|
||||
}
|
||||
// else: Mixed types (string + int or int + string)
|
||||
// Leave dst type as Unknown - LLVM will handle coercion at use-site
|
||||
} else {
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user