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
272impl std::fmt::Display for Type {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 match self {
275 Type::Int => write!(f, "Int"),
276 Type::Float => write!(f, "Float"),
277 Type::Bool => write!(f, "Bool"),
278 Type::String => write!(f, "String"),
279 Type::Symbol => write!(f, "Symbol"),
280 Type::Channel => write!(f, "Channel"),
281 Type::Quotation(effect) => write!(f, "[{}]", effect),
282 Type::Closure { effect, captures } => {
283 let cap_str: Vec<_> = captures.iter().map(|t| format!("{}", t)).collect();
284 write!(f, "Closure[{}, captures=({})]", effect, cap_str.join(", "))
285 }
286 Type::Union(name) => write!(f, "{}", name),
287 Type::Var(name) => write!(f, "{}", name),
288 }
289 }
290}
291
292impl std::fmt::Display for StackType {
293 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294 match self {
295 StackType::Empty => write!(f, "()"),
296 StackType::RowVar(name) => write!(f, "..{}", name),
297 StackType::Cons { rest, top } => {
298 // Collect all types from top to bottom
299 let mut types = vec![format!("{}", top)];
300 let mut current = rest.as_ref();
301 loop {
302 match current {
303 StackType::Empty => break,
304 StackType::RowVar(name) => {
305 types.push(format!("..{}", name));
306 break;
307 }
308 StackType::Cons { rest, top } => {
309 types.push(format!("{}", top));
310 current = rest;
311 }
312 }
313 }
314 types.reverse();
315 write!(f, "({})", types.join(" "))
316 }
317 }
318 }
319}
320
321impl std::fmt::Display for Effect {
322 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323 if self.effects.is_empty() {
324 write!(f, "{} -- {}", self.inputs, self.outputs)
325 } else {
326 let effects_str: Vec<_> = self.effects.iter().map(|e| format!("{}", e)).collect();
327 write!(
328 f,
329 "{} -- {} | {}",
330 self.inputs,
331 self.outputs,
332 effects_str.join(" ")
333 )
334 }
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_empty_stack() {
344 let stack = StackType::empty();
345 assert_eq!(stack, StackType::Empty);
346 }
347
348 #[test]
349 fn test_singleton_stack() {
350 let stack = StackType::singleton(Type::Int);
351 assert_eq!(
352 stack,
353 StackType::Cons {
354 rest: Box::new(StackType::Empty),
355 top: Type::Int
356 }
357 );
358 }
359
360 #[test]
361 fn test_push_pop() {
362 let stack = StackType::empty().push(Type::Int).push(Type::Bool);
363
364 let (rest, top) = stack.pop().unwrap();
365 assert_eq!(top, Type::Bool);
366
367 let (rest2, top2) = rest.pop().unwrap();
368 assert_eq!(top2, Type::Int);
369 assert_eq!(rest2, StackType::Empty);
370 }
371
372 #[test]
373 fn test_from_vec() {
374 let stack = StackType::from_vec(vec![Type::Int, Type::Bool, Type::String]);
375
376 // Stack should be: String on top of Bool on top of Int on top of Empty
377 let (rest, top) = stack.pop().unwrap();
378 assert_eq!(top, Type::String);
379
380 let (rest2, top2) = rest.pop().unwrap();
381 assert_eq!(top2, Type::Bool);
382
383 let (rest3, top3) = rest2.pop().unwrap();
384 assert_eq!(top3, Type::Int);
385 assert_eq!(rest3, StackType::Empty);
386 }
387
388 #[test]
389 fn test_row_variable() {
390 let stack = StackType::Cons {
391 rest: Box::new(StackType::RowVar("a".to_string())),
392 top: Type::Int,
393 };
394
395 // This represents: Int on top of ..a
396 let (rest, top) = stack.pop().unwrap();
397 assert_eq!(top, Type::Int);
398 assert_eq!(rest, StackType::RowVar("a".to_string()));
399 }
400
401 #[test]
402 fn test_effect() {
403 // Effect: ( Int -- Bool )
404 let effect = Effect::new(
405 StackType::singleton(Type::Int),
406 StackType::singleton(Type::Bool),
407 );
408
409 assert_eq!(effect.inputs, StackType::singleton(Type::Int));
410 assert_eq!(effect.outputs, StackType::singleton(Type::Bool));
411 }
412
413 #[test]
414 fn test_polymorphic_effect() {
415 // Effect: ( ..a Int -- ..a Bool )
416 let inputs = StackType::Cons {
417 rest: Box::new(StackType::RowVar("a".to_string())),
418 top: Type::Int,
419 };
420
421 let outputs = StackType::Cons {
422 rest: Box::new(StackType::RowVar("a".to_string())),
423 top: Type::Bool,
424 };
425
426 let effect = Effect::new(inputs, outputs);
427
428 // Verify structure
429 assert!(matches!(effect.inputs, StackType::Cons { .. }));
430 assert!(matches!(effect.outputs, StackType::Cons { .. }));
431 }
432}