docs(joinir): Phase 232-239 documentation and ExprLowerer refinements

Documentation:
- Move completion reports to docs/archive/reports/
- Add phase232-238 design/inventory documents
- Update joinir-architecture-overview.md
- Add doc-status-policy.md

Code refinements:
- ExprLowerer: condition catalog improvements
- ScopeManager: boundary clarifications
- CarrierInfo: cleanup

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-11 00:21:29 +09:00
parent 13a676d406
commit 448bf3d8c5
44 changed files with 1372 additions and 79 deletions

View File

@ -0,0 +1,10 @@
# JoinIR Lowering (ExprLowerer / ScopeManager / Envs)
このディレクトリは JoinIR lowering の中でも、条件式や環境まわりの箱ExprLowerer, ScopeManager, ConditionEnv, LoopBodyLocalEnv, UpdateEnv など)を扱う層だよ。コードを触るときは、以下の最小ルールを守ってね。
- ExprLowerer は **ScopeManager 経由のみ** で名前解決する。ConditionEnv / LoopBodyLocalEnv / CapturedEnv / CarrierInfo に直接触らない。
- 条件式から UpdateEnv を参照しない。UpdateEnv はキャリア更新専用で、header/break/continue 条件は ScopeManager→ConditionEnv で完結させる。
- ConditionEnv は「条件で参照する JoinIR ValueId だけ」を持つ。body-local を直接入れず、必要なら昇格ScopeManager に解決を任せる。
- Fail-Fast 原則: Unsupported/NotFound は明示エラーにして、by-name ヒューリスティックや静かなフォールバックは禁止。
詳しい境界ルールは `docs/development/current/main/phase238-exprlowerer-scope-boundaries.md` を参照してね。***

View File

@ -406,6 +406,54 @@ impl CarrierInfo {
pub fn trim_helper(&self) -> Option<&crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper> {
self.trim_helper.as_ref()
}
/// Phase 229/231: Resolve promoted LoopBodyLocal name to carrier JoinIR ValueId
///
/// This helper centralizes the naming convention for promoted variables so that
/// ScopeManager 実装がそれぞれ命名規約を再実装しなくて済むようにするよ。
///
/// 命名規約:
/// - DigitPos パターン: `"var"` → `"is_var"`(例: "digit_pos" → "is_digit_pos"
/// - Trim パターン : `"var"` → `"is_var_match"`(例: "ch" → "is_ch_match"
///
/// # Arguments
///
/// * `original_name` - 元の LoopBodyLocal 名(例: "digit_pos"
///
/// # Returns
///
/// * `Some(ValueId)` - 対応する carrier の join_id が見つかった場合
/// * `None` - promoted_loopbodylocals に含まれない、または join_id 未設定の場合
pub fn resolve_promoted_join_id(&self, original_name: &str) -> Option<ValueId> {
if !self.promoted_loopbodylocals.contains(&original_name.to_string()) {
return None;
}
let candidates = [
format!("is_{}", original_name), // DigitPos pattern
format!("is_{}_match", original_name), // Trim pattern
];
for carrier_name in &candidates {
// loop_var 自身が ConditionOnly carrier として扱われるケースは現状ほぼないが、
// 将来の拡張に備えて loop_var_name も一応チェックしておく。
if carrier_name == &self.loop_var_name {
if let Some(carrier) = self.carriers.iter().find(|c| c.name == self.loop_var_name) {
if let Some(join_id) = carrier.join_id {
return Some(join_id);
}
}
}
if let Some(carrier) = self.carriers.iter().find(|c| c.name == *carrier_name) {
if let Some(join_id) = carrier.join_id {
return Some(join_id);
}
}
}
None
}
}
/// Exit metadata returned by lowerers

View File

