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;