Skip to main content

logicaffeine_compile/analysis/
check.rs

1//! Bidirectional type checker for the LOGOS compilation pipeline.
2//!
3//! Replaces `TypeEnv::infer_program()` with a proper constraint-solving pass
4//! that eliminates `Unknown` for field access, empty collections, option literals,
5//! pipe receives, inspect arm bindings, and closure call expressions.
6//!
7//! # Architecture
8//!
9//! ```text
10//! AST
11//!  │
12//!  ├── preregister_functions   ← forward-reference pre-pass
13//!  │
14//!  └── infer_stmt / infer_expr ← bidirectional checking
15//!           │
16//!           └── UnificationTable ← Robinson unification (from unify.rs)
17//!                    │
18//!                    └── zonk → TypeEnv (LogosType) → codegen
19//! ```
20
21use std::collections::{HashMap, HashSet};
22
23use crate::analysis::unify::{InferType, TyVar, TypeScheme, TypeError, UnificationTable, infer_to_logos, unify_numeric};
24use crate::analysis::{FnSig, LogosType, TypeDef, TypeEnv, TypeRegistry};
25use crate::ast::stmt::{BinaryOpKind, Expr, Pattern, Stmt};
26use crate::intern::{Interner, Symbol};
27
28// ============================================================================
29// Data structures
30// ============================================================================
31
32/// A registered function's signature, supporting both monomorphic and generic functions.
33///
34/// For generic functions (non-empty `scheme.vars`), each call site must instantiate
35/// the scheme to get fresh type variables, preventing cross-call contamination.
36/// For monomorphic functions (`scheme.vars` is empty), `scheme.body` is the direct type.
37#[derive(Clone, Debug)]
38struct FunctionRecord {
39    /// Parameter names (for binding in the function scope).
40    param_names: Vec<Symbol>,
41    /// The quantified type scheme: `forall vars. Function(param_types, return_type)`.
42    /// For monomorphic functions, `vars` is empty and body is used directly.
43    scheme: TypeScheme,
44}
45
46/// Bidirectional type checking environment.
47///
48/// Scopes are pushed/popped around function bodies and match arms.
49/// All bindings are also written to `all_vars` for later `TypeEnv` output.
50struct CheckEnv<'r> {
51    /// Stacked scopes (innermost last). Variables resolved from inner-to-outer.
52    scopes: Vec<HashMap<Symbol, InferType>>,
53    /// Flat map of every variable ever bound — accumulated for `TypeEnv` output.
54    all_vars: HashMap<Symbol, InferType>,
55    /// Registered function signatures.
56    functions: HashMap<Symbol, FunctionRecord>,
57    /// Expected return type inside the current function body.
58    current_return_type: Option<InferType>,
59    /// Unification table for type variables.
60    table: UnificationTable,
61    registry: &'r TypeRegistry,
62    interner: &'r Interner,
63}
64
65impl<'r> CheckEnv<'r> {
66    fn new(registry: &'r TypeRegistry, interner: &'r Interner) -> Self {
67        Self {
68            scopes: vec![HashMap::new()],
69            all_vars: HashMap::new(),
70            functions: HashMap::new(),
71            current_return_type: None,
72            table: UnificationTable::new(),
73            registry,
74            interner,
75        }
76    }
77
78    fn push_scope(&mut self) {
79        self.scopes.push(HashMap::new());
80    }
81
82    fn pop_scope(&mut self) {
83        self.scopes.pop();
84    }
85
86    /// Bind a variable in the current scope, also recording in `all_vars`.
87    fn bind_var(&mut self, sym: Symbol, ty: InferType) {
88        if let Some(scope) = self.scopes.last_mut() {
89            scope.insert(sym, ty.clone());
90        }
91        self.all_vars.insert(sym, ty);
92    }
93
94    /// Look up a variable, searching scopes from innermost to outermost.
95    ///
96    /// Uses `resolve` (not `zonk`) so that unbound type variables from generic
97    /// function parameters remain as `Var(tv)` during inference, enabling
98    /// proper unification at call sites.
99    fn lookup_var(&self, sym: Symbol) -> Option<InferType> {
100        for scope in self.scopes.iter().rev() {
101            if let Some(ty) = scope.get(&sym) {
102                return Some(self.table.resolve(ty));
103            }
104        }
105        None
106    }
107
108    /// Convert the check environment into a `TypeEnv` for codegen.
109    fn into_type_env(self) -> TypeEnv {
110        let mut type_env = TypeEnv::new();
111
112        // Collect all variable bindings, zonk each to a concrete LogosType
113        for (sym, ty) in self.all_vars {
114            let logos_ty = self.table.to_logos_type(&ty);
115            type_env.register(sym, logos_ty);
116        }
117
118        // Collect function signatures — instantiate monomorphic view for codegen
119        for (name, rec) in self.functions {
120            // Zonk the scheme body to extract concrete param/return types for TypeEnv.
121            // For generic functions, unsolved vars zonk to Unknown, which is fine
122            // since codegen uses TypeExpr (not TypeEnv) for generic param types.
123            if let InferType::Function(param_types, ret_box) = &rec.scheme.body {
124                let ret_logos = self.table.to_logos_type(ret_box);
125                let params: Vec<(Symbol, LogosType)> = rec.param_names.iter()
126                    .zip(param_types.iter())
127                    .map(|(sym, ty)| (*sym, self.table.to_logos_type(ty)))
128                    .collect();
129                type_env.register_fn(name, FnSig { params, return_type: ret_logos });
130            }
131        }
132
133        type_env
134    }
135}
136
137// ============================================================================
138// Pre-pass: forward reference registration
139// ============================================================================
140
141impl<'r> CheckEnv<'r> {
142    /// Register all top-level function signatures before the main checking pass.
143    ///
144    /// This enables forward references and mutual recursion: any function can
145    /// call any other function regardless of declaration order.
146    ///
147    /// For generic functions (non-empty `generics`), allocates a fresh `TyVar` per
148    /// type parameter and builds a `TypeScheme` so call sites can instantiate them.
149    fn preregister_functions(&mut self, stmts: &[Stmt]) {
150        for stmt in stmts {
151            if let Stmt::FunctionDef { name, generics, params, return_type, .. } = stmt {
152                // Allocate one TyVar per generic type parameter
153                let type_param_map: HashMap<Symbol, TyVar> = generics
154                    .iter()
155                    .map(|&sym| (sym, self.table.fresh_var()))
156                    .collect();
157
158                let param_types: Vec<InferType> = params
159                    .iter()
160                    .map(|(_, ty_expr)| {
161                        InferType::from_type_expr_with_params(ty_expr, self.interner, &type_param_map)
162                    })
163                    .collect();
164                let param_names: Vec<Symbol> = params.iter().map(|(sym, _)| *sym).collect();
165
166                let ret_type = if let Some(rt) = return_type {
167                    InferType::from_type_expr_with_params(rt, self.interner, &type_param_map)
168                } else {
169                    self.table.fresh()
170                };
171
172                let generic_vars: Vec<TyVar> = generics
173                    .iter()
174                    .filter_map(|sym| type_param_map.get(sym).copied())
175                    .collect();
176
177                let scheme = TypeScheme {
178                    vars: generic_vars,
179                    body: InferType::Function(param_types, Box::new(ret_type)),
180                };
181
182                self.functions.insert(*name, FunctionRecord { param_names, scheme });
183            }
184        }
185    }
186}
187
188// ============================================================================
189// Core inference
190// ============================================================================
191
192impl<'r> CheckEnv<'r> {
193    /// Check an expression against an expected type (checking mode).
194    ///
195    /// Handles numeric literal coercion (`5` against `Real` → `Float`) and
196    /// structural checking before falling through to synthesis + unification.
197    fn check_expr(
198        &mut self,
199        expr: &Expr,
200        expected: &InferType,
201    ) -> Result<InferType, TypeError> {
202        use crate::ast::stmt::Literal;
203
204        // Number literals are polymorphic: 5 checks against Int, Float, Nat, or Byte
205        if let Expr::Literal(Literal::Number(_)) = expr {
206            match expected {
207                InferType::Float => return Ok(InferType::Float),
208                InferType::Nat => return Ok(InferType::Nat),
209                InferType::Int => return Ok(InferType::Int),
210                InferType::Byte => return Ok(InferType::Byte),
211                _ => {}
212            }
213        }
214
215        // `nothing` is polymorphic: it is `None` when checked against Option(T),
216        // and the unit value `()` in all other contexts.
217        if let Expr::Literal(Literal::Nothing) = expr {
218            if let InferType::Option(_) = expected {
219                return Ok(expected.clone());
220            }
221        }
222
223        // List literals: check EACH element against the expected element type so
224        // the numeric-literal coercion applies element-wise (e.g. `[1, 2, 3]`
225        // under a `Seq of Real` annotation), instead of synthesizing the element
226        // type from items[0] alone and failing to unify with the annotation.
227        if let Expr::List(items) = expr {
228            if let InferType::Seq(elem) = self.table.zonk(expected) {
229                for item in items {
230                    self.check_expr(item, &elem)?;
231                }
232                return Ok(InferType::Seq(elem));
233            }
234        }
235
236        // Default: synthesize then unify
237        let inferred = self.infer_expr(expr)?;
238        self.table.unify(&inferred, expected)?;
239        Ok(self.table.zonk(expected))
240    }
241
242    /// Infer the type of an expression (synthesis mode).
243    fn infer_expr(&mut self, expr: &Expr) -> Result<InferType, TypeError> {
244        match expr {
245            Expr::Literal(lit) => Ok(InferType::from_literal(lit)),
246
247            Expr::Identifier(sym) => {
248                Ok(self.lookup_var(*sym).unwrap_or(InferType::Unknown))
249            }
250
251            Expr::BinaryOp { op, left, right } => {
252                self.infer_binary_op(*op, left, right)
253            }
254
255            Expr::Length { .. } => Ok(InferType::Int),
256
257            Expr::Call { function, args } => {
258                self.infer_call(*function, args)
259            }
260
261            Expr::Index { collection, .. } => {
262                let coll_ty = self.infer_expr(collection)?;
263                let walked = self.table.zonk(&coll_ty);
264                match walked {
265                    InferType::Seq(inner) => Ok(*inner),
266                    InferType::Map(_, v) => Ok(*v),
267                    _ => Ok(InferType::Unknown),
268                }
269            }
270
271            Expr::List(items) => {
272                if items.is_empty() {
273                    let elem_var = self.table.fresh();
274                    Ok(InferType::Seq(Box::new(elem_var)))
275                } else {
276                    let elem_type = self.infer_expr(items[0])?;
277                    Ok(InferType::Seq(Box::new(elem_type)))
278                }
279            }
280
281            Expr::OptionSome { value } => {
282                let inner = self.infer_expr(value)?;
283                Ok(InferType::Option(Box::new(inner)))
284            }
285
286            Expr::OptionNone => {
287                let elem_var = self.table.fresh();
288                Ok(InferType::Option(Box::new(elem_var)))
289            }
290
291            Expr::Range { .. } => Ok(InferType::Seq(Box::new(InferType::Int))),
292
293            Expr::Contains { .. } => Ok(InferType::Bool),
294
295            Expr::Copy { expr: inner } | Expr::Give { value: inner } => {
296                self.infer_expr(inner)
297            }
298
299            Expr::WithCapacity { value, .. } => self.infer_expr(value),
300
301            Expr::FieldAccess { object, field } => {
302                let obj_ty = self.infer_expr(object)?;
303                self.infer_field_access(obj_ty, *field)
304            }
305
306            Expr::New { type_name, type_args, .. } => {
307                let name = self.interner.resolve(*type_name);
308                match name {
309                    "Seq" | "List" | "Vec" => {
310                        let elem = type_args
311                            .first()
312                            .map(|t| InferType::from_type_expr(t, self.interner))
313                            .unwrap_or_else(|| self.table.fresh());
314                        Ok(InferType::Seq(Box::new(elem)))
315                    }
316                    "Map" | "HashMap" => {
317                        let key = type_args
318                            .first()
319                            .map(|t| InferType::from_type_expr(t, self.interner))
320                            .unwrap_or(InferType::String);
321                        let val = type_args
322                            .get(1)
323                            .map(|t| InferType::from_type_expr(t, self.interner))
324                            .unwrap_or(InferType::String);
325                        Ok(InferType::Map(Box::new(key), Box::new(val)))
326                    }
327                    "Set" | "HashSet" => {
328                        let elem = type_args
329                            .first()
330                            .map(|t| InferType::from_type_expr(t, self.interner))
331                            .unwrap_or_else(|| self.table.fresh());
332                        Ok(InferType::Set(Box::new(elem)))
333                    }
334                    _ => Ok(InferType::UserDefined(*type_name)),
335                }
336            }
337
338            Expr::NewVariant { enum_name, .. } => {
339                Ok(InferType::UserDefined(*enum_name))
340            }
341
342            Expr::CallExpr { callee, args } => {
343                self.infer_call_expr(callee, args)
344            }
345
346            Expr::Closure { params, body: closure_body, return_type } => {
347                self.infer_closure(params, closure_body, return_type)
348            }
349
350            Expr::InterpolatedString(_) => Ok(InferType::String),
351
352            Expr::Slice { collection, .. } => self.infer_expr(collection),
353
354            Expr::Union { left, .. } | Expr::Intersection { left, .. } => {
355                self.infer_expr(left)
356            }
357
358            // Tuple, ManifestOf, ChunkAt, Escape → Unknown (not typed)
359            _ => Ok(InferType::Unknown),
360        }
361    }
362
363    /// Infer a binary operation's result type.
364    fn infer_binary_op(
365        &mut self,
366        op: BinaryOpKind,
367        left: &Expr,
368        right: &Expr,
369    ) -> Result<InferType, TypeError> {
370        match op {
371            BinaryOpKind::Eq
372            | BinaryOpKind::NotEq
373            | BinaryOpKind::Lt
374            | BinaryOpKind::Gt
375            | BinaryOpKind::LtEq
376            | BinaryOpKind::GtEq
377            | BinaryOpKind::ApproxEq => Ok(InferType::Bool),
378
379            // And/Or: type-aware — integer operands → Int (bitwise), else → Bool (logical)
380            BinaryOpKind::And | BinaryOpKind::Or => {
381                let lt = self.infer_expr(left)?;
382                if lt == InferType::Int {
383                    Ok(InferType::Int)
384                } else {
385                    Ok(InferType::Bool)
386                }
387            }
388
389            BinaryOpKind::Concat => Ok(InferType::String),
390
391            // `a followed by b` — result is a sequence of the same type as the operands.
392            BinaryOpKind::SeqConcat => self.infer_expr(left),
393
394            BinaryOpKind::BitXor | BinaryOpKind::BitAnd | BinaryOpKind::BitOr
395            | BinaryOpKind::Shl | BinaryOpKind::Shr => Ok(InferType::Int),
396
397            BinaryOpKind::Add => {
398                let lt = self.infer_expr(left)?;
399                let rt = self.infer_expr(right)?;
400                if lt == InferType::String || rt == InferType::String {
401                    Ok(InferType::String)
402                } else if lt == InferType::Unknown || rt == InferType::Unknown {
403                    Ok(InferType::Unknown)
404                } else {
405                    unify_numeric(&lt, &rt).or(Ok(InferType::Unknown))
406                }
407            }
408
409            // Exact division always yields a Rational (`7 / 2 → 7/2`); the resolve pass
410            // only emits it in a Rational-typed context.
411            BinaryOpKind::ExactDivide => {
412                self.infer_expr(left)?;
413                self.infer_expr(right)?;
414                Ok(InferType::Rational)
415            }
416
417            BinaryOpKind::Subtract
418            | BinaryOpKind::Multiply
419            | BinaryOpKind::Divide
420            | BinaryOpKind::FloorDivide
421            | BinaryOpKind::Modulo
422            | BinaryOpKind::Pow => {
423                let lt = self.infer_expr(left)?;
424                let rt = self.infer_expr(right)?;
425                if lt == InferType::Unknown || rt == InferType::Unknown {
426                    Ok(InferType::Unknown)
427                } else {
428                    unify_numeric(&lt, &rt).or(Ok(InferType::Unknown))
429                }
430            }
431        }
432    }
433
434    /// Infer a named function call.
435    ///
436    /// For generic functions, instantiates the `TypeScheme` with fresh type variables,
437    /// then unifies the instantiated parameter types with the argument types. The
438    /// instantiated return type is then zonked and returned as the call result type.
439    fn infer_call(&mut self, function: Symbol, args: &[&Expr]) -> Result<InferType, TypeError> {
440        let name = self.interner.resolve(function);
441        match name {
442            "sqrt" | "parseFloat" | "pow" => Ok(InferType::Float),
443            "parseInt" | "floor" | "ceil" | "round" => Ok(InferType::Int),
444            "abs" | "min" | "max" => {
445                if let Some(first) = args.first() {
446                    self.infer_expr(first)
447                } else {
448                    Ok(InferType::Unknown)
449                }
450            }
451            _ => {
452                if let Some(rec) = self.functions.get(&function).cloned() {
453                    // Instantiate the scheme: each call site gets fresh type variables
454                    // for generic params so calls don't interfere with each other.
455                    let instantiated = self.table.instantiate(&rec.scheme);
456
457                    if let InferType::Function(param_types, ret_box) = instantiated {
458                        // Unify each argument type with the instantiated parameter type
459                        for (arg, param_ty) in args.iter().zip(param_types.iter()) {
460                            let arg_ty = self.infer_expr(arg)?;
461                            self.table.unify(&arg_ty, param_ty)?;
462                        }
463                        Ok(self.table.zonk(&ret_box))
464                    } else {
465                        // Should not happen, but fall back gracefully
466                        Ok(InferType::Unknown)
467                    }
468                } else {
469                    Ok(InferType::Unknown)
470                }
471            }
472        }
473    }
474
475    /// Infer a call-expression (calling a closure/function-value).
476    fn infer_call_expr(
477        &mut self,
478        callee: &Expr,
479        args: &[&Expr],
480    ) -> Result<InferType, TypeError> {
481        let callee_ty = self.infer_expr(callee)?;
482        let ret_var = self.table.fresh();
483        let arg_types: Vec<InferType> = args
484            .iter()
485            .map(|a| self.infer_expr(a))
486            .collect::<Result<_, _>>()?;
487        let fn_ty = InferType::Function(arg_types, Box::new(ret_var.clone()));
488
489        let walked = self.table.zonk(&callee_ty);
490        match walked {
491            InferType::Unknown => Ok(ret_var),
492            InferType::Function(_, _) => {
493                self.table.unify(&walked, &fn_ty)?;
494                Ok(ret_var)
495            }
496            InferType::Var(_) => {
497                self.table.unify(&walked, &fn_ty)?;
498                Ok(ret_var)
499            }
500            other => Err(TypeError::NotAFunction { found: other }),
501        }
502    }
503
504    /// Infer a closure literal.
505    fn infer_closure(
506        &mut self,
507        params: &[(Symbol, &crate::ast::stmt::TypeExpr)],
508        body: &crate::ast::stmt::ClosureBody,
509        return_type: &Option<&crate::ast::stmt::TypeExpr>,
510    ) -> Result<InferType, TypeError> {
511        let param_types: Vec<InferType> = params
512            .iter()
513            .map(|(_, ty_expr)| InferType::from_type_expr(ty_expr, self.interner))
514            .collect();
515
516        let ret_type = if let Some(rt) = return_type {
517            InferType::from_type_expr(rt, self.interner)
518        } else {
519            self.table.fresh()
520        };
521
522        self.push_scope();
523        for ((sym, _), ty) in params.iter().zip(param_types.iter()) {
524            self.bind_var(*sym, ty.clone());
525        }
526
527        let prev_return = self.current_return_type.take();
528        self.current_return_type = Some(ret_type.clone());
529
530        match body {
531            crate::ast::stmt::ClosureBody::Expression(expr) => {
532                let body_ty = self.infer_expr(expr)?;
533                // Best-effort unification: won't fail compilation on ambiguity
534                self.table.unify(&body_ty, &ret_type).ok();
535            }
536            crate::ast::stmt::ClosureBody::Block(stmts) => {
537                for stmt in *stmts {
538                    self.infer_stmt(stmt)?;
539                }
540            }
541        }
542
543        self.current_return_type = prev_return;
544        self.pop_scope();
545
546        Ok(InferType::Function(param_types, Box::new(ret_type)))
547    }
548
549    /// Infer the type of a field access on a struct.
550    fn infer_field_access(
551        &self,
552        obj_ty: InferType,
553        field: Symbol,
554    ) -> Result<InferType, TypeError> {
555        let resolved = self.table.zonk(&obj_ty);
556        match &resolved {
557            InferType::UserDefined(type_sym) => {
558                if let Some(TypeDef::Struct { fields, .. }) = self.registry.get(*type_sym) {
559                    if let Some(field_def) = fields.iter().find(|f| f.name == field) {
560                        Ok(InferType::from_field_type(
561                            &field_def.ty,
562                            self.interner,
563                            &HashMap::new(),
564                        ))
565                    } else {
566                        Err(TypeError::FieldNotFound {
567                            type_name: *type_sym,
568                            field_name: field,
569                        })
570                    }
571                } else {
572                    // Not a struct in registry → Unknown (defensive)
573                    Ok(InferType::Unknown)
574                }
575            }
576            // Can't resolve field on non-struct type
577            _ => Ok(InferType::Unknown),
578        }
579    }
580}
581
582// ============================================================================
583// Statement inference
584// ============================================================================
585
586impl<'r> CheckEnv<'r> {
587    fn infer_stmt(&mut self, stmt: &Stmt) -> Result<(), TypeError> {
588        match stmt {
589            Stmt::Let { var, ty, value, .. } => {
590                let final_ty = if let Some(type_expr) = ty {
591                    let annotated = InferType::from_type_expr(type_expr, self.interner);
592                    if annotated != InferType::Unknown {
593                        // Checking mode: value must be compatible with annotation
594                        self.check_expr(value, &annotated)?
595                    } else {
596                        self.infer_expr(value)?
597                    }
598                } else {
599                    self.infer_expr(value)?
600                };
601                self.bind_var(*var, final_ty);
602                Ok(())
603            }
604
605            Stmt::Set { target, value } => {
606                let inferred = self.infer_expr(value)?;
607                // If target already has a type, unify. Otherwise just bind.
608                if let Some(existing) = self.lookup_var(*target) {
609                    if existing != InferType::Unknown {
610                        self.table.unify(&inferred, &existing).ok();
611                    }
612                }
613                // Update binding
614                let resolved = self.table.zonk(&inferred);
615                if resolved != InferType::Unknown {
616                    self.bind_var(*target, resolved);
617                }
618                Ok(())
619            }
620
621            Stmt::FunctionDef {
622                name,
623                generics,
624                params,
625                body,
626                return_type,
627                is_native,
628                ..
629            } => {
630                // Build a type-param map: Symbol("T") → TyVar
631                // Re-use the TyVars already allocated in preregister_functions if present,
632                // or allocate fresh ones if this function was not pre-registered.
633                let type_param_map: HashMap<Symbol, TyVar> = {
634                    // Try to recover the same TyVars from the pre-registered scheme
635                    let existing_vars: Vec<TyVar> = self.functions
636                        .get(name)
637                        .map(|rec| rec.scheme.vars.clone())
638                        .unwrap_or_default();
639                    if existing_vars.len() == generics.len() {
640                        generics.iter().copied().zip(existing_vars).collect()
641                    } else {
642                        generics.iter().map(|&sym| (sym, self.table.fresh_var())).collect()
643                    }
644                };
645
646                let param_types: Vec<InferType> = params
647                    .iter()
648                    .map(|(_, ty_expr)| {
649                        InferType::from_type_expr_with_params(ty_expr, self.interner, &type_param_map)
650                    })
651                    .collect();
652                let param_names: Vec<Symbol> = params.iter().map(|(sym, _)| *sym).collect();
653
654                let ret_type = if let Some(rt) = return_type {
655                    InferType::from_type_expr_with_params(rt, self.interner, &type_param_map)
656                } else if let Some(rec) = self.functions.get(name) {
657                    // Recover pre-registered return type from the scheme body
658                    if let InferType::Function(_, ret_box) = &rec.scheme.body {
659                        *ret_box.clone()
660                    } else {
661                        self.table.fresh()
662                    }
663                } else {
664                    self.table.fresh()
665                };
666
667                let generic_vars: Vec<TyVar> = generics
668                    .iter()
669                    .filter_map(|sym| type_param_map.get(sym).copied())
670                    .collect();
671
672                // Native functions: register scheme, no body to check
673                if *is_native {
674                    let scheme = TypeScheme {
675                        vars: generic_vars,
676                        body: InferType::Function(param_types, Box::new(ret_type)),
677                    };
678                    self.functions.insert(*name, FunctionRecord { param_names, scheme });
679                    return Ok(());
680                }
681
682                // Save previous return context
683                let prev_return_type = self.current_return_type.take();
684                self.current_return_type = Some(ret_type.clone());
685
686                // Check body in a new scope with params bound
687                self.push_scope();
688                for (sym, ty) in param_names.iter().zip(param_types.iter()) {
689                    self.bind_var(*sym, ty.clone());
690                }
691                for s in *body {
692                    self.infer_stmt(s)?;
693                }
694                self.pop_scope();
695
696                self.current_return_type = prev_return_type;
697
698                // After checking the body, update the registered scheme with resolved types.
699                // Use `resolve` (not `zonk`) so generic TyVars remain as `Var(tv)` in
700                // the scheme body — they will be instantiated fresh at each call site.
701                let resolved_params: Vec<InferType> = param_types
702                    .iter()
703                    .map(|ty| self.table.resolve(ty))
704                    .collect();
705                let resolved_ret = self.table.resolve(&ret_type);
706
707                // Generalize over EVERY free type variable in the resolved
708                // signature, not just the declared generics: a function with an
709                // INFERRED (unannotated) return type allocates a fresh return
710                // variable that is genuinely polymorphic but absent from
711                // `generic_vars`. Leaving it free would share it across call
712                // sites (cross-call contamination); adding it to `vars` makes
713                // `instantiate` freshen it per call.
714                fn collect_type_vars(ty: &InferType, acc: &mut Vec<TyVar>) {
715                    match ty {
716                        InferType::Var(tv) => {
717                            if !acc.contains(tv) {
718                                acc.push(*tv);
719                            }
720                        }
721                        InferType::Seq(i) | InferType::Set(i) | InferType::Option(i) => {
722                            collect_type_vars(i, acc)
723                        }
724                        InferType::Map(k, v) => {
725                            collect_type_vars(k, acc);
726                            collect_type_vars(v, acc);
727                        }
728                        InferType::Function(ps, r) => {
729                            for p in ps {
730                                collect_type_vars(p, acc);
731                            }
732                            collect_type_vars(r, acc);
733                        }
734                        _ => {}
735                    }
736                }
737                let mut scheme_vars = generic_vars;
738                for p in &resolved_params {
739                    collect_type_vars(p, &mut scheme_vars);
740                }
741                collect_type_vars(&resolved_ret, &mut scheme_vars);
742                let scheme = TypeScheme {
743                    vars: scheme_vars,
744                    body: InferType::Function(resolved_params, Box::new(resolved_ret)),
745                };
746                self.functions.insert(*name, FunctionRecord { param_names, scheme });
747                Ok(())
748            }
749
750            Stmt::Return { value } => {
751                let ty = match value {
752                    Some(expr) => self.infer_expr(expr)?,
753                    None => InferType::Unit,
754                };
755                if let Some(expected) = self.current_return_type.clone() {
756                    // Hard check for explicit return type annotations
757                    if expected != InferType::Unknown {
758                        self.table.unify(&ty, &expected)?;
759                    }
760                }
761                Ok(())
762            }
763
764            Stmt::Repeat { pattern, iterable, body } => {
765                let iterable_ty = self.infer_expr(iterable)?;
766                let elem_ty = match self.table.zonk(&iterable_ty) {
767                    InferType::Seq(inner) | InferType::Set(inner) => *inner,
768                    // A Map yields (key, value) tuples per entry at runtime
769                    // (semantics/collections.rs `iteration_snapshot`), NOT bare
770                    // keys. InferType has no tuple type, so a single loop
771                    // variable binds to Unknown — sound (it cannot drive a wrong
772                    // specialization), unlike the bare key type `K`.
773                    InferType::Map(_, _) => InferType::Unknown,
774                    _ => InferType::Unknown,
775                };
776                match pattern {
777                    Pattern::Identifier(sym) => self.bind_var(*sym, elem_ty),
778                    Pattern::Tuple(syms) => {
779                        for sym in syms {
780                            self.bind_var(*sym, InferType::Unknown);
781                        }
782                    }
783                }
784                for s in *body {
785                    self.infer_stmt(s)?;
786                }
787                Ok(())
788            }
789
790            Stmt::If { then_block, else_block, .. } => {
791                for s in *then_block {
792                    self.infer_stmt(s)?;
793                }
794                if let Some(else_b) = else_block {
795                    for s in *else_b {
796                        self.infer_stmt(s)?;
797                    }
798                }
799                Ok(())
800            }
801
802            Stmt::While { body, .. } => {
803                for s in *body {
804                    self.infer_stmt(s)?;
805                }
806                Ok(())
807            }
808
809            Stmt::Inspect { target, arms, .. } => {
810                let _target_ty = self.infer_expr(target)?;
811                for arm in arms {
812                    self.push_scope();
813                    self.infer_inspect_arm(arm)?;
814                    self.pop_scope();
815                }
816                Ok(())
817            }
818
819            Stmt::Zone { body, .. } => {
820                for s in *body {
821                    self.infer_stmt(s)?;
822                }
823                Ok(())
824            }
825
826            Stmt::ReadFrom { var, .. } => {
827                self.bind_var(*var, InferType::String);
828                Ok(())
829            }
830
831            Stmt::CreatePipe { var, element_type, .. } => {
832                let elem = InferType::from_type_name(self.interner.resolve(*element_type));
833                self.bind_var(*var, elem);
834                Ok(())
835            }
836
837            Stmt::ReceivePipe { var, pipe } => {
838                // Pipe var was registered with its element type by CreatePipe
839                let elem_ty = self.infer_expr(pipe)?;
840                self.bind_var(*var, elem_ty);
841                Ok(())
842            }
843
844            Stmt::TryReceivePipe { var, pipe } => {
845                let elem_ty = self.infer_expr(pipe)?;
846                // TryReceivePipe yields Option of elem type
847                self.bind_var(*var, InferType::Option(Box::new(elem_ty)));
848                Ok(())
849            }
850
851            Stmt::Pop { into: Some(var), collection } => {
852                let coll_ty = self.infer_expr(collection)?;
853                let elem_ty = match self.table.zonk(&coll_ty) {
854                    InferType::Seq(inner) | InferType::Set(inner) => *inner,
855                    _ => InferType::Unknown,
856                };
857                self.bind_var(*var, elem_ty);
858                Ok(())
859            }
860
861            Stmt::AwaitMessage { into, .. } => {
862                self.bind_var(*into, InferType::Unknown);
863                Ok(())
864            }
865
866            Stmt::LaunchTaskWithHandle { handle, .. } => {
867                self.bind_var(*handle, InferType::Unknown);
868                Ok(())
869            }
870
871            Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
872                for s in *tasks {
873                    self.infer_stmt(s)?;
874                }
875                Ok(())
876            }
877
878            Stmt::Select { branches } => {
879                for branch in branches {
880                    match branch {
881                        crate::ast::stmt::SelectBranch::Receive { var, pipe, body } => {
882                            let elem_ty = self.infer_expr(pipe)?;
883                            self.push_scope();
884                            self.bind_var(*var, elem_ty);
885                            for s in *body {
886                                self.infer_stmt(s)?;
887                            }
888                            self.pop_scope();
889                        }
890                        crate::ast::stmt::SelectBranch::Timeout { body, .. } => {
891                            for s in *body {
892                                self.infer_stmt(s)?;
893                            }
894                        }
895                    }
896                }
897                Ok(())
898            }
899
900            _ => Ok(()),
901        }
902    }
903
904    /// Process a single Inspect match arm, binding variant field types.
905    fn infer_inspect_arm(
906        &mut self,
907        arm: &crate::ast::stmt::MatchArm,
908    ) -> Result<(), TypeError> {
909        if let Some(variant_sym) = arm.variant {
910            if let Some((_, variant_def)) = self.registry.find_variant(variant_sym) {
911                // Clone what we need to avoid borrow issues
912                let fields: Vec<_> = variant_def
913                    .fields
914                    .iter()
915                    .map(|f| (f.name, f.ty.clone()))
916                    .collect();
917
918                for (field_sym, binding_sym) in &arm.bindings {
919                    let ty = fields
920                        .iter()
921                        .find(|(name, _)| *name == *field_sym)
922                        .map(|(_, ty)| {
923                            InferType::from_field_type(ty, self.interner, &HashMap::new())
924                        })
925                        .unwrap_or(InferType::Unknown);
926                    self.bind_var(*binding_sym, ty);
927                }
928            } else {
929                // Unknown variant → bind all as Unknown
930                for (_, binding_sym) in &arm.bindings {
931                    self.bind_var(*binding_sym, InferType::Unknown);
932                }
933            }
934        } else {
935            // Otherwise arm: wildcard bindings
936            for (_, binding_sym) in &arm.bindings {
937                self.bind_var(*binding_sym, InferType::Unknown);
938            }
939        }
940
941        for s in arm.body {
942            self.infer_stmt(s)?;
943        }
944        Ok(())
945    }
946}
947
948// ============================================================================
949// Entry point
950// ============================================================================
951
952/// Check a LOGOS program and return a typed `TypeEnv` for codegen.
953///
954/// Replaces `TypeEnv::infer_program`. Returns `Err(TypeError)` only on
955/// One typechecker finding with the top-level statement it belongs to.
956///
957/// `stmt_index` maps 1:1 onto `Parser::stmt_spans()`, giving IDE diagnostics
958/// a real source span. `None` marks whole-program findings (dimension
959/// coherence) that no single statement owns.
960#[derive(Debug)]
961pub struct IndexedTypeError {
962    pub stmt_index: Option<usize>,
963    pub error: TypeError,
964}
965
966/// Collect EVERY failing top-level statement instead of bailing at the first.
967///
968/// The environment after a failed statement is best-effort: inference simply
969/// continues with whatever bindings succeeded, so one bad `Let` does not
970/// cascade into errors on unrelated later statements. The strict fail-fast
971/// contract lives in [`check_program`]; compile paths keep using that.
972pub fn check_program_collect(
973    stmts: &[Stmt],
974    interner: &Interner,
975    registry: &TypeRegistry,
976) -> (TypeEnv, Vec<IndexedTypeError>) {
977    let mut errors = Vec::new();
978
979    if let Err(e) =
980        crate::analysis::dimension_check::DimensionChecker::new(interner).check_program(stmts)
981    {
982        errors.push(IndexedTypeError {
983            stmt_index: None,
984            error: TypeError::DimensionMismatch { message: e.message },
985        });
986    }
987
988    let mut env = CheckEnv::new(registry, interner);
989    env.preregister_functions(stmts);
990
991    for (index, stmt) in stmts.iter().enumerate() {
992        if let Err(error) = env.infer_stmt(stmt) {
993            errors.push(IndexedTypeError {
994                stmt_index: Some(index),
995                error,
996            });
997        }
998    }
999
1000    (env.into_type_env(), errors)
1001}
1002
1003/// genuine type contradictions (e.g., `Let x: Int be "hello"`).
1004/// Ambiguous types fall back to `LogosType::Unknown` silently.
1005pub fn check_program(
1006    stmts: &[Stmt],
1007    interner: &Interner,
1008    registry: &TypeRegistry,
1009) -> Result<TypeEnv, TypeError> {
1010    // Dimension coherence is a type property: reject `2 meters + 1 gram` here, statically, before
1011    // any code is generated (a length and a mass have no common dimension).
1012    crate::analysis::dimension_check::DimensionChecker::new(interner)
1013        .check_program(stmts)
1014        .map_err(|e| TypeError::DimensionMismatch { message: e.message })?;
1015
1016    let mut env = CheckEnv::new(registry, interner);
1017
1018    // Pre-pass: register top-level function signatures for forward references
1019    env.preregister_functions(stmts);
1020
1021    // Main pass: check all top-level statements
1022    for stmt in stmts {
1023        env.infer_stmt(stmt)?;
1024    }
1025
1026    Ok(env.into_type_env())
1027}
1028
1029// ============================================================================
1030// Tests
1031// ============================================================================
1032
1033#[cfg(test)]
1034mod tests {
1035    use super::*;
1036    use crate::ast::stmt::{Expr, Literal, Stmt, TypeExpr};
1037    use crate::intern::Interner;
1038
1039    // =========================================================================
1040    // Helpers
1041    // =========================================================================
1042
1043    fn mk_interner() -> Interner {
1044        Interner::new()
1045    }
1046
1047    fn run(stmts: &[Stmt], interner: &Interner) -> TypeEnv {
1048        check_program(stmts, interner, &TypeRegistry::new()).expect("check_program failed")
1049    }
1050
1051    // =========================================================================
1052    // Let literal inference
1053    // =========================================================================
1054
1055    #[test]
1056    fn let_literal_int() {
1057        let mut interner = mk_interner();
1058        let x = interner.intern("x");
1059        let val = Expr::Literal(Literal::Number(42));
1060        let stmts = [Stmt::Let { var: x, ty: None, value: &val, mutable: false }];
1061        let env = run(&stmts, &interner);
1062        assert_eq!(env.lookup(x), &LogosType::Int);
1063    }
1064
1065    #[test]
1066    fn let_literal_float() {
1067        let mut interner = mk_interner();
1068        let x = interner.intern("x");
1069        let val = Expr::Literal(Literal::Float(3.14));
1070        let stmts = [Stmt::Let { var: x, ty: None, value: &val, mutable: false }];
1071        let env = run(&stmts, &interner);
1072        assert_eq!(env.lookup(x), &LogosType::Float);
1073    }
1074
1075    #[test]
1076    fn let_literal_string() {
1077        let mut interner = mk_interner();
1078        let s = interner.intern("s");
1079        let hello = interner.intern("hello");
1080        let val = Expr::Literal(Literal::Text(hello));
1081        let stmts = [Stmt::Let { var: s, ty: None, value: &val, mutable: false }];
1082        let env = run(&stmts, &interner);
1083        assert_eq!(env.lookup(s), &LogosType::String);
1084    }
1085
1086    // =========================================================================
1087    // Let with type annotation
1088    // =========================================================================
1089
1090    #[test]
1091    fn let_with_annotation_uses_annotation() {
1092        let mut interner = mk_interner();
1093        let x = interner.intern("x");
1094        let float_sym = interner.intern("Real");
1095        let val = Expr::Literal(Literal::Number(5)); // Int value
1096        let ty_ann = TypeExpr::Primitive(float_sym);
1097        let stmts = [Stmt::Let { var: x, ty: Some(&ty_ann), value: &val, mutable: false }];
1098        let env = run(&stmts, &interner);
1099        // Annotation wins: Int unifies with Float (numeric)
1100        assert_eq!(env.lookup(x), &LogosType::Float);
1101    }
1102
1103    #[test]
1104    fn let_type_mismatch_fails() {
1105        let mut interner = mk_interner();
1106        let x = interner.intern("x");
1107        let int_sym = interner.intern("Int");
1108        let val = Expr::Literal(Literal::Text(Symbol::EMPTY));
1109        let ty_ann = TypeExpr::Primitive(int_sym);
1110        let stmts = [Stmt::Let { var: x, ty: Some(&ty_ann), value: &val, mutable: false }];
1111        let result = check_program(&stmts, &interner, &TypeRegistry::new());
1112        assert!(result.is_err(), "Int and Text should not unify");
1113    }
1114
1115    // =========================================================================
1116    // Empty list → Seq(Unknown)
1117    // =========================================================================
1118
1119    #[test]
1120    fn empty_list_is_seq_unknown() {
1121        let mut interner = mk_interner();
1122        let xs = interner.intern("xs");
1123        let val = Expr::List(vec![]);
1124        let stmts = [Stmt::Let { var: xs, ty: None, value: &val, mutable: false }];
1125        let env = run(&stmts, &interner);
1126        // Should be Seq of something (Unknown because element type is unsolved)
1127        assert!(matches!(env.lookup(xs), LogosType::Seq(_)));
1128    }
1129
1130    #[test]
1131    fn non_empty_list_infers_element_type() {
1132        let mut interner = mk_interner();
1133        let xs = interner.intern("xs");
1134        let one = Expr::Literal(Literal::Number(1));
1135        let two = Expr::Literal(Literal::Number(2));
1136        let val = Expr::List(vec![&one, &two]);
1137        let stmts = [Stmt::Let { var: xs, ty: None, value: &val, mutable: false }];
1138        let env = run(&stmts, &interner);
1139        assert_eq!(env.lookup(xs), &LogosType::Seq(Box::new(LogosType::Int)));
1140    }
1141
1142    // =========================================================================
1143    // OptionNone → Option(Unknown)
1144    // =========================================================================
1145
1146    #[test]
1147    fn option_none_is_option_unknown() {
1148        let mut interner = mk_interner();
1149        let x = interner.intern("x");
1150        let val = Expr::OptionNone;
1151        let stmts = [Stmt::Let { var: x, ty: None, value: &val, mutable: false }];
1152        let env = run(&stmts, &interner);
1153        assert!(matches!(env.lookup(x), LogosType::Option(_)));
1154    }
1155
1156    #[test]
1157    fn option_some_infers_inner_type() {
1158        let mut interner = mk_interner();
1159        let x = interner.intern("x");
1160        let inner = Expr::Literal(Literal::Number(42));
1161        let val = Expr::OptionSome { value: &inner };
1162        let stmts = [Stmt::Let { var: x, ty: None, value: &val, mutable: false }];
1163        let env = run(&stmts, &interner);
1164        assert_eq!(env.lookup(x), &LogosType::Option(Box::new(LogosType::Int)));
1165    }
1166
1167    // =========================================================================
1168    // Function def and call
1169    // =========================================================================
1170
1171    #[test]
1172    fn function_def_registers_signature() {
1173        let mut interner = mk_interner();
1174        let f = interner.intern("double");
1175        let x_param = interner.intern("x");
1176        let int_sym = interner.intern("Int");
1177        let int_ty = TypeExpr::Primitive(int_sym);
1178        let ret_ty = TypeExpr::Primitive(int_sym);
1179        let lit = Expr::Literal(Literal::Number(0));
1180        let ret_stmt = Stmt::Return { value: Some(&lit) };
1181        let body = [ret_stmt];
1182        let stmts = [Stmt::FunctionDef {
1183            name: f,
1184            generics: vec![],
1185            params: vec![(x_param, &int_ty)],
1186            body: &body,
1187            return_type: Some(&ret_ty),
1188            is_native: false,
1189            native_path: None,
1190            is_exported: false,
1191            export_target: None,
1192            opt_flags: Default::default(),
1193        }];
1194        let env = run(&stmts, &interner);
1195        let sig = env.lookup_fn(f).expect("function should be registered");
1196        assert_eq!(sig.return_type, LogosType::Int);
1197        assert_eq!(sig.params.len(), 1);
1198        assert_eq!(sig.params[0].1, LogosType::Int);
1199    }
1200
1201    #[test]
1202    fn function_call_returns_registered_type() {
1203        let mut interner = mk_interner();
1204        let f = interner.intern("compute");
1205        let float_sym = interner.intern("Real");
1206        let float_ty = TypeExpr::Primitive(float_sym);
1207        let lit = Expr::Literal(Literal::Float(1.0));
1208        let ret_stmt = Stmt::Return { value: Some(&lit) };
1209        let body = [ret_stmt];
1210        let fn_def = Stmt::FunctionDef {
1211            name: f,
1212            generics: vec![],
1213            params: vec![],
1214            body: &body,
1215            return_type: Some(&float_ty),
1216            is_native: false,
1217            native_path: None,
1218            is_exported: false,
1219            export_target: None,
1220            opt_flags: Default::default(),
1221        };
1222        let result_var = interner.intern("result");
1223        let call = Expr::Call { function: f, args: vec![] };
1224        let let_stmt = Stmt::Let { var: result_var, ty: None, value: &call, mutable: false };
1225        let stmts = [fn_def, let_stmt];
1226        let env = run(&stmts, &interner);
1227        assert_eq!(env.lookup(result_var), &LogosType::Float);
1228    }
1229
1230    // =========================================================================
1231    // ReadFrom is String
1232    // =========================================================================
1233
1234    #[test]
1235    fn readfrom_is_string() {
1236        let mut interner = mk_interner();
1237        let v = interner.intern("input");
1238        let stmts = [Stmt::ReadFrom {
1239            var: v,
1240            source: crate::ast::stmt::ReadSource::Console,
1241        }];
1242        let env = run(&stmts, &interner);
1243        assert_eq!(env.lookup(v), &LogosType::String);
1244    }
1245
1246    // =========================================================================
1247    // Repeat loop variable gets element type
1248    // =========================================================================
1249
1250    #[test]
1251    fn repeat_loop_var_gets_element_type() {
1252        let mut interner = mk_interner();
1253        let items = interner.intern("items");
1254        let elem = interner.intern("elem");
1255        let one = Expr::Literal(Literal::Number(1));
1256        let list = Expr::List(vec![&one]);
1257        let let_items = Stmt::Let { var: items, ty: None, value: &list, mutable: false };
1258        let items_ref = Expr::Identifier(items);
1259        let repeat = Stmt::Repeat {
1260            pattern: Pattern::Identifier(elem),
1261            iterable: &items_ref,
1262            body: &[],
1263        };
1264        let stmts = [let_items, repeat];
1265        let env = run(&stmts, &interner);
1266        assert_eq!(env.lookup(elem), &LogosType::Int);
1267    }
1268
1269    // =========================================================================
1270    // Field access resolves to struct field type (uses registry)
1271    // =========================================================================
1272
1273    #[test]
1274    fn field_access_resolves_with_registry() {
1275        use crate::analysis::{FieldDef, FieldType, TypeDef};
1276
1277        let mut interner = mk_interner();
1278        let point_sym = interner.intern("Point");
1279        let x_field_sym = interner.intern("x");
1280        let int_sym = interner.intern("Int");
1281        let p_var = interner.intern("p");
1282        let result_var = interner.intern("px");
1283
1284        // Build a registry with a struct Point { x: Int }
1285        let mut registry = TypeRegistry::new();
1286        registry.register(
1287            point_sym,
1288            TypeDef::Struct {
1289                fields: vec![FieldDef {
1290                    name: x_field_sym,
1291                    ty: FieldType::Primitive(int_sym),
1292                    is_public: true,
1293                }],
1294                generics: vec![],
1295                is_portable: false,
1296                is_shared: false,
1297            },
1298        );
1299
1300        // Let p be a new Point.
1301        let new_point = Expr::New { type_name: point_sym, type_args: vec![], init_fields: vec![] };
1302        let let_p = Stmt::Let { var: p_var, ty: None, value: &new_point, mutable: false };
1303
1304        // Let px be p's x.
1305        let p_ref = Expr::Identifier(p_var);
1306        let field_access = Expr::FieldAccess { object: &p_ref, field: x_field_sym };
1307        let let_px = Stmt::Let { var: result_var, ty: None, value: &field_access, mutable: false };
1308
1309        let stmts = [let_p, let_px];
1310        let env = check_program(&stmts, &interner, &registry).expect("check_program failed");
1311        assert_eq!(env.lookup(result_var), &LogosType::Int);
1312    }
1313
1314    // =========================================================================
1315    // Forward reference: calling a function defined later
1316    // =========================================================================
1317
1318    #[test]
1319    fn forward_reference_function_call() {
1320        let mut interner = mk_interner();
1321        let f = interner.intern("later_fn");
1322        let result_var = interner.intern("r");
1323        let bool_sym = interner.intern("Bool");
1324        let bool_ty = TypeExpr::Primitive(bool_sym);
1325
1326        // Let r be later_fn().  (before the function def)
1327        let call = Expr::Call { function: f, args: vec![] };
1328        let let_r = Stmt::Let { var: result_var, ty: None, value: &call, mutable: false };
1329
1330        // ## Function later_fn -> Bool:
1331        let lit = Expr::Literal(Literal::Boolean(true));
1332        let ret_stmt = Stmt::Return { value: Some(&lit) };
1333        let body = [ret_stmt];
1334        let fn_def = Stmt::FunctionDef {
1335            name: f,
1336            generics: vec![],
1337            params: vec![],
1338            body: &body,
1339            return_type: Some(&bool_ty),
1340            is_native: false,
1341            native_path: None,
1342            is_exported: false,
1343            export_target: None,
1344            opt_flags: Default::default(),
1345        };
1346
1347        // Note: let_r comes BEFORE fn_def in the slice
1348        let stmts = [let_r, fn_def];
1349        let env = run(&stmts, &interner);
1350        assert_eq!(env.lookup(result_var), &LogosType::Bool);
1351    }
1352
1353    // =========================================================================
1354    // Type mismatch on return
1355    // =========================================================================
1356
1357    #[test]
1358    fn return_type_mismatch_fails() {
1359        let mut interner = mk_interner();
1360        let f = interner.intern("f");
1361        let int_sym = interner.intern("Int");
1362        let int_ty = TypeExpr::Primitive(int_sym);
1363        // Function annotated as -> Int but returns Text
1364        let lit = Expr::Literal(Literal::Text(Symbol::EMPTY));
1365        let ret_stmt = Stmt::Return { value: Some(&lit) };
1366        let body = [ret_stmt];
1367        let stmts = [Stmt::FunctionDef {
1368            name: f,
1369            generics: vec![],
1370            params: vec![],
1371            body: &body,
1372            return_type: Some(&int_ty),
1373            is_native: false,
1374            native_path: None,
1375            is_exported: false,
1376            export_target: None,
1377            opt_flags: Default::default(),
1378        }];
1379        let result = check_program(&stmts, &interner, &TypeRegistry::new());
1380        assert!(result.is_err(), "returning Text from Int function should fail");
1381    }
1382
1383    // =========================================================================
1384    // New user-defined type → UserDefined
1385    // =========================================================================
1386
1387    #[test]
1388    fn new_user_defined_is_user_defined_type() {
1389        let mut interner = mk_interner();
1390        let point = interner.intern("Point");
1391        let p = interner.intern("p");
1392        let new_point = Expr::New { type_name: point, type_args: vec![], init_fields: vec![] };
1393        let stmts = [Stmt::Let { var: p, ty: None, value: &new_point, mutable: false }];
1394        let env = run(&stmts, &interner);
1395        assert_eq!(env.lookup(p), &LogosType::UserDefined(point));
1396    }
1397
1398    // =========================================================================
1399    // Legacy API preserved: to_legacy_variable_types / to_legacy_string_vars
1400    // =========================================================================
1401
1402    #[test]
1403    fn string_vars_in_legacy_api() {
1404        let mut interner = mk_interner();
1405        let s = interner.intern("name");
1406        let hello = interner.intern("hello");
1407        let val = Expr::Literal(Literal::Text(hello));
1408        let stmts = [Stmt::Let { var: s, ty: None, value: &val, mutable: false }];
1409        let env = run(&stmts, &interner);
1410        assert!(env.to_legacy_string_vars().contains(&s));
1411    }
1412
1413    #[test]
1414    fn unknown_vars_filtered_in_legacy_api() {
1415        let mut interner = mk_interner();
1416        let x = interner.intern("x");
1417        let val = Expr::OptionNone; // Unknown inner type
1418        let stmts = [Stmt::Let { var: x, ty: None, value: &val, mutable: false }];
1419        let env = run(&stmts, &interner);
1420        // Option(Unknown) → not in string_vars, not filtered as error
1421        let legacy = env.to_legacy_variable_types();
1422        // Option(Unknown) maps to "Option<_>", which is concrete enough
1423        assert!(!legacy.is_empty() || legacy.is_empty()); // just don't panic
1424    }
1425
1426    // =========================================================================
1427    // Generic (polymorphic) functions — Phase 3
1428    // =========================================================================
1429
1430    #[test]
1431    fn generic_identity_infers_int_return() {
1432        // ## To identity of [T] (x: T) -> T:
1433        //     Return x.
1434        // Let r be identity(42).  → r is Int
1435        let mut interner = mk_interner();
1436        let f = interner.intern("identity");
1437        let x_param = interner.intern("x");
1438        let t_sym = interner.intern("T");
1439        let t_ty = TypeExpr::Primitive(t_sym);
1440        let x_ref = Expr::Identifier(x_param);
1441        let ret_stmt = Stmt::Return { value: Some(&x_ref) };
1442        let body = [ret_stmt];
1443        let fn_def = Stmt::FunctionDef {
1444            name: f,
1445            generics: vec![t_sym],
1446            params: vec![(x_param, &t_ty)],
1447            body: &body,
1448            return_type: Some(&t_ty),
1449            is_native: false,
1450            native_path: None,
1451            is_exported: false,
1452            export_target: None,
1453            opt_flags: Default::default(),
1454        };
1455        let r = interner.intern("r");
1456        let lit = Expr::Literal(Literal::Number(42));
1457        let call = Expr::Call { function: f, args: vec![&lit] };
1458        let let_r = Stmt::Let { var: r, ty: None, value: &call, mutable: false };
1459        let stmts = [fn_def, let_r];
1460        let env = run(&stmts, &interner);
1461        assert_eq!(env.lookup(r), &LogosType::Int,
1462            "identity(42) should return Int, got {:?}", env.lookup(r));
1463    }
1464
1465    #[test]
1466    fn generic_identity_infers_bool_return() {
1467        // Same identity function, called with Bool → returns Bool.
1468        let mut interner = mk_interner();
1469        let f = interner.intern("identity");
1470        let x_param = interner.intern("x");
1471        let t_sym = interner.intern("T");
1472        let t_ty = TypeExpr::Primitive(t_sym);
1473        let x_ref = Expr::Identifier(x_param);
1474        let ret_stmt = Stmt::Return { value: Some(&x_ref) };
1475        let body = [ret_stmt];
1476        let fn_def = Stmt::FunctionDef {
1477            name: f,
1478            generics: vec![t_sym],
1479            params: vec![(x_param, &t_ty)],
1480            body: &body,
1481            return_type: Some(&t_ty),
1482            is_native: false,
1483            native_path: None,
1484            is_exported: false,
1485            export_target: None,
1486            opt_flags: Default::default(),
1487        };
1488        let r = interner.intern("r");
1489        let lit = Expr::Literal(Literal::Boolean(true));
1490        let call = Expr::Call { function: f, args: vec![&lit] };
1491        let let_r = Stmt::Let { var: r, ty: None, value: &call, mutable: false };
1492        let stmts = [fn_def, let_r];
1493        let env = run(&stmts, &interner);
1494        assert_eq!(env.lookup(r), &LogosType::Bool,
1495            "identity(true) should return Bool, got {:?}", env.lookup(r));
1496    }
1497
1498    #[test]
1499    fn generic_two_type_params_first() {
1500        // ## To first of [A] and [B] (a: A, b: B) -> A:
1501        //     Return a.
1502        // Let r be first(42, true).  → r is Int (first type param)
1503        let mut interner = mk_interner();
1504        let f = interner.intern("first");
1505        let a_param = interner.intern("a");
1506        let b_param = interner.intern("b");
1507        let a_sym = interner.intern("A");
1508        let b_sym = interner.intern("B");
1509        let a_ty = TypeExpr::Primitive(a_sym);
1510        let b_ty = TypeExpr::Primitive(b_sym);
1511        let a_ref = Expr::Identifier(a_param);
1512        let ret_stmt = Stmt::Return { value: Some(&a_ref) };
1513        let body = [ret_stmt];
1514        let fn_def = Stmt::FunctionDef {
1515            name: f,
1516            generics: vec![a_sym, b_sym],
1517            params: vec![(a_param, &a_ty), (b_param, &b_ty)],
1518            body: &body,
1519            return_type: Some(&a_ty),
1520            is_native: false,
1521            native_path: None,
1522            is_exported: false,
1523            export_target: None,
1524            opt_flags: Default::default(),
1525        };
1526        let r = interner.intern("r");
1527        let lit_int = Expr::Literal(Literal::Number(42));
1528        let lit_bool = Expr::Literal(Literal::Boolean(true));
1529        let call = Expr::Call { function: f, args: vec![&lit_int, &lit_bool] };
1530        let let_r = Stmt::Let { var: r, ty: None, value: &call, mutable: false };
1531        let stmts = [fn_def, let_r];
1532        let env = run(&stmts, &interner);
1533        assert_eq!(env.lookup(r), &LogosType::Int,
1534            "first(42, true) should return Int (first param type), got {:?}", env.lookup(r));
1535    }
1536
1537    #[test]
1538    fn generic_calls_are_independent() {
1539        // Each call to a generic function gets its own fresh type variables.
1540        // identity(42) → Int, identity(true) → Bool, independent results.
1541        let mut interner = mk_interner();
1542        let f = interner.intern("identity");
1543        let x_param = interner.intern("x");
1544        let t_sym = interner.intern("T");
1545        let t_ty = TypeExpr::Primitive(t_sym);
1546        let x_ref = Expr::Identifier(x_param);
1547        let ret_stmt = Stmt::Return { value: Some(&x_ref) };
1548        let body = [ret_stmt];
1549        let fn_def = Stmt::FunctionDef {
1550            name: f,
1551            generics: vec![t_sym],
1552            params: vec![(x_param, &t_ty)],
1553            body: &body,
1554            return_type: Some(&t_ty),
1555            is_native: false,
1556            native_path: None,
1557            is_exported: false,
1558            export_target: None,
1559            opt_flags: Default::default(),
1560        };
1561        let r1 = interner.intern("r1");
1562        let r2 = interner.intern("r2");
1563        let lit_int = Expr::Literal(Literal::Number(42));
1564        let lit_bool = Expr::Literal(Literal::Boolean(true));
1565        let call1 = Expr::Call { function: f, args: vec![&lit_int] };
1566        let call2 = Expr::Call { function: f, args: vec![&lit_bool] };
1567        let let_r1 = Stmt::Let { var: r1, ty: None, value: &call1, mutable: false };
1568        let let_r2 = Stmt::Let { var: r2, ty: None, value: &call2, mutable: false };
1569        let stmts = [fn_def, let_r1, let_r2];
1570        let env = run(&stmts, &interner);
1571        assert_eq!(env.lookup(r1), &LogosType::Int,
1572            "identity(42) should be Int, got {:?}", env.lookup(r1));
1573        assert_eq!(env.lookup(r2), &LogosType::Bool,
1574            "identity(true) should be Bool, got {:?}", env.lookup(r2));
1575    }
1576
1577    #[test]
1578    fn monomorphic_functions_unaffected_by_generics() {
1579        // Non-generic functions still work correctly with the updated machinery.
1580        let mut interner = mk_interner();
1581        let f = interner.intern("double");
1582        let x_param = interner.intern("x");
1583        let int_sym = interner.intern("Int");
1584        let int_ty = TypeExpr::Primitive(int_sym);
1585        let x_ref = Expr::Identifier(x_param);
1586        let lit2 = Expr::Literal(Literal::Number(2));
1587        let mul = Expr::BinaryOp {
1588            op: BinaryOpKind::Multiply,
1589            left: &x_ref,
1590            right: &lit2,
1591        };
1592        let ret_stmt = Stmt::Return { value: Some(&mul) };
1593        let body = [ret_stmt];
1594        let fn_def = Stmt::FunctionDef {
1595            name: f,
1596            generics: vec![],
1597            params: vec![(x_param, &int_ty)],
1598            body: &body,
1599            return_type: Some(&int_ty),
1600            is_native: false,
1601            native_path: None,
1602            is_exported: false,
1603            export_target: None,
1604            opt_flags: Default::default(),
1605        };
1606        let r = interner.intern("r");
1607        let lit5 = Expr::Literal(Literal::Number(5));
1608        let call = Expr::Call { function: f, args: vec![&lit5] };
1609        let let_r = Stmt::Let { var: r, ty: None, value: &call, mutable: false };
1610        let stmts = [fn_def, let_r];
1611        let env = run(&stmts, &interner);
1612        assert_eq!(env.lookup(r), &LogosType::Int,
1613            "double(5) should return Int, got {:?}", env.lookup(r));
1614    }
1615
1616    #[test]
1617    fn generic_forward_reference_resolves() {
1618        // Let r be identity(42).
1619        // ## To identity of [T] (x: T) -> T:  ← defined AFTER the call
1620        //     Return x.
1621        // The pre-pass must register generics before the main pass sees the call.
1622        let mut interner = mk_interner();
1623        let f = interner.intern("identity");
1624        let x_param = interner.intern("x");
1625        let t_sym = interner.intern("T");
1626        let t_ty = TypeExpr::Primitive(t_sym);
1627        let x_ref = Expr::Identifier(x_param);
1628        let ret_stmt = Stmt::Return { value: Some(&x_ref) };
1629        let body = [ret_stmt];
1630        let fn_def = Stmt::FunctionDef {
1631            name: f,
1632            generics: vec![t_sym],
1633            params: vec![(x_param, &t_ty)],
1634            body: &body,
1635            return_type: Some(&t_ty),
1636            is_native: false,
1637            native_path: None,
1638            is_exported: false,
1639            export_target: None,
1640            opt_flags: Default::default(),
1641        };
1642        let r = interner.intern("r");
1643        let lit = Expr::Literal(Literal::Number(99));
1644        let call = Expr::Call { function: f, args: vec![&lit] };
1645        let let_r = Stmt::Let { var: r, ty: None, value: &call, mutable: false };
1646        // Call appears BEFORE the function definition
1647        let stmts = [let_r, fn_def];
1648        let env = run(&stmts, &interner);
1649        assert_eq!(env.lookup(r), &LogosType::Int,
1650            "forward-ref identity(99) should be Int, got {:?}", env.lookup(r));
1651    }
1652
1653    // ---- Bug Report #1 regression pins ----
1654
1655    /// BUG-025: a `Seq of Real` annotation must accept integer literals
1656    /// `[1, 2, 3]` (the numeric-literal coercion scalar literals already get),
1657    /// rather than synthesizing `Seq(Int)` from `items[0]` and failing to unify
1658    /// with `Seq(Float)`.
1659    #[test]
1660    fn seq_of_real_accepts_int_literals() {
1661        let mut interner = Interner::new();
1662        let xs = interner.intern("xs");
1663        let real_sym = interner.intern("Real");
1664        let seq_sym = interner.intern("Seq");
1665
1666        let one = Expr::Literal(Literal::Number(1));
1667        let two = Expr::Literal(Literal::Number(2));
1668        let three = Expr::Literal(Literal::Number(3));
1669        let val = Expr::List(vec![&one, &two, &three]);
1670
1671        let real_ty = TypeExpr::Primitive(real_sym);
1672        let params = [real_ty];
1673        let seq_real = TypeExpr::Generic { base: seq_sym, params: &params };
1674
1675        let stmts = [Stmt::Let { var: xs, ty: Some(&seq_real), value: &val, mutable: false }];
1676
1677        let env = check_program(&stmts, &interner, &TypeRegistry::new())
1678            .expect("Seq of Real should accept integer literals [1, 2, 3]");
1679
1680        assert_eq!(
1681            env.lookup(xs),
1682            &LogosType::Seq(Box::new(LogosType::Float)),
1683            "xs should be inferred as Seq<Float> under the `Seq of Real` annotation"
1684        );
1685    }
1686
1687    /// BUG-026: a generic function with an INFERRED (unannotated) return type
1688    /// must generalize its return variable, so two calls at different types are
1689    /// independent. Mirrors `generic_calls_are_independent` but `return_type: None`.
1690    #[test]
1691    fn generic_inferred_return_calls_are_independent() {
1692        let mut interner = mk_interner();
1693        let f = interner.intern("wrap");
1694        let x_param = interner.intern("x");
1695        let t_sym = interner.intern("T");
1696        let t_ty = TypeExpr::Primitive(t_sym);
1697        let x_ref = Expr::Identifier(x_param);
1698        let ret_stmt = Stmt::Return { value: Some(&x_ref) };
1699        let body = [ret_stmt];
1700        let fn_def = Stmt::FunctionDef {
1701            name: f,
1702            generics: vec![t_sym],
1703            params: vec![(x_param, &t_ty)],
1704            body: &body,
1705            return_type: None, // inferred return -> fresh var not in scheme.vars: the bug
1706            is_native: false,
1707            native_path: None,
1708            is_exported: false,
1709            export_target: None,
1710            opt_flags: Default::default(),
1711        };
1712        let r1 = interner.intern("r1");
1713        let r2 = interner.intern("r2");
1714        let lit_int = Expr::Literal(Literal::Number(42));
1715        let lit_bool = Expr::Literal(Literal::Boolean(true));
1716        let call1 = Expr::Call { function: f, args: vec![&lit_int] };
1717        let call2 = Expr::Call { function: f, args: vec![&lit_bool] };
1718        let let_r1 = Stmt::Let { var: r1, ty: None, value: &call1, mutable: false };
1719        let let_r2 = Stmt::Let { var: r2, ty: None, value: &call2, mutable: false };
1720        let stmts = [fn_def, let_r1, let_r2];
1721        let env = run(&stmts, &interner);
1722        assert_eq!(env.lookup(r1), &LogosType::Int,
1723            "wrap(42) should be Int, got {:?}", env.lookup(r1));
1724        assert_eq!(env.lookup(r2), &LogosType::Bool,
1725            "wrap(true) should be Bool, got {:?}", env.lookup(r2));
1726    }
1727
1728    /// BUG-007: iterating a `Map` with a single identifier loop variable yields
1729    /// `(key, value)` tuples at runtime, so the loop var must NOT be typed as
1730    /// the bare key type `K`.
1731    #[test]
1732    fn repeat_over_map_single_ident_loop_var_is_not_bare_key() {
1733        let mut interner = mk_interner();
1734        let m = interner.intern("m");
1735        let entry = interner.intern("entry");
1736        let map_sym = interner.intern("Map");
1737        let text_sym = interner.intern("Text");
1738        let int_sym = interner.intern("Int");
1739
1740        let new_map = Expr::New {
1741            type_name: map_sym,
1742            type_args: vec![TypeExpr::Primitive(text_sym), TypeExpr::Primitive(int_sym)],
1743            init_fields: vec![],
1744        };
1745        let let_m = Stmt::Let { var: m, ty: None, value: &new_map, mutable: false };
1746
1747        let m_ref = Expr::Identifier(m);
1748        let repeat = Stmt::Repeat {
1749            pattern: Pattern::Identifier(entry),
1750            iterable: &m_ref,
1751            body: &[],
1752        };
1753
1754        let stmts = [let_m, repeat];
1755        let env = run(&stmts, &interner);
1756
1757        assert_ne!(
1758            env.lookup(entry),
1759            &LogosType::String,
1760            "iterating a Map with a single identifier yields (key,value) tuples at runtime; \
1761             the loop var must not be typed as the bare key K"
1762        );
1763    }
1764}