Skip to main content

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;