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;