From 0dd9ec97047149b6c4d6c5c9c17bfef5f78e1765 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Fri, 19 Dec 2025 20:30:58 +0900 Subject: [PATCH] feat(normalization): Phase 252 P1 - DebugOutputBox unification + this.methodcall tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **P1-1: Local Refactor**: - loop_with_if_phi_if_sum.rs: Replace 8 eprintln! with DebugOutputBox - Default output: 0 lines (clean) - With NYASH_JOINIR_DEBUG=1: Rich trace output **P1-2: Unit Tests** (3/3 PASS): - test_this_methodcall_in_condition: BoxCall generation - test_this_methodcall_requires_context: Static box requirement - test_this_methodcall_disallowed_method: Method whitelist enforcement **P1-3: v2 Smoke Fixture**: - phase252_p0_this_methodcall_break_cond_min.hako - StringUtils.count_leading_digits with this.is_digit break condition - VM + LLVM integration test scripts **P1-4: Test Fixes**: - condition_lowering_box.rs: current_static_box_name: None - loop_with_break_minimal/tests.rs: current_static_box_name: None ๐Ÿงช Tests: - cargo test condition_lowerer: 3 new tests PASS - cargo check --lib: PASS (0 errors) ๐Ÿ“Š Quick Profile Status: - json_pp_vm: โœ… PASS - json_lint_vm: โŒ FAIL (deferred to Phase 253) ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- ...252_p0_this_methodcall_break_cond_min.hako | 28 ++ src/mir/join_ir/lowering/condition_lowerer.rs | 278 ++++++++++++++++-- .../lowering/condition_lowering_box.rs | 22 ++ .../lowering/loop_with_break_minimal/tests.rs | 1 + .../lowering/loop_with_if_phi_if_sum.rs | 55 ++-- ..._p0_this_methodcall_break_cond_llvm_exe.sh | 19 ++ ...ase252_p0_this_methodcall_break_cond_vm.sh | 19 ++ 7 files changed, 375 insertions(+), 47 deletions(-) create mode 100644 apps/tests/phase252_p0_this_methodcall_break_cond_min.hako create mode 100644 tools/smokes/v2/profiles/integration/apps/phase252_p0_this_methodcall_break_cond_llvm_exe.sh create mode 100644 tools/smokes/v2/profiles/integration/apps/phase252_p0_this_methodcall_break_cond_vm.sh diff --git a/apps/tests/phase252_p0_this_methodcall_break_cond_min.hako b/apps/tests/phase252_p0_this_methodcall_break_cond_min.hako new file mode 100644 index 00000000..2e361c13 --- /dev/null +++ b/apps/tests/phase252_p0_this_methodcall_break_cond_min.hako @@ -0,0 +1,28 @@ +// Phase 252 P0: this.methodcall in break condition +// Minimal test fixture - simplified to avoid '-' operator (Phase 253 issue) + +static box StringUtils { + is_digit(ch) { + return ch >= "0" + } + + count_leading_digits(s) { + local i = 0 + local len = s.length() + loop(i < len) { + local ch = s.substring(i, i + 1) + if not this.is_digit(ch) { + break + } + i = i + 1 + } + return i + } +} + +static box Main { + main() { + local result = StringUtils.count_leading_digits("123abc") + return result + } +} diff --git a/src/mir/join_ir/lowering/condition_lowerer.rs b/src/mir/join_ir/lowering/condition_lowerer.rs index 00470a50..db08993a 100644 --- a/src/mir/join_ir/lowering/condition_lowerer.rs +++ b/src/mir/join_ir/lowering/condition_lowerer.rs @@ -50,6 +50,12 @@ use super::method_call_lowerer::MethodCallLowerer; /// 1. ConditionEnv (loop parameters, captured variables) /// 2. LoopBodyLocalEnv (body-local variables like `ch`) /// +/// # Phase 252: This-Method Support +/// +/// When lowering conditions in static box methods (e.g., `StringUtils.trim_end/1`), +/// the `current_static_box_name` parameter enables `this.method(...)` calls to be +/// resolved to the appropriate static box method. +/// /// # Example /// /// ```ignore @@ -73,6 +79,7 @@ use super::method_call_lowerer::MethodCallLowerer; /// &mut alloc_value, /// &env, /// Some(&body_env), // Phase 92 P2-2: Body-local support +/// Some("StringUtils"), // Phase 252: Static box name for this.method /// )?; /// ``` pub fn lower_condition_to_joinir( @@ -80,19 +87,27 @@ pub fn lower_condition_to_joinir( alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2 + current_static_box_name: Option<&str>, // Phase 252 ) -> Result<(ValueId, Vec), String> { let mut instructions = Vec::new(); - let result_value = lower_condition_recursive(cond_ast, alloc_value, env, body_local_env, &mut instructions)?; + let result_value = lower_condition_recursive( + cond_ast, + alloc_value, + env, + body_local_env, + current_static_box_name, + &mut instructions, + )?; Ok((result_value, instructions)) } -/// Convenience wrapper: lower a condition without body-local support. +/// Convenience wrapper: lower a condition without body-local or static box support. pub fn lower_condition_to_joinir_no_body_locals( cond_ast: &ASTNode, alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, ) -> Result<(ValueId, Vec), String> { - lower_condition_to_joinir(cond_ast, alloc_value, env, None) + lower_condition_to_joinir(cond_ast, alloc_value, env, None, None) } /// Recursive helper for condition lowering @@ -102,11 +117,17 @@ pub fn lower_condition_to_joinir_no_body_locals( /// # Phase 92 P2-2 /// /// Added `body_local_env` parameter to support body-local variable resolution. +/// +/// # Phase 252 +/// +/// Added `current_static_box_name` parameter to support `this.method(...)` calls +/// in static box method conditions. fn lower_condition_recursive( cond_ast: &ASTNode, alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2 + current_static_box_name: Option<&str>, // Phase 252 instructions: &mut Vec, ) -> Result { match cond_ast { @@ -123,10 +144,10 @@ fn lower_condition_recursive( | BinaryOperator::LessEqual | BinaryOperator::GreaterEqual | BinaryOperator::Greater => { - lower_comparison(operator, left, right, alloc_value, env, body_local_env, instructions) + lower_comparison(operator, left, right, alloc_value, env, body_local_env, current_static_box_name, instructions) } - BinaryOperator::And => lower_logical_and(left, right, alloc_value, env, body_local_env, instructions), - BinaryOperator::Or => lower_logical_or(left, right, alloc_value, env, body_local_env, instructions), + BinaryOperator::And => lower_logical_and(left, right, alloc_value, env, body_local_env, current_static_box_name, instructions), + BinaryOperator::Or => lower_logical_or(left, right, alloc_value, env, body_local_env, current_static_box_name, instructions), _ => Err(format!( "Unsupported binary operator in condition: {:?}", operator @@ -138,7 +159,7 @@ fn lower_condition_recursive( operator: UnaryOperator::Not, operand, .. - } => lower_not_operator(operand, alloc_value, env, body_local_env, instructions), + } => lower_not_operator(operand, alloc_value, env, body_local_env, current_static_box_name, instructions), // Phase 92 P2-2: Variables - resolve from ConditionEnv or LoopBodyLocalEnv ASTNode::Variable { name, .. } => { @@ -158,6 +179,69 @@ fn lower_condition_recursive( // Literals - emit as constants ASTNode::Literal { value, .. } => lower_literal(value, alloc_value, instructions), + // Phase 252: MethodCall support (this.method or builtin methods) + ASTNode::MethodCall { + object, + method, + arguments, + .. + } => { + // Check if this is a this.method(...) call + match object.as_ref() { + ASTNode::Me { .. } => { + // this.method(...) - requires current_static_box_name + let box_name = current_static_box_name.ok_or_else(|| { + format!( + "this.{}(...) requires current_static_box_name (not in static box context)", + method + ) + })?; + + // Check if method is allowed in condition context via UserMethodPolicy + if !super::user_method_policy::UserMethodPolicy::allowed_in_condition(box_name, method) { + return Err(format!( + "User-defined method not allowed in loop condition: {}.{}() (not whitelisted)", + box_name, method + )); + } + + // Lower arguments using lower_for_init whitelist + // (Arguments are value expressions, not conditions, so we use init whitelist) + let mut arg_vals = Vec::new(); + for arg_ast in arguments { + let arg_val = lower_value_expression( + arg_ast, + alloc_value, + env, + body_local_env, + current_static_box_name, + instructions, + )?; + arg_vals.push(arg_val); + } + + // Emit BoxCall instruction + let dst = alloc_value(); + instructions.push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(dst), + box_name: box_name.to_string(), + method: method.clone(), + args: arg_vals, + })); + + Ok(dst) + } + _ => { + // Not this.method - treat as value expression (builtin methods via CoreMethodId) + lower_value_expression(object, alloc_value, env, body_local_env, current_static_box_name, instructions)?; + Err(format!( + "MethodCall on non-this object not yet supported in condition: {:?}", + cond_ast + )) + } + } + } + _ => Err(format!("Unsupported AST node in condition: {:?}", cond_ast)), } } @@ -170,11 +254,12 @@ fn lower_comparison( alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2 + current_static_box_name: Option<&str>, // Phase 252 instructions: &mut Vec, ) -> Result { // Lower left and right sides - let lhs = lower_value_expression(left, alloc_value, env, body_local_env, instructions)?; - let rhs = lower_value_expression(right, alloc_value, env, body_local_env, instructions)?; + let lhs = lower_value_expression(left, alloc_value, env, body_local_env, current_static_box_name, instructions)?; + let rhs = lower_value_expression(right, alloc_value, env, body_local_env, current_static_box_name, instructions)?; let dst = alloc_value(); let cmp_op = match operator { @@ -205,11 +290,12 @@ fn lower_logical_and( alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2 + current_static_box_name: Option<&str>, // Phase 252 instructions: &mut Vec, ) -> Result { // Logical AND: evaluate both sides and combine - let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, instructions)?; - let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, instructions)?; + let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, current_static_box_name, instructions)?; + let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, current_static_box_name, instructions)?; let dst = alloc_value(); // Emit BinOp And instruction @@ -230,11 +316,12 @@ fn lower_logical_or( alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2 + current_static_box_name: Option<&str>, // Phase 252 instructions: &mut Vec, ) -> Result { // Logical OR: evaluate both sides and combine - let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, instructions)?; - let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, instructions)?; + let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, current_static_box_name, instructions)?; + let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, current_static_box_name, instructions)?; let dst = alloc_value(); // Emit BinOp Or instruction @@ -254,9 +341,10 @@ fn lower_not_operator( alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2 + current_static_box_name: Option<&str>, // Phase 252 instructions: &mut Vec, ) -> Result { - let operand_val = lower_condition_recursive(operand, alloc_value, env, body_local_env, instructions)?; + let operand_val = lower_condition_recursive(operand, alloc_value, env, body_local_env, current_static_box_name, instructions)?; let dst = alloc_value(); // Emit UnaryOp Not instruction @@ -308,11 +396,17 @@ fn lower_literal( /// /// Added `body_local_env` parameter to support body-local variable resolution /// (e.g., `ch` in `ch == '\\'`). +/// +/// # Phase 252 +/// +/// Added `current_static_box_name` parameter to support `this.method(...)` calls +/// in argument expressions. pub fn lower_value_expression( expr: &ASTNode, alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2 + current_static_box_name: Option<&str>, // Phase 252 instructions: &mut Vec, ) -> Result { match expr { @@ -340,7 +434,7 @@ pub fn lower_value_expression( left, right, .. - } => lower_arithmetic_binop(operator, left, right, alloc_value, env, body_local_env, instructions), + } => lower_arithmetic_binop(operator, left, right, alloc_value, env, body_local_env, current_static_box_name, instructions), // Phase 224-C: MethodCall support with arguments (e.g., s.length(), s.indexOf(ch)) ASTNode::MethodCall { @@ -350,7 +444,7 @@ pub fn lower_value_expression( .. } => { // 1. Lower receiver (object) to ValueId - let recv_val = lower_value_expression(object, alloc_value, env, body_local_env, instructions)?; + let recv_val = lower_value_expression(object, alloc_value, env, body_local_env, current_static_box_name, instructions)?; // 2. Lower method call using MethodCallLowerer (will lower arguments internally) MethodCallLowerer::lower_for_condition( @@ -378,10 +472,11 @@ fn lower_arithmetic_binop( alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2 + current_static_box_name: Option<&str>, // Phase 252 instructions: &mut Vec, ) -> Result { - let lhs = lower_value_expression(left, alloc_value, env, body_local_env, instructions)?; - let rhs = lower_value_expression(right, alloc_value, env, body_local_env, instructions)?; + let lhs = lower_value_expression(left, alloc_value, env, body_local_env, current_static_box_name, instructions)?; + let rhs = lower_value_expression(right, alloc_value, env, body_local_env, current_static_box_name, instructions)?; let dst = alloc_value(); let bin_op = match operator { @@ -616,7 +711,7 @@ mod tests { }; // Phase 92 P2-2: Use lower_condition_to_joinir with body_local_env - let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env)); + let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env), None); assert!( result.is_ok(), "Body-local variable resolution should succeed" @@ -678,7 +773,7 @@ mod tests { span: Span::unknown(), }; - let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env)); + let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env), None); assert!(result.is_ok(), "Variable resolution should succeed"); let (_cond_value, instructions) = result.unwrap(); @@ -724,7 +819,7 @@ mod tests { span: Span::unknown(), }; - let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env)); + let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env), None); assert!(result.is_err(), "Undefined variable should fail"); let err = result.unwrap_err(); @@ -737,4 +832,145 @@ mod tests { "Error message should indicate variable was not found" ); } + + /// Phase 252 P1: Test this.methodcall(...) in conditions + /// + /// Verifies that user-defined static box method calls work in conditions + #[test] + fn test_this_methodcall_in_condition() { + let env = create_test_env(); + let mut value_counter = 2u32; + let mut alloc_value = || { + let id = ValueId(value_counter); + value_counter += 1; + id + }; + + // AST: not this.is_whitespace(ch) + // Simulates StringUtils.trim_end break condition + let method_call = ASTNode::MethodCall { + object: Box::new(ASTNode::Me { + span: Span::unknown(), + }), + method: "is_whitespace".to_string(), + arguments: vec![ASTNode::Variable { + name: "ch".to_string(), + span: Span::unknown(), + }], + span: Span::unknown(), + }; + + let ast = ASTNode::UnaryOp { + operator: crate::ast::UnaryOperator::Not, + operand: Box::new(method_call), + span: Span::unknown(), + }; + + // Register 'ch' variable for the test + let mut env = env; + env.insert("ch".to_string(), ValueId(100)); + + let result = lower_condition_to_joinir( + &ast, + &mut alloc_value, + &env, + None, + Some("StringUtils"), // Phase 252: static box context + ); + + assert!(result.is_ok(), "this.methodcall should succeed: {:?}", result); + + let (_cond_value, instructions) = result.unwrap(); + + // Should have: BoxCall for is_whitespace, UnaryOp(Not) + assert!( + instructions.len() >= 2, + "Should generate BoxCall + Not instructions" + ); + + // Verify BoxCall instruction exists + let has_box_call = instructions.iter().any(|inst| matches!( + inst, + JoinInst::Compute(MirLikeInst::BoxCall { method, .. }) if method == "is_whitespace" + )); + assert!( + has_box_call, + "Should generate BoxCall for is_whitespace" + ); + } + + /// Phase 252 P1: Test this.methodcall fails without static box context + #[test] + fn test_this_methodcall_requires_context() { + let env = create_test_env(); + let mut value_counter = 2u32; + let mut alloc_value = || { + let id = ValueId(value_counter); + value_counter += 1; + id + }; + + // AST: this.is_whitespace(ch) + let ast = ASTNode::MethodCall { + object: Box::new(ASTNode::Me { + span: Span::unknown(), + }), + method: "is_whitespace".to_string(), + arguments: vec![], + span: Span::unknown(), + }; + + let result = lower_condition_to_joinir( + &ast, + &mut alloc_value, + &env, + None, + None, // No static box context + ); + + assert!(result.is_err(), "this.methodcall should fail without context"); + let err = result.unwrap_err(); + assert!( + err.contains("current_static_box_name"), + "Error should mention missing static box context" + ); + } + + /// Phase 252 P1: Test disallowed method fails + #[test] + fn test_this_methodcall_disallowed_method() { + let env = create_test_env(); + let mut value_counter = 2u32; + let mut alloc_value = || { + let id = ValueId(value_counter); + value_counter += 1; + id + }; + + // AST: this.trim("test") - trim is NOT allowed in conditions + let ast = ASTNode::MethodCall { + object: Box::new(ASTNode::Me { + span: Span::unknown(), + }), + method: "trim".to_string(), + arguments: vec![], + span: Span::unknown(), + }; + + let result = lower_condition_to_joinir( + &ast, + &mut alloc_value, + &env, + None, + Some("StringUtils"), + ); + + assert!(result.is_err(), "Disallowed method should fail"); + let err = result.unwrap_err(); + assert!( + err.contains("not allowed") || err.contains("not whitelisted"), + "Error should indicate method is not allowed: {}", + err + ); + } } diff --git a/src/mir/join_ir/lowering/condition_lowering_box.rs b/src/mir/join_ir/lowering/condition_lowering_box.rs index 076524f7..6b6b484f 100644 --- a/src/mir/join_ir/lowering/condition_lowering_box.rs +++ b/src/mir/join_ir/lowering/condition_lowering_box.rs @@ -30,6 +30,7 @@ use crate::mir::ValueId; /// * `loop_var_id` - ValueId of the loop variable in JoinIR space /// * `scope` - Reference to ScopeManager for variable lookup /// * `alloc_value` - ValueId allocator function +/// * `current_static_box_name` - Phase 252: Name of the static box being lowered (for this.method) /// /// # Example /// @@ -39,6 +40,7 @@ use crate::mir::ValueId; /// loop_var_id: ValueId(1), /// scope: &scope_manager, /// alloc_value: &mut alloc_fn, +/// current_static_box_name: Some("StringUtils".to_string()), // Phase 252 /// }; /// /// let value_id = lowerer.lower_condition(&ast, &context)?; @@ -55,6 +57,24 @@ pub struct ConditionContext<'a, S: ScopeManager> { /// ValueId allocator function pub alloc_value: &'a mut dyn FnMut() -> ValueId, + + /// Phase 252: Name of the static box being lowered (for this.method(...) support) + /// + /// When lowering a static box method (e.g., `StringUtils.trim_end/1`), + /// this field contains the box name ("StringUtils"). This allows + /// `this.is_whitespace(...)` to be resolved to `StringUtils.is_whitespace(...)`. + /// + /// # Example + /// + /// ```ignore + /// // Function: StringUtils.trim_end/1 + /// // Condition: not this.is_whitespace(s.substring(i, i + 1)) + /// // current_static_box_name: Some("StringUtils") + /// // โ†’ Resolves to: StringUtils.is_whitespace(...) + /// ``` + /// + /// Set to `None` for non-static-box contexts (e.g., `Main.main()` loops). + pub current_static_box_name: Option, } /// Phase 244: Unified condition lowering interface @@ -230,6 +250,7 @@ mod tests { loop_var_id: ValueId(1), scope: &scope, alloc_value: &mut alloc_fn, + current_static_box_name: None, // Phase 252: No static box in test }; // AST: i < 10 @@ -285,6 +306,7 @@ mod tests { loop_var_id: ValueId(1), scope: &scope, alloc_value: &mut alloc_fn, + current_static_box_name: None, // Phase 252: No static box in test }; assert_eq!(context.loop_var_name, "i"); diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs b/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs index 6cc6cd1b..a55de90f 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs @@ -149,6 +149,7 @@ fn test_pattern2_header_condition_via_exprlowerer() { condition_only_recipe: None, // Phase 93 P0: None for normal loops body_local_derived_recipe: None, // Phase 94: None for normal loops balanced_depth_scan_recipe: None, // Phase 107: None for normal loops + current_static_box_name: None, // Phase 252: No static box context in test }); assert!(result.is_ok(), "ExprLowerer header path should succeed"); diff --git a/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs b/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs index 488f7154..3bbe6b76 100644 --- a/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs +++ b/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs @@ -32,6 +32,7 @@ use crate::ast::ASTNode; use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta; use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::condition_lowerer::lower_value_expression; +use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; // Phase 252 P1 use crate::mir::join_ir::lowering::exit_meta_builder::IfSumExitMetaBuilderBox; #[cfg(debug_assertions)] use crate::mir::join_ir::lowering::condition_pattern::{ @@ -65,7 +66,9 @@ pub fn lower_if_sum_pattern( cond_env: &ConditionEnv, join_value_space: &mut JoinValueSpace, ) -> Result<(JoinModule, JoinFragmentMeta), String> { - eprintln!("[joinir/pattern3/if-sum] Starting AST-based if-sum lowering"); + // Phase 252 P1: Use DebugOutputBox for unified trace output + let trace = DebugOutputBox::new_dev("joinir/pattern3/if-sum"); + trace.log("start", "Starting AST-based if-sum lowering"); // Allocator for extracting condition values let mut alloc_value = || join_value_space.alloc_local(); @@ -86,9 +89,9 @@ pub fn lower_if_sum_pattern( // Uses cond_env for variable resolution (e.g., `len` in `i < len`) let (loop_var, loop_op, loop_lhs_val, loop_limit_val, loop_limit_insts) = extract_loop_condition(loop_condition, &mut alloc_value, cond_env)?; - eprintln!( - "[joinir/pattern3/if-sum] Loop condition: {} {:?} ValueId({})", - loop_var, loop_op, loop_limit_val.0 + trace.log( + "loop-cond", + &format!("{} {:?} ValueId({})", loop_var, loop_op, loop_limit_val.0) ); // Step 2: Extract if condition info (e.g., i > 0 โ†’ var="i", op=Gt, value=ValueId) @@ -96,23 +99,23 @@ pub fn lower_if_sum_pattern( // Phase 242-EX-A: Now supports complex LHS let (if_var, if_op, if_lhs_val, if_value_val, if_value_insts) = extract_if_condition(if_stmt, &mut alloc_value, cond_env)?; - eprintln!( - "[joinir/pattern3/if-sum] If condition: {} {:?} ValueId({})", - if_var, if_op, if_value_val.0 + trace.log( + "if-cond", + &format!("{} {:?} ValueId({})", if_var, if_op, if_value_val.0) ); // Step 3: Extract then-branch update (e.g., sum = sum + 1 โ†’ var="sum", addend=1) let (update_var, update_addend) = extract_then_update(if_stmt)?; - eprintln!( - "[joinir/pattern3/if-sum] Then update: {} += {}", - update_var, update_addend + trace.log( + "then-update", + &format!("{} += {}", update_var, update_addend) ); // Step 4: Extract counter update (e.g., i = i + 1 โ†’ var="i", step=1) let (counter_var, counter_step) = extract_counter_update(body, &loop_var)?; - eprintln!( - "[joinir/pattern3/if-sum] Counter update: {} += {}", - counter_var, counter_step + trace.log( + "counter-update", + &format!("{} += {}", counter_var, counter_step) ); // Step 5: Generate JoinIR @@ -331,18 +334,18 @@ pub fn lower_if_sum_pattern( // sum_final is the k_exit return value - this is what `return sum` should use let fragment_meta = JoinFragmentMeta::with_expr_result(sum_final, exit_meta); - eprintln!("[joinir/pattern3/if-sum] Generated AST-based JoinIR"); - eprintln!( - "[joinir/pattern3/if-sum] Loop: {} {:?} ValueId({})", - loop_var, loop_op, loop_limit_val.0 + trace.log("complete", "Generated AST-based JoinIR"); + trace.log( + "summary-loop", + &format!("{} {:?} ValueId({})", loop_var, loop_op, loop_limit_val.0) ); - eprintln!( - "[joinir/pattern3/if-sum] If: {} {:?} ValueId({})", - if_var, if_op, if_value_val.0 + trace.log( + "summary-if", + &format!("{} {:?} ValueId({})", if_var, if_op, if_value_val.0) ); - eprintln!( - "[joinir/pattern3/if-sum] Phase 215-2: expr_result={:?}", - sum_final + trace.log( + "summary-result", + &format!("expr_result={:?}", sum_final) ); Ok((join_module, fragment_meta)) @@ -411,7 +414,7 @@ where column: 1, }, }; - lower_value_expression(&var_node, alloc_value, cond_env, None, &mut limit_instructions)? // Phase 92 P2-2 + lower_value_expression(&var_node, alloc_value, cond_env, None, None, &mut limit_instructions)? // Phase 92 P2-2 + Phase 252 } }; @@ -447,10 +450,10 @@ where // Lower left-hand side (complex expression) let mut instructions = Vec::new(); - let lhs_val = lower_value_expression(left, alloc_value, cond_env, None, &mut instructions)?; // Phase 92 P2-2 + let lhs_val = lower_value_expression(left, alloc_value, cond_env, None, None, &mut instructions)?; // Phase 92 P2-2 + Phase 252 // Lower right-hand side - let rhs_val = lower_value_expression(right, alloc_value, cond_env, None, &mut instructions)?; // Phase 92 P2-2 + let rhs_val = lower_value_expression(right, alloc_value, cond_env, None, None, &mut instructions)?; // Phase 92 P2-2 + Phase 252 // Extract base variable name from LHS if possible let var_name = extract_base_variable(left); diff --git a/tools/smokes/v2/profiles/integration/apps/phase252_p0_this_methodcall_break_cond_llvm_exe.sh b/tools/smokes/v2/profiles/integration/apps/phase252_p0_this_methodcall_break_cond_llvm_exe.sh new file mode 100644 index 00000000..691b435f --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase252_p0_this_methodcall_break_cond_llvm_exe.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Phase 252 P0: this.methodcall in break condition (LLVM) +set -euo pipefail + +HAKO_PATH="apps/tests/phase252_p0_this_methodcall_break_cond_min.hako" + +# Phase 252: Test StringUtils.count_leading_digits with this.is_digit break condition +EXPECTED_EXIT=3 # "123abc" has 3 leading digits + +NYASH_LLVM_USE_HARNESS=1 $HAKORUNE_BIN --backend llvm "$HAKO_PATH" +actual_exit=$? + +if [[ $actual_exit -eq $EXPECTED_EXIT ]]; then + echo "โœ… phase252_p0_this_methodcall_break_cond_llvm_exe: PASS (exit=$actual_exit)" + exit 0 +else + echo "โŒ phase252_p0_this_methodcall_break_cond_llvm_exe: FAIL (expected=$EXPECTED_EXIT, got=$actual_exit)" + exit 1 +fi diff --git a/tools/smokes/v2/profiles/integration/apps/phase252_p0_this_methodcall_break_cond_vm.sh b/tools/smokes/v2/profiles/integration/apps/phase252_p0_this_methodcall_break_cond_vm.sh new file mode 100644 index 00000000..c45b5535 --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase252_p0_this_methodcall_break_cond_vm.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Phase 252 P0: this.methodcall in break condition (VM) +set -euo pipefail + +HAKO_PATH="apps/tests/phase252_p0_this_methodcall_break_cond_min.hako" + +# Phase 252: Test StringUtils.count_leading_digits with this.is_digit break condition +EXPECTED_EXIT=3 # "123abc" has 3 leading digits + +$HAKORUNE_BIN --backend vm "$HAKO_PATH" +actual_exit=$? + +if [[ $actual_exit -eq $EXPECTED_EXIT ]]; then + echo "โœ… phase252_p0_this_methodcall_break_cond_vm: PASS (exit=$actual_exit)" + exit 0 +else + echo "โŒ phase252_p0_this_methodcall_break_cond_vm: FAIL (expected=$EXPECTED_EXIT, got=$actual_exit)" + exit 1 +fi