seqc/
types.rs

1//! Type system for Seq
2//!
3//! Based on cem2's row polymorphism design with improvements.
4//! Supports stack effect declarations like: ( ..a Int -- ..a Bool )
5//!
6//! ## Computational Effects
7//!
8//! Beyond stack effects, Seq tracks computational side effects using the `|` syntax:
9//! - `( a -- b | Yield T )` - may yield values of type T (generators)
10//! - Effects propagate through function calls
11//! - `strand.weave` handles the Yield effect, `strand.spawn` requires pure quotations
12
13/// Computational side effects (beyond stack transformation)
14///
15/// These track effects that go beyond the stack transformation:
16/// - Yield: generator/coroutine that yields values
17/// - Future: IO, Throw, Async, etc.
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub enum SideEffect {
20    /// Yields values of type T (generator effect)
21    /// Used by strand.weave quotations
22    Yield(Box<Type>),
23}
24
25impl std::fmt::Display for SideEffect {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            SideEffect::Yield(ty) => write!(f, "Yield {}", ty),
29        }
30    }
31}
32
33/// Base types in the language
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub enum Type {
36    /// Integer type
37    Int,
38    /// Floating-point type (IEEE 754 double precision)
39    Float,
40    /// Boolean type
41    Bool,
42    /// String type
43    String,
44    /// Symbol type (interned identifier for dynamic variant construction)
45    /// Syntax: :foo, :some-name
46    Symbol,
47    /// Channel type (for CSP-style concurrency)
48    /// Channels are reference-counted handles - dup increments refcount
49    Channel,
50    /// Quotation type (stateless code block with stack effect)
51    /// Example: [ Int -- Int ] is a quotation that takes Int and produces Int
52    /// No captured values - backward compatible with existing quotations
53    Quotation(Box<Effect>),
54    /// Closure type (quotation with captured environment)
55    /// Example: `Closure { effect: [Int -- Int], captures: [Int] }`
56    /// A closure that captures one Int and takes another Int to produce Int
57    Closure {
58        /// Stack effect when the closure is called
59        effect: Box<Effect>,
60        /// Types of values captured from the creation site
61        /// Ordered top-down: `captures[0]` is top of stack at creation
62        captures: Vec<Type>,
63    },
64    /// Union type - references a union definition by name
65    /// Example: Message in `union Message { Get { ... } Increment { ... } }`
66    /// The full definition is looked up in the type environment
67    Union(String),
68    /// Type variable (for polymorphism)
69    /// Example: T in ( ..a T -- ..a T T )
70    Var(String),
71}
72
73/// Information about a variant field
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct VariantFieldInfo {
76    pub name: String,
77    pub field_type: Type,
78}
79
80/// Information about a union variant (used by type checker)
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct VariantInfo {
83    pub name: String,
84    pub fields: Vec<VariantFieldInfo>,
85}
86
87/// Type information for a union definition (used by type checker)
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct UnionTypeInfo {
90    pub name: String,
91    pub variants: Vec<VariantInfo>,
92}
93
94/// Stack types with row polymorphism
95///
96/// # Understanding Stack Type Representation
97///
98/// Seq uses **row polymorphism** to type stack operations. The stack is represented
99/// as a linked list structure using `Cons` cells (from Lisp terminology).
100///
101/// ## Components
102///
103/// - **`Cons { rest, top }`**: A "cons cell" pairing a value type with the rest of the stack
104///   - `top`: The type of the value at this position
105///   - `rest`: What's underneath (another `Cons`, `Empty`, or `RowVar`)
106///
107/// - **`RowVar("name")`**: A row variable representing "the rest of the stack we don't care about"
108///   - Enables polymorphic functions like `dup` that work regardless of stack depth
109///   - Written as `..name` in stack effect signatures
110///
111/// - **`Empty`**: An empty stack (no values)
112///
113/// ## Debug vs Display Format
114///
115/// The `Debug` format shows the internal structure (useful for compiler developers):
116/// ```text
117/// Cons { rest: Cons { rest: RowVar("a$5"), top: Int }, top: Int }
118/// ```
119///
120/// The `Display` format shows user-friendly notation (matches stack effect syntax):
121/// ```text
122/// (..a$5 Int Int)
123/// ```
124///
125/// ## Reading the Debug Format
126///
127/// To read `Cons { rest: Cons { rest: RowVar("a"), top: Int }, top: Float }`:
128///
129/// 1. Start from the outermost `Cons` - its `top` is the stack top: `Float`
130/// 2. Follow `rest` to the next `Cons` - its `top` is next: `Int`
131/// 3. Follow `rest` to `RowVar("a")` - this is the polymorphic "rest of stack"
132///
133/// ```text
134/// Cons { rest: Cons { rest: RowVar("a"), top: Int }, top: Float }
135/// │                                           │           │
136/// │                                           │           └── top of stack: Float
137/// │                                           └── second from top: Int
138/// └── rest of stack: ..a (whatever else is there)
139///
140/// Equivalent to: (..a Int Float)  or in signature: ( ..a Int Float -- ... )
141/// ```
142///
143/// ## Fresh Variables (e.g., "a$5")
144///
145/// During type checking, variables are "freshened" to avoid name collisions:
146/// - `a` becomes `a$0`, `a$1`, etc.
147/// - The number is just a unique counter, not semantically meaningful
148/// - `a$5` means "the 6th fresh variable generated with prefix 'a'"
149///
150/// ## Example Error Message
151///
152/// ```text
153/// divide: stack type mismatch. Expected (..a$0 Int Int), got (..rest Float Float)
154/// ```
155///
156/// Meaning:
157/// - `divide` expects two `Int` values on top of any stack (`..a$0`)
158/// - You provided two `Float` values on top of the stack (`..rest`)
159/// - The types don't match: `Int` vs `Float`
160#[derive(Debug, Clone, PartialEq, Eq, Hash)]
161pub enum StackType {
162    /// Empty stack - no values
163    Empty,
164
165    /// Stack with a value on top of rest (a "cons cell")
166    ///
167    /// Named after Lisp's cons (construct) operation that builds pairs.
168    /// Think of it as: `top` is the head, `rest` is the tail.
169    Cons {
170        /// The rest of the stack (may be Empty, another Cons, or RowVar)
171        rest: Box<StackType>,
172        /// The type on top of the stack at this position
173        top: Type,
174    },
175
176    /// Row variable representing "rest of stack" for polymorphism
177    ///
178    /// Allows functions to be polymorphic over stack depth.
179    /// Example: `dup` has effect `( ..a T -- ..a T T )` where `..a` means
180    /// "whatever is already on the stack stays there".
181    RowVar(String),
182}
183
184/// Stack effect: transformation from input stack to output stack
185/// Example: ( ..a Int -- ..a Bool ) means:
186///   - Consumes an Int from stack with ..a underneath
187///   - Produces a Bool on stack with ..a underneath
188///
189/// With computational effects: ( ..a Int -- ..a Bool | Yield Int )
190///   - Same stack transformation
191///   - May also yield Int values (generator effect)
192#[derive(Debug, Clone, PartialEq, Eq, Hash)]
193pub struct Effect {
194    /// Input stack type (before word executes)
195    pub inputs: StackType,
196    /// Output stack type (after word executes)
197    pub outputs: StackType,
198    /// Computational side effects (Yield, etc.)
199    pub effects: Vec<SideEffect>,
200}
201
202impl StackType {
203    /// Create an empty stack type
204    pub fn empty() -> Self {
205        StackType::Empty
206    }
207
208    /// Create a stack type with a single value
209    pub fn singleton(ty: Type) -> Self {
210        StackType::Cons {
211            rest: Box::new(StackType::Empty),
212            top: ty,
213        }
214    }
215
216    /// Push a type onto a stack type
217    pub fn push(self, ty: Type) -> Self {
218        StackType::Cons {
219            rest: Box::new(self),
220            top: ty,
221        }
222    }
223
224    /// Create a stack type from a vector of types (bottom to top)
225    pub fn from_vec(types: Vec<Type>) -> Self {
226        types
227            .into_iter()
228            .fold(StackType::Empty, |stack, ty| stack.push(ty))
229    }
230
231    /// Pop a type from a stack type, returning (rest, top) if successful
232    pub fn pop(self) -> Option<(StackType, Type)> {
233        match self {
234            StackType::Cons { rest, top } => Some((*rest, top)),
235            _ => None,
236        }
237    }
238}
239
240impl Effect {
241    /// Create a new stack effect (pure, no side effects)
242    pub fn new(inputs: StackType, outputs: StackType) -> Self {
243        Effect {
244            inputs,
245            outputs,
246            effects: Vec::new(),
247        }
248    }
249
250    /// Create a new stack effect with computational effects
251    pub fn with_effects(inputs: StackType, outputs: StackType, effects: Vec<SideEffect>) -> Self {
252        Effect {
253            inputs,
254            outputs,
255            effects,
256        }
257    }
258
259    /// Check if this effect is pure (no side effects)
260    pub fn is_pure(&self) -> bool {
261        self.effects.is_empty()
262    }
263
264    /// Check if this effect has a Yield effect
265    pub fn has_yield(&self) -> bool {
266        self.effects
267            .iter()
268            .any(|e| matches!(e, SideEffect::Yield(_)))
269    }
270
271    /// Get the yield type if this effect has one
272    pub fn yield_type(&self) -> Option<&Type> {
273        self.effects
274            .iter()
275            .map(|e| match e {
276                SideEffect::Yield(ty) => ty.as_ref(),
277            })
278            .next()
279    }
280}
281
282impl std::fmt::Display for Type {
283    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284        match self {
285            Type::Int => write!(f, "Int"),
286            Type::Float => write!(f, "Float"),
287            Type::Bool => write!(f, "Bool"),
288            Type::String => write!(f, "String"),
289            Type::Symbol => write!(f, "Symbol"),
290            Type::Channel => write!(f, "Channel"),
291            Type::Quotation(effect) => write!(f, "[{}]", effect),
292            Type::Closure { effect, captures } => {
293                let cap_str: Vec<_> = captures.iter().map(|t| format!("{}", t)).collect();
294                write!(f, "Closure[{}, captures=({})]", effect, cap_str.join(", "))
295            }
296            Type::Union(name) => write!(f, "{}", name),
297            Type::Var(name) => write!(f, "{}", name),
298        }
299    }
300}
301
302impl std::fmt::Display for StackType {
303    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304        match self {
305            StackType::Empty => write!(f, "()"),
306            StackType::RowVar(name) => write!(f, "..{}", name),
307            StackType::Cons { rest, top } => {
308                // Collect all types from top to bottom
309                let mut types = vec![format!("{}", top)];
310                let mut current = rest.as_ref();
311                loop {
312                    match current {
313                        StackType::Empty => break,
314                        StackType::RowVar(name) => {
315                            types.push(format!("..{}", name));
316                            break;
317                        }
318                        StackType::Cons { rest, top } => {
319                            types.push(format!("{}", top));
320                            current = rest;
321                        }
322                    }
323                }
324                types.reverse();
325                write!(f, "({})", types.join(" "))
326            }
327        }
328    }
329}
330
331impl std::fmt::Display for Effect {
332    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333        if self.effects.is_empty() {
334            write!(f, "{} -- {}", self.inputs, self.outputs)
335        } else {
336            let effects_str: Vec<_> = self.effects.iter().map(|e| format!("{}", e)).collect();
337            write!(
338                f,
339                "{} -- {} | {}",
340                self.inputs,
341                self.outputs,
342                effects_str.join(" ")
343            )
344        }
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351
352    #[test]
353    fn test_empty_stack() {
354        let stack = StackType::empty();
355        assert_eq!(stack, StackType::Empty);
356    }
357
358    #[test]
359    fn test_singleton_stack() {
360        let stack = StackType::singleton(Type::Int);
361        assert_eq!(
362            stack,
363            StackType::Cons {
364                rest: Box::new(StackType::Empty),
365                top: Type::Int
366            }
367        );
368    }
369
370    #[test]
371    fn test_push_pop() {
372        let stack = StackType::empty().push(Type::Int).push(Type::Bool);
373
374        let (rest, top) = stack.pop().unwrap();
375        assert_eq!(top, Type::Bool);
376
377        let (rest2, top2) = rest.pop().unwrap();
378        assert_eq!(top2, Type::Int);
379        assert_eq!(rest2, StackType::Empty);
380    }
381
382    #[test]
383    fn test_from_vec() {
384        let stack = StackType::from_vec(vec![Type::Int, Type::Bool, Type::String]);
385
386        // Stack should be: String on top of Bool on top of Int on top of Empty
387        let (rest, top) = stack.pop().unwrap();
388        assert_eq!(top, Type::String);
389
390        let (rest2, top2) = rest.pop().unwrap();
391        assert_eq!(top2, Type::Bool);
392
393        let (rest3, top3) = rest2.pop().unwrap();
394        assert_eq!(top3, Type::Int);
395        assert_eq!(rest3, StackType::Empty);
396    }
397
398    #[test]
399    fn test_row_variable() {
400        let stack = StackType::Cons {
401            rest: Box::new(StackType::RowVar("a".to_string())),
402            top: Type::Int,
403        };
404
405        // This represents: Int on top of ..a
406        let (rest, top) = stack.pop().unwrap();
407        assert_eq!(top, Type::Int);
408        assert_eq!(rest, StackType::RowVar("a".to_string()));
409    }
410
411    #[test]
412    fn test_effect() {
413        // Effect: ( Int -- Bool )
414        let effect = Effect::new(
415            StackType::singleton(Type::Int),
416            StackType::singleton(Type::Bool),
417        );
418
419        assert_eq!(effect.inputs, StackType::singleton(Type::Int));
420        assert_eq!(effect.outputs, StackType::singleton(Type::Bool));
421    }
422
423    #[test]
424    fn test_polymorphic_effect() {
425        // Effect: ( ..a Int -- ..a Bool )
426        let inputs = StackType::Cons {
427            rest: Box::new(StackType::RowVar("a".to_string())),
428            top: Type::Int,
429        };
430
431        let outputs = StackType::Cons {
432            rest: Box::new(StackType::RowVar("a".to_string())),
433            top: Type::Bool,
434        };
435
436        let effect = Effect::new(inputs, outputs);
437
438        // Verify structure
439        assert!(matches!(effect.inputs, StackType::Cons { .. }));
440        assert!(matches!(effect.outputs, StackType::Cons { .. }));
441    }
442}