Phase 74 implements BindingId as parallel allocation alongside ValueId for lexical scope tracking and shadowing-aware variable identity. Changes: - binding_id.rs: New BindingId type with overflow protection (5 unit tests) - builder.rs: Added next_binding_id counter and binding_map (4 integration tests) - lexical_scope.rs: Extended restoration logic for BindingId management - mod.rs: Public re-export of BindingId Tests: 9/9 new PASS, lib 958/958 PASS (no regressions) Architecture: Parallel BindingId/ValueId allocation for deterministic shadowing Phase 75-77 will build on this infrastructure for type-safe promotion tracking. 🤖 Generated with Claude Code Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
123 lines
3.5 KiB
Rust
123 lines
3.5 KiB
Rust
/*!
|
|
* BindingId - Lexical variable binding identity (Phase 74)
|
|
*
|
|
* Phase 74: BindingId Infrastructure
|
|
* Provides a parallel identifier system alongside ValueId for tracking
|
|
* lexical variable bindings independently from SSA values.
|
|
*
|
|
* ## What is a BindingId?
|
|
*
|
|
* A `BindingId` represents a unique lexical variable binding in the source code,
|
|
* separate from SSA `ValueId`s. While ValueId tracks SSA values that may be
|
|
* renamed through PHI nodes and shadowing, BindingId tracks the original
|
|
* variable binding identity.
|
|
*
|
|
* ### Relationship with ValueId
|
|
*
|
|
* - **ValueId**: SSA value identity (may change through PHI nodes, renaming)
|
|
* - **BindingId**: Lexical binding identity (stable across shadowing)
|
|
*
|
|
* Example:
|
|
* ```nyash
|
|
* local x = 1; // BindingId(0), ValueId(10)
|
|
* {
|
|
* local x = 2; // BindingId(1), ValueId(20) <- new binding, shadows outer x
|
|
* } // BindingId(1) goes out of scope, restore BindingId(0)
|
|
* ```
|
|
*
|
|
* ### Relationship with Shadowing
|
|
*
|
|
* Shadowing creates a **new BindingId** for the inner scope:
|
|
* - Outer `x`: BindingId(0) -> ValueId(10)
|
|
* - Inner `x`: BindingId(1) -> ValueId(20)
|
|
*
|
|
* On scope exit, BindingId(1) is discarded and BindingId(0) is restored.
|
|
*
|
|
* ## Design Goals
|
|
*
|
|
* 1. **Parallel Allocation**: BindingId and ValueId allocate independently
|
|
* 2. **Zero Runtime Cost**: Infrastructure layer only, no production impact
|
|
* 3. **Incremental Adoption**: Can be added without changing existing behavior
|
|
* 4. **Future-Ready**: Supports Phase 75+ ScopeManager migration
|
|
*/
|
|
|
|
/// Unique identifier for a lexical variable binding
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct BindingId(pub u32);
|
|
|
|
impl BindingId {
|
|
/// Create a new BindingId (raw constructor, prefer `allocate_binding_id()` in MirBuilder)
|
|
pub fn new(id: u32) -> Self {
|
|
debug_assert!(
|
|
id < u32::MAX,
|
|
"BindingId overflow: attempted to create BindingId({})",
|
|
id
|
|
);
|
|
BindingId(id)
|
|
}
|
|
|
|
/// Get the next sequential BindingId
|
|
pub fn next(self) -> Self {
|
|
debug_assert!(
|
|
self.0 < u32::MAX - 1,
|
|
"BindingId overflow: next() called on BindingId({})",
|
|
self.0
|
|
);
|
|
BindingId(self.0 + 1)
|
|
}
|
|
|
|
/// Get the raw u32 value
|
|
pub fn raw(self) -> u32 {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for BindingId {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "BindingId({})", self.0)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_binding_id_creation() {
|
|
let id = BindingId::new(0);
|
|
assert_eq!(id.0, 0);
|
|
assert_eq!(id.raw(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_binding_id_next() {
|
|
let id = BindingId::new(0);
|
|
let next = id.next();
|
|
assert_eq!(next.0, 1);
|
|
assert_eq!(next.raw(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_binding_id_display() {
|
|
let id = BindingId::new(42);
|
|
assert_eq!(format!("{}", id), "BindingId(42)");
|
|
}
|
|
|
|
#[test]
|
|
fn test_binding_id_ordering() {
|
|
let id0 = BindingId::new(0);
|
|
let id1 = BindingId::new(1);
|
|
assert!(id0 < id1);
|
|
assert!(id1 > id0);
|
|
assert_eq!(id0, id0);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "BindingId overflow")]
|
|
#[cfg(debug_assertions)]
|
|
fn test_binding_id_overflow() {
|
|
let id = BindingId::new(u32::MAX - 1);
|
|
let _ = id.next(); // Should panic in debug build
|
|
}
|
|
}
|