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