feat(joinir): Phase 75 - BindingId pilot lookup (dev-only)
Phase 75 pilots BindingId-based variable lookup in the ScopeManager and ConditionEnv components. Demonstrates "BindingId priority → name fallback" strategy with comprehensive testing and zero production impact. Changes: - scope_manager.rs: Added lookup_with_binding() trait method - condition_env.rs: Implemented resolve_var_with_binding() with 3-tier fallback - expr_lowerer.rs: Integrated pilot lookup paths - condition_lowering_box.rs: Updated with BindingId support Tests: 3/3 new pilot tests PASS (priority/fallback/legacy) Tests: lib 958/958 PASS, normalized_dev 54/54 PASS (no regressions) Design: Feature-gated with normalized_dev, enables Phase 76 expansion. 🤖 Generated with Claude Code Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -12,6 +12,8 @@
|
||||
//! - Extract variables from AST (that's condition_var_extractor.rs)
|
||||
//! - Manage HOST ↔ JoinIR bindings (that's inline_boundary.rs)
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
use crate::mir::BindingId; // Phase 75: BindingId-based lookup
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
|
||||
@ -54,6 +56,16 @@ pub struct ConditionEnv {
|
||||
/// - Used in loop condition or body
|
||||
/// - NOT included in header PHI or ExitLine (condition-only)
|
||||
pub captured: BTreeMap<String, ValueId>, // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
|
||||
/// Phase 75: BindingId → ValueId mapping (dev-only, pilot integration)
|
||||
///
|
||||
/// This map provides direct BindingId-based lookup, supporting gradual
|
||||
/// migration from name-based to BindingId-based variable resolution.
|
||||
///
|
||||
/// Populated by lowering code when BindingId information is available
|
||||
/// (e.g., from MirBuilder.binding_map).
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub binding_id_map: BTreeMap<BindingId, ValueId>,
|
||||
}
|
||||
|
||||
impl ConditionEnv {
|
||||
@ -62,6 +74,8 @@ impl ConditionEnv {
|
||||
Self {
|
||||
name_to_join: BTreeMap::new(), // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
captured: BTreeMap::new(), // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
binding_id_map: BTreeMap::new(), // Phase 75: BindingId → ValueId mapping
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,6 +171,89 @@ impl ConditionEnv {
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 75: Resolve variable with BindingId priority (dev-only, pilot integration)
|
||||
///
|
||||
/// Look up variable by BindingId first, falling back to name-based lookup.
|
||||
/// This supports gradual migration from name-based to BindingId-based
|
||||
/// variable resolution.
|
||||
///
|
||||
/// # Lookup Strategy
|
||||
///
|
||||
/// 1. If `binding_id` is Some, try binding_id_map lookup first
|
||||
/// 2. If BindingId lookup fails or is None, fall back to name-based lookup (get())
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `binding_id` - Optional BindingId for the variable
|
||||
/// * `name` - Variable name (used as fallback if BindingId lookup fails)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `Some(ValueId)` if found via BindingId or name, `None` otherwise.
|
||||
///
|
||||
/// # Dev Logging
|
||||
///
|
||||
/// When NYASH_JOINIR_DEBUG=1 is set:
|
||||
/// - `[binding_pilot/hit]` - BindingId lookup succeeded
|
||||
/// - `[binding_pilot/fallback]` - BindingId lookup failed, fell back to name
|
||||
/// - `[binding_pilot/legacy]` - No BindingId provided, used name lookup
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut env = ConditionEnv::new();
|
||||
/// env.insert("x".to_string(), ValueId(100));
|
||||
/// env.binding_id_map.insert(BindingId(5), ValueId(100));
|
||||
///
|
||||
/// // BindingId priority
|
||||
/// assert_eq!(env.resolve_var_with_binding(Some(BindingId(5)), "x"), Some(ValueId(100)));
|
||||
///
|
||||
/// // BindingId miss → name fallback
|
||||
/// assert_eq!(env.resolve_var_with_binding(Some(BindingId(99)), "x"), Some(ValueId(100)));
|
||||
///
|
||||
/// // Legacy (no BindingId)
|
||||
/// assert_eq!(env.resolve_var_with_binding(None, "x"), Some(ValueId(100)));
|
||||
/// ```
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
pub fn resolve_var_with_binding(
|
||||
&self,
|
||||
binding_id: Option<BindingId>,
|
||||
name: &str,
|
||||
) -> Option<ValueId> {
|
||||
if let Some(bid) = binding_id {
|
||||
// Try BindingId lookup first
|
||||
if let Some(&value_id) = self.binding_id_map.get(&bid) {
|
||||
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[binding_pilot/hit] BindingId({}) -> ValueId({}) for '{}'",
|
||||
bid.0, value_id.0, name
|
||||
);
|
||||
}
|
||||
return Some(value_id);
|
||||
} else {
|
||||
// BindingId miss, fall back to name
|
||||
let result = self.get(name);
|
||||
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[binding_pilot/fallback] BindingId({}) miss, name '{}' -> {:?}",
|
||||
bid.0, name, result
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
// Legacy: no BindingId, use name lookup
|
||||
let result = self.get(name);
|
||||
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[binding_pilot/legacy] No BindingId, name '{}' -> {:?}",
|
||||
name, result
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Binding between HOST and JoinIR ValueIds for condition variables
|
||||
@ -247,4 +344,46 @@ mod tests {
|
||||
assert_eq!(binding.host_value, ValueId(33));
|
||||
assert_eq!(binding.join_value, ValueId(1));
|
||||
}
|
||||
|
||||
/// Phase 75: Test BindingId priority lookup (BindingId hit)
|
||||
#[test]
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn test_condition_env_binding_id_priority() {
|
||||
use crate::mir::BindingId;
|
||||
|
||||
let mut env = ConditionEnv::new();
|
||||
env.insert("x".to_string(), ValueId(100));
|
||||
env.binding_id_map.insert(BindingId(5), ValueId(100));
|
||||
|
||||
// BindingId should be used if provided
|
||||
let result = env.resolve_var_with_binding(Some(BindingId(5)), "x");
|
||||
assert_eq!(result, Some(ValueId(100)));
|
||||
}
|
||||
|
||||
/// Phase 75: Test BindingId fallback (BindingId miss → name lookup)
|
||||
#[test]
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn test_condition_env_binding_id_fallback() {
|
||||
use crate::mir::BindingId;
|
||||
|
||||
let mut env = ConditionEnv::new();
|
||||
env.insert("x".to_string(), ValueId(100));
|
||||
// Note: BindingId(99) is NOT in binding_id_map
|
||||
|
||||
// BindingId miss → should fall back to name lookup
|
||||
let result = env.resolve_var_with_binding(Some(BindingId(99)), "x");
|
||||
assert_eq!(result, Some(ValueId(100)));
|
||||
}
|
||||
|
||||
/// Phase 75: Test legacy name-based lookup (no BindingId)
|
||||
#[test]
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn test_condition_env_binding_id_none() {
|
||||
let mut env = ConditionEnv::new();
|
||||
env.insert("x".to_string(), ValueId(100));
|
||||
|
||||
// No BindingId → should use name lookup
|
||||
let result = env.resolve_var_with_binding(None, "x");
|
||||
assert_eq!(result, Some(ValueId(100)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,6 +206,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
@ -260,6 +262,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
|
||||
@ -312,6 +312,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
@ -356,6 +358,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
@ -400,6 +404,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
@ -480,6 +486,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
let boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
|
||||
let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);
|
||||
@ -507,6 +515,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
let boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
|
||||
let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);
|
||||
@ -656,6 +666,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
|
||||
@ -23,6 +23,8 @@ use super::carrier_info::CarrierInfo;
|
||||
use super::condition_env::ConditionEnv;
|
||||
use super::loop_body_local_env::LoopBodyLocalEnv;
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
use crate::mir::BindingId; // Phase 75: BindingId-based lookup pilot
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Phase 231: Scope kind for variables
|
||||
@ -68,6 +70,37 @@ pub trait ScopeManager {
|
||||
/// This helps the caller understand where the variable comes from, which
|
||||
/// can affect code generation (e.g., PHI node generation, exit handling).
|
||||
fn scope_of(&self, name: &str) -> Option<VarScopeKind>;
|
||||
|
||||
/// Phase 75: BindingId-based lookup (dev-only, pilot integration)
|
||||
///
|
||||
/// Look up variable by BindingId first, falling back to name-based lookup.
|
||||
/// This supports gradual migration from name-based to BindingId-based
|
||||
/// variable resolution.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `binding_id` - Optional BindingId for the variable
|
||||
/// * `name` - Variable name (used as fallback if BindingId lookup fails)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `Some(ValueId)` if found via BindingId or name, `None` otherwise.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // BindingId available - priority lookup
|
||||
/// let value_id = scope.lookup_with_binding(Some(BindingId(5)), "x");
|
||||
///
|
||||
/// // BindingId not available - legacy name-based fallback
|
||||
/// let value_id = scope.lookup_with_binding(None, "x");
|
||||
/// ```
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn lookup_with_binding(&self, binding_id: Option<BindingId>, name: &str) -> Option<ValueId> {
|
||||
// Default implementation: BindingId not supported, use name lookup
|
||||
let _ = binding_id; // Suppress unused warning
|
||||
self.lookup(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 231: Pattern2-specific scope manager (pilot implementation)
|
||||
@ -176,6 +209,98 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Phase 76: BindingId-based lookup with promoted binding support (dev-only)
|
||||
///
|
||||
/// Extends Phase 75's BindingId priority lookup to check promoted_bindings map.
|
||||
/// This eliminates name-based hacks (`format!("is_{}", name)`) by using type-safe
|
||||
/// BindingId → BindingId mapping from CarrierInfo.
|
||||
///
|
||||
/// ## Lookup Order
|
||||
///
|
||||
/// 1. Direct BindingId lookup in ConditionEnv (if BindingId provided)
|
||||
/// 2. **NEW (Phase 76)**: Promoted BindingId lookup in CarrierInfo.promoted_bindings
|
||||
/// 3. Fallback to legacy name-based lookup (Phase 75 behavior)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `binding_id` - Optional BindingId from MirBuilder's binding_map
|
||||
/// * `name` - Variable name (fallback for legacy paths)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `Some(ValueId)` if found via BindingId/promoted/name, `None` otherwise.
|
||||
///
|
||||
/// # Example (Phase 76 Promotion Path)
|
||||
///
|
||||
/// ```ignore
|
||||
/// // Given:
|
||||
/// // - "digit_pos" has BindingId(5)
|
||||
/// // - "is_digit_pos" has BindingId(10)
|
||||
/// // - CarrierInfo.promoted_bindings[BindingId(5)] = BindingId(10)
|
||||
/// // - ConditionEnv.binding_id_map[BindingId(10)] = ValueId(102)
|
||||
///
|
||||
/// let scope = Pattern2ScopeManager { ... };
|
||||
///
|
||||
/// // Phase 76: BindingId-based promoted resolution (NEW!)
|
||||
/// let result = scope.lookup_with_binding(Some(BindingId(5)), "digit_pos");
|
||||
/// // Step 1: ConditionEnv[BindingId(5)] → None (not a carrier)
|
||||
/// // Step 2: CarrierInfo.promoted_bindings[BindingId(5)] → BindingId(10) ✓
|
||||
/// // Step 3: ConditionEnv[BindingId(10)] → ValueId(102) ✓
|
||||
/// assert_eq!(result, Some(ValueId(102)));
|
||||
///
|
||||
/// // Phase 75: Legacy name-based fallback still works
|
||||
/// let result = scope.lookup_with_binding(None, "digit_pos");
|
||||
/// // → Falls back to format!("is_digit_pos") lookup
|
||||
/// assert_eq!(result, Some(ValueId(102)));
|
||||
/// ```
|
||||
///
|
||||
/// # Phase 77 Migration Note
|
||||
///
|
||||
/// The legacy name-based path (step 3) will be removed in Phase 77 after all
|
||||
/// promoters populate promoted_bindings map and all call sites provide BindingId.
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
fn lookup_with_binding(&self, binding_id: Option<BindingId>, name: &str) -> Option<ValueId> {
|
||||
use crate::mir::BindingId;
|
||||
|
||||
if let Some(bid) = binding_id {
|
||||
// Step 1: Try direct BindingId lookup in ConditionEnv (Phase 75)
|
||||
if let Some(value_id) = self.condition_env.resolve_var_with_binding(Some(bid), name) {
|
||||
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[phase76/direct] BindingId({}) -> ValueId({}) for '{}'",
|
||||
bid.0, value_id.0, name
|
||||
);
|
||||
}
|
||||
return Some(value_id);
|
||||
}
|
||||
|
||||
// Step 2: **NEW (Phase 76)**: Check promoted_bindings map
|
||||
if let Some(promoted_bid) = self.carrier_info.resolve_promoted_with_binding(bid) {
|
||||
// Promoted BindingId found, lookup in ConditionEnv
|
||||
if let Some(value_id) = self.condition_env.resolve_var_with_binding(Some(promoted_bid), name) {
|
||||
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[phase76/promoted] BindingId({}) promoted to BindingId({}) -> ValueId({}) for '{}'",
|
||||
bid.0, promoted_bid.0, value_id.0, name
|
||||
);
|
||||
}
|
||||
return Some(value_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Fallback to legacy name-based lookup (Phase 75 fallback path)
|
||||
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[phase76/fallback] BindingId({}) miss, falling back to name '{}' lookup",
|
||||
bid.0, name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Legacy name-based lookup (Phase 75 behavior, will be removed in Phase 77)
|
||||
self.lookup(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -195,6 +320,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
@ -226,6 +353,8 @@ mod tests {
|
||||
}],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
@ -256,6 +385,8 @@ mod tests {
|
||||
}],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec!["digit_pos".to_string()],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
@ -283,6 +414,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
@ -316,6 +449,8 @@ mod tests {
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
|
||||
Reference in New Issue
Block a user