Files
hakorune/src/mir/builder/lifecycle.rs
nyash-codex 360ad48d93 feat(joinir): Phase 63-5 infer_type_from_phi degradation implementation (infrastructure)
Phase 63-5: 型ヒント優先のインターフェースを確立し、lifecycle.rs で呼び出し経路を統一

## Changes

### Core Implementation

1. **`infer_type_from_phi_with_hint()` 実装** (if_phi.rs:92-105)
   - Route B: `type_hint` があれば優先的に返す(JoinIR SSOT)
   - Route A: なければ `infer_type_from_phi()` へフォールバック
   - Fail-fast 原則遵守:既存挙動を一切変更しない

2. **lifecycle.rs 呼び出し経路統一** (2箇所)
   - lifecycle.rs:284, 303 で `infer_type_from_phi_with_hint(None, ...)` を呼び出し
   - 現時点では `type_hint=None` でフォールバック動作(既存挙動維持)
   - 将来 Phase 63-6+ で JoinIR からの型ヒント取得を実装

### Test Results

-  IfSelect 全 8 テスト PASS(test_type_hint_propagation_simple 含む)
-  JoinIR 全 57 テスト PASS
-  退行なし確認

### Documentation Updates

- **README.md**: Phase 63-5 完了セクション追加(実装内容・テスト結果・次ステップ)
- **README.md**: 削除条件チェックリスト更新(3/5 達成、60%)
- **PHI_BOX_INVENTORY.md**: if_phi.rs 行に Phase 63-5 完了マーク追加
- **CURRENT_TASK.md**: Phase 63-5 セクション追加

## Technical Achievements

- 型ヒント優先インターフェース確立
- lifecycle.rs 呼び出し経路統一
- Phase 63-6+ での段階的型ヒント供給の準備完了

## Deletion Condition Progress

**削除条件達成率**: 2/5 (40%) → **3/5 (60%)** ← Phase 63-5 完了で +20%

1.  JoinIR に `type_hint` 追加(Phase 63-3)
2.  代表ケースで `type_hint` 埋め込み(Phase 63-2)
3.  型ヒント優先に縮退(Phase 63-5)← NEW!
4.  P1 ケースで `type_hint` のみで型決定(Phase 63-6+)
5.  全関数で型ヒント化完了(Phase 64+)

## Files Changed

- src/mir/phi_core/if_phi.rs: +44行(infer_type_from_phi_with_hint() 追加)
- src/mir/builder/lifecycle.rs: 2箇所で _with_hint 呼び出しへ移行
- docs/private/roadmap2/phases/phase-63-joinir-type-info/README.md
- docs/private/roadmap2/phases/phase-30-final-joinir-world/PHI_BOX_INVENTORY.md
- CURRENT_TASK.md

## Next Steps

**Phase 63-6**: P1 ケース(IfSelectTest.simple/local)への型ヒント供給を実装
- JoinIR → MIR Bridge での型ヒント伝播
- lifecycle.rs で型ヒントを取得するパスの追加

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 18:07:38 +09:00

