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>
418 lines
20 KiB
Rust
418 lines
20 KiB
Rust
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 it(FunctionEmissionBox を使用)
|
||
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)
|
||
}
|
||
}
|