From 59f7f03efb5786eabfc924dd7c1cc17a3396fe6f Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Fri, 28 Nov 2025 11:07:01 +0900 Subject: [PATCH] feat(joinir): Phase 40-4.1 delete collect_assigned_vars (35 lines) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/private | 2 +- src/mir/loop_builder.rs | 29 +--- src/mir/phi_core/if_phi.rs | 189 +++++++++++---------- src/tests/phase40_array_ext_filter_test.rs | 108 +++++------- 4 files changed, 143 insertions(+), 185 deletions(-) diff --git a/docs/private b/docs/private index 16cc6452..ec5d1dfa 160000 --- a/docs/private +++ b/docs/private @@ -1 +1 @@ -Subproject commit 16cc6452b037e2397b2e83d56de5cab02aa85344 +Subproject commit ec5d1dfafd82d198f7f9f464f357364ae022739e diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index 5c251a8d..4bb47f88 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -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 = - 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(新)の両対応 diff --git a/src/mir/phi_core/if_phi.rs b/src/mir/phi_core/if_phi.rs index 36108253..930d2070 100644 --- a/src/mir/phi_core/if_phi.rs +++ b/src/mir/phi_core/if_phi.rs @@ -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) { - // 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>, ) -> std::collections::BTreeSet { - 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, +) { + 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, +) { + // 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 diff --git a/src/tests/phase40_array_ext_filter_test.rs b/src/tests/phase40_array_ext_filter_test.rs index c27e4cf7..86d84d38 100644 --- a/src/tests/phase40_array_ext_filter_test.rs +++ b/src/tests/phase40_array_ext_filter_test.rs @@ -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 = 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 = 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"); } // ========================================