refactor(joinir): Phase 286 コード品質改善 - ヘルパー共通化 + static box フィルタ

## 1. lower_*_ast ヘルパー共通化
- `lower_value_ast()` に MethodCall 対応追加
- Pattern8 normalizer も共有ヘルパーを使用
- Pattern1/8/9 で一貫した lowering ロジック

## 2. PLAN_EXTRACTORS ドキュメント追加
- `WithPostLoop` variant: 将来拡張用として残存理由を明記
- 現在は常に `&[]` を渡すが、post-loop segment analysis 用に保持

## 3. Legacy Pattern8 残存 + static box フィルタ
- Plan extractor は pure 関数(builder にアクセス不可)
- router 側で static box フィルタリングを実装
- static box コンテキストは legacy Pattern8 へ fallback
- legacy 残存理由をドキュメント化

## 検証
- Quick: 154 PASS, 0 FAILED
- Pattern8 integration: exit 7

🤖 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-26 03:35:02 +09:00
parent 832d018046
commit 21daf1b7dd
3 changed files with 85 additions and 6 deletions

View File

@ -1,7 +1,26 @@
//! Pattern 8: Boolean Predicate Scan (is_integer/is_valid form)
//! Pattern 8: Boolean Predicate Scan (is_integer/is_valid form) - LEGACY LINE
//!
//! Phase 259 P0: Dedicated pattern for boolean predicate validation loops
//!
//! ## Legacy Status (Phase 286 P3)
//!
//! **RETAINED FOR STATIC BOX CONTEXTS**
//!
//! - **Plan line** (Phase 286 P2.4): Handles regular loops via extract_pattern8_plan()
//! - **Legacy line** (this file): Handles static box loops ONLY
//!
//! ### Why Legacy is Still Needed:
//!
//! 1. **Static box filtering**: Plan extractors are pure functions without builder access
//! - Plan extractors cannot check `builder.comp_ctx.current_static_box`
//! - Legacy `can_lower()` rejects static box contexts (see static box check below)
//! 2. **ReceiverNormalizeBox coordination**: Static box `this.method()` calls
//! are normalized by ReceiverNormalizeBox, not Pattern8
//! 3. **Fail-Fast**: Plan line succeeds → legacy unreachable. Plan line fails → fallback to legacy
//!
//! **Deletion Condition**: Legacy can be removed when Plan extraction router
//! adds static box filtering OR when ReceiverNormalizeBox handles all static box loops.
//!
//! ## Pattern Structure
//!
//! ```nyash

View File

@ -178,6 +178,10 @@ enum PlanExtractorVariant {
WithFnBody(fn(&ASTNode, &[ASTNode], Option<&[ASTNode]>) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String>),
/// Extractor with post_loop_code: (condition, body, post_loop_code) - Pattern7
///
/// NOTE (Phase 286): Currently always called with &[] for post_loop_code.
/// This variant is kept for future extension (post-loop segment analysis).
/// The _post_loop_code parameter in Pattern7 extractor is intentionally unused.
WithPostLoop(fn(&ASTNode, &[ASTNode], &[ASTNode]) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String>),
}
@ -243,8 +247,22 @@ fn try_plan_extractors(
}
};
// If extraction succeeded, try lowering
// If extraction succeeded, check if we can lower it
if let Some(domain_plan) = plan_opt {
// Phase 286 P3: Pattern8 static box filtering
// Plan extractors are pure (no builder access), so we filter here
// Static box loops with `me.method()` should be handled by ReceiverNormalizeBox, not Pattern8
if entry.name.contains("Pattern8") && builder.comp_ctx.current_static_box.is_some() {
if ctx.debug {
trace::trace().debug(
"route/plan",
&format!("{} extracted but rejected: static box context (fallback to legacy)", entry.name),
);
}
// Skip this pattern, try next extractor
continue;
}
let log_msg = format!("route=plan strategy=extract pattern={}", entry.name);
trace::trace().pattern("route", &log_msg, true);

View File

@ -1277,6 +1277,47 @@ impl PlanNormalizer {
Ok((value_id, vec![const_effect]))
}
ASTNode::MethodCall { object, method, arguments, .. } => {
// Support method calls like s.length() in loop conditions
// This is needed for Pattern8 (i < s.length())
let object_name = match object.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => return Err(format!("[normalizer] Method call on non-variable object: {:?}", object)),
};
// Get object ValueId
let object_id = if let Some(&phi_dst) = phi_bindings.get(&object_name) {
phi_dst
} else if let Some(&value_id) = builder.variable_ctx.variable_map.get(&object_name) {
value_id
} else {
return Err(format!("[normalizer] Method call object {} not found", object_name));
};
// Lower method call arguments
let mut arg_ids = Vec::new();
let mut arg_effects = Vec::new();
for arg in arguments {
let (arg_id, mut effects) = Self::lower_value_ast(arg, builder, phi_bindings)?;
arg_ids.push(arg_id);
arg_effects.append(&mut effects);
}
// Allocate result ValueId
let result_id = builder.next_value_id();
builder.type_ctx.value_types.insert(result_id, MirType::Integer);
// Create MethodCall effect
arg_effects.push(CoreEffectPlan::MethodCall {
dst: Some(result_id),
object: object_id,
method: method.clone(),
args: arg_ids,
effects: EffectMask::PURE.add(Effect::Io),
});
Ok((result_id, arg_effects))
}
_ => Err(format!("[normalizer] Unsupported value AST: {:?}", ast)),
}
}
@ -1785,7 +1826,7 @@ impl PlanNormalizer {
let mut phi_bindings: BTreeMap<String, crate::mir::ValueId> = BTreeMap::new();
phi_bindings.insert(parts.loop_var.clone(), loop_var_current);
// Step 6: Lower loop condition (e.g., `i < s.length()`)
// Step 6: Lower loop condition (e.g., `i < s.length()`) using shared helper
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?;
@ -1798,8 +1839,9 @@ impl PlanNormalizer {
rhs: loop_cond_rhs,
});
// Step 8: Build body (predicate check)
let body = vec![
// Step 8: Build body (predicate check) - using shared helpers for consistency
// Note: Pattern8-specific: substring + predicate method call + NOT comparison
let mut body = vec![
// one = 1
CorePlan::Effect(CoreEffectPlan::Const {
dst: one_val,
@ -1842,7 +1884,7 @@ impl PlanNormalizer {
}),
];
// Step 9: Build step_effects (increment loop var)
// Step 9: Build step_effects (increment loop var) - reusing one_val from body
let step_effects = vec![
// loop_var_next = i + 1
CoreEffectPlan::BinOp {