//! 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, // ---- Control flow stacks ---- /// Stack of loop header blocks (innermost first) /// Used for break/continue target resolution pub(super) loop_header_stack: Vec, /// Stack of loop exit blocks (innermost first) #[allow(dead_code)] pub(super) loop_exit_stack: Vec, /// Stack of if/merge blocks (innermost first) /// Used for nested conditional lowering and jump generation pub(super) if_merge_stack: Vec, // ---- Function context ---- /// Current function being built pub(super) current_function: Option, /// Parameter names for current function /// Same lifecycle as current_function pub(super) function_param_names: HashSet, // ---- 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, } 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 { 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 { self.loop_header_stack.pop() } /// Get innermost loop header #[inline] pub(super) fn current_loop_header(&self) -> Option { 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 { 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 { self.if_merge_stack.pop() } // ---- Debug scope helpers ---- /// Push debug region identifier #[inline] pub(super) fn debug_push_region>(&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 { 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")); } }