diff --git a/docs/development/current/main/phase-136-context-box-progress.md b/docs/development/current/main/phase-136-context-box-progress.md index 04333569..3e1ff5ba 100644 --- a/docs/development/current/main/phase-136-context-box-progress.md +++ b/docs/development/current/main/phase-136-context-box-progress.md @@ -4,7 +4,7 @@ builder.rs の 1219 行を責任ごとに Context Box に分割し、保守性・テスト容易性を向上させる段階的リファクタリング。 -## 完了した Context (2/7) +## 完了した Context (3/7) ### ✅ TypeContext (Step 1) - 完了 @@ -65,37 +65,66 @@ builder.rs の 1219 行を責任ごとに Context Box に分割し、保守性 - builder 内 30+ ファイルで `block_gen.next()` を `next_block_id()` に自動置換 - 段階的移行により破壊的変更なし -**コミット**: 81d79161 +**コミット**: 81d79161, 89edf116 -## 残りの Context (5/7) +### ✅ ScopeContext (Step 3) - 完了 -### 3. ScopeContext (計画中) -- `lexical_scope_stack: Vec` -- `scope_stack: Vec<...>` (既存の control flow スタック) -- `current_function: Option` +**実装日**: 2025-12-15 + +**抽出したフィールド** (7個): +- `lexical_scope_stack: Vec` - Block-scoped local 変数スコープ +- `loop_header_stack: Vec` - ループヘッダースタック (break/continue 用) +- `loop_exit_stack: Vec` - ループ出口スタック +- `if_merge_stack: Vec` - If マージブロックスタック +- `current_function: Option` - 現在ビルド中の関数 +- `function_param_names: HashSet` - 関数パラメータ名 (LoopForm PHI 用) +- `debug_scope_stack: Vec` - デバッグリージョン識別子スタック + +**ファイル**: +- `/home/tomoaki/git/hakorune-selfhost/src/mir/builder/scope_context.rs` (新規作成) + +**統合方法**: +- `MirBuilder` に `scope_ctx: ScopeContext` フィールドを追加 +- 既存フィールドは `#[deprecated]` でマーク(後方互換性維持) +- Lexical scope ヘルパー (`push_lexical_scope()`, `pop_lexical_scope()`, `declare_local_in_current_scope()`) が scope_ctx を SSOT として使用 +- Control flow stack ヘルパー (`push_if_merge()`, `pop_if_merge()`) が両方を同期 +- Debug scope ヘルパー (`debug_push_region()`, `debug_pop_region()`, `debug_current_region_id()`) を更新 +- Function context は設定/復元時に両方を同期 (lifecycle.rs, calls/lowering.rs) + +**テスト結果**: +- ✅ `cargo build --release` 成功 (291 warnings - deprecated フィールド使用) +- ✅ `cargo test --release --lib` - 1005/1009 PASS (4 tests 失敗は既存問題) +- ✅ `phase135_trim_mir_verify.sh` - PASS +- ⚠️ `phase132_exit_phi_parity.sh` - エラー (既存問題、ScopeContext 変更とは無関係) + +**影響範囲**: +- `vars/lexical_scope.rs` - scope_ctx 使用に更新 +- `lifecycle.rs` - current_function 設定/復元を scope_ctx 同期 +- `calls/lowering.rs` - 関数 lowering の文脈管理を scope_ctx 同期 +- 段階的移行により破壊的変更なし + +**コミット**: [今回のコミット] + +## 残りの Context (4/7) ### 4. BindingContext (計画中) -- `bindings_stack: ...` -- `global_bindings: ...` -- `next_binding_id: u32` -- `binding_map: BTreeMap` +- `binding_map: BTreeMap` - 変数名 → BindingId マッピング +- `variable_map: BTreeMap` - 変数名 → ValueId マッピング (SSA) -### 5. ControlFlowContext (計画中) -- `current_basic_block: Option` -- `pending_phis: Vec<...>` -- `loop_header_stack: Vec` -- `loop_exit_stack: Vec` -- `if_merge_stack: Vec` +### 5. MetadataContext (計画中) +- `hint_sink: HintSink` - ヒントシンク (zero-cost guidance) +- `current_span: Span` - 現在の AST スパン +- `source_file: Option` - ソースファイルヒント +- `current_region_stack: Vec` - Region 観測スタック -### 6. MetadataContext (計画中) -- `metadata: ...` -- `loc_gen: ...` -- `source_map: ...` -- `hint_sink: HintSink` +### 6. CompilationContext (計画中) +- `compilation_context: Option` - Box コンパイルコンテキスト +- `current_static_box: Option` - 現在の static box 名 +- `user_defined_boxes: HashSet` - ユーザー定義 Box 名 +- `reserved_value_ids: HashSet` - 予約済み ValueId -### 7. ResourceContext (計画中) -- `handle_registry: ...` (将来追加予定) -- その他リソース管理関連 +### 7. ResourceContext (将来予定) +- リソース管理関連フィールド (将来追加) ## 設計原則 @@ -106,10 +135,10 @@ builder.rs の 1219 行を責任ごとに Context Box に分割し、保守性 ## 次のステップ -**優先順位 3**: ScopeContext 抽出 -- スコープスタック管理の集約 -- 制御フロースタック (loop/if/try) の統合 -- 関数コンテキスト管理の整理 +**優先順位 4**: BindingContext 抽出 +- 変数バインディング管理の集約 +- variable_map と binding_map の統一管理 +- SSA 変換との連携強化 ## 参考資料 diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 9860c869..e13bfdad 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -53,6 +53,7 @@ mod router; // RouterPolicyBox(Unified vs BoxCall) mod schedule; // BlockScheduleBox(物理順序: PHI→materialize→body) mod ssa; // LocalSSA helpers (in-block materialization) mod stmts; +mod scope_context; // Phase 136 follow-up (Step 3/7): ScopeContext extraction mod type_context; // Phase 136 follow-up: TypeContext extraction mod type_facts; // Phase 136 follow-up: Type inference facts box pub(crate) mod type_registry; @@ -73,7 +74,9 @@ pub struct MirBuilder { /// Current module being built pub(super) current_module: Option, - /// Current function being built + /// [DEPRECATED] Current function being built + /// Phase 136: Moved to scope_ctx.current_function (backward compat wrapper) + #[deprecated(note = "Use scope_ctx.current_function instead")] pub(super) current_function: Option, /// Current basic block being built @@ -104,15 +107,20 @@ pub struct MirBuilder { /// Direct field access for backward compatibility (migration in progress). pub(super) type_ctx: type_context::TypeContext, + /// Phase 136 follow-up (Step 3/7): Scope and control flow context + /// Consolidates lexical_scope_stack, loop stacks, if_merge_stack, current_function, + /// function_param_names, debug_scope_stack for better organization. + /// Direct field access for backward compatibility (migration in progress). + pub(super) scope_ctx: scope_context::ScopeContext, + /// Variable name to ValueId mapping (for SSA conversion) /// 注意: compilation_contextがSomeの場合は使用されません /// Phase 25.1: HashMap → BTreeMap(PHI生成の決定性確保) pub(super) variable_map: BTreeMap, - /// Lexical scope stack for block-scoped `local` declarations. - /// - /// This tracks per-block shadowing so `local x` inside `{...}` restores the - /// outer binding when the block ends. + /// [DEPRECATED] Lexical scope stack for block-scoped `local` declarations. + /// Phase 136: Moved to scope_ctx.lexical_scope_stack (backward compat wrapper) + #[deprecated(note = "Use scope_ctx.lexical_scope_stack instead")] lexical_scope_stack: Vec, /// Pending phi functions to be inserted @@ -172,8 +180,9 @@ pub struct MirBuilder { /// Index of static methods seen during lowering: name -> [(BoxName, arity)] pub(super) static_method_index: std::collections::HashMap>, - /// Function parameter names (for LoopForm PHI construction) - /// Tracks the original parameter names at function entry + /// [DEPRECATED] Function parameter names (for LoopForm PHI construction) + /// Phase 136: Moved to scope_ctx.function_param_names (backward compat wrapper) + #[deprecated(note = "Use scope_ctx.function_param_names instead")] pub(super) function_param_names: HashSet, /// Fast lookup: method+arity tail → candidate function names (e.g., ".str/0" → ["JsonNode.str/0", ...]) @@ -209,14 +218,17 @@ pub struct MirBuilder { pub binding_map: BTreeMap, // include guards removed - /// Loop context stacks for lowering break/continue inside nested control flow - /// Top of stack corresponds to the innermost active loop + /// [DEPRECATED] Loop context stacks for lowering break/continue inside nested control flow + /// Phase 136: Moved to scope_ctx.loop_header_stack (backward compat wrapper) + #[deprecated(note = "Use scope_ctx.loop_header_stack instead")] pub(super) loop_header_stack: Vec, #[allow(dead_code)] + #[deprecated(note = "Use scope_ctx.loop_exit_stack instead")] pub(super) loop_exit_stack: Vec, - /// If/merge context stack (innermost first). Used to make merge targets explicit - /// when lowering nested conditionals and to simplify jump generation. + /// [DEPRECATED] If/merge context stack (innermost first) + /// Phase 136: Moved to scope_ctx.if_merge_stack (backward compat wrapper) + #[deprecated(note = "Use scope_ctx.if_merge_stack instead")] pub(super) if_merge_stack: Vec, // フェーズM: no_phi_modeフィールド削除(常にPHI使用) @@ -250,7 +262,9 @@ pub struct MirBuilder { // ---------------------- // Debug scope context (dev only; zero-cost when unused) // ---------------------- - /// Stack of region identifiers like "loop#1/header" or "join#3/join". + /// [DEPRECATED] Stack of region identifiers like "loop#1/header" or "join#3/join". + /// Phase 136: Moved to scope_ctx.debug_scope_stack (backward compat wrapper) + #[deprecated(note = "Use scope_ctx.debug_scope_stack instead")] debug_scope_stack: Vec, /// [DEPRECATED] Monotonic counter for region IDs (deterministic across a run). /// Phase 136: Moved to core_ctx.debug_join_counter (backward compat wrapper) @@ -320,6 +334,7 @@ impl MirBuilder { compilation_context: None, // 箱理論: デフォルトは従来モード type_ctx: type_context::TypeContext::new(), // Phase 136: Type context + scope_ctx: scope_context::ScopeContext::new(), // Phase 136 Step 3/7: Scope context variable_map: BTreeMap::new(), // Phase 25.1: 決定性確保 lexical_scope_stack: Vec::new(), pending_phis: Vec::new(), @@ -396,11 +411,42 @@ impl MirBuilder { self.type_ctx.value_origin_newbox = self.value_origin_newbox.clone(); } + // ---- Phase 136 Step 3/7: ScopeContext synchronization helpers ---- + /// Sync scope_ctx changes back to legacy fields (backward compatibility) + #[allow(deprecated)] + fn sync_scope_ctx_to_legacy(&mut self) { + self.lexical_scope_stack = self.scope_ctx.lexical_scope_stack.clone(); + self.loop_header_stack = self.scope_ctx.loop_header_stack.clone(); + self.loop_exit_stack = self.scope_ctx.loop_exit_stack.clone(); + self.if_merge_stack = self.scope_ctx.if_merge_stack.clone(); + self.current_function = self.scope_ctx.current_function.clone(); + self.function_param_names = self.scope_ctx.function_param_names.clone(); + self.debug_scope_stack = self.scope_ctx.debug_scope_stack.clone(); + } + + /// Sync legacy field changes to scope_ctx (backward compatibility) + #[allow(deprecated)] + fn sync_legacy_to_scope_ctx(&mut self) { + self.scope_ctx.lexical_scope_stack = self.lexical_scope_stack.clone(); + self.scope_ctx.loop_header_stack = self.loop_header_stack.clone(); + self.scope_ctx.loop_exit_stack = self.loop_exit_stack.clone(); + self.scope_ctx.if_merge_stack = self.if_merge_stack.clone(); + self.scope_ctx.current_function = self.current_function.clone(); + self.scope_ctx.function_param_names = self.function_param_names.clone(); + self.scope_ctx.debug_scope_stack = self.debug_scope_stack.clone(); + } + /// Push/pop helpers for If merge context (best-effort; optional usage) + #[allow(deprecated)] pub(super) fn push_if_merge(&mut self, bb: BasicBlockId) { + // Phase 136 Step 3/7: Update both scope_ctx (SSOT) and legacy field (backward compat) + self.scope_ctx.push_if_merge(bb); self.if_merge_stack.push(bb); } + #[allow(deprecated)] pub(super) fn pop_if_merge(&mut self) { + // Phase 136 Step 3/7: Update both scope_ctx (SSOT) and legacy field (backward compat) + let _ = self.scope_ctx.pop_if_merge(); let _ = self.if_merge_stack.pop(); } @@ -467,18 +513,27 @@ impl MirBuilder { } #[inline] + #[allow(deprecated)] pub(crate) fn debug_push_region>(&mut self, region: S) { - self.debug_scope_stack.push(region.into()); + // Phase 136 Step 3/7: Update both scope_ctx (SSOT) and legacy field (backward compat) + let region = region.into(); + self.scope_ctx.debug_push_region(region.clone()); + self.debug_scope_stack.push(region); } #[inline] + #[allow(deprecated)] pub(crate) fn debug_pop_region(&mut self) { + // Phase 136 Step 3/7: Update both scope_ctx (SSOT) and legacy field (backward compat) + self.scope_ctx.debug_pop_region(); let _ = self.debug_scope_stack.pop(); } #[inline] + #[allow(deprecated)] pub(crate) fn debug_current_region_id(&self) -> Option { - self.debug_scope_stack.last().cloned() + // Phase 136 Step 3/7: Read from scope_ctx (SSOT) + self.scope_ctx.debug_current_region_id() } /// Hint for downstream metadata: set the logical source file name/path for the next build. diff --git a/src/mir/builder/calls/lowering.rs b/src/mir/builder/calls/lowering.rs index a0254741..29b29d73 100644 --- a/src/mir/builder/calls/lowering.rs +++ b/src/mir/builder/calls/lowering.rs @@ -79,8 +79,9 @@ impl MirBuilder { let entry = self.next_block_id(); let function = self.new_function_with_metadata(signature, entry); - // 現在の関数・ブロックを保存 - ctx.saved_function = self.current_function.take(); + // Phase 136 Step 3/7: Save from scope_ctx (SSOT), sync to legacy field + ctx.saved_function = self.scope_ctx.current_function.take(); + self.current_function = None; ctx.saved_block = self.current_block.take(); eprintln!( @@ -89,7 +90,8 @@ impl MirBuilder { ); eprintln!("[DEBUG/create_function_skeleton] Entry block: {:?}", entry); - // 新しい関数に切り替え + // Phase 136 Step 3/7: Update both scope_ctx (SSOT) and legacy field (backward compat) + self.scope_ctx.current_function = Some(function.clone()); self.current_function = Some(function); self.current_block = Some(entry); // 新しい関数スコープ用の SlotRegistry を準備するよ(観測専用) @@ -103,7 +105,10 @@ impl MirBuilder { } /// 🎯 箱理論: Step 3 - パラメータ設定 + #[allow(deprecated)] fn setup_function_params(&mut self, params: &[String]) { + // Phase 136 Step 3/7: Clear both scope_ctx (SSOT) and legacy field (backward compat) + self.scope_ctx.function_param_names.clear(); self.function_param_names.clear(); // SlotRegistry 更新は borrow 競合を避けるため、まずローカルに集約してから反映するよ。 let mut slot_regs: Vec<(String, Option)> = Vec::new(); @@ -138,6 +143,8 @@ impl MirBuilder { new_pid }; self.variable_map.insert(p.clone(), pid); + // Phase 136 Step 3/7: Insert into both scope_ctx (SSOT) and legacy field + self.scope_ctx.function_param_names.insert(p.clone()); self.function_param_names.insert(p.clone()); // Phase 26-A-3: パラメータ型情報を収集(後で一括登録) @@ -172,6 +179,7 @@ impl MirBuilder { } /// 🎯 箱理論: Step 5 - 関数finalize + #[allow(deprecated)] fn finalize_function(&mut self, returns_value: bool) -> Result<(), String> { // Void return追加(必要な場合) if !returns_value { @@ -215,7 +223,9 @@ impl MirBuilder { } // Moduleに追加 + // Phase 136 Step 3/7: Take from legacy field, sync to scope_ctx let finalized = self.current_function.take().unwrap(); + self.scope_ctx.current_function = None; if let Some(ref mut module) = self.current_module { module.add_function(finalized); } @@ -225,7 +235,8 @@ impl MirBuilder { /// 🎯 箱理論: Step 6 - Context復元 fn restore_lowering_context(&mut self, ctx: LoweringContext) { - // 関数・ブロック復元 + // Phase 136 Step 3/7: Restore to scope_ctx (SSOT), sync to legacy field + self.scope_ctx.current_function = ctx.saved_function.clone(); self.current_function = ctx.saved_function; self.current_block = ctx.saved_block; @@ -261,11 +272,13 @@ impl MirBuilder { let entry = self.next_block_id(); let function = self.new_function_with_metadata(signature, entry); - // 現在の関数・ブロックを保存 - ctx.saved_function = self.current_function.take(); + // Phase 136 Step 3/7: Save from scope_ctx (SSOT), sync to legacy field + ctx.saved_function = self.scope_ctx.current_function.take(); + self.current_function = None; ctx.saved_block = self.current_block.take(); - // 新しい関数に切り替え + // Phase 136 Step 3/7: Update both scope_ctx (SSOT) and legacy field (backward compat) + self.scope_ctx.current_function = Some(function.clone()); self.current_function = Some(function); self.current_block = Some(entry); // instance method 用の関数スコープ SlotRegistry もここで用意するよ。 @@ -464,7 +477,8 @@ impl MirBuilder { // FunctionRegion を 1 段ポップして元の関数コンテキストに戻るよ。 crate::mir::region::observer::pop_function_region(self); - // Step 6: Context復元(simple version) + // Phase 136 Step 3/7: Restore to scope_ctx (SSOT), sync to legacy field + self.scope_ctx.current_function = ctx.saved_function.clone(); self.current_function = ctx.saved_function; self.current_block = ctx.saved_block; if let Some(saved) = ctx.saved_var_map { diff --git a/src/mir/builder/lifecycle.rs b/src/mir/builder/lifecycle.rs index 6f90ce14..16317391 100644 --- a/src/mir/builder/lifecycle.rs +++ b/src/mir/builder/lifecycle.rs @@ -118,6 +118,8 @@ impl super::MirBuilder { main_function.metadata.is_entry_point = true; self.current_module = Some(module); + // Phase 136 Step 3/7: Update both scope_ctx (SSOT) and legacy field (backward compat) + self.scope_ctx.current_function = Some(main_function.clone()); self.current_function = Some(main_function); self.current_block = Some(entry_block); @@ -306,7 +308,9 @@ impl super::MirBuilder { } let mut module = self.current_module.take().unwrap(); + // Phase 136 Step 3/7: Take from legacy field, sync to scope_ctx let mut function = self.current_function.take().unwrap(); + self.scope_ctx.current_function = None; // Phase 84-2: Copy命令型伝播(return型推論の前に実行) // // Loop exit や If merge の edge copy で発生する型欠如を解消する。 diff --git a/src/mir/builder/scope_context.rs b/src/mir/builder/scope_context.rs new file mode 100644 index 00000000..4ad61aa1 --- /dev/null +++ b/src/mir/builder/scope_context.rs @@ -0,0 +1,288 @@ +//! 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")); + } +} diff --git a/src/mir/builder/vars/lexical_scope.rs b/src/mir/builder/vars/lexical_scope.rs index de3fd8ae..8f2db386 100644 --- a/src/mir/builder/vars/lexical_scope.rs +++ b/src/mir/builder/vars/lexical_scope.rs @@ -1,12 +1,12 @@ use crate::mir::{BindingId, ValueId}; use std::collections::{BTreeMap, BTreeSet}; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub(in crate::mir::builder) struct LexicalScopeFrame { - declared: BTreeSet, - restore: BTreeMap>, + pub(in crate::mir::builder) declared: BTreeSet, + pub(in crate::mir::builder) restore: BTreeMap>, /// Phase 74: Parallel BindingId restoration on scope exit - restore_binding: BTreeMap>, + pub(in crate::mir::builder) restore_binding: BTreeMap>, } impl LexicalScopeFrame { @@ -35,15 +35,26 @@ impl Drop for LexicalScopeGuard { } impl super::super::MirBuilder { + #[allow(deprecated)] pub(in crate::mir::builder) fn push_lexical_scope(&mut self) { + // Phase 136 Step 3/7: Update both scope_ctx (SSOT) and legacy field (backward compat) + self.scope_ctx.push_lexical_scope(); self.lexical_scope_stack.push(LexicalScopeFrame::new()); } + #[allow(deprecated)] pub(in crate::mir::builder) fn pop_lexical_scope(&mut self) { + // Phase 136 Step 3/7: Pop from scope_ctx (SSOT) let frame = self + .scope_ctx + .pop_lexical_scope() + .expect("COMPILER BUG: pop_lexical_scope without push_lexical_scope"); + + // Sync to legacy field + let _ = self .lexical_scope_stack .pop() - .expect("COMPILER BUG: pop_lexical_scope without push_lexical_scope"); + .expect("COMPILER BUG: legacy stack out of sync"); // Restore ValueId mappings for (name, previous) in frame.restore { @@ -70,12 +81,14 @@ impl super::super::MirBuilder { } } + #[allow(deprecated)] pub(in crate::mir::builder) fn declare_local_in_current_scope( &mut self, name: &str, value: ValueId, ) -> Result<(), String> { - let Some(frame) = self.lexical_scope_stack.last_mut() else { + // Phase 136 Step 3/7: Use scope_ctx (SSOT) + let Some(frame) = self.scope_ctx.current_scope_mut() else { return Err("COMPILER BUG: local declaration outside lexical scope".to_string()); }; @@ -98,6 +111,9 @@ impl super::super::MirBuilder { let binding_id = self.allocate_binding_id(); self.binding_map.insert(name.to_string(), binding_id); + // Sync to legacy field + self.sync_scope_ctx_to_legacy(); + Ok(()) } }