feat(joinir): Phase 259 P0 - Pattern8 BoolPredicateScan + Copy binding fix

Pattern8 (Boolean Predicate Scan) implementation for is_integer/1:
- New pattern detection for `loop + if not predicate() { return false }`
- JoinIR lowerer with main/loop_step/k_exit structure
- Me receiver passed as param (by-name 禁止)

Key fixes:
1. expr_result = Some(join_exit_value) (Pattern7 style)
2. Tail-call: dst: None (no extra Ret instruction)
3. instruction_rewriter: Add `&& is_loop_header_with_phi` check
   - Pattern8 has no carriers → no PHIs → MUST generate Copy bindings
   - Without this, ValueId(103/104/105) were undefined

Status: Copy instructions now generated correctly, but exit block
creation issue remains (next step: Step A-C in指示書).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-21 02:40:07 +09:00
parent e4f57ea83d
commit a767f0f3a9
10 changed files with 913 additions and 5 deletions

View File

@ -80,6 +80,7 @@ pub(in crate::mir::builder) mod pattern4_with_continue;
pub(in crate::mir::builder) mod pattern5_infinite_early_exit; // Phase 131-11
pub(in crate::mir::builder) mod pattern6_scan_with_init; // Phase 254 P0: index_of/find/contains pattern
pub(in crate::mir::builder) mod pattern7_split_scan; // Phase 256 P0: split/tokenization with variable step
pub(in crate::mir::builder) mod pattern8_scan_bool_predicate; // Phase 259 P0: boolean predicate scan (is_integer/is_valid)
pub(in crate::mir::builder) mod pattern_pipeline;
pub(in crate::mir::builder) mod router;
pub(in crate::mir::builder) mod trim_loop_lowering; // Phase 180: Dedicated Trim/P5 lowering module

View File

