|
|
|
|
@ -14,10 +14,19 @@
|
|
|
|
|
//!
|
|
|
|
|
//! - MIR context: `common_init.rs` delegates to this module
|
|
|
|
|
//! - JoinIR context: Uses `from_variable_map()` directly
|
|
|
|
|
//!
|
|
|
|
|
//! # Phase 76: BindingId-Based Promotion Tracking
|
|
|
|
|
//!
|
|
|
|
|
//! Replaces name-based promotion hacks (`"digit_pos"` → `"is_digit_pos"`) with
|
|
|
|
|
//! type-safe BindingId mapping. This eliminates fragile string matching while
|
|
|
|
|
//! maintaining backward compatibility through dual-path lookup.
|
|
|
|
|
|
|
|
|
|
use crate::mir::ValueId;
|
|
|
|
|
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
use crate::mir::BindingId;
|
|
|
|
|
|
|
|
|
|
/// Phase 227: CarrierRole - Distinguishes loop state carriers from condition-only carriers
|
|
|
|
|
///
|
|
|
|
|
/// When LoopBodyLocal variables are promoted to carriers, we need to know whether
|
|
|
|
|
@ -179,6 +188,45 @@ pub struct CarrierInfo {
|
|
|
|
|
///
|
|
|
|
|
/// Condition variable resolution dynamically infers the carrier name from this list.
|
|
|
|
|
pub promoted_loopbodylocals: Vec<String>,
|
|
|
|
|
|
|
|
|
|
/// Phase 76: Type-safe promotion tracking (dev-only)
|
|
|
|
|
///
|
|
|
|
|
/// Maps original BindingId to promoted BindingId, eliminating name-based hacks.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// DigitPos promotion:
|
|
|
|
|
/// - Original: BindingId(5) for `"digit_pos"`
|
|
|
|
|
/// - Promoted: BindingId(10) for `"is_digit_pos"`
|
|
|
|
|
/// - Map entry: `promoted_bindings[BindingId(5)] = BindingId(10)`
|
|
|
|
|
///
|
|
|
|
|
/// This enables type-safe resolution:
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// if let Some(promoted_bid) = carrier_info.promoted_bindings.get(&original_bid) {
|
|
|
|
|
/// // Lookup promoted carrier by BindingId (no string matching!)
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// # Migration Strategy (Phase 76)
|
|
|
|
|
///
|
|
|
|
|
/// - **Dual Path**: BindingId lookup (NEW) OR name-based fallback (LEGACY)
|
|
|
|
|
/// - **Populated by**: DigitPosPromoter, TrimLoopHelper (Phase 76)
|
|
|
|
|
/// - **Used by**: ConditionEnv::resolve_var_with_binding (Phase 75+)
|
|
|
|
|
/// - **Phase 77**: Remove name-based fallback after full migration
|
|
|
|
|
///
|
|
|
|
|
/// # Design Notes
|
|
|
|
|
///
|
|
|
|
|
/// **Q: Why BindingId map instead of name map?**
|
|
|
|
|
/// - **Type Safety**: Compiler-checked binding identity (no typos)
|
|
|
|
|
/// - **Shadowing-Aware**: BindingId distinguishes inner/outer scope vars
|
|
|
|
|
/// - **No Name Collisions**: BindingId is unique even if names shadow
|
|
|
|
|
///
|
|
|
|
|
/// **Q: Why not remove `promoted_loopbodylocals` immediately?**
|
|
|
|
|
/// - **Legacy Compatibility**: Existing code uses name-based lookup
|
|
|
|
|
/// - **Gradual Migration**: Phase 76 adds BindingId, Phase 77 removes name-based
|
|
|
|
|
/// - **Fail-Safe**: Dual path ensures no regressions during transition
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
pub promoted_bindings: BTreeMap<BindingId, BindingId>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CarrierInfo {
|
|
|
|
|
@ -239,6 +287,8 @@ impl CarrierInfo {
|
|
|
|
|
carriers,
|
|
|
|
|
trim_helper: None, // Phase 171-C-5: No Trim pattern by default
|
|
|
|
|
promoted_loopbodylocals: Vec::new(), // Phase 224: No promoted variables by default
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
promoted_bindings: BTreeMap::new(), // Phase 76: No promoted bindings by default
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -300,6 +350,8 @@ impl CarrierInfo {
|
|
|
|
|
carriers,
|
|
|
|
|
trim_helper: None, // Phase 171-C-5: No Trim pattern by default
|
|
|
|
|
promoted_loopbodylocals: Vec::new(), // Phase 224: No promoted variables by default
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
promoted_bindings: BTreeMap::new(), // Phase 76: No promoted bindings by default
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -327,6 +379,8 @@ impl CarrierInfo {
|
|
|
|
|
carriers,
|
|
|
|
|
trim_helper: None, // Phase 171-C-5: No Trim pattern by default
|
|
|
|
|
promoted_loopbodylocals: Vec::new(), // Phase 224: No promoted variables by default
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
promoted_bindings: BTreeMap::new(), // Phase 76: No promoted bindings by default
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -387,6 +441,14 @@ impl CarrierInfo {
|
|
|
|
|
self.promoted_loopbodylocals.push(promoted_var.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Phase 76: Merge promoted_bindings (dev-only)
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
{
|
|
|
|
|
for (original, promoted) in &other.promoted_bindings {
|
|
|
|
|
self.promoted_bindings.insert(*original, *promoted);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Phase 171-C-5: Get Trim pattern helper
|
|
|
|
|
@ -462,6 +524,86 @@ impl CarrierInfo {
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Phase 76: Type-safe promoted binding resolution (dev-only)
|
|
|
|
|
///
|
|
|
|
|
/// Resolves a promoted LoopBodyLocal binding via BindingId map, eliminating
|
|
|
|
|
/// name-based hacks (`format!("is_{}", name)`). Falls back to legacy name-based
|
|
|
|
|
/// lookup for backward compatibility during Phase 76-77 migration.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `original_binding` - Original LoopBodyLocal's BindingId (e.g., BindingId(5) for "digit_pos")
|
|
|
|
|
///
|
|
|
|
|
/// # Returns
|
|
|
|
|
///
|
|
|
|
|
/// * `Some(BindingId)` - Promoted carrier's BindingId (e.g., BindingId(10) for "is_digit_pos")
|
|
|
|
|
/// * `None` - No promotion mapping found
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// // DigitPos promotion: BindingId(5) "digit_pos" → BindingId(10) "is_digit_pos"
|
|
|
|
|
/// let original_bid = BindingId(5);
|
|
|
|
|
/// if let Some(promoted_bid) = carrier_info.resolve_promoted_with_binding(original_bid) {
|
|
|
|
|
/// // Lookup carrier by promoted BindingId (type-safe!)
|
|
|
|
|
/// let promoted_value = condition_env.get_by_binding(promoted_bid);
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// # Migration Path (Phase 76-77)
|
|
|
|
|
///
|
|
|
|
|
/// - **Phase 76**: BindingId map populated by promoters, dual path (BindingId OR name)
|
|
|
|
|
/// - **Phase 77**: Remove name-based fallback, BindingId-only lookup
|
|
|
|
|
///
|
|
|
|
|
/// # Design Notes
|
|
|
|
|
///
|
|
|
|
|
/// **Why not merge with `resolve_promoted_join_id()`?**
|
|
|
|
|
/// - Different input type: BindingId vs String
|
|
|
|
|
/// - Different output: BindingId vs ValueId
|
|
|
|
|
/// - Different usage: ScopeManager (BindingId) vs legacy lowerers (name)
|
|
|
|
|
///
|
|
|
|
|
/// **Why BTreeMap instead of HashMap?**
|
|
|
|
|
/// - Deterministic iteration (Phase 222.5-D consistency)
|
|
|
|
|
/// - Debug-friendly sorted output
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
pub fn resolve_promoted_with_binding(&self, original_binding: BindingId) -> Option<BindingId> {
|
|
|
|
|
self.promoted_bindings.get(&original_binding).copied()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Phase 76: Record a promoted binding (dev-only)
|
|
|
|
|
///
|
|
|
|
|
/// Helper method to populate the promoted_bindings map during promotion.
|
|
|
|
|
/// Called by wrapper functions that have access to both CarrierInfo and binding_map.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `original_binding` - Original LoopBodyLocal's BindingId
|
|
|
|
|
/// * `promoted_binding` - Promoted carrier's BindingId
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// // After DigitPosPromoter creates CarrierInfo, record the binding mapping:
|
|
|
|
|
/// carrier_info.record_promoted_binding(
|
|
|
|
|
/// binding_map.get("digit_pos").copied().unwrap(), // BindingId(5)
|
|
|
|
|
/// binding_map.get("is_digit_pos").copied().unwrap() // BindingId(10)
|
|
|
|
|
/// );
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// # Phase 76 Note
|
|
|
|
|
///
|
|
|
|
|
/// This method is currently UNUSED because promoters (DigitPosPromoter, TrimLoopHelper)
|
|
|
|
|
/// don't have access to binding_map. Actual population happens in a future phase when
|
|
|
|
|
/// we integrate BindingId tracking into the promotion pipeline.
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
pub fn record_promoted_binding(&mut self, original_binding: BindingId, promoted_binding: BindingId) {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[digitpos_promoter/phase76] Recorded promoted binding: {} → {}",
|
|
|
|
|
original_binding, promoted_binding
|
|
|
|
|
);
|
|
|
|
|
self.promoted_bindings.insert(original_binding, promoted_binding);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Exit metadata returned by lowerers
|
|
|
|
|
@ -799,4 +941,110 @@ mod tests {
|
|
|
|
|
let helper = carrier_info.trim_helper().unwrap();
|
|
|
|
|
assert_eq!(helper.original_var, "ch");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========== Phase 76: promoted_bindings tests ==========
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
fn test_promoted_bindings_record_and_resolve() {
|
|
|
|
|
use crate::mir::BindingId;
|
|
|
|
|
|
|
|
|
|
let mut carrier_info = test_carrier_info("i", 5, vec![]);
|
|
|
|
|
|
|
|
|
|
// Record a promotion: BindingId(5) → BindingId(10)
|
|
|
|
|
carrier_info.record_promoted_binding(BindingId(5), BindingId(10));
|
|
|
|
|
|
|
|
|
|
// Resolve should find the mapping
|
|
|
|
|
assert_eq!(
|
|
|
|
|
carrier_info.resolve_promoted_with_binding(BindingId(5)),
|
|
|
|
|
Some(BindingId(10))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Unknown BindingId should return None
|
|
|
|
|
assert_eq!(
|
|
|
|
|
carrier_info.resolve_promoted_with_binding(BindingId(99)),
|
|
|
|
|
None
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
fn test_promoted_bindings_multiple_mappings() {
|
|
|
|
|
use crate::mir::BindingId;
|
|
|
|
|
|
|
|
|
|
let mut carrier_info = test_carrier_info("i", 5, vec![]);
|
|
|
|
|
|
|
|
|
|
// Record multiple promotions (e.g., DigitPos + Trim in same loop)
|
|
|
|
|
carrier_info.record_promoted_binding(BindingId(5), BindingId(10)); // digit_pos → is_digit_pos
|
|
|
|
|
carrier_info.record_promoted_binding(BindingId(6), BindingId(11)); // ch → is_ch_match
|
|
|
|
|
|
|
|
|
|
// Both should resolve independently
|
|
|
|
|
assert_eq!(
|
|
|
|
|
carrier_info.resolve_promoted_with_binding(BindingId(5)),
|
|
|
|
|
Some(BindingId(10))
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
carrier_info.resolve_promoted_with_binding(BindingId(6)),
|
|
|
|
|
Some(BindingId(11))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
fn test_promoted_bindings_merge() {
|
|
|
|
|
use crate::mir::BindingId;
|
|
|
|
|
|
|
|
|
|
let mut carrier_info1 = test_carrier_info("i", 5, vec![test_carrier("sum", 10)]);
|
|
|
|
|
carrier_info1.record_promoted_binding(BindingId(1), BindingId(2));
|
|
|
|
|
|
|
|
|
|
let mut carrier_info2 = test_carrier_info("j", 20, vec![test_carrier("count", 15)]);
|
|
|
|
|
carrier_info2.record_promoted_binding(BindingId(3), BindingId(4));
|
|
|
|
|
|
|
|
|
|
// Merge carrier_info2 into carrier_info1
|
|
|
|
|
carrier_info1.merge_from(&carrier_info2);
|
|
|
|
|
|
|
|
|
|
// Both promoted_bindings should be present
|
|
|
|
|
assert_eq!(
|
|
|
|
|
carrier_info1.resolve_promoted_with_binding(BindingId(1)),
|
|
|
|
|
Some(BindingId(2))
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
carrier_info1.resolve_promoted_with_binding(BindingId(3)),
|
|
|
|
|
Some(BindingId(4))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
fn test_promoted_bindings_default_empty() {
|
|
|
|
|
use crate::mir::BindingId;
|
|
|
|
|
|
|
|
|
|
// Newly created CarrierInfo should have empty promoted_bindings
|
|
|
|
|
let carrier_info = test_carrier_info("i", 5, vec![test_carrier("sum", 10)]);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
carrier_info.resolve_promoted_with_binding(BindingId(0)),
|
|
|
|
|
None
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
fn test_promoted_bindings_overwrite() {
|
|
|
|
|
use crate::mir::BindingId;
|
|
|
|
|
|
|
|
|
|
let mut carrier_info = test_carrier_info("i", 5, vec![]);
|
|
|
|
|
|
|
|
|
|
// Record initial mapping
|
|
|
|
|
carrier_info.record_promoted_binding(BindingId(5), BindingId(10));
|
|
|
|
|
|
|
|
|
|
// Overwrite with new mapping (should replace)
|
|
|
|
|
carrier_info.record_promoted_binding(BindingId(5), BindingId(20));
|
|
|
|
|
|
|
|
|
|
// Should return the new value
|
|
|
|
|
assert_eq!(
|
|
|
|
|
carrier_info.resolve_promoted_with_binding(BindingId(5)),
|
|
|
|
|
Some(BindingId(20))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|