seqc/
capture_analysis.rs

1//! Capture Analysis for Closures
2//!
3//! This module handles the analysis of closure captures - determining which values
4//! from the creation site need to be captured in a closure's environment.
5//!
6//! The key insight is that closures bridge two stack effects:
7//! - **Body effect**: what the quotation body actually needs to execute
8//! - **Call effect**: what the call site will provide when the closure is invoked
9//!
10//! The difference between these determines what must be captured at creation time.
11//!
12//! ## Example
13//!
14//! ```text
15//! : add-to ( Int -- [Int -- Int] )
16//!   [ add ] ;
17//! ```
18//!
19//! Here:
20//! - Body needs: `(Int Int -- Int)` (add requires two integers)
21//! - Call provides: `(Int -- Int)` (caller provides one integer)
22//! - Captures: `[Int]` (one integer captured from creation site)
23
24use crate::types::{Effect, StackType, Type};
25
26/// Calculate capture types for a closure
27///
28/// Given:
29/// - `body_effect`: what the quotation body needs (e.g., `Int Int -- Int`)
30/// - `call_effect`: what the call site will provide (e.g., `Int -- Int`)
31///
32/// Returns:
33/// - `captures`: types to capture from creation stack (e.g., `[Int]`)
34///
35/// # Capture Ordering
36///
37/// Captures are returned bottom-to-top (deepest value first).
38/// This matches how `push_closure` pops from the stack:
39///
40/// ```text
41/// Stack at creation: ( ...rest bottom top )
42/// push_closure pops top-down: pop top, pop bottom
43/// Stores as: env[0]=top, env[1]=bottom (reversed)
44/// Closure function pushes: push env[0], push env[1]
45/// Result: bottom is deeper, top is shallower (correct order)
46/// ```
47///
48/// # Errors
49///
50/// Returns an error if the call site provides more values than the body needs.
51pub fn calculate_captures(body_effect: &Effect, call_effect: &Effect) -> Result<Vec<Type>, String> {
52    // Extract concrete types from stack types (bottom to top)
53    let body_inputs = extract_concrete_types(&body_effect.inputs);
54    let call_inputs = extract_concrete_types(&call_effect.inputs);
55
56    // Validate: call site shouldn't provide MORE than body needs
57    if call_inputs.len() > body_inputs.len() {
58        return Err(format!(
59            "Closure signature error: call site provides {} values but body only needs {}",
60            call_inputs.len(),
61            body_inputs.len()
62        ));
63    }
64
65    // Calculate how many to capture (from bottom of stack)
66    let capture_count = body_inputs.len() - call_inputs.len();
67
68    // Captures are the first N types (bottom of stack)
69    // Example: body needs [Int, String] (bottom to top), call provides [String]
70    // Captures: [Int] (the bottom type)
71    Ok(body_inputs[0..capture_count].to_vec())
72}
73
74/// Extract concrete types from a stack type (bottom to top order)
75///
76/// This function traverses a `StackType` and returns a vector of concrete types
77/// in bottom-to-top order (deepest stack element first).
78///
79/// # Example
80///
81/// ```text
82/// Input: Cons { rest: Cons { rest: Empty, top: Int }, top: String }
83/// Output: [Int, String]  (bottom to top)
84/// ```
85///
86/// # Row Variables
87///
88/// Row variables (like `..a`) are skipped - this function only extracts
89/// concrete types. This is appropriate for capture analysis where we need
90/// to know the actual types being captured.
91///
92/// # Performance
93///
94/// Uses recursion to build the vector in the correct order without needing
95/// to clone the entire stack structure or reverse the result.
96pub fn extract_concrete_types(stack: &StackType) -> Vec<Type> {
97    // Use recursion to build the vector in bottom-to-top order
98    fn collect(stack: &StackType, result: &mut Vec<Type>) {
99        match stack {
100            StackType::Cons { rest, top } => {
101                // First recurse to collect types below, then add this type
102                collect(rest, result);
103                result.push(top.clone());
104            }
105            StackType::Empty | StackType::RowVar(_) => {
106                // Base case: nothing more to collect
107            }
108        }
109    }
110
111    let mut types = Vec::new();
112    collect(stack, &mut types);
113    types
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use crate::types::{Effect, StackType, Type};
120
121    fn make_stack(types: &[Type]) -> StackType {
122        let mut stack = StackType::Empty;
123        for t in types {
124            stack = StackType::Cons {
125                rest: Box::new(stack),
126                top: t.clone(),
127            };
128        }
129        stack
130    }
131
132    fn make_effect(inputs: &[Type], outputs: &[Type]) -> Effect {
133        Effect {
134            inputs: make_stack(inputs),
135            outputs: make_stack(outputs),
136            effects: Vec::new(),
137        }
138    }
139
140    #[test]
141    fn test_extract_empty_stack() {
142        let types = extract_concrete_types(&StackType::Empty);
143        assert!(types.is_empty());
144    }
145
146    #[test]
147    fn test_extract_single_type() {
148        let stack = make_stack(&[Type::Int]);
149        let types = extract_concrete_types(&stack);
150        assert_eq!(types, vec![Type::Int]);
151    }
152
153    #[test]
154    fn test_extract_multiple_types() {
155        let stack = make_stack(&[Type::Int, Type::String, Type::Bool]);
156        let types = extract_concrete_types(&stack);
157        assert_eq!(types, vec![Type::Int, Type::String, Type::Bool]);
158    }
159
160    #[test]
161    fn test_calculate_no_captures() {
162        // Body needs (Int -- Int), call provides (Int -- Int)
163        let body = make_effect(&[Type::Int], &[Type::Int]);
164        let call = make_effect(&[Type::Int], &[Type::Int]);
165
166        let captures = calculate_captures(&body, &call).unwrap();
167        assert!(captures.is_empty());
168    }
169
170    #[test]
171    fn test_calculate_one_capture() {
172        // Body needs (Int Int -- Int), call provides (Int -- Int)
173        // Should capture one Int
174        let body = make_effect(&[Type::Int, Type::Int], &[Type::Int]);
175        let call = make_effect(&[Type::Int], &[Type::Int]);
176
177        let captures = calculate_captures(&body, &call).unwrap();
178        assert_eq!(captures, vec![Type::Int]);
179    }
180
181    #[test]
182    fn test_calculate_multiple_captures() {
183        // Body needs (Int String Bool -- Bool), call provides (Bool -- Bool)
184        // Should capture [Int, String] (bottom to top)
185        let body = make_effect(&[Type::Int, Type::String, Type::Bool], &[Type::Bool]);
186        let call = make_effect(&[Type::Bool], &[Type::Bool]);
187
188        let captures = calculate_captures(&body, &call).unwrap();
189        assert_eq!(captures, vec![Type::Int, Type::String]);
190    }
191
192    #[test]
193    fn test_calculate_all_captured() {
194        // Body needs (Int String -- Int), call provides ( -- Int)
195        // Should capture [Int, String]
196        let body = make_effect(&[Type::Int, Type::String], &[Type::Int]);
197        let call = make_effect(&[], &[Type::Int]);
198
199        let captures = calculate_captures(&body, &call).unwrap();
200        assert_eq!(captures, vec![Type::Int, Type::String]);
201    }
202
203    #[test]
204    fn test_calculate_error_too_many_call_inputs() {
205        // Body needs (Int -- Int), call provides (Int Int -- Int)
206        // Error: call provides more than body needs
207        let body = make_effect(&[Type::Int], &[Type::Int]);
208        let call = make_effect(&[Type::Int, Type::Int], &[Type::Int]);
209
210        let result = calculate_captures(&body, &call);
211        assert!(result.is_err());
212        assert!(
213            result
214                .unwrap_err()
215                .contains("provides 2 values but body only needs 1")
216        );
217    }
218}