Files
hakorune/src/mir/binding_id.rs
nyash-codex e1574af741 feat(mir): Phase 74 - BindingId infrastructure (dev-only)
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>
2025-12-13 05:34:56 +09:00

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
}
}