refactor(joinir): Phase 71-Pre - OwnershipPlanValidator Box (dev-only)

pattern3_with_if_phi.rs の check_ownership_plan_consistency() を独立した
OwnershipPlanValidator Box に抽出。P2/P4 での再利用を可能にする。

Key changes:
- plan_validator.rs: 新規作成 (~190行)
  - validate_relay_support(): Multi-hop relay Fail-Fast (Phase 70-A タグ)
  - validate_carrier_consistency(): Carrier set 整合性チェック (warn-only)
  - validate_condition_captures(): Condition captures チェック (warn-only)
  - validate_all(): All-in-one 検証

- pattern3_with_if_phi.rs: Validator box への委譲
  - check_ownership_plan_consistency() → OwnershipPlanValidator::validate_all()

Unit tests: 3 PASS
- test_validate_relay_support_single_hop_ok
- test_validate_relay_support_multihop_rejected
- test_validate_all_with_consistent_data

Tests: normalized_dev 50/50 PASS, lib 950/950 PASS

🤖 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-13 02:30:12 +09:00
parent 7b56a7c01d
commit 1424aac901
3 changed files with 201 additions and 53 deletions

View File

@ -311,63 +311,13 @@ impl MirBuilder {
/// 3. **Condition captures consistency**: plan captures vs condition bindings (warn-only)
///
/// Phase 70-A: Standardized error tag for runtime unsupported patterns.
/// Phase 71-Pre: Delegated to OwnershipPlanValidator box.
#[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)
// Tag: [ownership/relay:runtime_unsupported] - standardized for Phase 70-A
for relay in &plan.relay_writes {
if relay.relay_path.len() > 1 {
return Err(format!(
"[ownership/relay:runtime_unsupported] Multihop relay not executable yet: var='{}', owner={:?}, relay_path={:?}",
relay.name, relay.owner_scope, relay.relay_path
));
}
}
// 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(())
use crate::mir::join_ir::ownership::OwnershipPlanValidator;
OwnershipPlanValidator::validate_all(plan, carrier_info, condition_bindings)
}

View File

@ -24,6 +24,7 @@
//! - Phase 57: Analyzer implemented (dev-only)
//! - Phase 58: plan_to_lowering helper for P2 (analyzer-based testing only)
//! - Phase 59: plan_to_lowering helper for P3 (if-sum patterns)
//! - Phase 71-Pre: plan_validator box (reusable validation)
mod types;
mod analyzer;
@ -31,6 +32,8 @@ mod analyzer;
mod plan_to_lowering;
#[cfg(feature = "normalized_dev")]
mod ast_analyzer;
#[cfg(feature = "normalized_dev")]
mod plan_validator;
pub use types::*;
pub use analyzer::*;
@ -38,3 +41,5 @@ pub use analyzer::*;
pub use plan_to_lowering::*;
#[cfg(feature = "normalized_dev")]
pub use ast_analyzer::*;
#[cfg(feature = "normalized_dev")]
pub use plan_validator::*;

View File