@ -17,6 +17,7 @@
use crate::ast::{ASTNode, BinaryOperator, UnaryOperator};
use crate::mir::ValueId;
use crate::mir::join_ir::{JoinInst, MirLikeInst, BinOpKind, UnaryOp as JoinUnaryOp};
use crate::mir::builder::MirBuilder;
use super::scope_manager::ScopeManager;
use super::condition_lowerer::lower_condition_to_joinir;
@ -108,6 +109,12 @@ pub struct ExprLowerer<'env, 'builder, S: ScopeManager> {
/// Debug flag (inherited from caller)
debug: bool,
/// Last lowered instruction sequence (for testing/inspection)
///
/// Phase 235: Tests can inspect this to assert that appropriate Compare / BinOp / Not
/// instructions are emitted for supported patterns. Productionコードからは未使用。
last_instructions: Vec<JoinInst>,
}
impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
@ -124,6 +131,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
context,
builder,
debug: false,
last_instructions: Vec::new(),
}
}
@ -133,6 +141,14 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
self
}
/// Take the last lowered instruction sequence (mainly for tests)
///
/// Phase 235: This allows unit tests to validate that Compare / BinOp / Not
/// instructions are present without影響を与えずに ExprLowerer の外から観察できる。
pub fn take_last_instructions(&mut self) -> Vec<JoinInst> {
std::mem::take(&mut self.last_instructions)
}
/// Lower an expression to JoinIR ValueId
///
/// Phase 231: This is the main entry point. Currently delegates to
@ -184,12 +200,15 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
id
};
let (result_value, _instructions) = lower_condition_to_joinir(
let (result_value, instructions) = lower_condition_to_joinir(
ast,
&mut alloc_value,
&condition_env,
).map_err(|e| ExprLoweringError::LoweringError(e))?;
// Phase 235: 保存しておき、テストから観察できるようにする
self.last_instructions = instructions;
if self.debug {
eprintln!("[expr_lowerer/phase231] Lowered condition → ValueId({:?})", result_value);
}
@ -292,7 +311,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
mod tests {
use super::*;
use crate::ast::{Span, LiteralValue};
use crate::mir::join_ir::lowering::scope_manager::{Pattern2ScopeManager, VarScopeKind};
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
@ -301,6 +320,41 @@ mod tests {
MirBuilder::new()
}
fn span() -> Span {
Span::unknown()
}
fn var(name: &str) -> ASTNode {
ASTNode::Variable {
name: name.to_string(),
span: span(),
}
}
fn lit_i(value: i64) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Integer(value),
span: span(),
}
}
fn bin(op: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: op,
left: Box::new(left),
right: Box::new(right),
span: span(),
}
}
fn not(expr: ASTNode) -> ASTNode {
ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand: Box::new(expr),
span: span(),
}
}
#[test]
fn test_expr_lowerer_simple_comparison() {
let mut condition_env = ConditionEnv::new();
@ -451,4 +505,226 @@ mod tests {
};
assert!(!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
}
// Phase 235: Additional patterns for condition lowering
fn make_basic_scope() -> Pattern2ScopeManager<'static> {
// NOTE: we leak these small envs for the duration of the test to satisfy lifetimes simply.
// テスト専用なので許容する。
let mut condition_env = ConditionEnv::new();
condition_env.insert("i".to_string(), ValueId(1));
condition_env.insert("j".to_string(), ValueId(2));
let boxed_env: Box<ConditionEnv> = Box::new(condition_env);
let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env);
let carrier_info = CarrierInfo {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(1),
carriers: vec![],
trim_helper: None,
promoted_loopbodylocals: vec![],
};
let boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);
Pattern2ScopeManager {
condition_env: condition_env_ref,
loop_body_local_env: None,
captured_env: None,
carrier_info: carrier_ref,
}
}
fn assert_has_compare(instructions: &[JoinInst]) {
assert!(
instructions.iter().any(|inst| matches!(
inst,
JoinInst::Compute(MirLikeInst::Compare { .. }
))),
"Expected at least one Compare instruction, got {:?}",
instructions
);
}
fn assert_has_binop(instructions: &[JoinInst], op: BinOpKind) {
assert!(
instructions.iter().any(|inst| matches!(
inst,
JoinInst::Compute(MirLikeInst::BinOp { op: o, .. } ) if *o == op
)),
"Expected at least one BinOp {:?}, got {:?}",
op,
instructions
);
}
fn assert_has_not(instructions: &[JoinInst]) {
assert!(
instructions.iter().any(|inst| matches!(
inst,
JoinInst::Compute(MirLikeInst::UnaryOp { op: JoinUnaryOp::Not, .. }
))),
"Expected at least one UnaryOp::Not, got {:?}",
instructions
);
}
#[test]
fn test_expr_lowerer_var_less_literal_generates_compare() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// i < 10
let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "i < 10 should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(!instructions.is_empty(), "instructions should not be empty");
assert_has_compare(&instructions);
}
#[test]
fn test_expr_lowerer_literal_less_var_generates_compare() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// 0 < i
let ast = bin(BinaryOperator::Less, lit_i(0), var("i"));
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "0 < i should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(!instructions.is_empty(), "instructions should not be empty");
assert_has_compare(&instructions);
}
#[test]
fn test_expr_lowerer_greater_than_between_vars() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// i > j
let ast = bin(BinaryOperator::Greater, var("i"), var("j"));
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "i > j should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(!instructions.is_empty(), "instructions should not be empty");
assert_has_compare(&instructions);
}
#[test]
fn test_expr_lowerer_and_combination() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// i > 0 && j < 5
let left = bin(BinaryOperator::Greater, var("i"), lit_i(0));
let right = bin(BinaryOperator::Less, var("j"), lit_i(5));
let ast = bin(BinaryOperator::And, left, right);
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "i > 0 && j < 5 should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(!instructions.is_empty(), "instructions should not be empty");
assert_has_compare(&instructions);
assert_has_binop(&instructions, BinOpKind::And);
}
#[test]
fn test_expr_lowerer_not_of_comparison() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// !(i < 10)
let inner = bin(BinaryOperator::Less, var("i"), lit_i(10));
let ast = not(inner);
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "!(i < 10) should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(!instructions.is_empty(), "instructions should not be empty");
assert_has_compare(&instructions);
assert_has_not(&instructions);
}
#[test]
fn test_expr_lowerer_pattern2_break_digit_pos_less_zero_generates_compare() {
let mut condition_env = ConditionEnv::new();
condition_env.insert("digit_pos".to_string(), ValueId(10));
let carrier_info = CarrierInfo {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(1),
carriers: vec![],
trim_helper: None,
promoted_loopbodylocals: vec![],
};
let scope = Pattern2ScopeManager {
condition_env: &condition_env,
loop_body_local_env: None,
captured_env: None,
carrier_info: &carrier_info,
};
let mut builder = create_test_builder();
// digit_pos < 0
let ast = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0));
assert!(
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
"digit_pos < 0 should be supported in Pattern2 break condition"
);
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "digit_pos < 0 should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(
!instructions.is_empty(),
"instructions for digit_pos < 0 should not be empty"
);
assert_has_compare(&instructions);
}
#[test]
fn test_expr_lowerer_pattern2_break_methodcall_is_unsupported() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// s.length() (MethodCall) is not supported for Pattern2 break condition
let ast = ASTNode::MethodCall {
object: Box::new(var("s")),
method: "length".to_string(),
arguments: vec![],
span: span(),
};
assert!(
!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
"MethodCall should be rejected for Pattern2 break condition"
);
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(
matches!(result, Err(ExprLoweringError::UnsupportedNode(_))),
"MethodCall should fail-fast with UnsupportedNode"
);
}
}

