feat(joinir): Phase 40-4.1 delete collect_assigned_vars (35 lines)
Breaking: collect_assigned_vars function removed from if_phi.rs Changes: - Delete collect_assigned_vars() function (35 lines) - Make JoinIR route the default in loop_builder.rs - Rewrite collect_assigned_vars_via_joinir() with ast_to_json support - Now detects both Local declarations and Assignment nodes - Add extract_vars_from_json_stmts/stmt helpers - Update tests to use new implementation - phase40_joinir_detects_local_declarations - phase40_joinir_nested_if_local Test results: 407 passed, 11 failed (same as before, no regression) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -1061,31 +1061,12 @@ impl<'a> LoopBuilder<'a> {
|
||||
.debug_push_region(format!("join#{}", join_id) + "/join");
|
||||
|
||||
// Phase 25.1: HashSet → BTreeSet(決定性確保)
|
||||
// Phase 40-3.5: JoinIR route switching
|
||||
// Phase 40-4.1: JoinIR経路をデフォルト化(collect_assigned_vars削除)
|
||||
let vars: std::collections::BTreeSet<String> =
|
||||
if crate::config::env::use_joinir_for_array_filter() {
|
||||
// Route B: JoinIR Frontend経由
|
||||
crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(
|
||||
&then_body,
|
||||
else_body.as_ref(),
|
||||
)
|
||||
} else {
|
||||
// Route A: Legacy AST→MIR経由
|
||||
let mut vars = std::collections::BTreeSet::new();
|
||||
let then_prog = ASTNode::Program {
|
||||
statements: then_body.clone(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
crate::mir::phi_core::if_phi::collect_assigned_vars(&then_prog, &mut vars);
|
||||
if let Some(es) = &else_body {
|
||||
let else_prog = ASTNode::Program {
|
||||
statements: es.clone(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
crate::mir::phi_core::if_phi::collect_assigned_vars(&else_prog, &mut vars);
|
||||
}
|
||||
vars
|
||||
};
|
||||
crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(
|
||||
&then_body,
|
||||
else_body.as_ref(),
|
||||
);
|
||||
|
||||
// Phase 26-E: PhiBuilderBox 統合
|
||||
// Ops構造体: PhiMergeOps(Legacy)と PhiBuilderOps(新)の両対応
|
||||
|
||||
@ -16,13 +16,13 @@
|
||||
*
|
||||
* **if_phi.rs**: 315行 → 225行(28.6%削減達成)
|
||||
*
|
||||
* ## 🔜 Phase 40で削除予定(Level 2、115行)
|
||||
* ## ✅ Phase 40-4.1で削除済み(Level 2-A、35行)
|
||||
*
|
||||
* - `collect_assigned_vars` (32行)
|
||||
* - **削除条件**: array_ext.filter実装完了(if-in-loop AST lowering)
|
||||
* - **置換**: JoinIR Frontend `extract_if_in_loop_modified_vars()`
|
||||
* - **callsites**: loop_builder.rs:1069, 1075
|
||||
* - **Phase**: Phase 40-1
|
||||
* - `collect_assigned_vars` (35行) → **削除完了 2025-11-28**
|
||||
* - **置換**: `collect_assigned_vars_via_joinir()` (JoinIR Frontend経由)
|
||||
* - **理由**: A/Bテストで退行なし確認、むしろ2テスト改善
|
||||
*
|
||||
* ## 🔜 Phase 40で削除予定(Level 2-B、80行)
|
||||
*
|
||||
* - `compute_modified_names` (26行)
|
||||
* - **削除条件**: JoinIR Verifier conservative migration完了
|
||||
@ -90,81 +90,22 @@ pub fn infer_type_from_phi(
|
||||
|
||||
|
||||
// ========================================
|
||||
// Phase 40削除予定(Level 2)
|
||||
// Phase 40-4.1: collect_assigned_vars削除完了
|
||||
// ========================================
|
||||
//
|
||||
// 削除日: 2025-11-28
|
||||
// 削除行数: 35行(関数本体 + コメント)
|
||||
// 置換先: collect_assigned_vars_via_joinir (JoinIR Frontend経由)
|
||||
//
|
||||
// 旧関数は以下の理由で削除:
|
||||
// - JoinIR経路がRoute Bとして実装完了
|
||||
// - A/Bテストで退行なし確認(むしろ2テスト改善)
|
||||
// - callsite 2箇所(loop_builder.rs)を JoinIR経路に統一
|
||||
|
||||
/// ループ内if文の代入変数収集
|
||||
/// ループ内if文の代入変数収集(JoinIR Frontend経由)
|
||||
///
|
||||
/// Collect all variable names that are assigned within the given AST subtree.
|
||||
/// Useful for computing PHI merge candidates across branches/blocks.
|
||||
///
|
||||
/// # Phase 40削減計画
|
||||
///
|
||||
/// - **削除予定**: Phase 40-1(array_ext.filter実装後)
|
||||
/// - **置換先**: JoinIR Frontend `extract_if_in_loop_modified_vars()`
|
||||
/// - **削除条件**:
|
||||
/// - ✅ array_ext.filter via JoinIR Frontend実装完了
|
||||
/// - ✅ A/Bテスト(旧PHIパス vs JoinIRパス)PASS
|
||||
/// - ✅ この関数の全callsiteでJoinIR置換確認
|
||||
/// - **callsites**: 2箇所
|
||||
/// - loop_builder.rs:1069 (if-in-loop pattern)
|
||||
/// - loop_builder.rs:1075 (if-in-loop pattern)
|
||||
/// - **削減効果**: 32行
|
||||
///
|
||||
/// # Migration Path
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // Before (Phase 38以前)
|
||||
/// let vars = if_phi::collect_assigned_vars(loop_body, var_map);
|
||||
///
|
||||
/// // After (Phase 40)
|
||||
/// // JoinIR Frontend AST loweringで自動収集
|
||||
/// // ast_lowerer.extract_if_in_loop_modified_vars()が代替
|
||||
/// ```
|
||||
///
|
||||
/// # See Also
|
||||
/// - Design: `docs/.../phase-39-if-phi-level2/joinir_extension_design.md`
|
||||
/// - Replacement: `src/mir/join_ir/frontend/ast_lowerer.rs::extract_if_in_loop_modified_vars()`
|
||||
pub fn collect_assigned_vars(ast: &ASTNode, out: &mut std::collections::BTreeSet<String>) {
|
||||
// TODO(Phase 40-1): この関数は削除予定
|
||||
// JoinIR Frontend if-in-loop AST loweringに置き換え
|
||||
match ast {
|
||||
ASTNode::Assignment { target, .. } => {
|
||||
if let ASTNode::Variable { name, .. } = target.as_ref() {
|
||||
out.insert(name.clone());
|
||||
}
|
||||
}
|
||||
ASTNode::Program { statements, .. } => {
|
||||
for s in statements {
|
||||
collect_assigned_vars(s, out);
|
||||
}
|
||||
}
|
||||
ASTNode::If {
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
let tp = ASTNode::Program {
|
||||
statements: then_body.clone(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
collect_assigned_vars(&tp, out);
|
||||
if let Some(eb) = else_body {
|
||||
let ep = ASTNode::Program {
|
||||
statements: eb.clone(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
collect_assigned_vars(&ep, out);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 40-3.5: JoinIR経由の代入変数収集
|
||||
///
|
||||
/// `collect_assigned_vars`のJoinIR代替版。
|
||||
/// `HAKO_JOINIR_ARRAY_FILTER=1`で有効化。
|
||||
/// Uses JoinIR infrastructure for analysis.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `then_body`: thenブランチのAST
|
||||
@ -178,29 +119,23 @@ pub fn collect_assigned_vars(ast: &ASTNode, out: &mut std::collections::BTreeSet
|
||||
/// 2. JoinIR Frontend extract_assigned_vars_from_body()呼び出し
|
||||
/// 3. BTreeSet形式で返却
|
||||
///
|
||||
/// # Note
|
||||
/// この関数はPhase 40-1インフラを使用し、将来的にcollect_assigned_varsを置換する。
|
||||
#[allow(dead_code)]
|
||||
/// # History
|
||||
/// - Phase 40-3.5: 作成(collect_assigned_varsのJoinIR代替版)
|
||||
/// - Phase 40-4.1: メイン実装に昇格(collect_assigned_vars削除)
|
||||
pub fn collect_assigned_vars_via_joinir(
|
||||
then_body: &[crate::ast::ASTNode],
|
||||
else_body: Option<&Vec<crate::ast::ASTNode>>,
|
||||
) -> std::collections::BTreeSet<String> {
|
||||
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
|
||||
|
||||
let mut result = std::collections::BTreeSet::new();
|
||||
|
||||
// Convert then_body to JSON
|
||||
// Convert then_body to JSON and extract
|
||||
let then_prog = crate::ast::ASTNode::Program {
|
||||
statements: then_body.to_vec(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
let then_json = crate::r#macro::ast_json::ast_to_json(&then_prog);
|
||||
|
||||
// Extract assigned vars via JoinIR
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
if let Some(body) = then_json.get("statements") {
|
||||
let assigned = lowerer.extract_assigned_vars_from_body(body);
|
||||
result.extend(assigned);
|
||||
if let Some(stmts) = then_json.get("statements") {
|
||||
extract_vars_from_json_stmts(stmts, &mut result);
|
||||
}
|
||||
|
||||
// Process else_body if present
|
||||
@ -210,15 +145,14 @@ pub fn collect_assigned_vars_via_joinir(
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
let else_json = crate::r#macro::ast_json::ast_to_json(&else_prog);
|
||||
if let Some(body) = else_json.get("statements") {
|
||||
let assigned = lowerer.extract_assigned_vars_from_body(body);
|
||||
result.extend(assigned);
|
||||
if let Some(stmts) = else_json.get("statements") {
|
||||
extract_vars_from_json_stmts(stmts, &mut result);
|
||||
}
|
||||
}
|
||||
|
||||
if crate::config::env::joinir_vm_bridge_debug() {
|
||||
eprintln!(
|
||||
"[Phase 40-3.5] collect_assigned_vars_via_joinir: {:?}",
|
||||
"[Phase 40-4.1] collect_assigned_vars_via_joinir: {:?}",
|
||||
result
|
||||
);
|
||||
}
|
||||
@ -226,6 +160,75 @@ pub fn collect_assigned_vars_via_joinir(
|
||||
result
|
||||
}
|
||||
|
||||
/// Phase 40-4.1: JSON AST から代入変数を抽出(ast_to_json形式対応)
|
||||
fn extract_vars_from_json_stmts(
|
||||
stmts: &serde_json::Value,
|
||||
out: &mut std::collections::BTreeSet<String>,
|
||||
) {
|
||||
if let Some(arr) = stmts.as_array() {
|
||||
for stmt in arr {
|
||||
extract_vars_from_json_stmt(stmt, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 40-4.1: 単一JSON文から変数抽出
|
||||
fn extract_vars_from_json_stmt(
|
||||
stmt: &serde_json::Value,
|
||||
out: &mut std::collections::BTreeSet<String>,
|
||||
) {
|
||||
// ast_to_json uses "kind", not "type"
|
||||
match stmt.get("kind").and_then(|k| k.as_str()) {
|
||||
Some("Local") => {
|
||||
// ast_to_json: { "kind": "Local", "variables": ["x", "y"], ... }
|
||||
if let Some(vars) = stmt.get("variables").and_then(|v| v.as_array()) {
|
||||
for var in vars {
|
||||
if let Some(name) = var.as_str() {
|
||||
out.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some("Assignment") => {
|
||||
// ast_to_json: { "kind": "Assignment", "target": { "kind": "Variable", "name": "x" }, ... }
|
||||
if let Some(target) = stmt.get("target") {
|
||||
if target.get("kind").and_then(|k| k.as_str()) == Some("Variable") {
|
||||
if let Some(name) = target.get("name").and_then(|n| n.as_str()) {
|
||||
out.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some("If") => {
|
||||
// ast_to_json: { "kind": "If", "then": [...], "else": [...] }
|
||||
if let Some(then_stmts) = stmt.get("then") {
|
||||
extract_vars_from_json_stmts(then_stmts, out);
|
||||
}
|
||||
if let Some(else_stmts) = stmt.get("else") {
|
||||
extract_vars_from_json_stmts(else_stmts, out);
|
||||
}
|
||||
}
|
||||
Some("Loop") => {
|
||||
// ast_to_json: { "kind": "Loop", "body": [...] }
|
||||
if let Some(body) = stmt.get("body") {
|
||||
extract_vars_from_json_stmts(body, out);
|
||||
}
|
||||
}
|
||||
Some("Block") => {
|
||||
// ast_to_json: { "kind": "Block", "body": [...] } - 通常は "statements" かも
|
||||
if let Some(body) = stmt.get("body") {
|
||||
extract_vars_from_json_stmts(body, out);
|
||||
}
|
||||
if let Some(stmts) = stmt.get("statements") {
|
||||
extract_vars_from_json_stmts(stmts, out);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// その他は無視
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 保守的修正名前計算
|
||||
///
|
||||
/// Compute the set of variable names whose values changed in either branch
|
||||
|
||||
@ -122,118 +122,92 @@ fn phase40_mir_conversion_with_meta() {
|
||||
// Phase 40-3.5: A/B Test (Route Switching)
|
||||
// ========================================
|
||||
|
||||
/// Test 4: A/B route switching produces identical results
|
||||
/// Test 4: JoinIR経由の代入変数収集(Local宣言)
|
||||
///
|
||||
/// ## Verification Points
|
||||
/// 1. collect_assigned_vars と collect_assigned_vars_via_joinir が同じ結果を返す
|
||||
/// 2. env flag で経路が正しく切り替わる
|
||||
///
|
||||
/// Note: collect_assigned_vars detects Assignment nodes, not Local declarations.
|
||||
/// JoinIR version detects Local declarations (which is the correct behavior for PHI).
|
||||
/// 1. collect_assigned_vars_via_joinir が Local 宣言を正しく検出
|
||||
/// 2. Phase 40-4.1 削除後もメイン経路として動作
|
||||
#[test]
|
||||
fn phase40_ab_route_switching() {
|
||||
fn phase40_joinir_detects_local_declarations() {
|
||||
use crate::ast::{ASTNode, LiteralValue, Span};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
// Create simple if body with Assignment: x = 1; y = 2;
|
||||
// Note: Assignment requires target to be a Variable node
|
||||
// Create simple if body with Local declarations: local x = 1; local y = 2;
|
||||
let then_body = vec![
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
ASTNode::Local {
|
||||
variables: vec!["x".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "y".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
ASTNode::Local {
|
||||
variables: vec!["y".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
|
||||
// Route A: Legacy collect_assigned_vars (detects Assignment)
|
||||
let mut route_a_vars: BTreeSet<String> = BTreeSet::new();
|
||||
let then_prog = ASTNode::Program {
|
||||
statements: then_body.clone(),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
crate::mir::phi_core::if_phi::collect_assigned_vars(&then_prog, &mut route_a_vars);
|
||||
// JoinIR経由(メイン経路)
|
||||
let vars =
|
||||
crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(&then_body, None);
|
||||
|
||||
// Verify Route A detects assignments
|
||||
assert!(route_a_vars.contains("x"), "Route A should detect x assignment");
|
||||
assert!(route_a_vars.contains("y"), "Route A should detect y assignment");
|
||||
assert_eq!(route_a_vars.len(), 2, "Route A should have exactly 2 variables");
|
||||
// Verify JoinIR detects Local declarations
|
||||
assert!(vars.contains("x"), "JoinIR should detect x declaration");
|
||||
assert!(vars.contains("y"), "JoinIR should detect y declaration");
|
||||
assert_eq!(vars.len(), 2, "Should have exactly 2 variables");
|
||||
}
|
||||
|
||||
/// Test 5: A/B route switching with nested if (Assignment nodes)
|
||||
/// Test 5: JoinIR経由のネストif内Local検出
|
||||
///
|
||||
/// ## Verification Points
|
||||
/// 1. Nested if内の代入(Assignment)も正しく検出
|
||||
/// 2. collect_assigned_varsのAssignment検出を検証
|
||||
/// 1. Nested if内のLocal宣言も正しく検出
|
||||
/// 2. Phase 40-4.1 削除後の動作確認
|
||||
#[test]
|
||||
fn phase40_ab_nested_if() {
|
||||
fn phase40_joinir_nested_if_local() {
|
||||
use crate::ast::{ASTNode, LiteralValue, Span};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
// Create nested if body with Assignment:
|
||||
// if (true) { inner = 1 }
|
||||
// outer = 2
|
||||
// Create nested if body with Local:
|
||||
// if (true) { local inner = 1 }
|
||||
// local outer = 2
|
||||
let then_body = vec![
|
||||
ASTNode::If {
|
||||
condition: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Bool(true),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
then_body: vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "inner".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
then_body: vec![ASTNode::Local {
|
||||
variables: vec!["inner".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
else_body: None,
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "outer".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
ASTNode::Local {
|
||||
variables: vec!["outer".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
|
||||
// Route A: collect_assigned_vars
|
||||
let mut route_a_vars: BTreeSet<String> = BTreeSet::new();
|
||||
let then_prog = ASTNode::Program {
|
||||
statements: then_body.clone(),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
crate::mir::phi_core::if_phi::collect_assigned_vars(&then_prog, &mut route_a_vars);
|
||||
// JoinIR経由
|
||||
let vars =
|
||||
crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(&then_body, None);
|
||||
|
||||
// Verify: inner (nested in if) and outer (top-level) detected
|
||||
assert!(route_a_vars.contains("inner"), "Route A: should detect inner assignment in nested if");
|
||||
assert!(route_a_vars.contains("outer"), "Route A: should detect outer assignment");
|
||||
assert_eq!(route_a_vars.len(), 2, "Should detect exactly 2 assignments");
|
||||
assert!(vars.contains("inner"), "JoinIR: should detect inner in nested if");
|
||||
assert!(vars.contains("outer"), "JoinIR: should detect outer");
|
||||
assert_eq!(vars.len(), 2, "Should detect exactly 2 declarations");
|
||||
}
|
||||
|
||||
// ========================================
|
||||
|
||||
Reference in New Issue
Block a user