Skip to main content

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    /// Anonymous variant value — a tagged variant of unspecified union shape.
69    /// This is the compile-time mate of the runtime `Value::Variant` for cases
70    /// where we know the value is a variant but not which union it belongs to.
71    /// Used by the low-level `variant.*` builtins (variant.field-at, variant.tag,
72    /// variant.make-N, etc.). A `Type::Union(name)` is accepted where `Variant`
73    /// is expected (one-way relaxation, see unification).
74    Variant,
75    /// Type variable (for polymorphism)
76    /// Example: T in ( ..a T -- ..a T T )
77    Var(String),
78}
79
80/// Information about a variant field
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct VariantFieldInfo {
83    pub name: String,
84    pub field_type: Type,
85}
86
87/// Information about a union variant (used by type checker)
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct VariantInfo {
90    pub name: String,
91    pub fields: Vec<VariantFieldInfo>,
92}
93
94/// Type information for a union definition (used by type checker)
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct UnionTypeInfo {
97    pub name: String,
98    pub variants: Vec<VariantInfo>,
99}
100
101/// Stack types with row polymorphism
102///
103/// # Understanding Stack Type Representation
104///
105/// Seq uses **row polymorphism** to type stack operations. The stack is represented
106/// as a linked list structure using `Cons` cells (from Lisp terminology).
107///
108/// ## Components
109///
110/// - **`Cons { rest, top }`**: A "cons cell" pairing a value type with the rest of the stack
111///   - `top`: The type of the value at this position
112///   - `rest`: What's underneath (another `Cons`, `Empty`, or `RowVar`)
113///
114/// - **`RowVar("name")`**: A row variable representing "the rest of the stack we don't care about"
115///   - Enables polymorphic functions like `dup` that work regardless of stack depth
116///   - Written as `..name` in stack effect signatures
117///
118/// - **`Empty`**: An empty stack (no values)
119///
120/// ## Debug vs Display Format
121///
122/// The `Debug` format shows the internal structure (useful for compiler developers):
123/// ```text
124/// Cons { rest: Cons { rest: RowVar("a$5"), top: Int }, top: Int }
125/// ```
126///
127/// The `Display` format shows user-friendly notation (matches stack effect syntax):
128/// ```text
129/// (..a$5 Int Int)
130/// ```
131///
132/// ## Reading the Debug Format
133///
134/// To read `Cons { rest: Cons { rest: RowVar("a"), top: Int }, top: Float }`:
135///
136/// 1. Start from the outermost `Cons` - its `top` is the stack top: `Float`
137/// 2. Follow `rest` to the next `Cons` - its `top` is next: `Int`
138/// 3. Follow `rest` to `RowVar("a")` - this is the polymorphic "rest of stack"
139///
140/// ```text
141/// Cons { rest: Cons { rest: RowVar("a"), top: Int }, top: Float }
142/// │                                           │           │
143/// │                                           │           └── top of stack: Float
144/// │                                           └── second from top: Int
145/// └── rest of stack: ..a (whatever else is there)
146///
147/// Equivalent to: (..a Int Float)  or in signature: ( ..a Int Float -- ... )
148/// ```
149///
150/// ## Fresh Variables (e.g., "a$5")
151///
152/// During type checking, variables are "freshened" to avoid name collisions:
153/// - `a` becomes `a$0`, `a$1`, etc.
154/// - The number is just a unique counter, not semantically meaningful
155/// - `a$5` means "the 6th fresh variable generated with prefix 'a'"
156///
157/// ## Example Error Message
158///
159/// ```text
160/// divide: stack type mismatch. Expected (..a$0 Int Int), got (..rest Float Float)
161/// ```
162///
163/// Meaning:
164/// - `divide` expects two `Int` values on top of any stack (`..a$0`)
165/// - You provided two `Float` values on top of the stack (`..rest`)
166/// - The types don't match: `Int` vs `Float`
167#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub enum StackType {
169    /// Empty stack - no values
170    Empty,
171
172    /// Stack with a value on top of rest (a "cons cell")
173    ///
174    /// Named after Lisp's cons (construct) operation that builds pairs.
175    /// Think of it as: `top` is the head, `rest` is the tail.
176    Cons {
177        /// The rest of the stack (may be Empty, another Cons, or RowVar)
178        rest: Box<StackType>,
179        /// The type on top of the stack at this position
180        top: Type,
181    },
182
183    /// Row variable representing "rest of stack" for polymorphism
184    ///
185    /// Allows functions to be polymorphic over stack depth.
186    /// Example: `dup` has effect `( ..a T -- ..a T T )` where `..a` means
187    /// "whatever is already on the stack stays there".
188    RowVar(String),
189}
190
191/// Stack effect: transformation from input stack to output stack
192/// Example: ( ..a Int -- ..a Bool ) means:
193///   - Consumes an Int from stack with ..a underneath
194///   - Produces a Bool on stack with ..a underneath
195///
196/// With computational effects: ( ..a Int -- ..a Bool | Yield Int )
197///   - Same stack transformation
198///   - May also yield Int values (generator effect)
199#[derive(Debug, Clone, PartialEq, Eq, Hash)]
200pub struct Effect {
201    /// Input stack type (before word executes)
202    pub inputs: StackType,
203    /// Output stack type (after word executes)
204    pub outputs: StackType,
205    /// Computational side effects (Yield, etc.)
206    pub effects: Vec<SideEffect>,
207}
208
209impl StackType {
210    /// Create an empty stack type
211    pub fn empty() -> Self {
212        StackType::Empty
213    }
214
215    /// Create a stack type with a single value
216    pub fn singleton(ty: Type) -> Self {
217        StackType::Cons {
218            rest: Box::new(StackType::Empty),
219            top: ty,
220        }
221    }
222
223    /// Push a type onto a stack type
224    pub fn push(self, ty: Type) -> Self {
225        StackType::Cons {
226            rest: Box::new(self),
227            top: ty,
228        }
229    }
230
231    /// Create a stack type from a vector of types (bottom to top)
232    pub fn from_vec(types: Vec<Type>) -> Self {
233        types
234            .into_iter()
235            .fold(StackType::Empty, |stack, ty| stack.push(ty))
236    }
237
238    /// Pop a type from a stack type, returning (rest, top) if successful
239    pub fn pop(self) -> Option<(StackType, Type)> {
240        match self {
241            StackType::Cons { rest, top } => Some((*rest, top)),
242            _ => None,
243        }
244    }
245}
246
247impl Effect {
248    /// Create a new stack effect (pure, no side effects)
249    pub fn new(inputs: StackType, outputs: StackType) -> Self {
250        Effect {
251            inputs,
252            outputs,
253            effects: Vec::new(),
254        }
255    }
256
257    /// Create a new stack effect with computational effects
258    pub fn with_effects(inputs: StackType, outputs: StackType, effects: Vec<SideEffect>) -> Self {
259        Effect {
260            inputs,
261            outputs,
262            effects,
263        }
264    }
265
266    /// Check if this effect is pure (no side effects)
267    pub fn is_pure(&self) -> bool {
268        self.effects.is_empty()
269    }
270
271    /// Check if this effect has a Yield effect
272    pub fn has_yield(&self) -> bool {
273        self.effects
274            .iter()
275            .any(|e| matches!(e, SideEffect::Yield(_)))
276    }
277}
278
279impl std::fmt::Display for Type {
280    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281        match self {
282            Type::Int => write!(f, "Int"),
283            Type::Float => write!(f, "Float"),
284            Type::Bool => write!(f, "Bool"),
285            Type::String => write!(f, "String"),
286            Type::Symbol => write!(f, "Symbol"),
287            Type::Channel => write!(f, "Channel"),
288            Type::Quotation(effect) => write!(f, "[{}]", effect),
289            Type::Closure { effect, captures } => {
290                let cap_str: Vec<_> = captures.iter().map(|t| format!("{}", t)).collect();
291                write!(f, "Closure[{}, captures=({})]", effect, cap_str.join(", "))
292            }
293            Type::Union(name) => write!(f, "{}", name),
294            Type::Variant => write!(f, "Variant"),
295            Type::Var(name) => write!(f, "{}", name),
296        }
297    }
298}
299
300impl std::fmt::Display for StackType {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        match self {
303            StackType::Empty => write!(f, "()"),
304            StackType::RowVar(name) => write!(f, "..{}", name),
305            StackType::Cons { rest, top } => {
306                // Collect all types from top to bottom
307                let mut types = vec![format!("{}", top)];
308                let mut current = rest.as_ref();
309                loop {
310                    match current {
311                        StackType::Empty => break,
312                        StackType::RowVar(name) => {
313                            types.push(format!("..{}", name));
314                            break;
315                        }
316                        StackType::Cons { rest, top } => {
317                            types.push(format!("{}", top));
318                            current = rest;
319                        }
320                    }
321                }
322                types.reverse();
323                write!(f, "({})", types.join(" "))
324            }
325        }
326    }
327}
328
329impl std::fmt::Display for Effect {
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        if self.effects.is_empty() {
332            write!(f, "{} -- {}", self.inputs, self.outputs)
333        } else {
334            let effects_str: Vec<_> = self.effects.iter().map(|e| format!("{}", e)).collect();
335            write!(
336                f,
337                "{} -- {} | {}",
338                self.inputs,
339                self.outputs,
340                effects_str.join(" ")
341            )
342        }
343    }
344}
345
346#[cfg(test)]
347mod tests;