refactor(joinir): split Pattern2 facts from lowering orchestration

This commit is contained in:
nyash-codex
2025-12-17 21:34:11 +09:00
parent a05ce39a1f
commit 368b363694
4 changed files with 814 additions and 1167 deletions

View File

@ -57,6 +57,8 @@ pub(in crate::mir::builder) mod common_init;
pub(in crate::mir::builder) mod loop_true_counter_extractor; // Phase 104: loop(true) counter extraction for Pattern2
pub(in crate::mir::builder) mod read_digits_break_condition_box; // Phase 104: break cond normalization for read_digits(loop(true))
pub(in crate::mir::builder) mod pattern2_break_condition_policy_router; // Phase 105: policy router box for Pattern2 break condition
pub(in crate::mir::builder) mod pattern2_inputs_facts_box; // Phase 105: Pattern2 input facts (analysis only)
pub(in crate::mir::builder) mod pattern2_lowering_orchestrator; // Phase 105: Pattern2 orchestration (wiring/emission)
pub(in crate::mir::builder) mod condition_env_builder;
pub(in crate::mir::builder) mod conversion_pipeline;
pub(in crate::mir::builder) mod exit_binding;

View File

@ -0,0 +1,397 @@
//! Pattern2 input analysis (facts collection only)
//!
//! This box collects everything Pattern2 lowering needs *without* emitting JoinIR:
//! - capture/pinned local analysis
//! - mutable accumulator promotion into carriers
//! - condition env + JoinValueSpace initialization
//! - break-condition routing (policy SSOT)
//! - (optional) body-local allow-list + slot metadata (Phase 92 P3)
//!
//! The goal is to keep orchestration/emission code out of this module.
use super::pattern2_break_condition_policy_router::Pattern2BreakConditionPolicyRouterBox;
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit};
use crate::mir::join_ir::lowering::condition_env::{ConditionBinding, ConditionEnv};
use crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlot;
use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox;
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::ValueId;
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
pub(crate) struct Pattern2DebugLog {
verbose: bool,
debug: DebugOutputBox,
}
impl Pattern2DebugLog {
pub(crate) fn new(verbose: bool) -> Self {
Self {
verbose,
debug: DebugOutputBox::new_with_enabled("joinir/pattern2", verbose),
}
}
pub(crate) fn log(&self, tag: &str, message: impl AsRef<str>) {
if self.verbose {
self.debug.log(tag, message.as_ref());
}
}
}
pub(in crate::mir::builder) struct Pattern2Inputs {
pub loop_var_name: String,
pub loop_var_id: ValueId,
pub carrier_info: CarrierInfo,
pub scope: LoopScopeShape,
pub captured_env: CapturedEnv,
pub join_value_space: JoinValueSpace,
pub env: ConditionEnv,
pub condition_bindings: Vec<ConditionBinding>,
pub body_local_env: LoopBodyLocalEnv,
/// Phase 92 P3: Allow-list of LoopBodyLocal variable names permitted in conditions.
/// This must stay minimal (1 variable) and is validated by ReadOnlyBodyLocalSlotBox.
pub allowed_body_locals_for_conditions: Vec<String>,
/// Phase 92 P3: Diagnostics / debug metadata for the allow-listed variable.
pub read_only_body_local_slot: Option<ReadOnlyBodyLocalSlot>,
pub break_condition_node: ASTNode,
/// loop(true) + break-only digitsread_digits_from family
///
/// Policy-routed: multiple breaks are normalized into a single `break when true`
/// condition, and Pattern2 must schedule body-init before break check.
pub is_loop_true_read_digits: bool,
/// Phase 93 P0: ConditionOnly recipe for derived slot recalculation
pub condition_only_recipe: Option<
crate::mir::join_ir::lowering::common::condition_only_emitter::ConditionOnlyRecipe,
>,
/// Phase 94: BodyLocalDerived recipe for P5b "ch" reassignment + escape counter.
pub body_local_derived_recipe:
Option<crate::mir::join_ir::lowering::common::body_local_derived_emitter::BodyLocalDerivedRecipe>,
}
pub(crate) struct Pattern2InputsFactsBox;
impl Pattern2InputsFactsBox {
pub(in crate::mir::builder) fn analyze(
builder: &MirBuilder,
condition: &ASTNode,
body: &[ASTNode],
fn_body: Option<&[ASTNode]>,
ctx: &super::pattern_pipeline::PatternPipelineContext,
verbose: bool,
) -> Result<Pattern2Inputs, String> {
let log = Pattern2DebugLog::new(verbose);
use super::condition_env_builder::ConditionEnvBuilder;
use crate::mir::loop_pattern_detection::function_scope_capture::{analyze_captured_vars_v2, CapturedEnv};
let loop_var_name = ctx.loop_var_name.clone();
let loop_var_id = ctx.loop_var_id;
let mut carrier_info =
ctx.carrier_info.clone(); // Phase 100 P2-2: Make mutable for accumulator promotion
let scope = ctx.loop_scope.clone();
log.log(
"init",
format!(
"PatternPipelineContext: loop_var='{}', loop_var_id={:?}, carriers={}",
loop_var_name,
loop_var_id,
carrier_info.carriers.len()
),
);
// Capture analysis
log.log(
"phase200c",
format!(
"fn_body is {}",
if fn_body.is_some() { "SOME" } else { "NONE" }
),
);
let captured_env = if let Some(fn_body_ref) = fn_body {
log.log("phase200c", format!("fn_body has {} nodes", fn_body_ref.len()));
analyze_captured_vars_v2(fn_body_ref, condition, body, &scope)
} else {
log.log("phase200c", "fn_body is None, using empty CapturedEnv");
CapturedEnv::new()
};
if verbose {
log.log(
"capture",
format!("Phase 200-C: Captured {} variables", captured_env.vars.len()),
);
for var in &captured_env.vars {
log.log(
"capture",
format!(
" '{}': host_id={:?}, immutable={}",
var.name, var.host_id, var.is_immutable
),
);
}
}
// Phase 100 P1-3: Pinned Local Analysis (Judgment Box)
// Analyze loop body AST to identify pinned locals (read-only loop-outer locals)
let mut captured_env = captured_env; // Make mutable for pinned insertions
// Collect candidate locals from variable_map (all variables defined before loop)
let candidate_locals: std::collections::BTreeSet<String> =
builder.variable_ctx.variable_map.keys().cloned().collect();
if !candidate_locals.is_empty() {
use crate::mir::loop_pattern_detection::pinned_local_analyzer::analyze_pinned_locals;
match analyze_pinned_locals(body, &candidate_locals) {
Ok(pinned_names) => {
if verbose && !pinned_names.is_empty() {
log.log(
"phase100_p1",
format!("Detected {} pinned locals", pinned_names.len()),
);
}
for pinned_name in pinned_names {
if let Some(&host_id) = builder.variable_ctx.variable_map.get(&pinned_name) {
if verbose {
log.log(
"phase100_p1",
format!(
"Wiring pinned local '{}' with host_id={:?}",
pinned_name, host_id
),
);
}
captured_env.insert_pinned(pinned_name, host_id);
} else {
return Err(format!(
"Pinned local '{}' not found in variable_map (internal error)",
pinned_name
));
}
}
}
Err(e) => return Err(format!("Pinned local analysis failed: {}", e)),
}
}
// Phase 100 P2-2: Mutable Accumulator Analysis
use crate::mir::loop_pattern_detection::mutable_accumulator_analyzer::{
AccumulatorKind, MutableAccumulatorAnalyzer, RhsExprKind,
};
let mutable_spec = MutableAccumulatorAnalyzer::analyze(body)?;
if let Some(spec) = mutable_spec {
if verbose {
log.log(
"phase100_p2",
format!(
"Detected mutable accumulator: '{}' = '{}' + '{}'",
spec.target_name, spec.target_name, spec.rhs_var_or_lit
),
);
}
let target_id = builder
.variable_ctx
.variable_map
.get(&spec.target_name)
.ok_or_else(|| {
format!(
"[joinir/mutable-acc] Target '{}' not found in variable_map",
spec.target_name
)
})?;
let mut refined_kind = spec.kind;
if spec.rhs_expr_kind == RhsExprKind::Var && refined_kind == AccumulatorKind::Int {
use crate::mir::MirType;
if let Some(target_type) = builder.type_ctx.value_types.get(target_id) {
match target_type {
MirType::Box(box_name) if box_name == "StringBox" => {
refined_kind = AccumulatorKind::String;
if verbose {
log.log(
"phase100_p3",
format!(
"Refined accumulator kind: Int → String (target '{}' is StringBox)",
spec.target_name
),
);
}
}
MirType::Integer => {
if verbose {
log.log(
"phase100_p3",
format!(
"Confirmed accumulator kind: Int (target '{}' is Integer)",
spec.target_name
),
);
}
}
_ => {
if verbose {
log.log(
"phase100_p3",
format!(
"Accumulator kind: Int (default, target '{}' type unknown: {:?})",
spec.target_name, target_type
),
);
}
}
}
}
}
if refined_kind == AccumulatorKind::String {
match spec.rhs_expr_kind {
RhsExprKind::Var => {
if verbose {
log.log(
"phase100_p3",
format!(
"String accumulator '{}' = '{}' + '{}' (Variable RHS: OK)",
spec.target_name, spec.target_name, spec.rhs_var_or_lit
),
);
}
}
RhsExprKind::Literal => {
return Err(format!(
"[joinir/mutable-acc] String accumulator '{}' with Literal RHS not supported in Phase 100 P3 (will be P3.1)",
spec.target_name
));
}
}
}
match spec.rhs_expr_kind {
RhsExprKind::Literal => {}
RhsExprKind::Var => {
let rhs_name = &spec.rhs_var_or_lit;
let in_captured = captured_env.vars.iter().any(|v| &v.name == rhs_name);
let in_carrier = carrier_info.carriers.iter().any(|c| &c.name == rhs_name);
if in_carrier {
return Err(format!(
"[joinir/mutable-acc] RHS '{}' must be read-only (Condition/BodyLocal/Captured/Pinned), but found mutable Carrier",
rhs_name
));
} else if !in_captured && !builder.variable_ctx.variable_map.contains_key(rhs_name) {
if verbose {
log.log(
"phase100_p2",
format!(
"RHS '{}' not in captured/variable_map, assuming LoopBodyLocal (will validate later)",
rhs_name
),
);
}
}
}
}
if verbose {
log.log(
"phase100_p2",
format!("Promoting '{}' to mutable LoopState carrier", spec.target_name),
);
}
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
carrier_info
.carriers
.push(CarrierVar::new(spec.target_name.clone(), *target_id));
} else if verbose {
log.log("phase100_p2", "No mutable accumulator pattern detected");
}
// Value space + condition env
let mut join_value_space = JoinValueSpace::new();
let (mut env, mut condition_bindings, _loop_var_join_id) =
ConditionEnvBuilder::build_for_break_condition_v2(
condition,
&loop_var_name,
&builder.variable_ctx.variable_map,
loop_var_id,
&mut join_value_space,
)?;
// Phase 79-2: Register loop variable BindingId (dev-only)
#[cfg(feature = "normalized_dev")]
if let Some(loop_var_bid) = builder.binding_ctx.lookup(&loop_var_name) {
env.register_loop_var_binding(loop_var_bid, _loop_var_join_id);
log.log(
"phase79",
format!(
"Registered loop var BindingId: '{}' BindingId({}) → ValueId({})",
loop_var_name, loop_var_bid.0, _loop_var_join_id.0
),
);
}
// Add captured vars
for var in &captured_env.vars {
if let Some(&host_id) = builder.variable_ctx.variable_map.get(&var.name) {
let join_id = join_value_space.alloc_param();
env.insert(var.name.clone(), join_id);
condition_bindings.push(ConditionBinding {
name: var.name.clone(),
host_value: host_id,
join_value: join_id,
});
log.log(
"capture",
format!(
"Phase 201: Added captured '{}': host={:?}, join={:?}",
var.name, host_id, join_id
),
);
}
}
let body_local_env = LoopBodyLocalEnv::new();
// Break condition extraction is policy-routed (SSOT).
let break_routing = Pattern2BreakConditionPolicyRouterBox::route(condition, body)?;
let mut inputs = Pattern2Inputs {
loop_var_name,
loop_var_id,
carrier_info,
scope,
captured_env,
join_value_space,
env,
condition_bindings,
body_local_env,
allowed_body_locals_for_conditions: Vec::new(),
read_only_body_local_slot: None,
break_condition_node: break_routing.break_condition_node,
is_loop_true_read_digits: break_routing.is_loop_true_read_digits,
condition_only_recipe: None,
body_local_derived_recipe: None,
};
if !break_routing.allowed_body_locals_for_conditions.is_empty() {
use crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlotBox;
inputs.allowed_body_locals_for_conditions =
break_routing.allowed_body_locals_for_conditions.clone();
inputs.read_only_body_local_slot = Some(ReadOnlyBodyLocalSlotBox::extract_single(
&inputs.allowed_body_locals_for_conditions,
body,
)?);
}
Ok(inputs)
}
}

