## Summary Extract scope and control flow management into ScopeContext for better organization. ## Changes - **New file**: src/mir/builder/scope_context.rs (264 lines) - Lexical scope stack management - Control flow stacks (loop/if) - Function context tracking - Debug scope helpers - **Updated**: src/mir/builder.rs - Add scope_ctx field - Mark legacy fields as deprecated - Add sync helpers (sync_scope_ctx_to_legacy, sync_legacy_to_scope_ctx) - **Updated**: src/mir/builder/vars/lexical_scope.rs - Use scope_ctx as SSOT - Sync to legacy fields for backward compat - **Updated**: src/mir/builder/lifecycle.rs - Sync current_function via scope_ctx - **Updated**: src/mir/builder/calls/lowering.rs - Sync function context in lowering flow ## Extracted Fields (7) 1. lexical_scope_stack - Block-scoped locals 2. loop_header_stack - Loop headers for break/continue 3. loop_exit_stack - Loop exit blocks 4. if_merge_stack - If merge blocks 5. current_function - Currently building function 6. function_param_names - Function parameters (for LoopForm) 7. debug_scope_stack - Debug region identifiers ## Test Results - ✅ cargo build --release (291 warnings - deprecated usage) - ✅ cargo test --release --lib - 1005/1009 PASS - ✅ phase135_trim_mir_verify.sh - PASS - ⚠️ phase132_exit_phi_parity.sh - Error (pre-existing issue) ## Progress Phase 136: 3/7 Contexts complete (TypeContext, CoreContext, ScopeContext) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
289 lines
8.3 KiB
Rust
289 lines
8.3 KiB
Rust
//! ScopeContext - Lexical scope and control flow stack management
|
|
//!
|
|
//! Phase 136 Step 3/7: Extract scope-related state from MirBuilder
|
|
//!
|
|
//! # Responsibilities
|
|
//! - Lexical scope management (variable shadowing, block-scoped locals)
|
|
//! - Control flow stack management (loop header/exit, if merge)
|
|
//! - Function context management (current function, parameters)
|
|
//! - Debug scope tracking (region identifiers)
|
|
//!
|
|
//! # Design
|
|
//! - Encapsulates scope/control flow state for cleaner separation
|
|
//! - Provides type-safe push/pop operations
|
|
//! - Maintains deterministic iteration order (BTreeMap/BTreeSet)
|
|
|
|
use crate::mir::{BasicBlockId, MirFunction};
|
|
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
|
|
|
pub(in crate::mir::builder) use super::vars::lexical_scope::LexicalScopeFrame;
|
|
|
|
/// Scope and control flow context for MIR building
|
|
#[derive(Debug)]
|
|
pub(super) struct ScopeContext {
|
|
// ---- Lexical scope management ----
|
|
/// Stack of lexical scopes for block-scoped `local` declarations
|
|
/// Tracks per-block shadowing so variables restore on scope exit
|
|
pub(super) lexical_scope_stack: Vec<LexicalScopeFrame>,
|
|
|
|
// ---- Control flow stacks ----
|
|
/// Stack of loop header blocks (innermost first)
|
|
/// Used for break/continue target resolution
|
|
pub(super) loop_header_stack: Vec<BasicBlockId>,
|
|
|
|
/// Stack of loop exit blocks (innermost first)
|
|
#[allow(dead_code)]
|
|
pub(super) loop_exit_stack: Vec<BasicBlockId>,
|
|
|
|
/// Stack of if/merge blocks (innermost first)
|
|
/// Used for nested conditional lowering and jump generation
|
|
pub(super) if_merge_stack: Vec<BasicBlockId>,
|
|
|
|
// ---- Function context ----
|
|
/// Current function being built
|
|
pub(super) current_function: Option<MirFunction>,
|
|
|
|
/// Parameter names for current function
|
|
/// Same lifecycle as current_function
|
|
pub(super) function_param_names: HashSet<String>,
|
|
|
|
// ---- Debug scope ----
|
|
/// Stack of region identifiers (e.g., "loop#1/header", "join#3/join")
|
|
/// Zero-cost when unused (dev only)
|
|
pub(super) debug_scope_stack: Vec<String>,
|
|
}
|
|
|
|
impl ScopeContext {
|
|
/// Create new scope context (empty state)
|
|
pub(super) fn new() -> Self {
|
|
Self {
|
|
lexical_scope_stack: Vec::new(),
|
|
loop_header_stack: Vec::new(),
|
|
loop_exit_stack: Vec::new(),
|
|
if_merge_stack: Vec::new(),
|
|
current_function: None,
|
|
function_param_names: HashSet::new(),
|
|
debug_scope_stack: Vec::new(),
|
|
}
|
|
}
|
|
|
|
// ---- Lexical scope helpers ----
|
|
|
|
/// Push new lexical scope frame
|
|
#[inline]
|
|
pub(super) fn push_lexical_scope(&mut self) {
|
|
self.lexical_scope_stack
|
|
.push(LexicalScopeFrame::default());
|
|
}
|
|
|
|
/// Pop lexical scope frame (returns frame for restoration)
|
|
#[inline]
|
|
pub(super) fn pop_lexical_scope(&mut self) -> Option<LexicalScopeFrame> {
|
|
self.lexical_scope_stack.pop()
|
|
}
|
|
|
|
/// Get mutable reference to current scope frame
|
|
#[inline]
|
|
pub(super) fn current_scope_mut(&mut self) -> Option<&mut LexicalScopeFrame> {
|
|
self.lexical_scope_stack.last_mut()
|
|
}
|
|
|
|
// ---- Control flow stack helpers ----
|
|
|
|
/// Push loop header block
|
|
#[inline]
|
|
pub(super) fn push_loop_header(&mut self, bb: BasicBlockId) {
|
|
self.loop_header_stack.push(bb);
|
|
}
|
|
|
|
/// Pop loop header block
|
|
#[inline]
|
|
pub(super) fn pop_loop_header(&mut self) -> Option<BasicBlockId> {
|
|
self.loop_header_stack.pop()
|
|
}
|
|
|
|
/// Get innermost loop header
|
|
#[inline]
|
|
pub(super) fn current_loop_header(&self) -> Option<BasicBlockId> {
|
|
self.loop_header_stack.last().copied()
|
|
}
|
|
|
|
/// Push loop exit block
|
|
#[inline]
|
|
pub(super) fn push_loop_exit(&mut self, bb: BasicBlockId) {
|
|
self.loop_exit_stack.push(bb);
|
|
}
|
|
|
|
/// Pop loop exit block
|
|
#[inline]
|
|
pub(super) fn pop_loop_exit(&mut self) -> Option<BasicBlockId> {
|
|
self.loop_exit_stack.pop()
|
|
}
|
|
|
|
/// Push if/merge block
|
|
#[inline]
|
|
pub(super) fn push_if_merge(&mut self, bb: BasicBlockId) {
|
|
self.if_merge_stack.push(bb);
|
|
}
|
|
|
|
/// Pop if/merge block
|
|
#[inline]
|
|
pub(super) fn pop_if_merge(&mut self) -> Option<BasicBlockId> {
|
|
self.if_merge_stack.pop()
|
|
}
|
|
|
|
// ---- Debug scope helpers ----
|
|
|
|
/// Push debug region identifier
|
|
#[inline]
|
|
pub(super) fn debug_push_region<S: Into<String>>(&mut self, region: S) {
|
|
self.debug_scope_stack.push(region.into());
|
|
}
|
|
|
|
/// Pop debug region identifier
|
|
#[inline]
|
|
pub(super) fn debug_pop_region(&mut self) {
|
|
let _ = self.debug_scope_stack.pop();
|
|
}
|
|
|
|
/// Get current debug region identifier
|
|
#[inline]
|
|
pub(super) fn debug_current_region_id(&self) -> Option<String> {
|
|
self.debug_scope_stack.last().cloned()
|
|
}
|
|
}
|
|
|
|
impl Default for ScopeContext {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_lexical_scope_stack() {
|
|
let mut ctx = ScopeContext::new();
|
|
|
|
// Initially empty
|
|
assert_eq!(ctx.lexical_scope_stack.len(), 0);
|
|
|
|
// Push scope
|
|
ctx.push_lexical_scope();
|
|
assert_eq!(ctx.lexical_scope_stack.len(), 1);
|
|
|
|
// Push another
|
|
ctx.push_lexical_scope();
|
|
assert_eq!(ctx.lexical_scope_stack.len(), 2);
|
|
|
|
// Pop scope
|
|
let frame = ctx.pop_lexical_scope();
|
|
assert!(frame.is_some());
|
|
assert_eq!(ctx.lexical_scope_stack.len(), 1);
|
|
|
|
// Pop last
|
|
let frame = ctx.pop_lexical_scope();
|
|
assert!(frame.is_some());
|
|
assert_eq!(ctx.lexical_scope_stack.len(), 0);
|
|
|
|
// Pop from empty
|
|
let frame = ctx.pop_lexical_scope();
|
|
assert!(frame.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_loop_stacks() {
|
|
let mut ctx = ScopeContext::new();
|
|
|
|
let header1 = BasicBlockId(1);
|
|
let header2 = BasicBlockId(2);
|
|
|
|
// Initially empty
|
|
assert!(ctx.current_loop_header().is_none());
|
|
|
|
// Push first loop
|
|
ctx.push_loop_header(header1);
|
|
assert_eq!(ctx.current_loop_header(), Some(header1));
|
|
|
|
// Push nested loop (innermost)
|
|
ctx.push_loop_header(header2);
|
|
assert_eq!(ctx.current_loop_header(), Some(header2));
|
|
|
|
// Pop innermost
|
|
assert_eq!(ctx.pop_loop_header(), Some(header2));
|
|
assert_eq!(ctx.current_loop_header(), Some(header1));
|
|
|
|
// Pop outermost
|
|
assert_eq!(ctx.pop_loop_header(), Some(header1));
|
|
assert!(ctx.current_loop_header().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_if_merge_stack() {
|
|
let mut ctx = ScopeContext::new();
|
|
|
|
let merge1 = BasicBlockId(10);
|
|
let merge2 = BasicBlockId(20);
|
|
|
|
// Push merge blocks
|
|
ctx.push_if_merge(merge1);
|
|
ctx.push_if_merge(merge2);
|
|
|
|
// Pop in LIFO order
|
|
assert_eq!(ctx.pop_if_merge(), Some(merge2));
|
|
assert_eq!(ctx.pop_if_merge(), Some(merge1));
|
|
assert_eq!(ctx.pop_if_merge(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_debug_scope_stack() {
|
|
let mut ctx = ScopeContext::new();
|
|
|
|
// Initially empty
|
|
assert!(ctx.debug_current_region_id().is_none());
|
|
|
|
// Push region
|
|
ctx.debug_push_region("loop#1/header");
|
|
assert_eq!(
|
|
ctx.debug_current_region_id(),
|
|
Some("loop#1/header".to_string())
|
|
);
|
|
|
|
// Push nested region
|
|
ctx.debug_push_region("join#3/join");
|
|
assert_eq!(
|
|
ctx.debug_current_region_id(),
|
|
Some("join#3/join".to_string())
|
|
);
|
|
|
|
// Pop
|
|
ctx.debug_pop_region();
|
|
assert_eq!(
|
|
ctx.debug_current_region_id(),
|
|
Some("loop#1/header".to_string())
|
|
);
|
|
|
|
ctx.debug_pop_region();
|
|
assert!(ctx.debug_current_region_id().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_function_context() {
|
|
let mut ctx = ScopeContext::new();
|
|
|
|
// Initially no function
|
|
assert!(ctx.current_function.is_none());
|
|
assert!(ctx.function_param_names.is_empty());
|
|
|
|
// Simulate setting function context
|
|
ctx.function_param_names.insert("x".to_string());
|
|
ctx.function_param_names.insert("y".to_string());
|
|
|
|
assert_eq!(ctx.function_param_names.len(), 2);
|
|
assert!(ctx.function_param_names.contains("x"));
|
|
assert!(ctx.function_param_names.contains("y"));
|
|
}
|
|
}
|