View File

@ -379,6 +379,7 @@ pub fn analyze_loop_updates(carrier_names: &[String]) -> LoopUpdateSummary {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
#[test]
fn test_update_kind_name() {
@ -387,91 +388,174 @@ mod tests {
assert_eq!(UpdateKind::Other.name(), "Other");
}
// NOTE: Phase 222 - test_typical_index_names commented out
// Function is_typical_index_name() was removed in earlier phase
// #[test]
// fn test_typical_index_names() {
// assert!(is_typical_index_name("i"));
// assert!(is_typical_index_name("idx"));
// assert!(is_typical_index_name("pos"));
// assert!(!is_typical_index_name("result"));
// assert!(!is_typical_index_name("sum"));
// }
fn span() -> Span {
Span::unknown()
}
fn var(name: &str) -> ASTNode {
ASTNode::Variable {
name: name.to_string(),
span: span(),
}
}
fn lit_i(value: i64) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Integer(value),
span: span(),
}
}
fn add(lhs: ASTNode, rhs: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(lhs),
right: Box::new(rhs),
span: span(),
}
}
fn assign(name: &str, value: ASTNode) -> ASTNode {
ASTNode::Assignment {
target: Box::new(var(name)),
value: Box::new(value),
span: span(),
}
}
fn if_with_updates(
condition: ASTNode,
then_body: Vec<ASTNode>,
else_body: Option<Vec<ASTNode>>,
) -> ASTNode {
ASTNode::If {
condition: Box::new(condition),
then_body,
else_body,
span: span(),
}
}
#[test]
fn test_analyze_single_counter() {
fn test_analyze_single_counter_from_ast() {
let names = vec!["i".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![assign("i", add(var("i"), lit_i(1)))];
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(summary.has_single_counter());
assert!(!summary.has_accumulation());
assert_eq!(summary.counter_count(), 1);
assert_eq!(summary.accumulation_count(), 0);
}
#[test]
fn test_analyze_accumulation() {
let names = vec!["result".to_string()];
let summary = analyze_loop_updates(&names);
fn test_analyze_accumulation_from_ast() {
let names = vec!["sum".to_string()];
let loop_body = vec![assign("sum", add(var("sum"), lit_i(1)))];
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(!summary.has_single_counter());
assert!(summary.has_accumulation());
assert_eq!(summary.counter_count(), 0);
assert_eq!(summary.accumulation_count(), 1);
}
#[test]
fn test_analyze_mixed() {
fn test_analyze_mixed_from_ast() {
let names = vec!["i".to_string(), "sum".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![
assign("sum", add(var("sum"), var("i"))),
assign("i", add(var("i"), lit_i(1))),
];
assert!(!summary.has_single_counter()); // 2つあるので false
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(!summary.has_single_counter());
assert!(summary.has_accumulation());
assert_eq!(summary.counter_count(), 1);
assert_eq!(summary.accumulation_count(), 1);
}
// Phase 213 tests for is_simple_if_sum_pattern
#[test]
fn test_is_simple_if_sum_pattern_basic() {
// phase212_if_sum_min.hako pattern: i (counter) + sum (accumulator)
fn test_is_simple_if_sum_pattern_basic_ast() {
let names = vec!["i".to_string(), "sum".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![
if_with_updates(
var("cond"),
vec![assign("sum", add(var("sum"), var("i")))],
None,
),
assign("i", add(var("i"), lit_i(1))),
];
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(summary.is_simple_if_sum_pattern());
assert_eq!(summary.counter_count(), 1);
assert_eq!(summary.accumulation_count(), 1);
}
#[test]
fn test_is_simple_if_sum_pattern_with_count() {
// Phase 195 pattern: i (counter) + sum + count (2 accumulators)
fn test_is_simple_if_sum_pattern_with_count_ast() {
let names = vec!["i".to_string(), "sum".to_string(), "count".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![
if_with_updates(
var("cond"),
vec![
assign("sum", add(var("sum"), var("i"))),
assign("count", add(var("count"), lit_i(1))),
],
None,
),
assign("i", add(var("i"), lit_i(1))),
];
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(summary.is_simple_if_sum_pattern());
assert_eq!(summary.counter_count(), 1);
assert_eq!(summary.accumulation_count(), 2);
}
#[test]
fn test_is_simple_if_sum_pattern_no_accumulator() {
// Only counter, no accumulator
fn test_is_simple_if_sum_pattern_no_accumulator_ast() {
let names = vec!["i".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![assign("i", add(var("i"), lit_i(1)))];
assert!(!summary.is_simple_if_sum_pattern()); // No accumulator
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(!summary.is_simple_if_sum_pattern());
assert_eq!(summary.counter_count(), 1);
assert_eq!(summary.accumulation_count(), 0);
}
#[test]
fn test_is_simple_if_sum_pattern_no_counter() {
// Only accumulator, no counter
fn test_is_simple_if_sum_pattern_no_counter_ast() {
let names = vec!["sum".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![assign("sum", add(var("sum"), lit_i(1)))];
assert!(!summary.is_simple_if_sum_pattern()); // No counter
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(!summary.is_simple_if_sum_pattern());
assert_eq!(summary.counter_count(), 0);
assert_eq!(summary.accumulation_count(), 1);
}
#[test]
fn test_is_simple_if_sum_pattern_multiple_counters() {
// Multiple counters (not supported)
fn test_is_simple_if_sum_pattern_multiple_counters_ast() {
let names = vec!["i".to_string(), "j".to_string(), "sum".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![
assign("i", add(var("i"), lit_i(1))),
assign("j", add(var("j"), lit_i(1))),
assign("sum", add(var("sum"), var("i"))),
];
assert!(!summary.is_simple_if_sum_pattern()); // 2 counters
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(!summary.is_simple_if_sum_pattern());
assert_eq!(summary.counter_count(), 2);
assert_eq!(summary.accumulation_count(), 1);
}
}

View File

@ -251,12 +251,57 @@ pub(crate) fn lower_loop_with_break_minimal(
// After condition lowering, allocate remaining ValueIds
let exit_cond = alloc_value(); // Exit condition (negated loop condition)
// Phase 170-B: Lower break condition using condition_to_joinir (no hardcoding!)
let (break_cond_value, mut break_cond_instructions) = lower_condition_to_joinir(
break_condition,
&mut alloc_value,
env,
)?;
// Phase 170-B / Phase 236-EX: Lower break condition
//
// - ループ条件: 既存どおり ConditionEnv + lower_condition_to_joinir 経路を使用
// - break 条件: Phase 236 から ExprLowerer/ScopeManager 経由に切り替え
//
// ExprLowerer 自体は内部で lower_condition_to_joinir を呼び出すため、JoinIR レベルの命令列は
// 従来経路と構造的に同等になることを期待している。
let (break_cond_value, mut break_cond_instructions) = {
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer, ExprLoweringError};
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
use crate::mir::builder::MirBuilder;
// Phase 236-EX: ExprLowerer は MirBuilder 参照を要求するが、
// Pattern2 の JoinIR lowering では既存の JoinInst バッファと ValueId allocator を直接使っている。
// ここでは MirBuilder は ExprLowerer 内での API 互換のためだけに使い、
// 実際の JoinIR 命令は lower_condition_to_joinir が返すものを採用する。
let mut dummy_builder = MirBuilder::new();
// Build minimal ScopeManager view for break condition lowering.
let empty_body_env = LoopBodyLocalEnv::new();
let empty_captured_env = CapturedEnv::new();
let scope_manager = Pattern2ScopeManager {
condition_env: env,
loop_body_local_env: Some(&empty_body_env),
captured_env: Some(&empty_captured_env),
carrier_info,
};
let mut expr_lowerer = ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
let value_id = match expr_lowerer.lower(break_condition) {
Ok(v) => v,
Err(ExprLoweringError::UnsupportedNode(msg)) => {
return Err(format!(
"[joinir/pattern2] ExprLowerer does not support break condition node: {}",
msg
));
}
Err(e) => {
return Err(format!(
"[joinir/pattern2] ExprLowerer failed to lower break condition: {}",
e
));
}
};
let instructions = expr_lowerer.take_last_instructions();
(value_id, instructions)
};
let _const_1 = alloc_value(); // Increment constant
let _i_next = alloc_value(); // i + 1

View File

@ -146,31 +146,9 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
}
}
// 4. Promoted LoopBodyLocal → Carrier lookup
// If this variable was promoted, try to find its carrier equivalent
if self.carrier_info.promoted_loopbodylocals.contains(&name.to_string()) {
// Try naming conventions
for carrier_name in &[
format!("is_{}", name), // DigitPos pattern
format!("is_{}_match", name), // Trim pattern
] {
// Check if it's the loop variable (unlikely but possible)
if carrier_name == &self.carrier_info.loop_var_name {
if let Some(id) = self.condition_env.get(&self.carrier_info.loop_var_name) {
return Some(id);
}
}
// Otherwise check carriers
if let Some(carrier) = self.carrier_info.carriers.iter().find(|c| c.name == *carrier_name) {
if let Some(join_id) = carrier.join_id {
return Some(join_id);
}
}
}
}
None
// 4. Promoted LoopBodyLocal → Carrier lookup(命名規約は CarrierInfo 側に集約)
self.carrier_info
.resolve_promoted_join_id(name)
}
fn scope_of(&self, name: &str) -> Option<VarScopeKind> {
@ -323,4 +301,36 @@ mod tests {
assert_eq!(scope.lookup("temp"), Some(ValueId(200)));
assert_eq!(scope.scope_of("temp"), Some(VarScopeKind::LoopBodyLocal));
}
#[test]
fn test_pattern2_scope_manager_captured() {
let mut condition_env = ConditionEnv::new();
condition_env.insert("i".to_string(), ValueId(100));
condition_env.insert("len".to_string(), ValueId(201));
let mut captured_env = crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv::new();
captured_env.add_var(CapturedVar {
name: "len".to_string(),
host_id: ValueId(42),
is_immutable: true,
});
let carrier_info = CarrierInfo {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(1),
carriers: vec![],
trim_helper: None,
promoted_loopbodylocals: vec![],
};
let scope = Pattern2ScopeManager {
condition_env: &condition_env,
loop_body_local_env: None,
captured_env: Some(&captured_env),
carrier_info: &carrier_info,
};
assert_eq!(scope.lookup("len"), Some(ValueId(201)));
assert_eq!(scope.scope_of("len"), Some(VarScopeKind::Captured));
}
}