View File

@ -0,0 +1,403 @@
//! Pattern2 lowering orchestration (wiring + emission)
//!
//! This module is the "do the work" side of Pattern2:
//! - promotion/slot routing for body-local vars in conditions
//! - trim normalization (when enabled)
//! - derived-slot routing (Phase 94)
//! - carrier update analysis + filtering
//! - invoking JoinIR lowerer + boundary merge pipeline
//!
//! It intentionally depends on `Pattern2InputsFactsBox` for analysis-only inputs.
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit};
use crate::mir::join_ir::lowering::condition_env::ConditionBinding;
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
use crate::mir::loop_pattern_detection::error_messages;
use crate::mir::ValueId;
use super::pattern2_inputs_facts_box::{Pattern2DebugLog, Pattern2Inputs, Pattern2InputsFactsBox};
use super::body_local_policy::{classify_for_pattern2, BodyLocalRoute};
use super::policies::p5b_escape_derived_policy::{classify_p5b_escape_derived, P5bEscapeDerivedDecision};
use super::policies::PolicyDecision;
use std::collections::BTreeMap;
pub(crate) struct Pattern2LoweringOrchestrator;
impl Pattern2LoweringOrchestrator {
pub(in crate::mir::builder) fn run(
builder: &mut MirBuilder,
condition: &ASTNode,
body: &[ASTNode],
func_name: &str,
debug: bool,
fn_body: Option<&[ASTNode]>,
skeleton: Option<&crate::mir::loop_canonicalizer::LoopSkeleton>,
) -> Result<Option<ValueId>, String> {
use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal;
let verbose = debug || crate::config::env::joinir_dev_enabled();
let log = Pattern2DebugLog::new(verbose);
super::super::trace::trace().debug("pattern2", "Calling Pattern 2 minimal lowerer");
use super::pattern_pipeline::{build_pattern_context, PatternVariant};
let ctx = build_pattern_context(builder, condition, body, PatternVariant::Pattern2)?;
super::super::trace::trace().varmap("pattern2_start", &builder.variable_ctx.variable_map);
let mut inputs = Pattern2InputsFactsBox::analyze(builder, condition, body, fn_body, &ctx, verbose)?;
promote_and_prepare_carriers(builder, condition, body, &mut inputs, debug, verbose)?;
let (effective_break_condition, normalized_body) =
apply_trim_and_normalize(builder, condition, body, &mut inputs, verbose)?;
let analysis_body = normalized_body.as_deref().unwrap_or(body);
// Phase 94: Detect P5b escape-derived (`ch` reassignment + escape counter).
match classify_p5b_escape_derived(analysis_body, &inputs.loop_var_name) {
P5bEscapeDerivedDecision::Use(recipe) => {
log.log(
"phase94",
format!(
"Phase 94: Enabled BodyLocalDerived for '{}' (counter='{}', pre_delta={}, post_delta={})",
recipe.name, recipe.loop_counter_name, recipe.pre_delta, recipe.post_delta
),
);
inputs.body_local_derived_recipe = Some(recipe);
}
P5bEscapeDerivedDecision::Reject(reason) => return Err(format!("[cf_loop/pattern2] {}", reason)),
P5bEscapeDerivedDecision::None => {
let has_ch_reassign = analysis_body.iter().any(|n| match n {
ASTNode::Assignment { target, .. } => matches!(
target.as_ref(),
ASTNode::Variable { name, .. } if name == "ch"
),
_ => false,
});
if crate::config::env::joinir_dev::strict_enabled() && has_ch_reassign {
return Err(format!(
"[cf_loop/pattern2] {}",
crate::mir::join_ir::lowering::error_tags::freeze(
"[phase94/body_local_derived/contract/unhandled_reassign] Body-local reassignment to 'ch' detected but not supported by Phase 94 recipe"
)
));
}
}
}
use crate::mir::join_ir::lowering::loop_update_analyzer::LoopUpdateAnalyzer;
let carrier_updates =
LoopUpdateAnalyzer::analyze_carrier_updates(analysis_body, &inputs.carrier_info.carriers);
log.log(
"updates",
format!("Phase 176-3: Analyzed {} carrier updates", carrier_updates.len()),
);
let original_carrier_count = inputs.carrier_info.carriers.len();
filter_carriers_for_updates(&mut inputs.carrier_info, &carrier_updates);
log.log(
"updates",
format!(
"Phase 176-4: Filtered carriers: {}{} (kept only carriers with updates/condition-only/loop-local-zero)",
original_carrier_count,
inputs.carrier_info.carriers.len()
),
);
// Ensure env has join-ids for carriers that are referenced only from body updates.
for carrier in &inputs.carrier_info.carriers {
if inputs.env.get(&carrier.name).is_none() {
let join_value = carrier
.join_id
.unwrap_or_else(|| inputs.join_value_space.alloc_param());
inputs.env.insert(carrier.name.clone(), join_value);
if carrier.init != CarrierInit::LoopLocalZero {
inputs.condition_bindings.push(ConditionBinding {
name: carrier.name.clone(),
host_value: carrier.host_id,
join_value,
});
} else {
log.log(
"updates",
format!(
"Phase 247-EX: Skipping host binding for loop-local carrier '{}' (init=LoopLocalZero)",
carrier.name
),
);
}
}
}
let lowering_inputs = crate::mir::join_ir::lowering::loop_with_break_minimal::LoopWithBreakLoweringInputs {
scope: inputs.scope,
condition,
break_condition: &effective_break_condition,
env: &inputs.env,
carrier_info: &inputs.carrier_info,
carrier_updates: &carrier_updates,
body_ast: analysis_body,
body_local_env: Some(&mut inputs.body_local_env),
allowed_body_locals_for_conditions: if inputs.allowed_body_locals_for_conditions.is_empty() {
None
} else {
Some(inputs.allowed_body_locals_for_conditions.as_slice())
},
join_value_space: &mut inputs.join_value_space,
skeleton,
condition_only_recipe: inputs.condition_only_recipe.as_ref(),
body_local_derived_recipe: inputs.body_local_derived_recipe.as_ref(),
};
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(lowering_inputs) {
Ok((module, meta)) => (module, meta),
Err(e) => {
super::super::trace::trace().debug("pattern2", &format!("Pattern 2 lowerer failed: {}", e));
return Err(format!("[cf_loop/pattern2] Lowering failed: {}", e));
}
};
let exit_meta = &fragment_meta.exit_meta;
use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector;
let exit_bindings = ExitMetaCollector::collect(builder, exit_meta, Some(&inputs.carrier_info), debug);
// JoinIR main() params: [ValueId(0), ValueId(1), ...]
let mut join_input_slots = vec![ValueId(0)];
let mut host_input_values = vec![inputs.loop_var_id];
for (idx, carrier) in inputs.carrier_info.carriers.iter().enumerate() {
join_input_slots.push(ValueId((idx + 1) as u32));
host_input_values.push(carrier.host_id);
}
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(join_input_slots, host_input_values)
.with_condition_bindings(inputs.condition_bindings)
.with_exit_bindings(exit_bindings.clone())
.with_expr_result(fragment_meta.expr_result)
.with_loop_var_name(Some(inputs.loop_var_name.clone()))
.with_carrier_info(inputs.carrier_info.clone())
.build();
use super::conversion_pipeline::JoinIRConversionPipeline;
let _ = JoinIRConversionPipeline::execute(builder, join_module, Some(&boundary), "pattern2", debug)?;
let void_val = crate::mir::builder::emission::constant::emit_void(builder);
super::super::trace::trace().debug("pattern2", &format!("Loop complete, returning Void {:?}", void_val));
Ok(Some(void_val))
}
}
/// Updated carriers filtering helper (shared by orchestrator + tests).
fn filter_carriers_for_updates(carrier_info: &mut CarrierInfo, carrier_updates: &BTreeMap<String, UpdateExpr>) {
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole};
carrier_info.carriers.retain(|carrier| {
carrier_updates.contains_key(&carrier.name)
|| carrier.role == CarrierRole::ConditionOnly
|| carrier.init == CarrierInit::LoopLocalZero
});
}
pub(in crate::mir::builder) fn promote_and_prepare_carriers(
builder: &mut MirBuilder,
condition: &ASTNode,
body: &[ASTNode],
inputs: &mut Pattern2Inputs,
debug: bool,
verbose: bool,
) -> Result<(), String> {
use crate::mir::join_ir::lowering::digitpos_condition_normalizer::DigitPosConditionNormalizer;
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
let cond_scope = LoopConditionScopeBox::analyze(
&inputs.loop_var_name,
&vec![condition, &inputs.break_condition_node],
Some(&inputs.scope),
);
let log = Pattern2DebugLog::new(verbose);
let mut promoted_pairs: Vec<(String, String)> = Vec::new();
let cond_body_local_vars: Vec<String> = cond_scope
.vars
.iter()
.filter(|v| matches!(v.scope, crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope::LoopBodyLocal))
.map(|v| v.name.clone())
.collect();
if cond_scope.has_loop_body_local() {
if !inputs.is_loop_true_read_digits {
match classify_for_pattern2(
builder,
&inputs.loop_var_name,
&inputs.scope,
&inputs.break_condition_node,
&cond_scope,
body,
) {
PolicyDecision::Use(BodyLocalRoute::Promotion {
promoted_carrier,
promoted_var,
carrier_name,
}) => {
let is_trim_promotion = promoted_carrier.trim_helper().is_some();
if !is_trim_promotion {
promoted_pairs.push((promoted_var.clone(), carrier_name.clone()));
}
#[cfg(feature = "normalized_dev")]
{
use crate::mir::join_ir::lowering::carrier_binding_assigner::CarrierBindingAssigner;
let mut promoted_carrier = promoted_carrier;
CarrierBindingAssigner::assign_promoted_binding(
builder,
&mut promoted_carrier,
&promoted_var,
&carrier_name,
)
.map_err(|e| format!("[phase78/binding_assign] {:?}", e))?;
inputs.carrier_info.merge_from(&promoted_carrier);
}
#[cfg(not(feature = "normalized_dev"))]
{
inputs.carrier_info.merge_from(&promoted_carrier);
}
inputs
.carrier_info
.promoted_loopbodylocals
.push(promoted_var.clone());
if !is_trim_promotion {
inputs.break_condition_node = DigitPosConditionNormalizer::normalize(
&inputs.break_condition_node,
&promoted_var,
&carrier_name,
);
}
}
PolicyDecision::Use(BodyLocalRoute::ReadOnlySlot(slot)) => {
inputs.allowed_body_locals_for_conditions = vec![slot.name.clone()];
inputs.read_only_body_local_slot = Some(slot);
}
PolicyDecision::Reject(reason) => {
return Err(error_messages::format_error_pattern2_promotion_failed(
&cond_body_local_vars,
&reason,
));
}
PolicyDecision::None => {}
}
}
}
// Allocate join_ids for carriers and register bindings.
for carrier in &mut inputs.carrier_info.carriers {
let carrier_join_id = inputs.join_value_space.alloc_param();
carrier.join_id = Some(carrier_join_id);
#[cfg(feature = "normalized_dev")]
if let Some(binding_id) = carrier.binding_id {
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
match carrier.role {
CarrierRole::ConditionOnly => inputs.env.register_condition_binding(binding_id, carrier_join_id),
CarrierRole::LoopState => inputs.env.register_carrier_binding(binding_id, carrier_join_id),
}
}
}
for (promoted_var, promoted_carrier_name) in promoted_pairs {
let join_id = inputs
.carrier_info
.find_carrier(&promoted_carrier_name)
.and_then(|c| c.join_id)
.ok_or_else(|| format!("[phase229] promoted carrier '{}' has no join_id", promoted_carrier_name))?;
inputs.env.insert(promoted_var, join_id);
}
// ExprLowerer validation (best-effort; unchanged behavior)
{
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer, ExprLoweringError};
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
let scope_manager = Pattern2ScopeManager {
condition_env: &inputs.env,
loop_body_local_env: Some(&inputs.body_local_env),
captured_env: Some(&inputs.captured_env),
carrier_info: &inputs.carrier_info,
};
match ExprLowerer::new(&scope_manager, ExprContext::Condition, builder)
.with_debug(debug)
.lower(&inputs.break_condition_node)
{
Ok(_) => {}
Err(ExprLoweringError::UnsupportedNode(_)) => {}
Err(_) => {}
}
}
Ok(())
}
fn apply_trim_and_normalize(
builder: &mut MirBuilder,
condition: &ASTNode,
body: &[ASTNode],
inputs: &mut Pattern2Inputs,
verbose: bool,
) -> Result<(ASTNode, Option<Vec<ASTNode>>), String> {
let log = Pattern2DebugLog::new(verbose);
let mut alloc_join_value = || inputs.join_value_space.alloc_param();
let disable_trim = inputs.is_loop_true_read_digits;
let effective_break_condition = if !disable_trim {
if let Some(trim_result) = super::trim_loop_lowering::TrimLoopLowerer::try_lower_trim_like_loop(
builder,
&inputs.scope,
condition,
&inputs.break_condition_node,
body,
&inputs.loop_var_name,
&mut inputs.carrier_info,
&mut alloc_join_value,
)? {
log.log("trim", "TrimLoopLowerer processed Trim pattern successfully");
inputs.carrier_info = trim_result.carrier_info;
inputs.condition_only_recipe = trim_result.condition_only_recipe;
trim_result.condition
} else {
inputs.break_condition_node.clone()
}
} else {
inputs.break_condition_node.clone()
};
use crate::mir::join_ir::lowering::complex_addend_normalizer::{ComplexAddendNormalizer, NormalizationResult};
let mut normalized_body = Vec::new();
let mut has_normalization = false;
for node in body {
match ComplexAddendNormalizer::normalize_assign(node) {
NormalizationResult::Normalized { temp_def, new_assign, .. } => {
normalized_body.push(temp_def);
normalized_body.push(new_assign);
has_normalization = true;
}
NormalizationResult::Unchanged => normalized_body.push(node.clone()),
}
}
Ok((
effective_break_condition,
if has_normalization { Some(normalized_body) } else { None },
))
}