phase29ap(p13): remove dead code after legacy removal

This commit is contained in:
2025-12-31 08:28:49 +09:00
parent b53c12eb29
commit 4e3c3c0723
13 changed files with 58 additions and 1722 deletions

View File

@ -3,7 +3,7 @@
## Current Focus
- Phase: `docs/development/current/main/phases/phase-29ap/README.md`
- Next: Phase 29ap P13 (planned; see `docs/development/current/main/phases/phase-29ap/README.md`)
- Next: Phase 29ap P14 (planned; see `docs/development/current/main/phases/phase-29ap/README.md`)
## Gate (SSOT)

View File

@ -5,7 +5,7 @@ Scope: 「次にやる候補」を短く列挙するメモ。入口は `docs/dev
## Active
- Phase 29ap: `docs/development/current/main/phases/phase-29ap/README.md` (Next: P13 planned)
- Phase 29ap: `docs/development/current/main/phases/phase-29ap/README.md` (Next: P14 planned)
- JoinIR regression gate SSOT: `docs/development/current/main/phases/phase-29ae/README.md`
- CorePlan hardening (docs-first): `docs/development/current/main/phases/phase-29al/README.md`

View File

@ -34,7 +34,7 @@ Related:
## 1.1 Current (active)
- Active phase: `docs/development/current/main/phases/phase-29ap/README.md`
- Next step: Phase 29ap P13 (planned)
- Next step: Phase 29ap P14 (planned)
## 2. すでに固めた SSOT再発防止の土台

View File

@ -137,6 +137,15 @@ Gate (SSOT):
- No change to logs or error strings.
- Gate stays green.
## P13: Warning cleanup (dead_code) ✅
- Scope:
- Remove unused JoinIR lowering modules (legacy remnants).
- Trim unused Pattern4 extractor fields.
- Guardrails:
- No change to logs or error strings.
- Gate stays green.
## Next (planned)
- P13: Dead-code cleanup (warnings-only, no behavior change)
- P14: Phase 29ap closeout (docs-only)

View File

@ -10,7 +10,6 @@ use super::common_helpers::{count_control_flow, has_break_statement, ControlFlow
#[derive(Debug, Clone)]
pub(crate) struct Pattern4Parts {
pub loop_var: String, // Extracted from condition (i < n)
pub continue_count: usize, // Must be >= 1 for Pattern4
}
/// Extract Pattern4 (Loop with Continue) parts
@ -70,10 +69,7 @@ pub(crate) fn extract_loop_with_continue_parts(
// Phase 5: Return extracted parts
// USER CORRECTION: No loop_var update validation - defer to Pattern4CarrierAnalyzer
Ok(Some(Pattern4Parts {
loop_var,
continue_count,
}))
Ok(Some(Pattern4Parts { loop_var }))
}
// ============================================================================
@ -119,17 +115,17 @@ pub(crate) fn extract_pattern4_plan(
use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern4ContinuePlan};
// Step 1: Validate via existing extractor
let parts = extract_loop_with_continue_parts(condition, body)?;
if parts.is_none() {
return Ok(None); // Not Pattern4 → legacy fallback
}
// Step 2: Extract loop variable from condition `i < N`
let loop_var = match extract_loop_condition_plan(condition)? {
Some((var, _bound)) => var,
None => return Ok(None), // Unsupported condition
let parts = match extract_loop_with_continue_parts(condition, body)? {
Some(parts) => parts,
None => return Ok(None), // Not Pattern4 → legacy fallback
};
// Step 2: Require loop condition `i < N` and re-use extracted loop_var
if extract_loop_condition_plan(condition)?.as_deref() != Some(parts.loop_var.as_str()) {
return Ok(None); // Unsupported condition
}
let loop_var = parts.loop_var;
// Step 3: Find continue condition from body
let continue_cond = match find_continue_condition_plan(body)? {
Some(cond) => cond,
@ -160,7 +156,7 @@ pub(crate) fn extract_pattern4_plan(
}
/// Extract loop condition: supports `<var> < <int_lit>` only
fn extract_loop_condition_plan(cond: &ASTNode) -> Result<Option<(String, i64)>, String> {
fn extract_loop_condition_plan(cond: &ASTNode) -> Result<Option<String>, String> {
use crate::ast::{BinaryOperator, LiteralValue};
if let ASTNode::BinaryOp { operator, left, right, .. } = cond {
@ -174,13 +170,18 @@ fn extract_loop_condition_plan(cond: &ASTNode) -> Result<Option<(String, i64)>,
return Ok(None);
};
let bound = if let ASTNode::Literal { value: LiteralValue::Integer(n), .. } = right.as_ref() {
*n
} else {
let has_int_bound = matches!(
right.as_ref(),
ASTNode::Literal {
value: LiteralValue::Integer(_),
..
}
);
if !has_int_bound {
return Ok(None);
};
}
Ok(Some((var_name, bound)))
Ok(Some(var_name))
} else {
Ok(None)
}
@ -200,6 +201,8 @@ fn find_continue_condition_plan(body: &[ASTNode]) -> Result<Option<ASTNode>, Str
Ok(None)
}
/// Extract carrier updates: `<var> = <var> + <var>` pattern only
fn extract_carrier_updates_plan(
body: &[ASTNode],
@ -330,7 +333,7 @@ mod tests {
let parts = parts.unwrap();
assert_eq!(parts.loop_var, "i");
assert_eq!(parts.continue_count, 1);
assert_eq!(count_continue_statements_recursive(&body), 1);
}
#[test]
@ -383,7 +386,7 @@ mod tests {
assert!(parts.is_some()); // Pattern4 extraction succeeds
let parts = parts.unwrap();
assert_eq!(parts.loop_var, "i");
assert_eq!(parts.continue_count, 1);
assert_eq!(count_continue_statements_recursive(&body), 1);
}
#[test]
@ -413,6 +416,6 @@ mod tests {
let parts = parts.unwrap();
assert_eq!(parts.loop_var, "i");
assert_eq!(parts.continue_count, 2); // Two continue statements detected
assert_eq!(count_continue_statements_recursive(&body), 2); // Two continue statements detected
}
}

View File

@ -1,174 +0,0 @@
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox;
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
use crate::mir::join_ir::{BinOpKind, JoinInst, MirLikeInst};
use crate::mir::ValueId;
use std::collections::BTreeMap;
pub(crate) fn emit_carrier_updates(
carrier_info: &CarrierInfo,
carrier_updates: &BTreeMap<String, UpdateExpr>,
carrier_param_ids: &[ValueId],
carrier_next_ids: &[ValueId],
carrier_merged_ids: &[ValueId],
continue_cond: ValueId,
const_1: ValueId,
i_next: ValueId,
debug: &DebugOutputBox,
body: &mut Vec<JoinInst>,
) -> Result<(), String> {
let carrier_count = carrier_info.carriers.len();
debug_assert_eq!(carrier_param_ids.len(), carrier_count);
debug_assert_eq!(carrier_next_ids.len(), carrier_count);
debug_assert_eq!(carrier_merged_ids.len(), carrier_count);
for idx in 0..carrier_count {
let carrier_param = carrier_param_ids[idx];
let carrier_next = carrier_next_ids[idx];
let carrier_merged = carrier_merged_ids[idx];
let carrier_name = &carrier_info.carriers[idx].name;
debug.log(
"carrier_update",
&format!("Processing carrier '{}' (idx={})", carrier_name, idx),
);
let rhs = if let Some(update_expr) = carrier_updates.get(carrier_name) {
debug.log(
"carrier_update",
&format!("Found update expr: {:?}", update_expr),
);
match update_expr {
UpdateExpr::BinOp { op, rhs, .. } => {
if *op != BinOpKind::Add {
debug.log(
"warn",
&format!(
"Carrier '{}' uses unsupported operator {:?}, defaulting to Add",
carrier_name, op
),
);
}
match rhs {
UpdateRhs::Const(n) => {
if *n == 1 {
const_1
} else {
debug.log(
"warn",
&format!(
"Carrier '{}' uses const {}, only const_1 is pre-allocated, using const_1",
carrier_name, n
),
);
const_1
}
}
UpdateRhs::Variable(var_name) => {
if var_name == &carrier_info.loop_var_name {
debug.log(
"carrier_update",
&format!(
"Using i_next (ValueId({})) for variable '{}'",
i_next.0, var_name
),
);
i_next
} else {
debug.log(
"warn",
&format!(
"Carrier '{}' updates with unknown variable '{}', using const_1",
carrier_name, var_name
),
);
const_1
}
}
UpdateRhs::NumberAccumulation { .. } => {
debug.log(
"phase190",
&format!(
"Carrier '{}' has number accumulation - not supported in Pattern 4, using Select passthrough",
carrier_name
),
);
body.push(JoinInst::Select {
dst: carrier_merged,
cond: continue_cond,
then_val: carrier_param,
else_val: carrier_param,
type_hint: None,
});
continue;
}
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
debug.log(
"phase178",
&format!(
"Carrier '{}' has string/complex update - skipping BinOp, using Select passthrough",
carrier_name
),
);
body.push(JoinInst::Select {
dst: carrier_merged,
cond: continue_cond,
then_val: carrier_param,
else_val: carrier_param,
type_hint: None,
});
continue;
}
}
}
UpdateExpr::Const(n) => {
if *n == 1 {
const_1
} else {
debug.log(
"warn",
&format!(
"Carrier '{}' uses const {}, only const_1 is pre-allocated, using const_1",
carrier_name, n
),
);
const_1
}
}
}
} else {
debug.log(
"warn",
&format!(
"No update expression for carrier '{}', defaulting to +1",
carrier_name
),
);
const_1
};
debug.log(
"carrier_update",
&format!(
"Generating: ValueId({}) = ValueId({}) + ValueId({})",
carrier_next.0, carrier_param.0, rhs.0
),
);
body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: carrier_next,
op: BinOpKind::Add,
lhs: carrier_param,
rhs,
}));
body.push(JoinInst::Select {
dst: carrier_merged,
cond: continue_cond,
then_val: carrier_param,
else_val: carrier_next,
type_hint: None,
});
}
Ok(())
}

View File

@ -1,86 +0,0 @@
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
use crate::mir::join_ir::lowering::condition_lowering_box::{
ConditionContext, ConditionLoweringBox,
};
use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox;
use crate::mir::join_ir::lowering::error_tags;
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer};
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
use crate::mir::join_ir::JoinInst;
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
use crate::mir::ValueId;
fn make_scope_manager<'a>(
condition_env: &'a ConditionEnv,
body_local_env: Option<&'a LoopBodyLocalEnv>,
captured_env: Option<&'a CapturedEnv>,
carrier_info: &'a CarrierInfo,
) -> Pattern2ScopeManager<'a> {
Pattern2ScopeManager {
condition_env,
loop_body_local_env: body_local_env,
captured_env,
carrier_info,
}
}
pub(crate) fn lower_header_condition(
condition: &ASTNode,
env: &ConditionEnv,
carrier_info: &CarrierInfo,
loop_var_name: &str,
loop_var_id: ValueId,
alloc_value: &mut dyn FnMut() -> ValueId,
current_static_box_name: Option<&str>,
debug: &DebugOutputBox,
) -> Result<(ValueId, Vec<JoinInst>), String> {
let empty_body_env = LoopBodyLocalEnv::new();
let empty_captured_env = CapturedEnv::new();
let scope_manager = make_scope_manager(
env,
Some(&empty_body_env),
Some(&empty_captured_env),
carrier_info,
);
if !ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(condition) {
return Err(error_tags::lowering_error(
"pattern4/condition",
"ConditionLoweringBox does not support this condition (legacy path removed)",
));
}
let mut dummy_builder = MirBuilder::new();
let mut expr_lowerer =
ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
let mut context = ConditionContext {
loop_var_name: loop_var_name.to_string(),
loop_var_id,
scope: &scope_manager,
alloc_value,
current_static_box_name: current_static_box_name.map(|s| s.to_string()),
};
let value_id = expr_lowerer.lower_condition(condition, &mut context).map_err(|e| {
format!(
"[joinir/pattern4/phase244] ConditionLoweringBox failed on supported condition: {:?}",
e
)
})?;
let instructions = expr_lowerer.take_last_instructions();
debug.log(
"phase244",
&format!(
"Header condition via ConditionLoweringBox: {} instructions",
instructions.len()
),
);
Ok((value_id, instructions))
}

View File

@ -1,557 +0,0 @@
//! Phase 195-196: Pattern 4 (Loop with Continue) Lowerer
//!
//! Phase 195: Initial minimal implementation for single-carrier (sum only)
//! Phase 196: Extended to support multiple carrier variables (sum, count, etc.)
//! Phase 202-C: Unified dual counters into JoinValueSpace
//!
//! Target: apps/tests/loop_continue_pattern4.hako (single carrier)
//! apps/tests/loop_continue_multi_carrier.hako (multi carrier)
//!
//! Code (multi-carrier):
//! ```nyash
//! static box Main {
//! main() {
//! local i = 0
//! local sum = 0
//! local count = 0
//! loop(i < 10) {
//! i = i + 1
//! if (i % 2 == 0) {
//! continue
//! }
//! sum = sum + i
//! count = count + 1
//! }
//! print(sum) // 25
//! print(count) // 5
//! return 0
//! }
//! }
//! ```
//!
//! Expected output: sum = 25 (1+3+5+7+9), count = 5 (five odd numbers)
//!
//! ## Design Notes (Phase 196)
//!
//! This lowerer now generates JoinIR for N carrier variables dynamically:
//! - Loop function takes (i, carrier1, carrier2, ...) as parameters
//! - Each carrier gets a Select instruction for continue vs normal path
//! - k_exit returns all carriers, ExitMeta maps each to its exit ValueId
//!
//! Key changes from Phase 195:
//! - CarrierInfo passed in to enumerate all carriers
//! - ExitMeta::multiple() instead of ExitMeta::single()
//! - Dynamic parameter/slot allocation based on carrier count
//!
//! ## Phase 202-C: JoinValueSpace Unification
//!
//! Previously used dual counters:
//! - `value_counter`: JoinIR internal ValueIds (0, 1, 2, ...)
//! - `join_value_counter`: Condition-only variables (carrier_count + 1, ...)
//!
//! Now unified via JoinValueSpace:
//! - **Param region (100+)**: ConditionEnv, CarrierInfo parameters
//! - **Local region (1000+)**: Const, BinOp, intermediate values
//!
//! This prevents ValueId collisions after remapping (Phase 201 design).
//!
//! Following the "80/20 rule" from CLAUDE.md - now generalizing after working.
mod carrier_updates;
mod condition_lowering;
mod validation;
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta};
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox;
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
use crate::mir::join_ir::lowering::return_collector::ReturnInfo; // Phase 284 P1
use crate::mir::join_ir::lowering::return_jump_emitter::emit_return_conditional_jump; // Phase 284 P1
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
UnaryOp,
};
use crate::mir::ValueId;
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
use carrier_updates::emit_carrier_updates;
use condition_lowering::lower_header_condition;
use validation::validate_condition_scope;
/// Lower Pattern 4 (Loop with Continue) to JoinIR
///
/// # Phase 195-196: Pure JoinIR Fragment Generation
/// # Phase 202-C: JoinValueSpace Unification
///
/// This version generates JoinIR using **JoinValueSpace-allocated ValueIds**.
/// It has NO knowledge of the host function's ValueId space. The boundary mapping
/// is handled separately via JoinInlineBoundary.
///
/// ## Design Philosophy
///
/// - **Box A**: JoinIR Frontend (doesn't know about host ValueIds)
/// - **Box B**: This function - converts to JoinIR with JoinValueSpace IDs
/// - **Box C**: JoinInlineBoundary - stores boundary info
/// - **Box D**: merge_joinir_mir_blocks - injects Copy instructions
///
/// This clean separation ensures JoinIR lowerers are:
/// - Pure transformers (no side effects)
/// - Reusable (same lowerer works in any context)
/// - Testable (can test JoinIR independently)
///
/// # Arguments
///
/// * `_scope` - LoopScopeShape (reserved for future generic implementation)
/// * `condition` - Loop condition AST node (e.g., `i < end`) (Phase 169)
/// * `_builder` - MirBuilder for variable resolution (Phase 169)
/// * `carrier_info` - Phase 196: Carrier metadata for dynamic multi-carrier support
/// * `carrier_updates` - Phase 197: Update expressions for each carrier variable
/// * `join_value_space` - Phase 202-C: Unified JoinIR ValueId allocator (Local region: 1000+)
/// * `return_info` - Phase 284 P1: Optional return statement info for early exit
///
/// # Returns
///
/// * `Ok((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata
/// * `Err(String)` - Pattern not matched or lowering error
///
/// # Boundary Contract (Phase 196 updated, Phase 202-C: JoinValueSpace)
///
/// This function returns a JoinModule with:
/// - **Input slots**: Local region (1000+) = i_init, carrier values
/// - **Output slots**: k_exit returns all carrier values
/// - **Exit metadata**: ExitMeta containing all carrier bindings
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds
///
/// # Phase 284 P1: Return Handling
///
/// If `return_info` is provided, generates:
/// - A `k_return` function that returns the literal value
/// - Conditional jump to k_return when return condition is met
pub(crate) fn lower_loop_with_continue_minimal(
_scope: LoopScopeShape,
condition: &ASTNode,
_builder: &mut MirBuilder,
carrier_info: &CarrierInfo,
carrier_updates: &BTreeMap<String, UpdateExpr>, // Phase 222.5-D: HashMap → BTreeMap for determinism
join_value_space: &mut JoinValueSpace,
return_info: Option<&ReturnInfo>, // Phase 284 P1
#[cfg(feature = "normalized_dev")] binding_map: Option<
&std::collections::BTreeMap<String, crate::mir::BindingId>,
>,
) -> Result<(JoinModule, ExitMeta), String> {
// Phase 170-D-impl-3: Validate that loop condition only uses supported variable scopes
// LoopConditionScopeBox checks that loop conditions don't reference loop-body-local variables
let loop_var_name = carrier_info.loop_var_name.clone();
let loop_cond_scope = validate_condition_scope(&_scope, condition, &loop_var_name)?;
let debug = DebugOutputBox::new_dev("joinir/pattern4");
debug.log(
"phase170d",
&format!(
"Condition variables verified: {:?}",
loop_cond_scope.var_names()
),
);
let mut join_module = JoinModule::new();
let carrier_count = carrier_info.carriers.len();
debug.log(
"phase202c",
&format!(
"Generating JoinIR for {} carriers: {:?}",
carrier_count,
carrier_info
.carriers
.iter()
.map(|c| &c.name)
.collect::<Vec<_>>()
),
);
// ==================================================================
// Function IDs allocation
// ==================================================================
let main_id = JoinFuncId::new(0);
let loop_step_id = JoinFuncId::new(1);
let k_exit_id = JoinFuncId::new(2);
// Phase 284 P1: k_return for early return handling
let k_return_id = JoinFuncId::new(3);
// ==================================================================
// ValueId allocation (Phase 202-C: Dynamic based on carrier count)
// ==================================================================
// Phase 286 P1: Function parameters MUST use alloc_param() (Param region: 100-999)
// Local variables use alloc_local() (Local region: 1000+)
// main() parameters: [i_init, carrier1_init, carrier2_init, ...]
let i_init = join_value_space.alloc_param(); // PARAM region
let mut carrier_init_ids: Vec<ValueId> = Vec::new();
for _ in 0..carrier_count {
carrier_init_ids.push(join_value_space.alloc_param()); // PARAM region
}
let loop_result = join_value_space.alloc_local(); // result from loop_step (LOCAL region)
// loop_step() parameters: [i_param, carrier1_param, carrier2_param, ...]
let i_param = join_value_space.alloc_param(); // PARAM region
let mut carrier_param_ids: Vec<ValueId> = Vec::new();
for _ in 0..carrier_count {
carrier_param_ids.push(join_value_space.alloc_param()); // PARAM region
}
// k_exit() parameters: [carrier1_exit, carrier2_exit, ...]
// Phase 286 P1: Function parameters MUST use alloc_param() (PARAM region: 100-999)
let mut carrier_exit_ids: Vec<ValueId> = Vec::new();
for _ in 0..carrier_count {
carrier_exit_ids.push(join_value_space.alloc_param()); // PARAM region
}
// Phase 202-C: Build ConditionEnv for condition lowering using JoinValueSpace
// Loop variable and condition-only variables use Param region (100+)
use crate::mir::join_ir::lowering::condition_to_joinir::extract_condition_variables;
let mut env = ConditionEnv::new();
// Add loop parameter to env (using i_param which is in Local region)
let loop_var_name = carrier_info.loop_var_name.clone();
env.insert(loop_var_name.clone(), i_param);
// Extract and add condition-only variables (Param region)
let condition_var_names = extract_condition_variables(condition, &[loop_var_name.clone()]);
let mut condition_var_join_ids = std::collections::BTreeMap::new();
for var_name in &condition_var_names {
let join_id = join_value_space.alloc_param();
env.insert(var_name.clone(), join_id);
condition_var_join_ids.insert(var_name.clone(), join_id);
}
// Phase 80-C (P2): Register BindingIds for condition variables (dev-only)
#[cfg(feature = "normalized_dev")]
if let Some(binding_map) = binding_map {
let debug = DebugOutputBox::new_dev("phase80/p4");
// Register loop variable BindingId
if let Some(bid) = binding_map.get(&loop_var_name) {
env.register_loop_var_binding(*bid, i_param);
#[cfg(debug_assertions)]
debug.log(
"register",
&format!(
"Registered loop var '{}' BindingId({}) -> ValueId({})",
loop_var_name, bid.0, i_param.0
),
);
}
// Register condition binding BindingIds
for (var_name, join_id) in &condition_var_join_ids {
if let Some(bid) = binding_map.get(var_name) {
env.register_condition_binding(*bid, *join_id);
#[cfg(debug_assertions)]
debug.log(
"register",
&format!(
"Registered condition binding '{}' BindingId({}) -> ValueId({})",
var_name, bid.0, join_id.0
),
);
}
}
}
// Phase 202-C: Create allocator closure AFTER all direct allocations
// This avoids borrow checker issues
let mut alloc_value = || join_value_space.alloc_local();
// Phase 169 / Phase 171-fix / Phase 244: Lower condition using ConditionLoweringBox trait
let (cond_value, mut cond_instructions) = lower_header_condition(
condition,
&env,
carrier_info,
&loop_var_name,
i_param,
&mut alloc_value,
None,
&debug,
)?;
// Loop control temporaries
let exit_cond = alloc_value();
let const_1 = alloc_value();
let i_next = alloc_value();
let const_2 = alloc_value();
let remainder = alloc_value();
let const_0 = alloc_value();
let continue_cond = alloc_value();
// Per-carrier: next value and merged value (after Select)
let mut carrier_next_ids: Vec<ValueId> = Vec::new();
let mut carrier_merged_ids: Vec<ValueId> = Vec::new();
for _ in 0..carrier_count {
carrier_next_ids.push(alloc_value()); // carrier_next = carrier + ...
carrier_merged_ids.push(alloc_value()); // carrier_merged = Select(...)
}
// ==================================================================
// main() function
// ==================================================================
// Phase 196: main() takes i and all carriers as parameters
let mut main_params = vec![i_init];
main_params.extend(carrier_init_ids.iter().copied());
let mut main_func = JoinFunction::new(main_id, "main".to_string(), main_params.clone());
// result = loop_step(i_init, carrier1_init, carrier2_init, ...)
main_func.body.push(JoinInst::Call {
func: loop_step_id,
args: main_params.clone(),
k_next: None,
dst: Some(loop_result),
});
// return result
main_func.body.push(JoinInst::Ret {
value: Some(loop_result),
});
join_module.add_function(main_func);
// ==================================================================
// loop_step(i, carrier1, carrier2, ...) function
// ==================================================================
let mut loop_step_params = vec![i_param];
loop_step_params.extend(carrier_param_ids.iter().copied());
let mut loop_step_func =
JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_step_params);
// ------------------------------------------------------------------
// Natural Exit Condition Check (Phase 169: from AST)
// ------------------------------------------------------------------
// Insert all condition evaluation instructions
loop_step_func.body.append(&mut cond_instructions);
// Negate the condition for exit check: exit_cond = !cond_value
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: exit_cond,
op: UnaryOp::Not,
operand: cond_value,
}));
// Jump(k_exit, [carrier1, carrier2, ...], cond=exit_cond)
// Phase 196: Pass ALL carrier params as exit values
loop_step_func.body.push(JoinInst::Jump {
cont: k_exit_id.as_cont(),
args: carrier_param_ids.clone(),
cond: Some(exit_cond),
});
// ------------------------------------------------------------------
// Loop Body: i_next = i + 1
// ------------------------------------------------------------------
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_next,
op: BinOpKind::Add,
lhs: i_param,
rhs: const_1,
}));
// ------------------------------------------------------------------
// Phase 284 P1: Return Condition Check (if return_info present)
// ------------------------------------------------------------------
// For fixture: if (i == 3) { return 7 }
// Condition is checked against i_next (after increment)
//
// Phase 284 P1 Refactor: Use emit_return_conditional_jump SSOT
emit_return_conditional_jump(
&mut loop_step_func,
return_info,
k_return_id,
i_next,
&loop_var_name,
&mut alloc_value,
&debug,
)?;
// ------------------------------------------------------------------
// Continue Condition Check: (i_next % 2 == 0)
// ------------------------------------------------------------------
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_2,
value: ConstValue::Integer(2),
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: remainder,
op: BinOpKind::Mod,
lhs: i_next,
rhs: const_2,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_0,
value: ConstValue::Integer(0),
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: continue_cond,
op: CompareOp::Eq,
lhs: remainder,
rhs: const_0,
}));
emit_carrier_updates(
carrier_info,
carrier_updates,
&carrier_param_ids,
&carrier_next_ids,
&carrier_merged_ids,
continue_cond,
const_1,
i_next,
&debug,
&mut loop_step_func.body,
)?;
// ------------------------------------------------------------------
// Tail call: loop_step(i_next, carrier1_merged, carrier2_merged, ...)
// ------------------------------------------------------------------
let mut tail_call_args = vec![i_next];
tail_call_args.extend(carrier_merged_ids.iter().copied());
loop_step_func.body.push(JoinInst::Call {
func: loop_step_id,
args: tail_call_args,
k_next: None, // CRITICAL: None for tail call
dst: None,
});
join_module.add_function(loop_step_func);
// ==================================================================
// k_exit(carrier1_exit, carrier2_exit, ...) function
// ==================================================================
let mut k_exit_func =
JoinFunction::new(k_exit_id, "k_exit".to_string(), carrier_exit_ids.clone());
// For now, return the first carrier's exit value (or void if no carriers)
// TODO: Consider returning a tuple or using a different mechanism
let ret_value = if carrier_exit_ids.is_empty() {
None
} else {
Some(carrier_exit_ids[0])
};
k_exit_func.body.push(JoinInst::Ret { value: ret_value });
join_module.add_function(k_exit_func);
// ==================================================================
// Phase 284 P1: k_return() function for early return
// ==================================================================
if let Some(ret_info) = return_info {
let return_value_id = alloc_value();
let mut k_return_func = JoinFunction::new(k_return_id, "k_return".to_string(), vec![]);
// Generate: return_value = Const(ret_info.value)
k_return_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: return_value_id,
value: ConstValue::Integer(ret_info.value),
}));
// Generate: Ret(return_value)
k_return_func.body.push(JoinInst::Ret {
value: Some(return_value_id),
});
join_module.add_function(k_return_func);
debug.log(
"phase284",
&format!(
"Generated k_return function: returns {}",
ret_info.value
),
);
}
// Set entry point
join_module.entry = Some(main_id);
debug.log_simple("Phase 202-C: Generated JoinIR for Loop with Continue Pattern");
debug.log_simple("Functions: main, loop_step, k_exit");
debug.log_simple("Continue: Select-based skip");
debug.log_simple("ValueId allocation: JoinValueSpace (Local region 1000+)");
debug.log(
"summary",
&format!(
"Carriers: {} ({:?})",
carrier_count,
carrier_info
.carriers
.iter()
.map(|c| c.name.as_str())
.collect::<Vec<_>>()
),
);
// ==================================================================
// Phase 197-B: Build ExitMeta with carrier_param_ids (Jump arguments)
// ==================================================================
// Previously used carrier_exit_ids (k_exit parameters) which are not defined
// when MIR functions are merged. Now use carrier_param_ids which are the
// actual values passed to k_exit via Jump instruction.
//
// k_exit receives carrier values via: Jump(k_exit, [carrier_param1, carrier_param2, ...])
// These carrier_param_ids are defined in loop_step and properly remapped.
let mut exit_values: Vec<(String, ValueId)> = Vec::new();
for (idx, carrier) in carrier_info.carriers.iter().enumerate() {
// Phase 197-B: Use carrier_param_ids instead of carrier_exit_ids
let exit_id = carrier_param_ids[idx];
exit_values.push((carrier.name.clone(), exit_id));
debug.log(
"exit_meta",
&format!(
"ExitMeta: {} → ValueId({}) (carrier_param)",
carrier.name, exit_id.0
),
);
}
let exit_meta = ExitMeta::multiple(exit_values);
debug.log(
"phase169",
&format!(
"ExitMeta total: {} bindings (condition from AST)",
exit_meta.exit_values.len()
),
);
Ok((join_module, exit_meta))
}

