diff --git a/src/mir/join_ir/lowering/scope_manager.rs b/src/mir/join_ir/lowering/scope_manager.rs index a09a2183..62b69a27 100644 --- a/src/mir/join_ir/lowering/scope_manager.rs +++ b/src/mir/join_ir/lowering/scope_manager.rs @@ -465,6 +465,7 @@ mod tests { name: "len".to_string(), host_id: ValueId(42), is_immutable: true, + kind: crate::mir::loop_pattern_detection::function_scope_capture::CapturedKind::Explicit, }); let carrier_info = CarrierInfo { diff --git a/src/mir/loop_pattern_detection/function_scope_capture/analyzers.rs b/src/mir/loop_pattern_detection/function_scope_capture/analyzers.rs index c9a7dfab..b1a2cc80 100644 --- a/src/mir/loop_pattern_detection/function_scope_capture/analyzers.rs +++ b/src/mir/loop_pattern_detection/function_scope_capture/analyzers.rs @@ -6,7 +6,7 @@ use crate::mir::ValueId; use std::collections::BTreeSet; use super::helpers::*; -use super::types::{CapturedEnv, CapturedVar}; +use super::types::{CapturedEnv, CapturedKind, CapturedVar}; /// Analyzes function-scoped variables that can be safely captured for loop conditions/body. /// @@ -159,6 +159,7 @@ pub(crate) fn analyze_captured_vars( name: name.clone(), host_id: ValueId(0), // Placeholder, will be resolved in ConditionEnvBuilder is_immutable: true, + kind: CapturedKind::Explicit, }); } @@ -296,6 +297,7 @@ pub(crate) fn analyze_captured_vars_v2( name: name.clone(), host_id: ValueId(0), // Placeholder, will be resolved in ConditionEnvBuilder is_immutable: true, + kind: CapturedKind::Explicit, }); } @@ -343,6 +345,7 @@ pub(crate) fn analyze_captured_vars_v2( name: name.clone(), host_id: ValueId(0), // Placeholder, will be resolved in ConditionEnvBuilder is_immutable: true, + kind: CapturedKind::Explicit, }); } diff --git a/src/mir/loop_pattern_detection/function_scope_capture/mod.rs b/src/mir/loop_pattern_detection/function_scope_capture/mod.rs index 20809230..9feb4383 100644 --- a/src/mir/loop_pattern_detection/function_scope_capture/mod.rs +++ b/src/mir/loop_pattern_detection/function_scope_capture/mod.rs @@ -54,4 +54,4 @@ mod types; // Public re-exports pub(crate) use analyzers::analyze_captured_vars_v2; -pub use types::{CapturedEnv, CapturedVar}; +pub use types::{CapturedEnv, CapturedKind, CapturedVar}; diff --git a/src/mir/loop_pattern_detection/function_scope_capture/types.rs b/src/mir/loop_pattern_detection/function_scope_capture/types.rs index 0070286f..6062561f 100644 --- a/src/mir/loop_pattern_detection/function_scope_capture/types.rs +++ b/src/mir/loop_pattern_detection/function_scope_capture/types.rs @@ -2,6 +2,18 @@ use crate::mir::ValueId; +/// Classification of captured variable (Phase 100 expansion) +/// +/// - `Explicit`: Captured for condition/carrier usage (traditional, Phase 200-A/B) +/// - `Pinned`: Read-only local from loop-outer scope, used as receiver in loop body (Phase 100) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CapturedKind { + /// Traditional capture (condition variables, carriers) + Explicit, + /// Phase 100: read-only local (dynamic construction allowed, immutable in loop) + Pinned, +} + /// A variable captured from function scope for use in loop conditions/body. /// /// Example: `local digits = "0123456789"` in JsonParser._atoi() @@ -11,9 +23,10 @@ use crate::mir::ValueId; /// - `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 +/// - `kind`: Classification as Explicit (traditional) or Pinned (Phase 100 read-only) #[derive(Debug, Clone)] pub struct CapturedVar { - /// Variable name (e.g., "digits", "table") + /// Variable name (e.g., "digits", "table", "s") pub name: String, /// MIR ValueId of the original definition in the host function @@ -24,6 +37,9 @@ pub struct CapturedVar { /// 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, + + /// Phase 100: Classification of captured variable + pub kind: CapturedKind, } /// Environment containing function-scoped captured variables. @@ -53,6 +69,26 @@ impl CapturedEnv { self.vars.push(var); } + /// Add an explicit captured variable (traditional capture) + pub fn insert(&mut self, name: String, host_id: ValueId) { + self.add_var(CapturedVar { + name, + host_id, + is_immutable: true, + kind: CapturedKind::Explicit, + }); + } + + /// Add a pinned captured variable (Phase 100: read-only local) + pub fn insert_pinned(&mut self, name: String, host_id: ValueId) { + self.add_var(CapturedVar { + name, + host_id, + is_immutable: true, + kind: CapturedKind::Pinned, + }); + } + /// Look up a captured variable by name /// /// Returns `Some(&CapturedVar)` if found, `None` otherwise. @@ -79,6 +115,7 @@ mod tests { name: "digits".to_string(), host_id: ValueId(42), is_immutable: true, + kind: CapturedKind::Explicit, }); assert!(!env.is_empty()); @@ -86,6 +123,7 @@ mod tests { assert_eq!(var.name, "digits"); assert_eq!(var.host_id, ValueId(42)); assert!(var.is_immutable); + assert_eq!(var.kind, CapturedKind::Explicit); } #[test] @@ -95,11 +133,13 @@ mod tests { name: "digits".to_string(), host_id: ValueId(42), is_immutable: true, + kind: CapturedKind::Explicit, }); env.add_var(CapturedVar { name: "table".to_string(), host_id: ValueId(100), is_immutable: true, + kind: CapturedKind::Pinned, }); assert_eq!(env.vars.len(), 2); @@ -107,4 +147,22 @@ mod tests { assert!(env.get("table").is_some()); assert!(env.get("nonexistent").is_none()); } + + #[test] + fn test_captured_env_insert_explicit() { + let mut env = CapturedEnv::new(); + env.insert("x".to_string(), ValueId(10)); + + let var = env.get("x").unwrap(); + assert_eq!(var.kind, CapturedKind::Explicit); + } + + #[test] + fn test_captured_env_insert_pinned() { + let mut env = CapturedEnv::new(); + env.insert_pinned("s".to_string(), ValueId(20)); + + let var = env.get("s").unwrap(); + assert_eq!(var.kind, CapturedKind::Pinned); + } }