@ -0,0 +1,193 @@
//! OwnershipPlan Validator Box
//!
//! Phase 71-Pre: Extracted from pattern3_with_if_phi.rs for reuse across patterns.
//!
//! # Responsibility
//!
//! Validates OwnershipPlan against CarrierInfo and condition bindings.
//! This is **analysis-only** - no MIR generation or lowering.
//!
//! # Checks
//!
//! 1. **Relay support**: Multi-hop relay → Err with `[ownership/relay:runtime_unsupported]`
//! 2. **Carrier consistency**: Plan carriers vs existing carriers (warn-only)
//! 3. **Condition captures**: Plan captures vs condition bindings (warn-only)
use super::OwnershipPlan;
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
use std::collections::BTreeSet;
/// Ownership Plan Validator
///
/// Provides reusable validation methods for OwnershipPlan consistency checks.
/// Used by Pattern 2, 3, 4 lowering to ensure plan integrity before execution.
pub struct OwnershipPlanValidator;
impl OwnershipPlanValidator {
/// Validate relay support (Fail-Fast)
///
/// Returns Err if any relay has `relay_path.len() > 1` (multi-hop).
/// Tag: `[ownership/relay:runtime_unsupported]`
///
/// # Phase 70-A
///
/// This is the standardized runtime guard. When Phase 70-B+ implements
/// multi-hop execution, this check will be relaxed.
pub fn validate_relay_support(plan: &OwnershipPlan) -> Result<(), String> {
for relay in &plan.relay_writes {
if relay.relay_path.len() > 1 {
return Err(format!(
"[ownership/relay:runtime_unsupported] Multihop relay not executable yet: var='{}', owner={:?}, relay_path={:?}",
relay.name, relay.owner_scope, relay.relay_path
));
}
}
Ok(())
}
/// Validate carrier set consistency (warn-only)
///
/// Compares plan's owned_vars (is_written=true) against existing CarrierInfo.
/// Warns on mismatch but does not fail (order SSOT not yet implemented).
pub fn validate_carrier_consistency(
plan: &OwnershipPlan,
carrier_info: &CarrierInfo,
) -> Result<(), String> {
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!("[ownership/validator] Carrier set mismatch (warn-only):");
eprintln!(" OwnershipPlan carriers: {:?}", plan_carriers);
eprintln!(" Existing carriers: {:?}", existing_carriers);
}
Ok(())
}
/// Validate condition captures consistency (warn-only)
///
/// Checks that plan's condition_captures are a subset of condition_bindings.
/// Warns on extra captures but does not fail.
pub fn validate_condition_captures(
plan: &OwnershipPlan,
condition_bindings: &BTreeSet<String>,
) -> Result<(), String> {
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!(
"[ownership/validator] Extra condition captures in plan (warn-only): {:?}",
extra
);
}
Ok(())
}
/// Validate all checks (fail-fast on any Err)
///
/// Runs all validation checks in order:
/// 1. validate_relay_support (Fail-Fast)
/// 2. validate_carrier_consistency (warn-only)
/// 3. validate_condition_captures (warn-only)
pub fn validate_all(
plan: &OwnershipPlan,
carrier_info: &CarrierInfo,
condition_bindings: &BTreeSet<String>,
) -> Result<(), String> {
Self::validate_relay_support(plan)?;
Self::validate_carrier_consistency(plan, carrier_info)?;
Self::validate_condition_captures(plan, condition_bindings)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole, CarrierVar};
use crate::mir::join_ir::ownership::{CapturedVar, RelayVar, ScopeId, ScopeOwnedVar};
use crate::mir::ValueId;
#[test]
fn test_validate_relay_support_single_hop_ok() {
let mut plan = OwnershipPlan::new(ScopeId(1));
plan.relay_writes.push(RelayVar {
name: "sum".to_string(),
owner_scope: ScopeId(0),
relay_path: vec![ScopeId(1)], // Single hop
});
let result = OwnershipPlanValidator::validate_relay_support(&plan);
assert!(result.is_ok());
}
#[test]
fn test_validate_relay_support_multihop_rejected() {
let mut plan = OwnershipPlan::new(ScopeId(2));
plan.relay_writes.push(RelayVar {
name: "sum".to_string(),
owner_scope: ScopeId(0),
relay_path: vec![ScopeId(2), ScopeId(1)], // Multi-hop
});
let result = OwnershipPlanValidator::validate_relay_support(&plan);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.contains("[ownership/relay:runtime_unsupported]"),
"Error should contain standard tag: {}",
err
);
}
#[test]
fn test_validate_all_with_consistent_data() {
let mut plan = OwnershipPlan::new(ScopeId(1));
plan.owned_vars.push(ScopeOwnedVar {
name: "sum".to_string(),
is_written: true,
is_condition_only: false,
});
plan.condition_captures.push(CapturedVar {
name: "limit".to_string(),
owner_scope: ScopeId(0),
});
let carrier_info = CarrierInfo {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(0),
carriers: vec![CarrierVar::with_role_and_init(
"sum".to_string(),
ValueId(1),
CarrierRole::LoopState,
CarrierInit::FromHost,
)],
trim_helper: None,
promoted_loopbodylocals: vec![],
};
let condition_bindings: BTreeSet<String> =
["limit".to_string(), "i".to_string()].into_iter().collect();
let result =
OwnershipPlanValidator::validate_all(&plan, &carrier_info, &condition_bindings);
assert!(result.is_ok());
}
}