Files
hakorune/src/mir/builder/lifecycle.rs
nyash-codex dc70d0de1b feat(mir): Phase 63-6-3/4/5 P1 type hint完全実装 & 削除条件4/5達成
Phase 63-6-3: lifecycle.rs で型ヒント取得・使用
- `get_phi_type_hint()` ヘルパー関数追加(lifecycle.rs:44-60)
- P1 ケース(IfSelectTest.*)限定で PHI の type_hint を取得
- lifecycle.rs:313-316, 335-338 で型ヒント使用
- 関数名フィルタでガード、他は None(既存挙動維持)

Phase 63-6-4: P1 ケーステスト追加(A/B 検証)
- `test_p1_ab_type_inference()` 追加(mir_joinir_if_select.rs:684-721)
- Route B(JoinIR 型ヒント経由)の動作確認
- Select type_hint = Some(Integer) 検証
- P1 function name filter 検証

Phase 63-6-5: ドキュメント更新(削除条件 4/5 達成)
- Phase 63 README.md 更新:削除条件 4/5 を  完了に
- 達成率 3/5(60%)→ 4/5(80%)に更新
- Phase 63-6 完了セクション追加(実装内容・成果・ファイル一覧)
- CURRENT_TASK.md に Phase 63-6 完了記録追加

削減実績: 0行(段階的拡大のため削除なし)

**削除条件達成率: 4/5(80%)← Phase 63-6 完了で +20%**

技術的成果:
- **P1 ケースで JoinIR 型ヒントのみで型決定(削除条件 4/5 達成)**
- JoinIR が If 系 PHI の型情報 SSOT として機能確立
- lifecycle.rs が型ヒント優先で推論する基盤完成
- Select → PHI → lifecycle.rs の全経路が動作

Modified files:
- src/mir/builder/lifecycle.rs: get_phi_type_hint() 追加、P1 型ヒント使用
- src/tests/mir_joinir_if_select.rs: A/B テスト追加
- CURRENT_TASK.md: Phase 63-6 完了記録
- docs/private/roadmap2/phases/phase-63-joinir-type-info/README.md: 削除条件更新

Test results:
-  test_p1_ab_type_inference: PASS
-  test_if_select_pattern_matching: PASS
-  All if_select tests: 8/8 PASS

次のステップ: Phase 64 で P2/P3 ケースへ拡大、全関数で型ヒント化完了(削除条件 5/5)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 04:45:11 +09:00

455 lines
21 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
}
/// Phase 63-6-3: PHI命令から型ヒントを取得
///
/// ValueId が PHI 命令の結果である場合、その type_hint を返す。
/// P1 ケースIfSelectTest.simple/localでのみ使用を想定。
///
/// # Arguments
///
/// * `function` - 対象のMIR関数
/// * `value_id` - 型ヒントを取得したいValueId
///
/// # Returns
///
/// PHI命令に type_hint があれば Some(MirType)、なければ None
fn get_phi_type_hint(
function: &super::MirFunction,
value_id: ValueId,
) -> Option<MirType> {
// 全ブロックを走査してPHI命令を探す
for (_block_id, block) in function.blocks.iter() {
for inst in block.instructions.iter() {
if let MirInstruction::Phi { dst, type_hint, .. } = inst {
if *dst == value_id {
// Phase 63-6-3: PHI の type_hint をそのまま返す
return type_hint.clone();
}
}
}
}
None
}
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-6-3: P1 ケースIfSelectTest.*)で型ヒント使用
let hint = if function.signature.name.starts_with("IfSelectTest.") {
get_phi_type_hint(&function, *v)
} else {
None
};
if let Some(mt) = crate::mir::phi_core::if_phi::infer_type_from_phi_with_hint(
hint, // Phase 63-6-3: P1 の場合は PHI の type_hint、それ以外は None
&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-6-3: P1 ケースIfSelectTest.*)で型ヒント使用
let hint = if function.signature.name.starts_with("IfSelectTest.") {
get_phi_type_hint(&function, *v)
} else {
None
};
if let Some(mt) = crate::mir::phi_core::if_phi::infer_type_from_phi_with_hint(
hint, // Phase 63-6-3: P1 の場合は PHI の type_hint、それ以外は None
&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)
}
}