feat(joinir): Phase 64 - Ownership P3 production integration (dev-only)

P3(if-sum) 本番ルートに OwnershipPlan 解析を接続(analysis-only)。

Key changes:
- ast_analyzer.rs: Added analyze_loop() helper
  - Loop-specific analysis API for production integration
  - build_plan_for_scope() helper for single-scope extraction

- pattern3_with_if_phi.rs: P3 production integration
  - OwnershipPlan analysis after ConditionEnv building
  - check_ownership_plan_consistency() for validation
  - Feature-gated #[cfg(feature = "normalized_dev")]

Consistency checks:
- Fail-Fast: Multi-hop relay (relay_path.len() > 1)
- Warn-only: Carrier set mismatch (order SSOT deferred)
- Warn-only: Condition captures (some patterns have extras)

Tests: 49/49 PASS (2 new Phase 64 tests)
- test_phase64_p3_ownership_prod_integration
- test_phase64_p3_multihop_relay_detection
- Zero regressions

Design: Analysis-only, no behavior change
- Integration point: After ConditionEnv, before JoinIR lowering
- Dev-only validation for future SSOT migration

Next: Phase 65+ - Carrier order SSOT, owner-based init

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-12 23:02:40 +09:00
parent 0ff96612cf
commit ba6e420f31
7 changed files with 929 additions and 9 deletions

View File

@ -142,6 +142,54 @@ impl MirBuilder {
);
}
// Phase 64: Ownership analysis (dev-only, analysis-only)
#[cfg(feature = "normalized_dev")]
{
use crate::mir::join_ir::ownership::analyze_loop;
// Collect parent-defined variables from function scope
// For now, use all variables in variable_map except loop_var
let parent_defined: Vec<String> = self
.variable_map
.keys()
.filter(|name| *name != &loop_var_name)
.cloned()
.collect();
match analyze_loop(condition, body, &parent_defined) {
Ok(plan) => {
// Convert ConditionBinding Vec to BTreeSet<String> for consistency check
let condition_binding_names: std::collections::BTreeSet<String> =
condition_bindings.iter().map(|b| b.name.clone()).collect();
// Run consistency checks
if let Err(e) =
check_ownership_plan_consistency(&plan, &ctx.carrier_info, &condition_binding_names)
{
eprintln!("[phase64/ownership] Consistency check failed: {}", e);
return Err(e);
}
trace::trace().debug(
"pattern3/if-sum",
&format!(
"OwnershipPlan analysis succeeded: {} owned vars, {} relay writes, {} captures",
plan.owned_vars.len(),
plan.relay_writes.len(),
plan.captures.len()
),
);
}
Err(e) => {
eprintln!(
"[phase64/ownership] Analysis failed (continuing with legacy): {}",
e
);
// Don't fail - analysis is optional in Phase 64
}
}
}
// Call AST-based if-sum lowerer with ConditionEnv
let (join_module, fragment_meta) =
lower_if_sum_pattern(condition, if_stmt, body, &cond_env, &mut join_value_space)?;
@ -250,3 +298,73 @@ impl MirBuilder {
// Phase 242-EX-A: lower_pattern3_legacy removed - all patterns now use AST-based lowering
}
/// Phase 64: Ownership plan consistency checks (dev-only)
///
/// Validates OwnershipPlan against existing CarrierInfo and ConditionBindings.
/// This is analysis-only - no behavior change.
///
/// # Checks
///
/// 1. **Multi-hop relay rejection**: `relay_path.len() > 1` → Err (out of scope)
/// 2. **Carrier set consistency**: plan carriers vs existing carriers (warn-only)
/// 3. **Condition captures consistency**: plan captures vs condition bindings (warn-only)
#[cfg(feature = "normalized_dev")]
fn check_ownership_plan_consistency(
plan: &crate::mir::join_ir::ownership::OwnershipPlan,
carrier_info: &crate::mir::join_ir::lowering::carrier_info::CarrierInfo,
condition_bindings: &std::collections::BTreeSet<String>,
) -> Result<(), String> {
use std::collections::BTreeSet;
// Check 1: Multi-hop relay is rejected (Fail-Fast)
for relay in &plan.relay_writes {
if relay.relay_path.len() > 1 {
return Err(format!(
"Phase 64 limitation: multi-hop relay not supported. Variable '{}' has relay path length {}",
relay.name, relay.relay_path.len()
));
}
}
// Check 2: Carrier set consistency (warn-only, order SSOT deferred)
let plan_carriers: BTreeSet<String> = plan
.owned_vars
.iter()
.filter(|v| v.is_written)
.map(|v| v.name.clone())
.collect();
let existing_carriers: BTreeSet<String> = carrier_info
.carriers
.iter()
.map(|c| c.name.clone())
.collect();
if plan_carriers != existing_carriers {
eprintln!("[phase64/ownership] Carrier set mismatch (warn-only, order SSOT deferred):");
eprintln!(" OwnershipPlan carriers: {:?}", plan_carriers);
eprintln!(" Existing carriers: {:?}", existing_carriers);
// Don't fail - just warn (order SSOT not yet implemented)
}
// Check 3: Condition captures consistency (warn-only)
let plan_cond_captures: BTreeSet<String> = plan
.condition_captures
.iter()
.map(|c| c.name.clone())
.collect();
if !plan_cond_captures.is_subset(condition_bindings) {
let extra: Vec<_> = plan_cond_captures
.difference(condition_bindings)
.collect();
eprintln!(
"[phase64/ownership] Extra condition captures in plan (warn-only): {:?}",
extra
);
// Warn only - this might be expected in some cases
}
Ok(())
}