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:
@ -613,6 +613,139 @@ impl Default for AstOwnershipAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 64: Analyze a single loop (condition + body) with parent context.
|
||||
///
|
||||
/// This helper is designed for P3 production integration. It creates a temporary
|
||||
/// function scope for parent-defined variables, then analyzes the loop scope.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `condition` - Loop condition AST node
|
||||
/// * `body` - Loop body statements
|
||||
/// * `parent_defined` - Variables defined in parent scope (function params/locals)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// OwnershipPlan for the loop scope only (not the temporary function scope).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // loop(i < 10) { local sum=0; sum=sum+1; i=i+1; }
|
||||
/// let condition = /* i < 10 */;
|
||||
/// let body = vec![/* local sum=0; sum=sum+1; i=i+1; */];
|
||||
/// let parent_defined = vec![];
|
||||
/// let plan = analyze_loop(&condition, &body, &parent_defined)?;
|
||||
/// // plan.owned_vars contains: sum (is_written=true), i (is_written=true)
|
||||
/// ```
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub fn analyze_loop(
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
parent_defined: &[String],
|
||||
) -> Result<OwnershipPlan, String> {
|
||||
let mut analyzer = AstOwnershipAnalyzer::new();
|
||||
|
||||
// Create temporary function scope for parent context
|
||||
let parent_scope = analyzer.alloc_scope(ScopeKind::Function, None);
|
||||
for var in parent_defined {
|
||||
analyzer
|
||||
.scopes
|
||||
.get_mut(&parent_scope)
|
||||
.unwrap()
|
||||
.defined
|
||||
.insert(var.clone());
|
||||
}
|
||||
|
||||
// Create loop scope
|
||||
let loop_scope = analyzer.alloc_scope(ScopeKind::Loop, Some(parent_scope));
|
||||
|
||||
// Analyze condition (with is_condition=true flag)
|
||||
analyzer.analyze_node(condition, loop_scope, true)?;
|
||||
|
||||
// Analyze body statements
|
||||
for stmt in body {
|
||||
analyzer.analyze_node(stmt, loop_scope, false)?;
|
||||
}
|
||||
|
||||
// Propagate to parent
|
||||
analyzer.propagate_to_parent(loop_scope);
|
||||
|
||||
// Build plan for loop only
|
||||
analyzer.build_plan_for_scope(loop_scope)
|
||||
}
|
||||
|
||||
impl AstOwnershipAnalyzer {
|
||||
/// Phase 64: Build OwnershipPlan for a specific scope (helper for analyze_loop).
|
||||
///
|
||||
/// This is a private helper used by `analyze_loop()` to extract a single
|
||||
/// scope's OwnershipPlan without building plans for all scopes.
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn build_plan_for_scope(&self, scope_id: ScopeId) -> Result<OwnershipPlan, String> {
|
||||
let scope = self
|
||||
.scopes
|
||||
.get(&scope_id)
|
||||
.ok_or_else(|| format!("Scope {:?} not found", scope_id))?;
|
||||
|
||||
let mut plan = OwnershipPlan::new(scope_id);
|
||||
|
||||
// Collect owned vars
|
||||
for name in &scope.defined {
|
||||
let is_written = scope.writes.contains(name);
|
||||
let is_condition_only = is_written && scope.condition_reads.contains(name);
|
||||
plan.owned_vars.push(ScopeOwnedVar {
|
||||
name: name.clone(),
|
||||
is_written,
|
||||
is_condition_only,
|
||||
});
|
||||
}
|
||||
|
||||
// Collect relay writes
|
||||
for name in &scope.writes {
|
||||
if scope.defined.contains(name) {
|
||||
continue;
|
||||
}
|
||||
if let Some((owner_scope, relay_path)) = self.find_owner(scope_id, name) {
|
||||
plan.relay_writes.push(RelayVar {
|
||||
name: name.clone(),
|
||||
owner_scope,
|
||||
relay_path,
|
||||
});
|
||||
} else {
|
||||
return Err(format!(
|
||||
"AstOwnershipAnalyzer: relay write '{}' in scope {:?} has no owner",
|
||||
name, scope_id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Collect captures
|
||||
for name in &scope.reads {
|
||||
if scope.defined.contains(name) || scope.writes.contains(name) {
|
||||
continue;
|
||||
}
|
||||
if let Some((owner_scope, _)) = self.find_owner(scope_id, name) {
|
||||
plan.captures.push(CapturedVar {
|
||||
name: name.clone(),
|
||||
owner_scope,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Collect condition captures
|
||||
for cap in &plan.captures {
|
||||
if scope.condition_reads.contains(&cap.name) {
|
||||
plan.condition_captures.push(cap.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
plan.verify_invariants()?;
|
||||
|
||||
Ok(plan)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user