View File

@ -1,24 +0,0 @@
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::loop_pattern_detection::error_messages::{
extract_body_local_names, format_unsupported_condition_error,
};
use crate::mir::loop_pattern_detection::loop_condition_scope::{LoopConditionScope, LoopConditionScopeBox};
pub(crate) fn validate_condition_scope(
scope: &LoopScopeShape,
condition: &ASTNode,
loop_var_name: &str,
) -> Result<LoopConditionScope, String> {
let loop_cond_scope = LoopConditionScopeBox::analyze(loop_var_name, &[condition], Some(scope));
if loop_cond_scope.has_loop_body_local() {
let body_local_names = extract_body_local_names(&loop_cond_scope.vars);
return Err(format_unsupported_condition_error(
"pattern4",
&body_local_names,
));
}
Ok(loop_cond_scope)
}

View File

@ -64,9 +64,7 @@ pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carri
pub mod loop_update_summary; // Phase 170-C-2: Update pattern summary for shape detection
pub(crate) mod loop_view_builder; // Phase 33-23: Loop lowering dispatch
pub mod loop_with_break_minimal; // Phase 188-Impl-2: Pattern 2 minimal lowerer
pub mod loop_with_continue_minimal;
pub(crate) mod return_collector; // Phase 284 P1: Return statement collector SSOT
pub(crate) mod return_jump_emitter; // Phase 284 P1: Return jump emission helper (Pattern4/5 reuse)
pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven)
pub mod user_method_policy; // Phase 252: User-defined method policy (SSOT for static box method whitelists)
pub mod method_return_hint; // Phase 83: P3-D 既知メソッド戻り値型推論箱
@ -77,7 +75,6 @@ pub(crate) mod step_schedule; // Phase 47-A: Generic step scheduler for P2/P3 (r
// Phase 242-EX-A: loop_with_if_phi_minimal removed - replaced by loop_with_if_phi_if_sum
pub mod loop_with_if_phi_if_sum; // Phase 213: Pattern 3 AST-based if-sum lowerer (Phase 242-EX-A: supports complex conditions)
pub mod min_loop;
pub mod nested_loop_minimal; // Phase 188.3 P1: Pattern 6 nested loop minimal lowerer
pub mod simple_while_minimal; // Phase 188-Impl-1: Pattern 1 minimal lowerer
pub mod scan_with_init_minimal; // Phase 254 P1: Pattern 6 minimal lowerer (index_of/find/contains)
pub mod scan_with_init_reverse; // Phase 257 P0: Pattern 6 reverse scan lowerer (last_index_of)