418 lines
20 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::{
BasicBlockId, EffectMask, FunctionSignature, MirInstruction, MirModule, MirType, ValueId,
};
use crate::ast::ASTNode;
// Lifecycle routines extracted from builder.rs
fn has_main_static(ast: &ASTNode) -> bool {
use crate::ast::ASTNode as N;
if let N::Program { statements, .. } = ast {
for st in statements {
if let N::BoxDeclaration {
name,
methods,
is_static,
..
} = st
{
if *is_static && name == "Main" {
if let Some(m) = methods.get("main") {
if let N::FunctionDeclaration { .. } = m {
return true;
}
}
}
}
}
}
false
}
impl super::MirBuilder {
/// Unified declaration indexing (Phase A): collect symbols before lowering
/// - user_defined_boxes: non-static Box names (for NewBox birth() skip)
/// - static_method_index: name -> [(BoxName, arity)] (for bare-call fallback)
fn index_declarations(&mut self, node: &ASTNode) {
match node {
ASTNode::Program { statements, .. } => {
for st in statements {
self.index_declarations(st);
}
}
ASTNode::BoxDeclaration {
name,
methods,
is_static,
..
} => {
if !*is_static {
self.user_defined_boxes.insert(name.clone());
} else {
for (mname, mast) in methods {
if let ASTNode::FunctionDeclaration { params, .. } = mast {
self.static_method_index
.entry(mname.clone())
.or_insert_with(Vec::new)
.push((name.clone(), params.len()));
}
}
}
}
_ => {}
}
}
pub(super) fn prepare_module(&mut self) -> Result<(), String> {
let mut module = MirModule::new("main".to_string());
module.metadata.source_file = self.current_source_file();
let main_signature = FunctionSignature {
name: "main".to_string(),
params: vec![],
return_type: MirType::Void,
effects: EffectMask::PURE,
};
let entry_block = self.block_gen.next();
let mut main_function = self.new_function_with_metadata(main_signature, entry_block);
main_function.metadata.is_entry_point = true;
self.current_module = Some(module);
self.current_function = Some(main_function);
self.current_block = Some(entry_block);
// 関数スコープの SlotRegistry を初期化するよ(観測専用)。
// main 関数用のスロット登録箱として使う想定だよ。
self.current_slot_registry =
Some(crate::mir::region::function_slot_registry::FunctionSlotRegistry::new());
// Region 観測レイヤ: main 関数の FunctionRegion を 1 つ作っておくよ。
crate::mir::region::observer::observe_function_region(self);
// Hint: scope enter at function entry (id=0 for main)
self.hint_scope_enter(0);
if std::env::var("NYASH_BUILDER_SAFEPOINT_ENTRY")
.ok()
.as_deref()
== Some("1")
{
self.emit_instruction(MirInstruction::Safepoint)?;
}
Ok(())
}
pub(super) fn lower_root(&mut self, ast: ASTNode) -> Result<ValueId, String> {
// Pre-index static methods to enable safe fallback for bare calls in using-prepended code
let snapshot = ast.clone();
// Phase A: collect declarations in one pass (symbols available to lowering)
self.index_declarations(&snapshot);
// Decide root mode (App vs Script) once per module based on presence of static box Main.main
// true => App mode (Main.main is entry)
// false => Script/Test mode (top-level Program runs sequentially)
let is_app_mode = self
.root_is_app_mode
.unwrap_or_else(|| has_main_static(&snapshot));
self.root_is_app_mode = Some(is_app_mode);
// Phase B: top-level program lowering with declaration-first pass
match ast {
ASTNode::Program { statements, .. } => {
use crate::ast::ASTNode as N;
// First pass: lower declarations (static boxes except Main, and instance boxes)
let mut main_static: Option<(String, std::collections::HashMap<String, ASTNode>)> =
None;
for st in &statements {
if let N::BoxDeclaration {
name,
methods,
is_static,
fields,
constructors,
weak_fields,
..
} = st
{
if *is_static {
if name == "Main" {
main_static = Some((name.clone(), methods.clone()));
} else {
// Script/Test モードでは static box の lowering は exprs.rs 側に任せる
if is_app_mode {
// Dev: trace which static box is being lowered (env-gated)
self.trace_compile(format!("lower static box {}", name));
// 🎯 箱理論: 各static boxに専用のコンパイルコンテキストを作成
// これにより、using文や前のboxからのメタデータ汚染を構造的に防止
// スコープを抜けると自動的にコンテキストが破棄される
{
let ctx = super::context::BoxCompilationContext::new();
self.compilation_context = Some(ctx);
// Lower all static methods into standalone functions: BoxName.method/Arity
for (mname, mast) in methods.iter() {
if let N::FunctionDeclaration { params, body, .. } =
mast
{
let func_name = format!(
"{}.{}{}",
name,
mname,
format!("/{}", params.len())
);
self.lower_static_method_as_function(
func_name,
params.clone(),
body.clone(),
)?;
self.static_method_index
.entry(mname.clone())
.or_insert_with(Vec::new)
.push((name.clone(), params.len()));
}
}
}
// 🎯 箱理論: コンテキストをクリア(スコープ終了で自動破棄)
// これにより、次のstatic boxは汚染されていない状態から開始される
self.compilation_context = None;
}
}
} else {
// Instance box: register type and lower instance methods/ctors as functions
self.user_defined_boxes.insert(name.clone());
self.build_box_declaration(
name.clone(),
methods.clone(),
fields.clone(),
weak_fields.clone(),
)?;
for (ctor_key, ctor_ast) in constructors.iter() {
if let N::FunctionDeclaration { params, body, .. } = ctor_ast {
// Keep constructor function name as "Box.birth/N" where ctor_key already encodes arity.
// ctor_key format comes from parser as "birth/<arity>".
let func_name = format!("{}.{}", name, ctor_key);
self.lower_method_as_function(
func_name,
name.clone(),
params.clone(),
body.clone(),
)?;
}
}
for (mname, mast) in methods.iter() {
if let N::FunctionDeclaration {
params,
body,
is_static,
..
} = mast
{
if !*is_static {
let func_name = format!(
"{}.{}{}",
name,
mname,
format!("/{}", params.len())
);
self.lower_method_as_function(
func_name,
name.clone(),
params.clone(),
body.clone(),
)?;
}
}
}
}
}
}
// Second pass: mode-dependent entry lowering
if is_app_mode {
// App モード: Main.main をエントリとして扱う
if let Some((box_name, methods)) = main_static {
self.build_static_main_box(box_name, methods)
} else {
// 理論上は起こりにくいが、安全のため Script モードと同じフォールバックにする
self.cf_block(statements)
}
} else {
// Script/Test モード: トップレベル Program をそのまま順次実行
self.cf_block(statements)
}
}
other => self.build_expression(other),
}
}
pub(super) fn finalize_module(&mut self, result_value: ValueId) -> Result<MirModule, String> {
// Hint: scope leave at function end (id=0 for main)
self.hint_scope_leave(0);
if let Some(block_id) = self.current_block {
if let Some(ref mut function) = self.current_function {
if let Some(block) = function.get_block_mut(block_id) {
if !block.is_terminated() {
block.add_instruction(MirInstruction::Return {
value: Some(result_value),
});
}
if let Some(mt) = self.value_types.get(&result_value).cloned() {
function.signature.return_type = mt;
}
}
}
}
let mut module = self.current_module.take().unwrap();
let mut function = self.current_function.take().unwrap();
function.metadata.value_types = self.value_types.clone();
if matches!(
function.signature.return_type,
super::MirType::Void | super::MirType::Unknown
) {
let mut inferred: Option<super::MirType> = None;
'outer: for (_bid, bb) in function.blocks.iter() {
for inst in bb.instructions.iter() {
if let super::MirInstruction::Return { value: Some(v) } = inst {
if let Some(mt) = self.value_types.get(v).cloned() {
inferred = Some(mt);
break 'outer;
}
// Phase 63-5: JoinIR 型ヒント優先への縮退
// TODO P1: IfSelectTest.simple/local から型ヒントを取得
// 現時点では type_hint=None でフォールバック動作(既存挙動維持)
if let Some(mt) = crate::mir::phi_core::if_phi::infer_type_from_phi_with_hint(
None, // Phase 63-5: P1 ケースの型ヒント取得は Phase 63-5-2 で実装
&function,
*v,
&self.value_types,
) {
inferred = Some(mt);
break 'outer;
}
}
}
if let Some(super::MirInstruction::Return { value: Some(v) }) = &bb.terminator {
if let Some(mt) = self.value_types.get(v).cloned() {
inferred = Some(mt);
break;
}
// Phase 63-5: JoinIR 型ヒント優先への縮退
// TODO P1: IfSelectTest.simple/local から型ヒントを取得
// 現時点では type_hint=None でフォールバック動作(既存挙動維持)
if let Some(mt) = crate::mir::phi_core::if_phi::infer_type_from_phi_with_hint(
None, // Phase 63-5: P1 ケースの型ヒント取得は Phase 63-5-2 で実装
&function,
*v,
&self.value_types,
) {
inferred = Some(mt);
break;
}
}
}
if let Some(mt) = inferred {
function.signature.return_type = mt;
}
}
// Dev-only verify: NewBox → birth() invariant (warn if missing)
if crate::config::env::using_is_dev() {
let mut warn_count = 0usize;
for (_bid, bb) in function.blocks.iter() {
let insns = &bb.instructions;
let mut idx = 0usize;
while idx < insns.len() {
if let MirInstruction::NewBox {
dst,
box_type,
args,
} = &insns[idx]
{
// Skip StringBox (literal optimization path)
if box_type != "StringBox" {
let expect_tail = format!("{}.birth/{}", box_type, args.len());
// Look ahead up to 3 instructions for either BoxCall("birth") on dst or Global(expect_tail)
let mut ok = false;
let mut j = idx + 1;
let mut last_const_name: Option<String> = None;
while j < insns.len() && j <= idx + 3 {
match &insns[j] {
MirInstruction::BoxCall {
box_val, method, ..
} => {
if method == "birth" && box_val == dst {
ok = true;
break;
}
}
MirInstruction::Const { value, .. } => {
if let super::ConstValue::String(s) = value {
last_const_name = Some(s.clone());
}
}
MirInstruction::Call { func: _, .. } => {
// If immediately preceded by matching Const String, accept
if let Some(prev) = last_const_name.as_ref() {
if prev == &expect_tail {
ok = true;
break;
}
}
// Heuristic: in some forms, builder may reuse a shared const; best-effort only
}
_ => {}
}
j += 1;
}
if !ok {
eprintln!("[warn] dev verify: NewBox {} at v{} not followed by birth() call (expect {})", box_type, dst, expect_tail);
warn_count += 1;
}
}
}
idx += 1;
}
}
if warn_count > 0 {
eprintln!(
"[warn] dev verify: NewBox→birth invariant warnings: {}",
warn_count
);
}
}
module.add_function(function);
// Dev stub: provide condition_fn when missing to satisfy predicate calls in JSON lexers
// Returns integer 1 (truthy) and accepts one argument (unused).
//
// NOTE:
// - MirFunction::new() はシグネチャの params に応じて
// [ValueId(0)..ValueId(param_count-1)] を事前に予約する。
// - ここでは追加の next_value_id()/params.push() は行わず、
// 予約済みのパラメータ集合をそのまま使う。
if module.functions.get("condition_fn").is_none() {
let sig = FunctionSignature {
name: "condition_fn".to_string(),
params: vec![MirType::Integer], // accept one i64-like arg
return_type: MirType::Integer,
effects: EffectMask::PURE,
};
let entry = BasicBlockId::new(0);
let mut f = self.new_function_with_metadata(sig, entry);
// body: const 1; return itFunctionEmissionBox を使用)
let one = crate::mir::function_emission::emit_const_integer(&mut f, entry, 1);
crate::mir::function_emission::emit_return_value(&mut f, entry, one);
module.add_function(f);
}
// main 関数スコープの Region スタックをポップするよ。
crate::mir::region::observer::pop_function_region(self);
// main 関数スコープの SlotRegistry を解放するよ。
self.current_slot_registry = None;
Ok(module)
}
}