feat(pattern8): Phase 269 P1 - SSA fix and call_method type annotation
Phase 269 P1.0: Pattern8 SSA correctness - Add PHI node for loop variable `i` in pattern8_scan_bool_predicate.rs - Ensure proper SSA form: i_current = phi [(preheader, i_init), (step, i_next)] - Create loop_predicate_scan.rs for Pattern8 Frag emission - Pre-allocate PHI destination before block generation - Use insert_phi_at_head_spanned() for span synchronization Phase 269 P1.1: call_method return type SSOT propagation - Add callee_sig_name() helper in annotation.rs for function name formatting - Annotate call_method return types in emit_unified_call_impl() - Use module signature as SSOT for return type resolution (no hardcoding) - Arity-aware function name: "BoxName.method/arity" Fixes: is_integer() now correctly returns Bool instead of String Test: Simple call_method test returns exit=7 (loop test has pre-existing bug) Unit tests: All 1389 tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -54,9 +54,9 @@ struct BoolPredicateScanParts {
|
||||
step_lit: i64,
|
||||
}
|
||||
|
||||
/// Phase 259 P0: Detection for Pattern 8 (BoolPredicateScan)
|
||||
/// Phase 269 P1: Detection for Pattern 8 (BoolPredicateScan)
|
||||
/// Now uses EdgeCFG Frag lowering via emission entrypoint
|
||||
pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool {
|
||||
eprintln!("[pattern8/can_lower] Called for function: {}", ctx.func_name);
|
||||
match extract_bool_predicate_scan_parts(ctx.condition, ctx.body) {
|
||||
Ok(Some(_)) => {
|
||||
if ctx.debug {
|
||||
@ -101,8 +101,7 @@ fn extract_bool_predicate_scan_parts(
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
) -> Result<Option<BoolPredicateScanParts>, String> {
|
||||
eprintln!("[pattern8/extract] Starting extraction");
|
||||
eprintln!("[pattern8/extract] Body statements: {}", body.len());
|
||||
// Phase 269 P1: Debug output removed (was breaking quick smoke output)
|
||||
|
||||
// 1. Check loop condition: i < s.length()
|
||||
let (loop_var, haystack) = match condition {
|
||||
@ -114,10 +113,7 @@ fn extract_bool_predicate_scan_parts(
|
||||
} => {
|
||||
let loop_var = match left.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] REJECT: loop condition left is not Variable");
|
||||
return Ok(None);
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
let haystack = match right.as_ref() {
|
||||
@ -125,33 +121,21 @@ fn extract_bool_predicate_scan_parts(
|
||||
object, method, ..
|
||||
} if method == "length" => match object.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] REJECT: length() object is not Variable");
|
||||
return Ok(None);
|
||||
}
|
||||
_ => return Ok(None),
|
||||
},
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] REJECT: loop condition right is not .length()");
|
||||
return Ok(None);
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
(loop_var, haystack)
|
||||
}
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] REJECT: loop condition is not BinaryOp::Less");
|
||||
return Ok(None);
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
eprintln!("[pattern8/extract] ✅ Loop condition OK: {} < {}.length()", loop_var, haystack);
|
||||
|
||||
// 2. Find if statement with predicate check and return false
|
||||
let mut predicate_receiver_opt = None;
|
||||
let mut predicate_method_opt = None;
|
||||
|
||||
eprintln!("[pattern8/extract] Step 2: Searching for predicate pattern in {} statements", body.len());
|
||||
for (i, stmt) in body.iter().enumerate() {
|
||||
eprintln!("[pattern8/extract] Statement {}: {:?}", i, stmt);
|
||||
for stmt in body.iter() {
|
||||
if let ASTNode::If {
|
||||
condition: if_cond,
|
||||
then_body,
|
||||
@ -179,10 +163,7 @@ fn extract_bool_predicate_scan_parts(
|
||||
let receiver = match object.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
ASTNode::Me { .. } => "me".to_string(), // Me is registered as "me" in MirBuilder
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] Receiver is not Variable or Me: {:?}", object);
|
||||
continue;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// P0: Expect 1 argument: s.substring(i, i + 1)
|
||||
@ -260,7 +241,6 @@ fn extract_bool_predicate_scan_parts(
|
||||
..
|
||||
} = ret_val.as_ref()
|
||||
{
|
||||
eprintln!("[pattern8/extract] ✅ Found predicate pattern: {}.{}", receiver, method);
|
||||
predicate_receiver_opt = Some(receiver);
|
||||
predicate_method_opt = Some(method.clone());
|
||||
}
|
||||
@ -272,17 +252,11 @@ fn extract_bool_predicate_scan_parts(
|
||||
}
|
||||
}
|
||||
|
||||
if predicate_receiver_opt.is_none() {
|
||||
eprintln!("[pattern8/extract] REJECT: No predicate pattern found");
|
||||
}
|
||||
|
||||
let predicate_receiver = predicate_receiver_opt.ok_or_else(|| "No predicate pattern found")?;
|
||||
let predicate_method = predicate_method_opt.ok_or_else(|| "No predicate method found")?;
|
||||
|
||||
// 3. Check for step: i = i + 1
|
||||
let mut step_lit_opt = None;
|
||||
|
||||
eprintln!("[pattern8/extract] Step 3: Searching for step pattern ({} = {} + 1)", loop_var, loop_var);
|
||||
for stmt in body {
|
||||
if let ASTNode::Assignment { target, value, .. } = stmt {
|
||||
if let ASTNode::Variable { name: target_name, .. } = target.as_ref() {
|
||||
@ -301,7 +275,6 @@ fn extract_bool_predicate_scan_parts(
|
||||
..
|
||||
} = right.as_ref()
|
||||
{
|
||||
eprintln!("[pattern8/extract] ✅ Found step pattern: {} = {} + {}", loop_var, loop_var, lit);
|
||||
step_lit_opt = Some(*lit);
|
||||
}
|
||||
}
|
||||
@ -312,22 +285,13 @@ fn extract_bool_predicate_scan_parts(
|
||||
}
|
||||
}
|
||||
|
||||
if step_lit_opt.is_none() {
|
||||
eprintln!("[pattern8/extract] REJECT: No step pattern found");
|
||||
}
|
||||
|
||||
let step_lit = step_lit_opt.ok_or_else(|| "No step pattern found")?;
|
||||
|
||||
// P0: Step must be 1
|
||||
if step_lit != 1 {
|
||||
eprintln!("[pattern8/extract] REJECT: Step is {}, expected 1", step_lit);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
eprintln!("[pattern8/extract] ✅✅✅ ACCEPT: Pattern8 extraction successful!");
|
||||
eprintln!("[pattern8/extract] Parts: loop_var={}, haystack={}, predicate={}.{}, step={}",
|
||||
loop_var, haystack, predicate_receiver, predicate_method, step_lit);
|
||||
|
||||
Ok(Some(BoolPredicateScanParts {
|
||||
loop_var,
|
||||
haystack,
|
||||
@ -337,17 +301,229 @@ fn extract_bool_predicate_scan_parts(
|
||||
}))
|
||||
}
|
||||
|
||||
/// Phase 259 P0: Lowering function for Pattern 8
|
||||
/// Phase 269 P1: Lowering function for Pattern 8 (Frag-based)
|
||||
/// Now uses EdgeCFG Frag API via emission entrypoint
|
||||
pub(crate) fn lower(
|
||||
builder: &mut MirBuilder,
|
||||
ctx: &super::router::LoopPatternContext,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
builder.cf_loop_pattern8_bool_predicate_impl(
|
||||
ctx.condition,
|
||||
ctx.body,
|
||||
ctx.func_name,
|
||||
ctx.debug,
|
||||
)
|
||||
use crate::mir::types::{BinaryOp, CompareOp, ConstValue, UnaryOp};
|
||||
use crate::mir::{Effect, EffectMask, MirInstruction, MirType};
|
||||
|
||||
let trace = trace::trace();
|
||||
|
||||
// Step 1: Extract pattern parts (SSOT)
|
||||
let parts = extract_bool_predicate_scan_parts(ctx.condition, ctx.body)?
|
||||
.ok_or_else(|| format!("[pattern8] Not a boolean predicate scan pattern in {}", ctx.func_name))?;
|
||||
|
||||
if ctx.debug {
|
||||
trace.debug(
|
||||
"pattern8/lower",
|
||||
&format!(
|
||||
"Pattern8 Frag lowering: loop_var={}, haystack={}, predicate={}.{}",
|
||||
parts.loop_var, parts.haystack, parts.predicate_receiver, parts.predicate_method
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 2: Get me_val using build_me_expression() (SSOT - no receiver name speculation)
|
||||
let me_val = builder.build_me_expression()?;
|
||||
|
||||
// Step 3: Get i and s from variable_map
|
||||
let i_init_val = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.loop_var)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern8] Variable '{}' not found", parts.loop_var))?;
|
||||
|
||||
let s_val = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.haystack)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern8] Variable '{}' not found", parts.haystack))?;
|
||||
|
||||
// Step 4a: Capture preheader block (entry to loop) for PHI input
|
||||
let preheader_bb = builder.current_block
|
||||
.ok_or_else(|| "[pattern8] No current block for loop entry".to_string())?;
|
||||
|
||||
// Step 4b: Allocate PHI destination for loop variable BEFORE generating blocks
|
||||
let i_current = builder.next_value_id();
|
||||
builder.type_ctx.value_types.insert(i_current, MirType::Integer);
|
||||
|
||||
// Step 4c: Allocate BasicBlockIds for 5 blocks
|
||||
let header_bb = builder.next_block_id();
|
||||
let body_bb = builder.next_block_id();
|
||||
let step_bb = builder.next_block_id();
|
||||
let after_bb = builder.next_block_id();
|
||||
let ret_false_bb = builder.next_block_id();
|
||||
|
||||
// Add Jump from current block to header_bb (to terminate the previous block)
|
||||
if let Some(current) = builder.current_block {
|
||||
builder.emit_instruction(MirInstruction::Jump {
|
||||
target: header_bb,
|
||||
edge_args: None,
|
||||
})?;
|
||||
}
|
||||
|
||||
// Build header_bb: len = s.length(), cond_loop = (i < len)
|
||||
builder.start_new_block(header_bb)?;
|
||||
|
||||
// Note: PHI node for i_current will be inserted AFTER all blocks are generated
|
||||
// (see Step 4 below, after step_bb generates i_next_val)
|
||||
|
||||
let len_val = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(len_val),
|
||||
box_val: s_val,
|
||||
method: "length".to_string(),
|
||||
method_id: None,
|
||||
args: vec![],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(len_val, MirType::Integer);
|
||||
|
||||
let cond_loop = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::Compare {
|
||||
dst: cond_loop,
|
||||
lhs: i_current, // Use PHI result, not initial value
|
||||
op: CompareOp::Lt,
|
||||
rhs: len_val,
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(cond_loop, MirType::Bool);
|
||||
|
||||
// Create ret_false_val in header_bb (dominates both step_bb and ret_false_bb)
|
||||
let ret_false_val = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::Const {
|
||||
dst: ret_false_val,
|
||||
value: ConstValue::Bool(false),
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(ret_false_val, MirType::Bool);
|
||||
|
||||
// Build body_bb: ch = s.substring(i, i+1), ok = me.<predicate_method>(ch), cond_fail = not ok
|
||||
builder.start_new_block(body_bb)?;
|
||||
|
||||
let one = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::Const {
|
||||
dst: one,
|
||||
value: ConstValue::Integer(1),
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(one, MirType::Integer);
|
||||
|
||||
let i_plus_one = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::BinOp {
|
||||
dst: i_plus_one,
|
||||
lhs: i_current, // Use PHI result, not initial value
|
||||
op: BinaryOp::Add,
|
||||
rhs: one,
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(i_plus_one, MirType::Integer);
|
||||
|
||||
let ch_val = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(ch_val),
|
||||
box_val: s_val,
|
||||
method: "substring".to_string(),
|
||||
method_id: None,
|
||||
args: vec![i_current, i_plus_one], // Use PHI result, not initial value
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(ch_val, MirType::String);
|
||||
|
||||
let ok_val = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(ok_val),
|
||||
box_val: me_val,
|
||||
method: parts.predicate_method.clone(),
|
||||
method_id: None,
|
||||
args: vec![ch_val],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(ok_val, MirType::Bool);
|
||||
|
||||
let cond_fail = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::UnaryOp {
|
||||
dst: cond_fail,
|
||||
op: UnaryOp::Not,
|
||||
operand: ok_val,
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(cond_fail, MirType::Bool);
|
||||
|
||||
// Build step_bb: i = i + 1
|
||||
builder.start_new_block(step_bb)?;
|
||||
|
||||
let i_next_val = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::BinOp {
|
||||
dst: i_next_val,
|
||||
lhs: i_current, // Use PHI result, not initial value
|
||||
op: BinaryOp::Add,
|
||||
rhs: one,
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(i_next_val, MirType::Integer);
|
||||
// Note: Do NOT update variable_map here - PHI will handle SSA renaming
|
||||
|
||||
// Ensure ret_false_bb and after_bb exist (they don't have instructions, but must exist for emit_frag)
|
||||
builder.ensure_block_exists(ret_false_bb)?;
|
||||
builder.ensure_block_exists(after_bb)?;
|
||||
|
||||
// Step 4: Insert PHI at head of header_bb with proper span synchronization
|
||||
use crate::mir::ssot::cf_common::insert_phi_at_head_spanned;
|
||||
|
||||
let phi_inputs = vec![
|
||||
(preheader_bb, i_init_val), // Entry edge: initial value
|
||||
(step_bb, i_next_val), // Latch edge: updated value
|
||||
];
|
||||
|
||||
// Access current_function for PHI insertion
|
||||
if let Some(ref mut func) = builder.scope_ctx.current_function {
|
||||
insert_phi_at_head_spanned(
|
||||
func,
|
||||
header_bb,
|
||||
i_current, // PHI destination
|
||||
phi_inputs,
|
||||
builder.metadata_ctx.current_span(),
|
||||
);
|
||||
} else {
|
||||
return Err("[pattern8] No current function for PHI insertion".to_string());
|
||||
}
|
||||
|
||||
// Step 5: Call emission entrypoint
|
||||
use crate::mir::builder::emission::loop_predicate_scan::emit_bool_predicate_scan_edgecfg;
|
||||
|
||||
if crate::config::env::is_joinir_debug() {
|
||||
eprintln!("[pattern8] using edgecfg (Frag版)");
|
||||
}
|
||||
|
||||
emit_bool_predicate_scan_edgecfg(
|
||||
builder,
|
||||
header_bb,
|
||||
body_bb,
|
||||
step_bb,
|
||||
after_bb,
|
||||
ret_false_bb,
|
||||
cond_loop,
|
||||
cond_fail,
|
||||
ret_false_val,
|
||||
)?;
|
||||
|
||||
// Step 6: Update variable_map to use final loop variable value
|
||||
// (This is the value when loop exits normally via i >= len)
|
||||
builder.variable_ctx.variable_map.insert(parts.loop_var.clone(), i_current);
|
||||
|
||||
// Step 7: Setup after_bb for subsequent AST lowering (return true)
|
||||
// CRITICAL: Use start_new_block() to create actual block, not just set current_block
|
||||
builder.start_new_block(after_bb)?;
|
||||
|
||||
// Step 7: Return Void (loop as statement, not expression)
|
||||
use crate::mir::builder::emission::constant::emit_void;
|
||||
let void_val = emit_void(builder);
|
||||
|
||||
if ctx.debug {
|
||||
trace.debug("pattern8/lower", "Pattern8 Frag lowering complete");
|
||||
}
|
||||
|
||||
Ok(Some(void_val))
|
||||
}
|
||||
|
||||
impl MirBuilder {
|
||||
@ -364,6 +540,10 @@ impl MirBuilder {
|
||||
|
||||
let trace = trace::trace();
|
||||
|
||||
if crate::config::env::is_joinir_debug() {
|
||||
eprintln!("[pattern8] using joinir (JoinIR版)");
|
||||
}
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern8/lower",
|
||||
|
||||
Reference in New Issue
Block a user