seqc/typechecker.rs
1//! Enhanced type checker for Seq with full type tracking
2//!
3//! Uses row polymorphism and unification to verify stack effects.
4//! Based on cem2's type checker but simplified for Phase 8.5.
5
6use crate::call_graph::CallGraph;
7use crate::types::{Effect, StackType, Type, UnionTypeInfo};
8use std::collections::HashMap;
9
10/// Format a line number as an error message prefix (e.g., "at line 42: ").
11/// Line numbers are 0-indexed internally, so we add 1 for display.
12fn format_line_prefix(line: usize) -> String {
13 format!("at line {}: ", line + 1)
14}
15
16/// Validate that `main` has an allowed signature (Issue #355).
17///
18/// Only `( -- )` and `( -- Int )` are accepted. The first is "void main"
19/// (process exits with 0). The second is "int main" (return value is the
20/// process exit code).
21///
22/// Any other shape — extra inputs, multiple outputs, non-Int output —
23/// is rejected with an actionable error.
24fn validate_main_effect(effect: &Effect) -> Result<(), String> {
25 // Inputs must be empty (just the row var, no concrete types)
26 let inputs_ok = matches!(&effect.inputs, StackType::Empty | StackType::RowVar(_));
27
28 // Outputs: either empty (void main) or exactly one Int (int main)
29 let outputs_ok = match &effect.outputs {
30 StackType::Empty | StackType::RowVar(_) => true,
31 StackType::Cons {
32 rest,
33 top: Type::Int,
34 } if matches!(**rest, StackType::Empty | StackType::RowVar(_)) => true,
35 _ => false,
36 };
37
38 if inputs_ok && outputs_ok {
39 return Ok(());
40 }
41
42 Err(format!(
43 "Word 'main' has an invalid stack effect: ( {} -- {} ).\n\
44 `main` must be declared with one of:\n\
45 ( -- ) — void main, process exits with code 0\n\
46 ( -- Int ) — exit code is the returned Int\n\
47 Other shapes are not allowed.",
48 effect.inputs, effect.outputs
49 ))
50}
51
52pub struct TypeChecker {
53 /// Environment mapping word names to their effects
54 env: HashMap<String, Effect>,
55 /// Union type registry - maps union names to their type information
56 /// Contains variant names and field types for each union
57 unions: HashMap<String, UnionTypeInfo>,
58 /// Counter for generating fresh type variables
59 fresh_counter: std::cell::Cell<usize>,
60 /// Quotation types tracked during type checking
61 /// Maps quotation ID (from AST) to inferred type (Quotation or Closure)
62 /// This type map is used by codegen to generate appropriate code
63 quotation_types: std::cell::RefCell<HashMap<usize, Type>>,
64 /// Expected quotation/closure type (from word signature, if any)
65 /// Used during type-driven capture inference
66 expected_quotation_type: std::cell::RefCell<Option<Type>>,
67 /// Current word being type-checked (for detecting recursive tail calls)
68 /// Used to identify divergent branches in if/else expressions
69 /// Stores (name, line_number) for better error messages
70 current_word: std::cell::RefCell<Option<(String, Option<usize>)>>,
71 /// Per-statement type info for codegen optimization (Issue #186)
72 /// Maps (word_name, statement_index) -> concrete top-of-stack type before statement
73 /// Only stores trivially-copyable types (Int, Float, Bool) to enable optimizations
74 statement_top_types: std::cell::RefCell<HashMap<(String, usize), Type>>,
75 /// Call graph for detecting mutual recursion (Issue #229)
76 /// Used to improve divergent branch detection beyond direct recursion
77 call_graph: Option<CallGraph>,
78 /// Current aux stack type during word body checking (Issue #350)
79 /// Tracked per-word; reset to Empty at each word boundary.
80 current_aux_stack: std::cell::RefCell<StackType>,
81 /// Maximum aux stack depth per word, for codegen alloca sizing (Issue #350)
82 /// Maps word_name -> max_depth (number of %Value allocas needed)
83 aux_max_depths: std::cell::RefCell<HashMap<String, usize>>,
84 /// Maximum aux stack depth per quotation, for codegen alloca sizing (Issue #393)
85 /// Maps quotation_id -> max_depth (number of %Value allocas needed)
86 /// Quotation IDs are program-wide unique, assigned by the parser.
87 quotation_aux_depths: std::cell::RefCell<HashMap<usize, usize>>,
88 /// Stack of currently-active quotation IDs during type checking (Issue #393).
89 /// Pushed when entering `infer_quotation`, popped on exit. The top of the
90 /// stack is the innermost quotation. Empty means we're in word body scope.
91 /// Replaces the old `in_quotation_scope` boolean.
92 quotation_id_stack: std::cell::RefCell<Vec<usize>>,
93 /// Resolved arithmetic sugar: maps (line, column) -> concrete op name.
94 /// Keyed by source location, which is unique per occurrence and available
95 /// to both the typechecker and codegen via the AST span.
96 resolved_sugar: std::cell::RefCell<HashMap<(usize, usize), String>>,
97}
98
99mod combinators;
100mod control_flow;
101mod driver;
102mod freshen;
103mod pick_roll;
104mod quotations;
105mod stack_utils;
106mod state;
107mod validation;
108mod words;
109
110impl Default for TypeChecker {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116#[cfg(test)]
117mod tests;