@ -0,0 +1,549 @@
//! Pattern 8: Boolean Predicate Scan (is_integer/is_valid form)
//!
//! Phase 259 P0: Dedicated pattern for boolean predicate validation loops
//!
//! ## Pattern Structure
//!
//! ```nyash
//! is_integer(s) {
//! local i = start // Computed in prelude
//! loop(i < s.length()) {
//! if not this.is_digit(s.substring(i, i + 1)) {
//! return false
//! }
//! i = i + 1
//! }
//! return true
//! }
//! ```
//!
//! ## Detection Criteria (P0: Fixed Form Only)
//!
//! 1. Loop condition: `i < s.length()`
//! 2. Loop body has if statement with:
//! - Condition: `not this.method(...)` (UnaryOp::Not + MethodCall)
//! - Then branch: `return false` (early exit)
//! 3. Loop body has step: `i = i + 1`
//! 4. Post-loop: `return true`
//!
//! ## vs Pattern 6
//!
//! - Pattern 6: Match scan (substring == needle → return i)
//! - Pattern 8: Predicate scan (not is_digit → return false, else true)
//! - Pattern 6: Returns integer (index or -1)
//! - Pattern 8: Returns boolean (true/false)
use super::super::trace;
use super::common::var;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;
/// Phase 259 P0: Extracted structure for boolean predicate scan
#[derive(Debug, Clone)]
struct BoolPredicateScanParts {
/// Loop variable name (e.g., "i")
loop_var: String,
/// Haystack variable name (e.g., "s")
haystack: String,
/// Predicate method receiver (e.g., "this")
predicate_receiver: String,
/// Predicate method name (e.g., "is_digit")
predicate_method: String,
/// Step literal (P0: must be 1)
step_lit: i64,
}
/// Phase 259 P0: Detection for Pattern 8 (BoolPredicateScan)
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 {
trace::trace().debug(
"pattern8/can_lower",
"accept: boolean predicate scan pattern extractable",
);
}
true
}
Ok(None) => {
if ctx.debug {
trace::trace().debug(
"pattern8/can_lower",
"reject: not a boolean predicate scan pattern",
);
}
false
}
Err(e) => {
if ctx.debug {
trace::trace().debug(
"pattern8/can_lower",
&format!("reject: extraction error: {}", e),
);
}
false
}
}
}
/// Phase 259 P0: Extract boolean predicate scan pattern parts
///
/// # P0 Restrictions (Fail-Fast)
///
/// - Loop condition: `i < s.length()` (forward only)
/// - If condition: `not this.method(s.substring(i, i + 1))` (UnaryOp::Not)
/// - Then branch: `return false` (Literal::Bool(false))
/// - Step: `i = i + 1` (step_lit == 1)
/// - Post-loop: `return true` (enforced by caller)
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());
// 1. Check loop condition: i < s.length()
let (loop_var, haystack) = match condition {
ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left,
right,
..
} => {
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);
}
};
let haystack = match right.as_ref() {
ASTNode::MethodCall {
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);
}
},
_ => {
eprintln!("[pattern8/extract] REJECT: loop condition right is not .length()");
return Ok(None);
}
};
(loop_var, haystack)
}
_ => {
eprintln!("[pattern8/extract] REJECT: loop condition is not BinaryOp::Less");
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);
if let ASTNode::If {
condition: if_cond,
then_body,
..
} = stmt
{
// Check if condition is: not this.method(...)
if let ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand,
..
} = if_cond.as_ref()
{
// Operand must be MethodCall
if let ASTNode::MethodCall {
object,
method,
arguments,
..
} = operand.as_ref()
{
// Extract receiver (e.g., "me")
// Phase 259 P0: Support both Variable and Me node
// IMPORTANT: Me is registered as "me" in variable_map (not "this")
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;
}
};
// P0: Expect 1 argument: s.substring(i, i + 1)
if arguments.len() != 1 {
continue;
}
// Validate argument is substring call
if let ASTNode::MethodCall {
object: substr_obj,
method: substr_method,
arguments: substr_args,
..
} = &arguments[0]
{
if substr_method != "substring" {
continue;
}
// Object must be haystack
if let ASTNode::Variable { name, .. } = substr_obj.as_ref() {
if name != &haystack {
continue;
}
} else {
continue;
}
// Args: (i, i + 1)
if substr_args.len() != 2 {
continue;
}
// Arg 0: loop_var
match &substr_args[0] {
ASTNode::Variable { name, .. } if name == &loop_var => {}
_ => continue,
}
// Arg 1: loop_var + 1
match &substr_args[1] {
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left,
right,
..
} => {
// Left: loop_var
match left.as_ref() {
ASTNode::Variable { name, .. } if name == &loop_var => {}
_ => continue,
}
// Right: Literal(1)
match right.as_ref() {
ASTNode::Literal {
value: LiteralValue::Integer(1),
..
} => {}
_ => continue,
}
}
_ => continue,
}
} else {
continue;
}
// Check then_body contains: return false
if then_body.len() == 1 {
if let ASTNode::Return { value, .. } = &then_body[0] {
if let Some(ret_val) = value {
if let ASTNode::Literal {
value: LiteralValue::Bool(false),
..
} = ret_val.as_ref()
{
eprintln!("[pattern8/extract] ✅ Found predicate pattern: {}.{}", receiver, method);
predicate_receiver_opt = Some(receiver);
predicate_method_opt = Some(method.clone());
}
}
}
}
}
}
}
}
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() {
if target_name == &loop_var {
if let ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left,
right,
..
} = value.as_ref()
{
if let ASTNode::Variable { name: left_name, .. } = left.as_ref() {
if left_name == &loop_var {
if let ASTNode::Literal {
value: LiteralValue::Integer(lit),
..
} = right.as_ref()
{
eprintln!("[pattern8/extract] ✅ Found step pattern: {} = {} + {}", loop_var, loop_var, lit);
step_lit_opt = Some(*lit);
}
}
}
}
}
}
}
}
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,
predicate_receiver,
predicate_method,
step_lit,
}))
}
/// Phase 259 P0: Lowering function for Pattern 8
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,
)
}
impl MirBuilder {
/// Phase 259 P0: Pattern 8 (BoolPredicateScan) implementation
pub(crate) fn cf_loop_pattern8_bool_predicate_impl(
&mut self,
condition: &ASTNode,
body: &[ASTNode],
func_name: &str,
debug: bool,
) -> Result<Option<ValueId>, String> {
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
let trace = trace::trace();
if debug {
trace.debug(
"pattern8/lower",
&format!("Phase 259 P0: BoolPredicateScan lowering for {}", func_name),
);
}
// Step 1: Extract pattern parts
let parts = extract_bool_predicate_scan_parts(condition, body)?
.ok_or_else(|| format!("[pattern8] Not a boolean predicate scan pattern in {}", func_name))?;
if debug {
trace.debug(
"pattern8/lower",
&format!(
"Extracted: loop_var={}, haystack={}, predicate={}.{}, step={}",
parts.loop_var, parts.haystack, parts.predicate_receiver, parts.predicate_method, parts.step_lit
),
);
}
// Step 2: Get host ValueIds for variables
eprintln!("[pattern8/lower] variable_map contents:");
for (name, vid) in &self.variable_ctx.variable_map {
eprintln!("[pattern8/lower] {} -> {:?}", name, vid);
}
eprintln!("[pattern8/lower] Looking for receiver: {}", parts.predicate_receiver);
let s_host = self
.variable_ctx
.variable_map
.get(&parts.haystack)
.copied()
.ok_or_else(|| format!("[pattern8] Variable {} not found", parts.haystack))?;
let i_host = self
.variable_ctx
.variable_map
.get(&parts.loop_var)
.copied()
.ok_or_else(|| format!("[pattern8] Variable {} not found", parts.loop_var))?;
if debug {
trace.debug(
"pattern8/lower",
&format!("Host ValueIds: s={:?}, i={:?}", s_host, i_host),
);
}
// Step 3: Create JoinModule
let mut join_value_space = JoinValueSpace::new();
use crate::mir::join_ir::lowering::scan_bool_predicate_minimal::lower_scan_bool_predicate_minimal;
let join_module = lower_scan_bool_predicate_minimal(
&mut join_value_space,
&parts.predicate_receiver,
&parts.predicate_method,
);
// Step 4: Build boundary (SSOT - use entry.params)
use super::common::get_entry_function;
let main_func = get_entry_function(&join_module, "pattern8")?;
// SSOT: Use actual params from JoinModule entry
let join_inputs = main_func.params.clone();
// host_inputs in same order: [i, me, s] (alphabetical)
// Phase 259 P0: me_host = receiver ValueId
// IMPORTANT: Me receiver might not be in variable_map yet, so we use build_me_expression()
let me_host = if parts.predicate_receiver == "me" {
self.build_me_expression()?
} else {
self
.variable_ctx
.variable_map
.get(&parts.predicate_receiver)
.copied()
.ok_or_else(|| format!("[pattern8] Receiver {} not found", parts.predicate_receiver))?
};
let host_inputs = vec![i_host, me_host, s_host];
if debug {
trace.debug(
"pattern8/lower",
&format!(
"Boundary inputs: join_inputs={:?}, host_inputs={:?}",
join_inputs, host_inputs
),
);
}
// Verify count consistency
if join_inputs.len() != host_inputs.len() {
return Err(format!(
"[pattern8] Params count mismatch: join_inputs={}, host_inputs={}",
join_inputs.len(), host_inputs.len()
));
}
// Step 5: Build exit_bindings (no carriers, only expr_result)
// Pattern8 returns ret_bool directly (not loop variable)
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
let k_exit_func = join_module.require_function(
crate::mir::join_ir::lowering::canonical_names::K_EXIT,
"Pattern 8",
);
let join_exit_value = k_exit_func
.params
.first()
.copied()
.expect("k_exit must have parameter for exit value");
// Phase 259 P0: Allocate host ValueId for return value
let result_host = self.next_value_id();
self.type_ctx.value_types.insert(result_host, crate::mir::MirType::Bool);
// Phase 259 P0: exit_bindings contains ret_bool binding (Pattern7 style)
// This allows remapper to map join_exit_value → result_host
let result_exit_binding = LoopExitBinding {
carrier_name: "ret_bool".to_string(), // Logical name for return value
join_exit_value,
host_slot: result_host,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
};
let exit_bindings = vec![result_exit_binding];
// Step 6: Build boundary with expr_result
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
let carrier_info = CarrierInfo {
loop_var_name: parts.loop_var.clone(),
loop_var_id: i_host,
carriers: vec![], // No carriers - Pattern8 uses expr_result only
trim_helper: None,
promoted_loopbodylocals: Vec::new(),
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(),
};
// Phase 259 P0: expr_result = join_exit_value (Pattern7 style)
// Pattern8 returns boolean from k_exit, not loop variable
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(join_inputs, host_inputs)
.with_exit_bindings(exit_bindings)
.with_carrier_info(carrier_info)
.with_expr_result(Some(join_exit_value)) // ✅ CRITICAL: Set expr_result to k_exit param
.build();
if debug {
trace.debug(
"pattern8/lower",
"Built boundary with expr_result (ret_bool from k_exit)",
);
}
// Step 7: Execute JoinIRConversionPipeline
use super::conversion_pipeline::JoinIRConversionPipeline;
let result = JoinIRConversionPipeline::execute(
self,
join_module,
Some(&boundary),
"pattern8",
debug,
)?;
eprintln!("[pattern8/lower] Pipeline execution complete, result: {:?}", result);
if debug {
trace.debug(
"pattern8/lower",
&format!("Pattern 8 complete, result: {:?}", result),
);
}
// Pattern8 returns ret_bool (expr_result), not Void
eprintln!("[pattern8/lower] Returning result: {:?}", result);
Ok(result)
}
}

View File

@ -203,6 +203,11 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
detect: super::pattern7_split_scan::can_lower,
lower: super::pattern7_split_scan::lower,
},
LoopPatternEntry {
name: "Pattern8_BoolPredicateScan", // Phase 259 P0: boolean predicate scan (is_integer/is_valid)
detect: super::pattern8_scan_bool_predicate::can_lower,
lower: super::pattern8_scan_bool_predicate::lower,
},
LoopPatternEntry {
name: "Pattern3_WithIfPhi",
detect: super::pattern3_with_if_phi::can_lower,