View File

@ -1,397 +0,0 @@
//! Phase 188.3 P1: Pattern 6 (Nested Loop Minimal) JoinIR Lowerer
//!
//! Target: apps/tests/phase1883_nested_minimal.hako
//!
//! Code:
//! ```nyash
//! static box Main {
//! main() {
//! local sum = 0
//! local i = 0
//! loop(i < 3) {
//! local j = 0
//! loop(j < 3) {
//! sum = sum + 1
//! j = j + 1
//! }
//! i = i + 1
//! }
//! return sum
//! }
//! }
//! ```
//!
//! Expected JoinIR (4 functions):
//! ```text
//! fn main(i0, sum0):
//! Call(loop_step, [i0, sum0])
//! Ret 0
//!
//! fn loop_step(i, sum): // outer loop
//! exit_cond = !(i < 3)
//! Jump(k_exit, [sum], cond=exit_cond)
//! j0 = 0
//! Call(inner_step, [j0, i, sum])
//!
//! fn inner_step(j, i_outer, sum): // inner loop (tail recursion)
//! exit_cond = !(j < 3)
//! Jump(k_inner_exit, [i_outer, sum], cond=exit_cond)
//! sum_next = sum + 1
//! j_next = j + 1
//! Call(inner_step, [j_next, i_outer, sum_next])
//!
//! fn k_inner_exit(i, sum): // outer continuation (after inner loop)
//! i_next = i + 1
//! Call(loop_step, [i_next, sum])
//!
//! fn k_exit(sum):
//! Ret sum
//! ```
//!
//! ## Design Notes (Phase 188.3)
//!
//! - **4-function model**: main, loop_step (outer), inner_step, k_inner_exit, k_exit
//! - **Carrier design**: sum is passed as argument (not global)
//! - **Merge control**: continuation_func_ids includes k_exit, k_inner_exit, inner_step
//! - **Pattern1-based**: Both outer and inner are Pattern1 (no break/continue)
use crate::mir::join_ir::lowering::canonical_names as cn;
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
UnaryOp,
};
/// Canonical names for Pattern 6 continuations
pub const INNER_STEP: &str = "inner_step";
pub const K_INNER_EXIT: &str = "k_inner_exit";
/// Lower Pattern 6 (Nested Loop Minimal) to JoinIR
///
/// # Phase 188.3 P1: Minimal nested loop implementation
///
/// This version generates JoinIR using **JoinValueSpace** for unified ValueId allocation.
/// It uses the Local region (1000+) to avoid collision with Param region (100-999).
///
/// ## 4-Function Model
///
/// - **main(i0, sum0)**: Entry point, calls loop_step
/// - **loop_step(i, sum)**: Outer loop header, initializes & calls inner loop
/// - **inner_step(j, i_outer, sum)**: Inner loop body (tail recursion)
/// - **k_inner_exit(i, sum)**: Outer continuation after inner loop exits
/// - **k_exit(sum)**: Final exit continuation
///
/// # Arguments
///
/// * `_scope` - LoopScopeShape (reserved for future generic implementation)
/// * `join_value_space` - Unified ValueId allocator (Phase 202-A)
///
/// # Returns
///
/// * `Some(JoinModule)` - Successfully lowered to JoinIR
/// * `None` - Pattern not matched (fallback to other lowerers)
///
/// # Boundary Contract
///
/// This function returns a JoinModule with:
/// - **Input slots**: main() params (i0, sum0) for outer loop variables
/// - **Caller responsibility**: Create JoinInlineBoundary to map param ValueIds to host variables
pub(crate) fn lower_nested_loop_minimal(
_scope: LoopScopeShape,
join_value_space: &mut JoinValueSpace,
) -> Option<JoinModule> {
let mut join_module = JoinModule::new();
// ==================================================================
// Function IDs allocation
// ==================================================================
let main_id = JoinFuncId::new(0);
let loop_step_id = JoinFuncId::new(1);
let inner_step_id = JoinFuncId::new(2);
let k_inner_exit_id = JoinFuncId::new(3);
let k_exit_id = JoinFuncId::new(4);
// ==================================================================
// ValueId allocation - main() function
// ==================================================================
let i_main_param = join_value_space.alloc_param(); // i0 (outer loop var)
let sum_main_param = join_value_space.alloc_param(); // sum0 (carrier)
let loop_result = join_value_space.alloc_local(); // result from loop_step
let const_0_main = join_value_space.alloc_local(); // return value
// ==================================================================
// ValueId allocation - loop_step (outer loop)
// ==================================================================
let i_step_param = join_value_space.alloc_param(); // outer loop var
let sum_step_param = join_value_space.alloc_param(); // sum carrier
let const_3 = join_value_space.alloc_local(); // outer limit (3)
let cmp_i_lt = join_value_space.alloc_local(); // i < 3
let exit_cond_outer = join_value_space.alloc_local(); // !(i < 3)
let j0 = join_value_space.alloc_local(); // inner loop init (0)
// ==================================================================
// ValueId allocation - inner_step (inner loop)
// ==================================================================
let j_inner_param = join_value_space.alloc_param(); // inner loop var
let i_inner_param = join_value_space.alloc_param(); // outer var (read-only)
let sum_inner_param = join_value_space.alloc_param(); // sum carrier
let const_3_inner = join_value_space.alloc_local(); // inner limit (3)
let cmp_j_lt = join_value_space.alloc_local(); // j < 3
let exit_cond_inner = join_value_space.alloc_local(); // !(j < 3)
let const_1_sum = join_value_space.alloc_local(); // increment (1)
let sum_next = join_value_space.alloc_local(); // sum + 1
let const_1_j = join_value_space.alloc_local(); // increment (1)
let j_next = join_value_space.alloc_local(); // j + 1
// ==================================================================
// ValueId allocation - k_inner_exit (outer continuation)
// ==================================================================
let i_kexit_param = join_value_space.alloc_param(); // outer var
let sum_kexit_param = join_value_space.alloc_param(); // sum carrier
let const_1_i = join_value_space.alloc_local(); // increment (1)
let i_next = join_value_space.alloc_local(); // i + 1
// ==================================================================
// ValueId allocation - k_exit (final exit)
// ==================================================================
let sum_exit_param = join_value_space.alloc_param(); // sum for return
// ==================================================================
// main() function
// ==================================================================
let mut main_func =
JoinFunction::new(main_id, cn::MAIN.to_string(), vec![i_main_param, sum_main_param]);
// result = loop_step(i_main_param, sum_main_param)
main_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![i_main_param, sum_main_param],
k_next: None,
dst: Some(loop_result),
});
// return 0 (statement position)
main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_0_main,
value: ConstValue::Integer(0),
}));
main_func.body.push(JoinInst::Ret {
value: Some(const_0_main),
});
join_module.add_function(main_func);
// ==================================================================
// loop_step(i, sum) - outer loop header
// ==================================================================
let mut loop_step_func = JoinFunction::new(
loop_step_id,
cn::LOOP_STEP.to_string(),
vec![i_step_param, sum_step_param],
);
// exit_cond = !(i < 3)
// Step 1: const 3
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_3,
value: ConstValue::Integer(3),
}));
// Step 2: cmp_i_lt = (i < 3)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_i_lt,
op: CompareOp::Lt,
lhs: i_step_param,
rhs: const_3,
}));
// Step 3: exit_cond_outer = !cmp_i_lt
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: exit_cond_outer,
op: UnaryOp::Not,
operand: cmp_i_lt,
}));
// Jump(k_exit, [sum], cond=exit_cond_outer)
loop_step_func.body.push(JoinInst::Jump {
cont: k_exit_id.as_cont(),
args: vec![sum_step_param],
cond: Some(exit_cond_outer),
});
// j0 = 0 (inner loop initialization)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: j0,
value: ConstValue::Integer(0),
}));
// Call(inner_step, [j0, i, sum]) - no continuation (tail call)
loop_step_func.body.push(JoinInst::Call {
func: inner_step_id,
args: vec![j0, i_step_param, sum_step_param],
k_next: None,
dst: None,
});
join_module.add_function(loop_step_func);
// ==================================================================
// inner_step(j, i_outer, sum) - inner loop (tail recursion)
// ==================================================================
let mut inner_step_func = JoinFunction::new(
inner_step_id,
INNER_STEP.to_string(),
vec![j_inner_param, i_inner_param, sum_inner_param],
);
// exit_cond = !(j < 3)
// Step 1: const 3
inner_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_3_inner,
value: ConstValue::Integer(3),
}));
// Step 2: cmp_j_lt = (j < 3)
inner_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_j_lt,
op: CompareOp::Lt,
lhs: j_inner_param,
rhs: const_3_inner,
}));
// Step 3: exit_cond_inner = !cmp_j_lt
inner_step_func
.body
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: exit_cond_inner,
op: UnaryOp::Not,
operand: cmp_j_lt,
}));
// Jump(k_inner_exit, [i_outer, sum], cond=exit_cond_inner)
inner_step_func.body.push(JoinInst::Jump {
cont: k_inner_exit_id.as_cont(),
args: vec![i_inner_param, sum_inner_param],
cond: Some(exit_cond_inner),
});
// sum_next = sum + 1
// Step 1: const 1
inner_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1_sum,
value: ConstValue::Integer(1),
}));
// Step 2: sum_next = sum + 1
inner_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: sum_next,
op: BinOpKind::Add,
lhs: sum_inner_param,
rhs: const_1_sum,
}));
// j_next = j + 1
// Step 1: const 1
inner_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1_j,
value: ConstValue::Integer(1),
}));
// Step 2: j_next = j + 1
inner_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: j_next,
op: BinOpKind::Add,
lhs: j_inner_param,
rhs: const_1_j,
}));
// Call(inner_step, [j_next, i_outer, sum_next]) - tail recursion
inner_step_func.body.push(JoinInst::Call {
func: inner_step_id,
args: vec![j_next, i_inner_param, sum_next],
k_next: None,
dst: None,
});
join_module.add_function(inner_step_func);
// ==================================================================
// k_inner_exit(i, sum) - outer continuation (after inner loop)
// ==================================================================
let mut k_inner_exit_func = JoinFunction::new(
k_inner_exit_id,
K_INNER_EXIT.to_string(),
vec![i_kexit_param, sum_kexit_param],
);
// i_next = i + 1
// Step 1: const 1
k_inner_exit_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1_i,
value: ConstValue::Integer(1),
}));
// Step 2: i_next = i + 1
k_inner_exit_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_next,
op: BinOpKind::Add,
lhs: i_kexit_param,
rhs: const_1_i,
}));
// Call(loop_step, [i_next, sum]) - tail call to outer loop
k_inner_exit_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![i_next, sum_kexit_param],
k_next: None,
dst: None,
});
join_module.add_function(k_inner_exit_func);
// ==================================================================
// k_exit(sum) - final exit continuation
// ==================================================================
let mut k_exit_func = JoinFunction::new(k_exit_id, cn::K_EXIT.to_string(), vec![sum_exit_param]);
// return sum
k_exit_func.body.push(JoinInst::Ret {
value: Some(sum_exit_param),
});
join_module.add_function(k_exit_func);
// Set entry point
join_module.entry = Some(main_id);
eprintln!("[joinir/pattern6] Generated JoinIR for Nested Loop Minimal Pattern");
eprintln!("[joinir/pattern6] Functions: main, loop_step, inner_step, k_inner_exit, k_exit");
Some(join_module)
}

