//! Phase 200-A: Function scope capture infrastructure //! //! This module provides types for capturing function-scoped variables //! that are effectively immutable within a loop context. //! //! # Example //! //! For a function like JsonParser._atoi(): //! //! ```nyash //! method _atoi(s, pos, len) { //! local digits = "0123456789" // <-- Captured variable //! local value = 0 //! loop(pos < len) { //! local ch = s.charAt(pos) //! local digit = digits.indexOf(ch) // Uses captured 'digits' //! if (digit < 0) { break } //! value = value * 10 + digit //! pos = pos + 1 //! } //! return value //! } //! ``` //! //! Here, `digits` is: //! - Declared in function scope (before the loop) //! - Never reassigned (effectively immutable) //! - Referenced in loop body (digits.indexOf(ch)) //! //! Phase 200-A creates the infrastructure to capture such variables. //! Phase 200-B will implement the actual detection logic. use crate::mir::ValueId; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::ast::ASTNode; /// A variable captured from function scope for use in loop conditions/body. /// /// Example: `local digits = "0123456789"` in JsonParser._atoi() /// /// # Invariants /// /// - `name`: Variable name as it appears in the source code /// - `host_id`: MIR ValueId of the original definition in the host function /// - `is_immutable`: True if the variable is never reassigned in the function #[derive(Debug, Clone)] pub struct CapturedVar { /// Variable name (e.g., "digits", "table") pub name: String, /// MIR ValueId of the original definition in the host function pub host_id: ValueId, /// Whether this variable is never reassigned in the function /// /// Phase 200-B will implement assignment analysis to determine this. /// For now, this is always set to true as a conservative default. pub is_immutable: bool, } /// Environment containing function-scoped captured variables. /// /// Phase 200-A: Type definition only, not yet integrated with ConditionEnv. /// Phase 200-B: Will be populated by FunctionScopeCaptureAnalyzer and /// integrated into ConditionEnv via ConditionEnvBuilder v2. #[derive(Debug, Clone, Default)] pub struct CapturedEnv { /// List of captured variables pub vars: Vec, } impl CapturedEnv { /// Create a new empty environment pub fn new() -> Self { Self { vars: Vec::new() } } /// Check if the environment is empty pub fn is_empty(&self) -> bool { self.vars.is_empty() } /// Add a captured variable to the environment pub fn add_var(&mut self, var: CapturedVar) { self.vars.push(var); } /// Look up a captured variable by name /// /// Returns `Some(&CapturedVar)` if found, `None` otherwise. pub fn get(&self, name: &str) -> Option<&CapturedVar> { self.vars.iter().find(|v| v.name == name) } } /// Analyzes function-scoped variables that can be safely captured for loop conditions/body. /// /// # Phase 200-A Status /// /// Currently returns empty CapturedEnv (skeleton implementation). /// Actual capture detection will be implemented in Phase 200-B. /// /// # Future Detection Criteria (Phase 200-B+) /// /// A variable is captured if ALL of the following conditions are met: /// /// 1. **Declared before the loop**: Variable must be declared in function scope before the loop /// 2. **Never reassigned**: Variable is never reassigned within the function (is_immutable = true) /// 3. **Referenced in loop**: Variable is referenced in loop condition or body /// 4. **Not a loop parameter**: Variable is not the loop iteration variable /// 5. **Not a body-local**: Variable is not declared inside the loop body /// /// # Example /// /// ```nyash /// method _atoi(s, pos, len) { /// local digits = "0123456789" // ✅ Captured (declared before loop, never reassigned) /// local value = 0 // ❌ Not captured (reassigned in loop body) /// loop(pos < len) { /// local ch = s.charAt(pos) // ❌ Not captured (body-local) /// local digit = digits.indexOf(ch) /// value = value * 10 + digit /// pos = pos + 1 /// } /// } /// ``` /// /// # Arguments /// /// * `_fn_body` - AST nodes of the function body (for analysis) /// * `_loop_ast` - AST node of the loop statement /// * `_scope` - LoopScopeShape (for excluding loop params and body-locals) /// /// # Returns /// /// `CapturedEnv` containing all captured variables (empty in Phase 200-A) pub fn analyze_captured_vars( _fn_body: &[ASTNode], _loop_ast: &ASTNode, _scope: &LoopScopeShape, ) -> CapturedEnv { // Phase 200-A: Skeleton implementation // TODO(Phase 200-B): Implement actual capture detection // // Detection algorithm: // 1. Find all `local` declarations before the loop in fn_body // 2. For each declaration: // a. Check if it's never reassigned in the function (is_immutable = true) // b. Check if it's referenced in loop condition or body // c. Exclude if it's in scope.pinned, scope.carriers, or scope.body_locals // 3. Collect matching variables into CapturedEnv // 4. Return the populated environment CapturedEnv::new() } #[cfg(test)] mod tests { use super::*; #[test] fn test_captured_env_empty() { let env = CapturedEnv::new(); assert!(env.is_empty()); assert!(env.get("digits").is_none()); } #[test] fn test_captured_env_add_and_get() { let mut env = CapturedEnv::new(); env.add_var(CapturedVar { name: "digits".to_string(), host_id: ValueId(42), is_immutable: true, }); assert!(!env.is_empty()); let var = env.get("digits").unwrap(); assert_eq!(var.name, "digits"); assert_eq!(var.host_id, ValueId(42)); assert!(var.is_immutable); } #[test] fn test_captured_env_multiple_vars() { let mut env = CapturedEnv::new(); env.add_var(CapturedVar { name: "digits".to_string(), host_id: ValueId(42), is_immutable: true, }); env.add_var(CapturedVar { name: "table".to_string(), host_id: ValueId(100), is_immutable: true, }); assert_eq!(env.vars.len(), 2); assert!(env.get("digits").is_some()); assert!(env.get("table").is_some()); assert!(env.get("nonexistent").is_none()); } }