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}