View File

@ -9,11 +9,6 @@
//! - Return value must be integer literal (other types → Err)
//! - Return can be in top-level if's then/else (recursive scan)
//! - Return in nested loop → Err
//!
//! # Design
//!
//! This collector extracts return info from AST, which is later converted
//! to JoinInst::Ret in the JoinIR lowerer for proper MIR generation.
use crate::ast::{ASTNode, LiteralValue};
@ -22,11 +17,6 @@ use crate::ast::{ASTNode, LiteralValue};
pub struct ReturnInfo {
/// The integer literal value (P1 scope: only integer literals supported)
pub value: i64,
/// The if condition (Some if return is inside an if block)
/// This is used to generate conditional jump in JoinIR
pub condition: Option<Box<ASTNode>>,
/// Whether the return is in the else branch (negates condition)
pub in_else: bool,
}
/// Collect return statement from loop body
@ -40,13 +30,13 @@ pub struct ReturnInfo {
///
/// # Returns
///
/// - `Ok(Some(info))` - Single return found with integer literal value and condition
/// - `Ok(Some(info))` - Single return found with integer literal value
/// - `Ok(None)` - No return found
/// - `Err(msg)` - Unsupported pattern (Fail-Fast)
pub fn collect_return_from_body(body: &[ASTNode]) -> Result<Option<ReturnInfo>, String> {
let mut found_returns: Vec<ReturnInfo> = Vec::new();
collect_returns_recursive(body, &mut found_returns, None, false)?;
collect_returns_recursive(body, &mut found_returns)?;
match found_returns.len() {
0 => Ok(None),
@ -59,18 +49,9 @@ pub fn collect_return_from_body(body: &[ASTNode]) -> Result<Option<ReturnInfo>,
}
/// Recursive helper to collect return statements from if branches
///
/// # Arguments
///
/// * `body` - Current body to scan
/// * `found` - Accumulator for found return statements
/// * `current_condition` - If condition if we're inside an if block
/// * `in_else` - Whether we're in the else branch
fn collect_returns_recursive(
body: &[ASTNode],
found: &mut Vec<ReturnInfo>,
current_condition: Option<&ASTNode>,
in_else: bool,
) -> Result<(), String> {
for stmt in body {
match stmt {
@ -97,24 +78,17 @@ fn collect_returns_recursive(
}
};
found.push(ReturnInfo {
value: return_value,
condition: current_condition.map(|c| Box::new(c.clone())),
in_else,
});
found.push(ReturnInfo { value: return_value });
}
ASTNode::If {
condition,
then_body,
else_body,
..
} => {
// Recurse into if branches with the condition tracked
// For then branch: use condition as-is
collect_returns_recursive(then_body, found, Some(condition.as_ref()), false)?;
// For else branch: mark in_else=true (condition should be negated)
// Recurse into if branches
collect_returns_recursive(then_body, found)?;
if let Some(else_b) = else_body {
collect_returns_recursive(else_b, found, Some(condition.as_ref()), true)?;
collect_returns_recursive(else_b, found)?;
}
}
ASTNode::Loop { body: nested, .. } => {
@ -198,97 +172,42 @@ mod tests {
#[test]
fn test_no_return() {
let body = vec![];
let result = collect_return_from_body(&body);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
let result = collect_return_from_body(&body).unwrap();
assert!(result.is_none());
}
#[test]
fn test_single_top_level_return() {
fn test_single_return() {
let body = vec![make_int_return(7)];
let result = collect_return_from_body(&body);
assert!(result.is_ok());
let info = result.unwrap().unwrap();
assert_eq!(info.value, 7);
assert!(info.condition.is_none()); // Top-level, no condition
assert!(!info.in_else);
let result = collect_return_from_body(&body).unwrap().unwrap();
assert_eq!(result.value, 7);
}
#[test]
fn test_return_in_if_then() {
let body = vec![make_if_with_return(42)];
let result = collect_return_from_body(&body);
assert!(result.is_ok());
let info = result.unwrap().unwrap();
assert_eq!(info.value, 42);
assert!(info.condition.is_some()); // Has condition from if
assert!(!info.in_else); // In then branch
fn test_return_in_if() {
let body = vec![make_if_with_return(5)];
let result = collect_return_from_body(&body).unwrap().unwrap();
assert_eq!(result.value, 5);
}
#[test]
fn test_return_in_if_else() {
let body = vec![ASTNode::If {
condition: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: Span::unknown(),
}),
then_body: vec![],
else_body: Some(vec![make_int_return(99)]),
span: Span::unknown(),
}];
let result = collect_return_from_body(&body);
assert!(result.is_ok());
let info = result.unwrap().unwrap();
assert_eq!(info.value, 99);
assert!(info.condition.is_some()); // Has condition from if
assert!(info.in_else); // In else branch (condition should be negated)
}
#[test]
fn test_multiple_returns_err() {
fn test_multiple_returns_error() {
let body = vec![make_int_return(1), make_int_return(2)];
let result = collect_return_from_body(&body);
assert!(result.is_err());
assert!(result.unwrap_err().contains("multiple return"));
}
#[test]
fn test_return_in_nested_loop_err() {
fn test_return_in_nested_loop_error() {
let body = vec![ASTNode::Loop {
condition: Box::new(ASTNode::Literal {
value: LiteralValue::Bool(true),
value: LiteralValue::Boolean(true),
span: Span::unknown(),
}),
body: vec![make_int_return(7)],
body: vec![make_int_return(3)],
span: Span::unknown(),
}];
let result = collect_return_from_body(&body);
assert!(result.is_err());
assert!(result.unwrap_err().contains("nested loop"));
}
#[test]
fn test_non_integer_return_err() {
let body = vec![ASTNode::Return {
value: Some(Box::new(ASTNode::Literal {
value: LiteralValue::String("hello".to_string()),
span: Span::unknown(),
})),
span: Span::unknown(),
}];
let result = collect_return_from_body(&body);
assert!(result.is_err());
assert!(result.unwrap_err().contains("integer literal"));
}
#[test]
fn test_void_return_err() {
let body = vec![ASTNode::Return {
value: None,
span: Span::unknown(),
}];
let result = collect_return_from_body(&body);
assert!(result.is_err());
assert!(result.unwrap_err().contains("void return"));
}
}

View File

@ -1,354 +0,0 @@
//! Return Jump Emitter
//!
//! Phase 284 P1: Extracted from loop_with_continue_minimal.rs (lines 430-530)
//!
//! This module provides a reusable helper for emitting conditional Jump to k_return
//! in JoinIR loop patterns (Pattern4/5 and future patterns).
//!
//! ## Design Rationale
//!
//! Pattern4/5 both need to handle early return statements in loop bodies:
//! ```nyash
//! loop(i < 10) {
//! i = i + 1
//! if (i == 3) {
//! return 7 // Early return
//! }
//! // ... loop body continues
//! }
//! ```
//!
//! The JoinIR representation emits a conditional Jump to k_return:
//! ```text
//! return_cond = (i_next == 3)
//! Jump(k_return, [], cond=return_cond)
//! ```
//!
//! This helper consolidates the code generation logic into a single SSOT,
//! preventing duplication across multiple pattern lowerers.
//!
//! ## Usage
//!
//! ```rust,ignore
//! use crate::mir::join_ir::lowering::return_jump_emitter::emit_return_conditional_jump;
//!
//! if let Some(ret_info) = return_info {
//! emit_return_conditional_jump(
//! &mut loop_step_func,
//! &Some(ret_info),
//! k_return_id,
//! &mut alloc_value,
//! &debug,
//! )?;
//! }
//! ```
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox;
use crate::mir::join_ir::lowering::return_collector::ReturnInfo;
use crate::mir::join_ir::{CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, MirLikeInst};
use crate::mir::ValueId;
/// Emit conditional Jump to k_return
///
/// Generates JoinIR instructions for early return handling in loop bodies.
///
/// # Arguments
///
/// * `loop_step_func` - The loop step function (body) to append instructions to
/// * `return_info` - Return statement metadata (value, condition, in_else flag)
/// * `k_return_id` - The k_return continuation function ID
/// * `loop_var_id` - ValueId of the loop variable to check (typically i_next after increment)
/// * `loop_var_name` - Name of the loop variable (for condition validation)
/// * `alloc_value` - Value ID allocator closure
/// * `debug` - Debug output box for logging
///
/// # Returns
///
/// * `Ok(())` - Instructions emitted successfully
/// * `Err(msg)` - Unsupported return pattern (Fail-Fast)
///
/// # Phase 284 P1 Scope
///
/// - Single return statement only
/// - Return condition: Simple comparison (loop_var == N)
/// - Return value: Integer literal
/// - Supports then/else branches (via `in_else` flag)
///
/// # Implementation Notes
///
/// The generated instructions follow this pattern:
///
/// **Unconditional return:**
/// ```text
/// Jump(k_return, [])
/// ```
///
/// **Conditional return (then branch):**
/// ```text
/// const_cmp = Const(3)
/// return_cond = Compare(Eq, i_next, const_cmp)
/// Jump(k_return, [], cond=return_cond)
/// ```
///
/// **Conditional return (else branch):**
/// ```text
/// const_cmp = Const(3)
/// return_cond = Compare(Ne, i_next, const_cmp) // Note: Ne (negated)
/// Jump(k_return, [], cond=return_cond)
/// ```
///
/// # Future Extensions (Pattern4/5 reuse)
///
/// This function is designed to be reusable across:
/// - Pattern4 (loop with continue)
/// - Pattern5 (infinite loop with early exit)
/// - Future patterns with early return support
///
/// When extending, consider:
/// - Complex condition support (beyond loop_var == N)
/// - Multiple return statements (Phase 284 P2+)
/// - Return value expressions (beyond integer literals)
pub fn emit_return_conditional_jump(
loop_step_func: &mut JoinFunction,
return_info: Option<&ReturnInfo>,
k_return_id: JoinFuncId,
loop_var_id: ValueId,
loop_var_name: &str,
alloc_value: &mut dyn FnMut() -> ValueId,
debug: &DebugOutputBox,
) -> Result<(), String> {
let Some(ret_info) = return_info else {
// No return statement - nothing to emit
return Ok(());
};
debug.log(
"phase284",
&format!(
"Generating return handling: value={}, has_condition={}",
ret_info.value,
ret_info.condition.is_some()
),
);
if let Some(ref cond_ast) = ret_info.condition {
// Return is inside an if block - need to evaluate condition
// Phase 284 P1: Only support simple comparison (loop_var == N)
// Extract the comparison value from the condition AST
if let ASTNode::BinaryOp {
operator: crate::ast::BinaryOperator::Equal,
left,
right,
..
} = cond_ast.as_ref()
{
// Check if left is variable and right is integer
let compare_value = match (left.as_ref(), right.as_ref()) {
(
ASTNode::Variable { name, .. },
ASTNode::Literal {
value: crate::ast::LiteralValue::Integer(n),
..
},
) if name == loop_var_name => Some(*n),
_ => None,
};
if let Some(cmp_val) = compare_value {
// Generate: return_cond = (i_next == cmp_val)
let return_cmp_const = alloc_value();
let return_cond = alloc_value();
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: return_cmp_const,
value: ConstValue::Integer(cmp_val),
}));
// Phase 284 P1: Use loop_var_id (i_next) because fixture has increment BEFORE the if check
// Execution: i_next = i_param + 1, then check if i_next == compare_value
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: return_cond,
op: if ret_info.in_else {
CompareOp::Ne
} else {
CompareOp::Eq
},
lhs: loop_var_id, // Use loop_var_id (post-increment value)
rhs: return_cmp_const,
}));
// Generate: Jump(k_return, [], cond=return_cond)
loop_step_func.body.push(JoinInst::Jump {
cont: k_return_id.as_cont(),
args: vec![],
cond: Some(return_cond),
});
debug.log(
"phase284",
&format!("Return condition: {} == {} → jump to k_return", loop_var_name, cmp_val),
);
Ok(())
} else {
debug.log(
"phase284",
"Return condition not supported (not loop_var == N pattern)",
);
Err(format!(
"Phase 284 P1 scope: return condition variable must match loop variable '{}' (got: {:?})",
loop_var_name, left
))
}
} else {
Err(
"Phase 284 P1 scope: return condition must be Equal comparison".to_string(),
)
}
} else {
// Unconditional return - always jump to k_return
loop_step_func.body.push(JoinInst::Jump {
cont: k_return_id.as_cont(),
args: vec![],
cond: None,
});
debug.log("phase284", "Unconditional return → jump to k_return");
Ok(())
}
}
// ============================================================================
// Unit Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Span;
fn make_debug() -> DebugOutputBox {
DebugOutputBox::new_dev("test")
}
#[test]
fn test_unconditional_return() {
let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]);
let info = ReturnInfo {
value: 42,
condition: None,
in_else: false,
};
let return_info = Some(&info);
let k_return_id = JoinFuncId(99);
let loop_var_id = ValueId(100);
let mut counter = 0u32;
let mut alloc = || {
counter += 1;
ValueId(1000 + counter)
};
let debug = make_debug();
let result = emit_return_conditional_jump(
&mut func,
return_info,
k_return_id,
loop_var_id,
"i",
&mut alloc,
&debug,
);
assert!(result.is_ok());
assert_eq!(func.body.len(), 1);
match &func.body[0] {
JoinInst::Jump { cont, args, cond } => {
assert_eq!(*cont, k_return_id.as_cont());
assert_eq!(args.len(), 0);
assert!(cond.is_none());
}
_ => panic!("Expected Jump instruction"),
}
}
#[test]
fn test_no_return() {
let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]);
let return_info: Option<&ReturnInfo> = None;
let k_return_id = JoinFuncId(99);
let loop_var_id = ValueId(100);
let mut counter = 0u32;
let mut alloc = || {
counter += 1;
ValueId(1000 + counter)
};
let debug = make_debug();
let result = emit_return_conditional_jump(
&mut func,
return_info,
k_return_id,
loop_var_id,
"i",
&mut alloc,
&debug,
);
assert!(result.is_ok());
assert_eq!(func.body.len(), 0); // No instructions emitted
}
#[test]
fn test_conditional_return_success() {
// Phase 284 P1: Test successful conditional return generation
let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]);
let condition = Box::new(ASTNode::BinaryOp {
operator: crate::ast::BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: crate::ast::LiteralValue::Integer(3),
span: Span::unknown(),
}),
span: Span::unknown(),
});
let info = ReturnInfo {
value: 7,
condition: Some(condition),
in_else: false,
};
let return_info = Some(&info);
let k_return_id = JoinFuncId(99);
let loop_var_id = ValueId(100); // i_next
let mut counter = 0u32;
let mut alloc = || {
counter += 1;
ValueId(1000 + counter)
};
let debug = make_debug();
let result = emit_return_conditional_jump(
&mut func,
return_info,
k_return_id,
loop_var_id,
"i",
&mut alloc,
&debug,
);
// Should succeed
assert!(result.is_ok(), "Failed: {:?}", result.err());
// Check generated instructions
// 1. Const(3)
// 2. Compare(Eq, loop_var_id, const)
// 3. Jump(k_return, [], cond)
assert_eq!(func.body.len(), 3);
}
}