Skip to main content

logicaffeine_compile/analysis/
types.rs

1//! Type inference pass for the LOGOS compilation pipeline.
2//!
3//! Provides a structured type representation (`LogosType`) and a type environment
4//! (`TypeEnv`) that replaces the ad-hoc string-based type tracking previously
5//! scattered across codegen.rs.
6//!
7//! # Pipeline Position
8//!
9//! ```text
10//! Parse → TypeInfer → Optimize → Codegen
11//!              ^ THIS MODULE
12//! ```
13//!
14//! The `TypeEnv` is computed once from the AST and passed immutably to codegen,
15//! replacing `variable_types: HashMap<Symbol, String>` and `string_vars: HashSet<Symbol>`.
16
17use std::collections::HashMap;
18
19use crate::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt, TypeExpr};
20use crate::intern::{Interner, Symbol};
21use crate::analysis::TypeRegistry;
22
23/// Structured type representation for LOGOS values.
24///
25/// Replaces string-based type tracking (e.g., `"Vec<i64>"`, `"String"`)
26/// with a proper algebraic data type that supports precise queries.
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
28pub enum LogosType {
29    Int,
30    Float,
31    Bool,
32    Char,
33    Byte,
34    String,
35    Unit,
36    Seq(Box<LogosType>),
37    Map(Box<LogosType>, Box<LogosType>),
38    Set(Box<LogosType>),
39    Option(Box<LogosType>),
40    Duration,
41    Date,
42    Moment,
43    Time,
44    Span,
45    Nat,
46    /// Exact rational (`logicaffeine_base::Rational`). A `Rational`-typed binding keeps
47    /// division exact (`7 / 2 → 7/2`) where a bare `Int` floors. Lowers to `LogosRational`.
48    Rational,
49    /// Exact base-10 fixed-point (money). `+ − ×` stay exact `Decimal` (scale preserved);
50    /// `÷` widens to `Rational`. Built via `decimal("19.99")`. Lowers to `LogosDecimal`.
51    Decimal,
52    /// Exact complex number `re + im·i`. `+ − × ÷` stay `Complex` (closed field). Built via
53    /// `complex(re, im)`. NOT ordered. Lowers to `LogosComplex`.
54    Complex,
55    /// An element of the ring ℤ/nℤ. `+ − × ÷` stay `Modular` (same modulus required, no
56    /// auto-lift). Built via `modular(value, modulus)`. NOT ordered. Lowers to `LogosModular`.
57    Modular,
58    /// A dimensioned physical quantity (`quantity(2, "inch")`). `+ −` require the same dimension,
59    /// `× ÷` combine dimensions, and it may be scaled by a number; built via `quantity`/`convert`.
60    /// The magnitude rides the exact rational tower. Lowers to `LogosQuantity`. Dimension safety is
61    /// enforced at runtime (a clean error), like `Modular`'s ring check.
62    Quantity,
63    /// An exact monetary amount in a currency (`money(19.99, "USD")` / `19.99 USD`). `+ −` require
64    /// the same currency, `× ÷` scale by a number; the amount rides the Decimal tower. Lowers to
65    /// `LogosMoney`. Currency safety is enforced at runtime (a clean error), like `Quantity`.
66    Money,
67    /// A 128-bit UUID (`uuid "…"`, `a random uuid`). An opaque identifier — no arithmetic; lowers to
68    /// `LogosUuid`. Comparable/orderable by its bytes.
69    Uuid,
70    UserDefined(Symbol),
71    /// First-class function type: fn(P1, P2, ...) -> R
72    Function(Vec<LogosType>, Box<LogosType>),
73    Unknown,
74}
75
76/// Function signature for tracked functions.
77#[derive(Debug, Clone)]
78pub struct FnSig {
79    pub params: Vec<(Symbol, LogosType)>,
80    pub return_type: LogosType,
81}
82
83/// Type environment built by a forward pass over the AST.
84///
85/// Computed once before codegen and shared immutably. Replaces the
86/// `variable_types: HashMap<Symbol, String>` and `string_vars: HashSet<Symbol>`
87/// that were incrementally built during codegen.
88#[derive(Debug)]
89pub struct TypeEnv {
90    variables: HashMap<Symbol, LogosType>,
91    functions: HashMap<Symbol, FnSig>,
92}
93
94impl LogosType {
95    /// Whether this type is Copy in Rust (no .clone() needed).
96    ///
97    /// This is the single source of truth for clone decisions,
98    /// replacing `is_copy_type()`, `has_copy_element_type()`, `has_copy_value_type()`.
99    pub fn is_copy(&self) -> bool {
100        matches!(
101            self,
102            LogosType::Int
103                | LogosType::Float
104                | LogosType::Bool
105                | LogosType::Char
106                | LogosType::Byte
107                | LogosType::Unit
108                | LogosType::Nat
109        )
110    }
111
112    /// Whether this type is numeric (Int or Float).
113    pub fn is_numeric(&self) -> bool {
114        matches!(self, LogosType::Int | LogosType::Float | LogosType::Nat)
115    }
116
117    /// Whether this type is a string.
118    pub fn is_string(&self) -> bool {
119        matches!(self, LogosType::String)
120    }
121
122    /// Whether this type is a float.
123    pub fn is_float(&self) -> bool {
124        matches!(self, LogosType::Float)
125    }
126
127    /// Get the element type for sequences and sets.
128    pub fn element_type(&self) -> Option<&LogosType> {
129        match self {
130            LogosType::Seq(inner) | LogosType::Set(inner) => Some(inner),
131            _ => None,
132        }
133    }
134
135    /// Get the value type for maps.
136    pub fn value_type(&self) -> Option<&LogosType> {
137        match self {
138            LogosType::Map(_, v) => Some(v),
139            _ => None,
140        }
141    }
142
143    /// Get the key type for maps.
144    pub fn key_type(&self) -> Option<&LogosType> {
145        match self {
146            LogosType::Map(k, _) => Some(k),
147            _ => None,
148        }
149    }
150
151    /// Numeric promotion: Z embeds into R.
152    /// If either operand is Float, the result is Float.
153    /// If both are Int (or Nat), the result is Int.
154    pub fn numeric_promotion(a: &LogosType, b: &LogosType) -> LogosType {
155        if matches!(a, LogosType::Quantity) || matches!(b, LogosType::Quantity) {
156            // A quantity carries through `+ − × ÷` (same-dimension for `+ −`, dimension-combining
157            // for `× ÷`, or scaling by a number) — the dimensional rule is enforced at runtime.
158            LogosType::Quantity
159        } else if matches!(a, LogosType::Money) || matches!(b, LogosType::Money) {
160            // Money carries through `+ −` (same currency) and scaling by a number. (A same-currency
161            // `Money ÷ Money` is a Rational ratio — that operator-specific narrowing is handled in
162            // the binary-op inference, not this operator-agnostic promotion.)
163            LogosType::Money
164        } else if a.is_float() || b.is_float() {
165            LogosType::Float
166        } else if matches!(a, LogosType::Rational) || matches!(b, LogosType::Rational) {
167            // A Rational operand carries through `+ − ×`: the exact type wins over Int.
168            LogosType::Rational
169        } else if matches!(a, LogosType::Modular) && matches!(b, LogosType::Modular) {
170            // Modular combines only with Modular (same ring; no auto-lift of a bare integer).
171            LogosType::Modular
172        } else if matches!(a, LogosType::Complex) || matches!(b, LogosType::Complex) {
173            // Complex is the top of the exact tower: a real embeds as `re + 0i`, so any
174            // operation with a Complex stays Complex (the field is closed under + − × ÷).
175            LogosType::Complex
176        } else if matches!(a, LogosType::Decimal) || matches!(b, LogosType::Decimal) {
177            // A Decimal carries through `+ − ×` (Decimal ∘ Int stays Decimal, scale kept).
178            LogosType::Decimal
179        } else if a.is_numeric() && b.is_numeric() {
180            LogosType::Int
181        } else {
182            LogosType::Unknown
183        }
184    }
185
186    /// Convert to the Rust type string used in codegen output.
187    ///
188    /// Replaces all ad-hoc string-based type conversions scattered
189    /// across codegen.rs. This is the single point of truth for
190    /// LogosType → Rust type string mapping.
191    pub fn to_rust_type(&self) -> std::string::String {
192        match self {
193            LogosType::Int => "i64".into(),
194            LogosType::Float => "f64".into(),
195            LogosType::Bool => "bool".into(),
196            LogosType::Char => "char".into(),
197            LogosType::Byte => "u8".into(),
198            LogosType::String => "String".into(),
199            LogosType::Unit => "()".into(),
200            LogosType::Nat => "u64".into(),
201            LogosType::Rational => "LogosRational".into(),
202            LogosType::Decimal => "LogosDecimal".into(),
203            LogosType::Complex => "LogosComplex".into(),
204            LogosType::Modular => "LogosModular".into(),
205            LogosType::Quantity => "LogosQuantity".into(),
206            LogosType::Money => "LogosMoney".into(),
207            LogosType::Uuid => "LogosUuid".into(),
208            LogosType::Duration => "std::time::Duration".into(),
209            LogosType::Date => "LogosDate".into(),
210            LogosType::Moment => "LogosMoment".into(),
211            LogosType::Time => "LogosTime".into(),
212            LogosType::Span => "LogosSpan".into(),
213            LogosType::Seq(inner) => format!("LogosSeq<{}>", inner.to_rust_type()),
214            LogosType::Map(k, v) => format!(
215                "LogosMap<{}, {}>",
216                k.to_rust_type(),
217                v.to_rust_type()
218            ),
219            LogosType::Set(inner) => {
220                format!("Set<{}>", inner.to_rust_type())
221            }
222            LogosType::Option(inner) => format!("Option<{}>", inner.to_rust_type()),
223            LogosType::Function(params, ret) => {
224                let params_str = params
225                    .iter()
226                    .map(|p| p.to_rust_type())
227                    .collect::<Vec<_>>()
228                    .join(", ");
229                format!("impl Fn({}) -> {}", params_str, ret.to_rust_type())
230            }
231            LogosType::UserDefined(_) => "_".into(),
232            LogosType::Unknown => "_".into(),
233        }
234    }
235
236    /// Build a LogosType from a TypeExpr AST node.
237    pub fn from_type_expr(ty: &TypeExpr, interner: &Interner) -> LogosType {
238        match ty {
239            // A `mutable` parameter erases to its underlying type; the marker only
240            // affects the parameter-passing convention (Mutable Value Semantics).
241            TypeExpr::Mutable { inner } => Self::from_type_expr(inner, interner),
242            TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
243                Self::from_type_name(interner.resolve(*sym))
244            }
245            TypeExpr::Generic { base, params } => {
246                let base_name = interner.resolve(*base);
247                match base_name {
248                    "Seq" | "List" | "Vec" => {
249                        let elem = params
250                            .first()
251                            .map(|p| LogosType::from_type_expr(p, interner))
252                            .unwrap_or(LogosType::Unit);
253                        LogosType::Seq(Box::new(elem))
254                    }
255                    "Map" | "HashMap" => {
256                        let key = params
257                            .first()
258                            .map(|p| LogosType::from_type_expr(p, interner))
259                            .unwrap_or(LogosType::String);
260                        let val = params
261                            .get(1)
262                            .map(|p| LogosType::from_type_expr(p, interner))
263                            .unwrap_or(LogosType::String);
264                        LogosType::Map(Box::new(key), Box::new(val))
265                    }
266                    "Set" | "HashSet" => {
267                        let elem = params
268                            .first()
269                            .map(|p| LogosType::from_type_expr(p, interner))
270                            .unwrap_or(LogosType::Unit);
271                        LogosType::Set(Box::new(elem))
272                    }
273                    "Option" | "Maybe" => {
274                        let inner = params
275                            .first()
276                            .map(|p| LogosType::from_type_expr(p, interner))
277                            .unwrap_or(LogosType::Unit);
278                        LogosType::Option(Box::new(inner))
279                    }
280                    _ => LogosType::Unknown,
281                }
282            }
283            TypeExpr::Refinement { base, .. } => LogosType::from_type_expr(base, interner),
284            TypeExpr::Persistent { inner } => LogosType::from_type_expr(inner, interner),
285            TypeExpr::Function { inputs, output } => {
286                let params = inputs
287                    .iter()
288                    .map(|i| LogosType::from_type_expr(i, interner))
289                    .collect();
290                let ret = Box::new(LogosType::from_type_expr(output, interner));
291                LogosType::Function(params, ret)
292            }
293        }
294    }
295
296    /// Parse a LOGOS type name string into a LogosType.
297    pub(crate) fn from_type_name(name: &str) -> LogosType {
298        match name {
299            "Int" => LogosType::Int,
300            "Nat" => LogosType::Nat,
301            "Rational" => LogosType::Rational,
302            "Decimal" => LogosType::Decimal,
303            "Complex" => LogosType::Complex,
304            "Modular" => LogosType::Modular,
305            "Quantity" => LogosType::Quantity,
306            "Money" => LogosType::Money,
307            "Uuid" | "UUID" => LogosType::Uuid,
308            "Real" | "Float" => LogosType::Float,
309            "Bool" | "Boolean" => LogosType::Bool,
310            "Text" | "String" => LogosType::String,
311            "Char" => LogosType::Char,
312            "Byte" => LogosType::Byte,
313            "Unit" | "()" => LogosType::Unit,
314            "Duration" => LogosType::Duration,
315            "Date" => LogosType::Date,
316            "Moment" => LogosType::Moment,
317            "Time" => LogosType::Time,
318            "Span" => LogosType::Span,
319            _ => LogosType::Unknown,
320        }
321    }
322
323    /// Parse a Rust type string back into a LogosType (legacy bridge).
324    /// Handles strings like "i64", "f64", "String", "Vec<i64>",
325    /// "std::collections::HashMap<String, i64>", etc.
326    pub fn from_rust_type_str(s: &str) -> LogosType {
327        // Strip loop-bounds hoisting sentinel (e.g. "Vec<i64>|__hl:left_len" → "Vec<i64>").
328        let s = s.split("|__hl:").next().unwrap_or(s);
329        // Strip the promotable-Int sentinel ("i64|__bigint"): the value still classifies as
330        // Int (so the exact-arithmetic path fires); the tag only steers codegen to store the
331        // overflow-promoting `LogosInt` instead of a bare `i64`.
332        let s = s.split("|__bigint").next().unwrap_or(s);
333        match s {
334            "i64" => LogosType::Int,
335            "u64" => LogosType::Nat,
336            "f64" => LogosType::Float,
337            "bool" => LogosType::Bool,
338            "char" => LogosType::Char,
339            "u8" => LogosType::Byte,
340            "LogosRational" => LogosType::Rational,
341            "LogosDecimal" => LogosType::Decimal,
342            "LogosComplex" => LogosType::Complex,
343            "LogosModular" => LogosType::Modular,
344            "LogosQuantity" => LogosType::Quantity,
345            "LogosMoney" => LogosType::Money,
346            "LogosUuid" => LogosType::Uuid,
347            "String" => LogosType::String,
348            "()" => LogosType::Unit,
349            "std::time::Duration" => LogosType::Duration,
350            "LogosDate" => LogosType::Date,
351            "LogosMoment" => LogosType::Moment,
352            "LogosTime" => LogosType::Time,
353            "LogosSpan" => LogosType::Span,
354            _ => {
355                // Try to parse generic types
356                if let Some(inner) = s.strip_prefix("LogosSeq<")
357                    .or_else(|| s.strip_prefix("Vec<"))
358                    .and_then(|s| s.strip_suffix('>'))
359                {
360                    LogosType::Seq(Box::new(Self::from_rust_type_str(inner)))
361                } else if let Some(inner) = s
362                    .strip_prefix("LogosMap<")
363                    .or_else(|| s.strip_prefix("std::collections::HashMap<"))
364                    .or_else(|| s.strip_prefix("HashMap<"))
365                    .or_else(|| s.strip_prefix("rustc_hash::FxHashMap<"))
366                    .or_else(|| s.strip_prefix("FxHashMap<"))
367                    .and_then(|s| s.strip_suffix('>'))
368                {
369                    if let Some((key, val)) = inner.split_once(", ") {
370                        LogosType::Map(
371                            Box::new(Self::from_rust_type_str(key)),
372                            Box::new(Self::from_rust_type_str(val)),
373                        )
374                    } else {
375                        LogosType::Unknown
376                    }
377                } else if let Some(inner) = s
378                    .strip_prefix("std::collections::HashSet<")
379                    .or_else(|| s.strip_prefix("HashSet<"))
380                    .or_else(|| s.strip_prefix("rustc_hash::FxHashSet<"))
381                    .or_else(|| s.strip_prefix("FxHashSet<"))
382                    .or_else(|| s.strip_prefix("Set<"))
383                    .and_then(|s| s.strip_suffix('>'))
384                {
385                    LogosType::Set(Box::new(Self::from_rust_type_str(inner)))
386                } else if let Some(inner) = s.strip_prefix("Option<").and_then(|s| s.strip_suffix('>')) {
387                    LogosType::Option(Box::new(Self::from_rust_type_str(inner)))
388                } else {
389                    LogosType::Unknown
390                }
391            }
392        }
393    }
394
395    /// Infer a LogosType from a literal expression.
396    pub fn from_literal(lit: &Literal) -> LogosType {
397        match lit {
398            Literal::Number(_) => LogosType::Int,
399            Literal::Float(_) => LogosType::Float,
400            Literal::Text(_) => LogosType::String,
401            Literal::Boolean(_) => LogosType::Bool,
402            Literal::Char(_) => LogosType::Char,
403            Literal::Nothing => LogosType::Unit,
404            Literal::Duration(_) => LogosType::Duration,
405            Literal::Date(_) => LogosType::Date,
406            Literal::Moment(_) => LogosType::Moment,
407            Literal::Span { .. } => LogosType::Span,
408            Literal::Time(_) => LogosType::Time,
409        }
410    }
411}
412
413impl TypeEnv {
414    pub fn new() -> Self {
415        Self {
416            variables: HashMap::new(),
417            functions: HashMap::new(),
418        }
419    }
420
421    /// Look up the type of a variable.
422    pub fn lookup(&self, sym: Symbol) -> &LogosType {
423        self.variables.get(&sym).unwrap_or(&LogosType::Unknown)
424    }
425
426    /// Look up a function signature.
427    pub fn lookup_fn(&self, sym: Symbol) -> Option<&FnSig> {
428        self.functions.get(&sym)
429    }
430
431    /// Register a variable with its type.
432    pub fn register(&mut self, sym: Symbol, ty: LogosType) {
433        self.variables.insert(sym, ty);
434    }
435
436    /// Register a function signature.
437    pub fn register_fn(&mut self, sym: Symbol, sig: FnSig) {
438        self.functions.insert(sym, sig);
439    }
440
441    /// Infer the type of an expression given the current environment.
442    pub fn infer_expr(&self, expr: &Expr, interner: &Interner) -> LogosType {
443        match expr {
444            Expr::Literal(lit) => LogosType::from_literal(lit),
445
446            Expr::Identifier(sym) => self.lookup(*sym).clone(),
447
448            Expr::BinaryOp { op, left, right } => {
449                match op {
450                    // Comparison operators always produce Bool
451                    BinaryOpKind::Eq
452                    | BinaryOpKind::NotEq
453                    | BinaryOpKind::Lt
454                    | BinaryOpKind::Gt
455                    | BinaryOpKind::LtEq
456                    | BinaryOpKind::GtEq
457                    | BinaryOpKind::ApproxEq => LogosType::Bool,
458
459                    // And/Or: logical — truthiness in, Bool out (`&`/`|` are the bitwise spellings).
460                    BinaryOpKind::And | BinaryOpKind::Or => LogosType::Bool,
461
462                    // Concat always produces String
463                    BinaryOpKind::Concat => LogosType::String,
464
465                    // `a followed by b` — a sequence of the same type as the operands.
466                    BinaryOpKind::SeqConcat => self.infer_expr(left, interner),
467
468                    // Bitwise ops always produce Int
469                    // `& | ^` on Sets yield a Set of the left operand's type;
470                    // on ints they stay Int.
471                    BinaryOpKind::BitAnd | BinaryOpKind::BitOr => {
472                        let lt = self.infer_expr(left, interner);
473                        if matches!(lt, LogosType::Set(_)) { lt } else { LogosType::Int }
474                    }
475                    BinaryOpKind::BitXor | BinaryOpKind::Shl | BinaryOpKind::Shr => LogosType::Int,
476
477                    // Add: could be numeric or string concatenation
478                    BinaryOpKind::Add => {
479                        let lt = self.infer_expr(left, interner);
480                        let rt = self.infer_expr(right, interner);
481                        if lt.is_string() || rt.is_string() {
482                            LogosType::String
483                        } else {
484                            LogosType::numeric_promotion(&lt, &rt)
485                        }
486                    }
487
488                    // Exact division always yields a Rational (`7 / 2 → 7/2`); the
489                    // resolve pass only emits it in a Rational-typed context.
490                    BinaryOpKind::ExactDivide => LogosType::Rational,
491
492                    // Other arithmetic: numeric promotion
493                    BinaryOpKind::Subtract
494                    | BinaryOpKind::Pow
495                    | BinaryOpKind::Multiply
496                    | BinaryOpKind::Divide
497                    | BinaryOpKind::FloorDivide
498                    | BinaryOpKind::Modulo => {
499                        let lt = self.infer_expr(left, interner);
500                        let rt = self.infer_expr(right, interner);
501                        let promoted = LogosType::numeric_promotion(&lt, &rt);
502                        // Decimal division widens to an exact Rational (base-10 division need
503                        // not terminate); `+ − ×` keep it Decimal. Complex stays Complex.
504                        if matches!(op, BinaryOpKind::Divide) && promoted == LogosType::Decimal {
505                            LogosType::Rational
506                        } else {
507                            promoted
508                        }
509                    }
510                }
511            }
512
513            // `not x` — logical negation of truthiness, Bool out (`~` is the bitwise complement).
514            Expr::Not { .. } => LogosType::Bool,
515
516            Expr::Length { .. } => LogosType::Int,
517
518            Expr::Call { function, args } => {
519                let name = interner.resolve(*function);
520                match name {
521                    "sqrt" | "parseFloat" | "pow" => LogosType::Float,
522                    "decimal" => LogosType::Decimal,
523                    "complex" => LogosType::Complex,
524                    "modular" => LogosType::Modular,
525                    // `quantity(value, "unit")` and `convert(q, "unit")` both yield a Quantity.
526                    "quantity" | "convert" => LogosType::Quantity,
527                    "money" => LogosType::Money,
528                    // `<money> in EUR` desugars to `to_currency(money, "EUR")` — still Money.
529                    "to_currency" => LogosType::Money,
530                    // UUID constructors all yield a Uuid; `uuid_version` reads its version nibble.
531                    "uuid" | "uuid_nil" | "uuid_max" | "uuid_v1" | "uuid_v3" | "uuid_v4" | "uuid_v5"
532                    | "uuid_v6" | "uuid_v7" | "uuid_v8" | "uuid_dns" | "uuid_url" | "uuid_oid"
533                    | "uuid_x500" => LogosType::Uuid,
534                    "uuid_version" => LogosType::Int,
535                    // Byte-level primitives for the Logos-written UUID constructors: hashes and byte
536                    // views are `Seq of Int`; `uuid_from_bytes` rebuilds a Uuid.
537                    "md5" | "sha1" | "text_bytes" | "uuid_bytes" => {
538                        LogosType::Seq(Box::new(LogosType::Int))
539                    }
540                    "uuid_from_bytes" => LogosType::Uuid,
541                    "set_rate" | "set_rates" => LogosType::Unit,
542                    "parse_timestamp" => LogosType::Moment,
543                    "format_timestamp" => LogosType::String,
544                    "year_of" | "month_of" | "day_of" | "weekday_of" | "hour_of" | "minute_of"
545                    | "second_of" | "week_of" | "quarter_of" => LogosType::Int,
546                    "date_of" => LogosType::Date,
547                    "time_of" => LogosType::Time,
548                    "seconds_between" | "months_between" | "years_between" => LogosType::Int,
549                    "add_seconds" => LogosType::Moment,
550                    "in_zone" => LogosType::String,
551                    "local_instant" => LogosType::Moment,
552                    "parseInt" | "floor" | "ceil" | "round" => LogosType::Int,
553                    "abs" | "min" | "max" => {
554                        // Preserves type of arguments — infer from first arg
555                        if let Some(first) = args.first() {
556                            self.infer_expr(first, interner)
557                        } else {
558                            LogosType::Unknown
559                        }
560                    }
561                    // `{k: v, …}` desugars to `mapOf(k, v, …)` (flat pairs) — a Map of the first
562                    // pair's key/value types; `{a, …}` to `setOf(a, …)` — a Set of the first
563                    // element's type. Typing the literal lets numeric key coercion and element
564                    // reads specialise.
565                    "mapOf" => match (args.first(), args.get(1)) {
566                        (Some(k), Some(v)) => LogosType::Map(
567                            Box::new(self.infer_expr(k, interner)),
568                            Box::new(self.infer_expr(v, interner)),
569                        ),
570                        _ => LogosType::Unknown,
571                    },
572                    "setOf" => match args.first() {
573                        Some(e) => LogosType::Set(Box::new(self.infer_expr(e, interner))),
574                        None => LogosType::Unknown,
575                    },
576                    _ => {
577                        // Look up function signature
578                        if let Some(sig) = self.lookup_fn(*function) {
579                            sig.return_type.clone()
580                        } else {
581                            LogosType::Unknown
582                        }
583                    }
584                }
585            }
586
587            Expr::Index { collection, .. } => {
588                let coll_ty = self.infer_expr(collection, interner);
589                match &coll_ty {
590                    LogosType::Seq(inner) => (**inner).clone(),
591                    LogosType::Map(_, v) => (**v).clone(),
592                    _ => LogosType::Unknown,
593                }
594            }
595
596            Expr::List(items) => {
597                let elem_type = items
598                    .first()
599                    .map(|e| self.infer_expr(e, interner))
600                    .unwrap_or(LogosType::Unknown);
601                LogosType::Seq(Box::new(elem_type))
602            }
603
604            Expr::New { type_name, type_args, .. } => {
605                let name = interner.resolve(*type_name);
606                match name {
607                    "Seq" | "List" | "Vec" => {
608                        let elem = type_args
609                            .first()
610                            .map(|t| LogosType::from_type_expr(t, interner))
611                            .unwrap_or(LogosType::Unit);
612                        LogosType::Seq(Box::new(elem))
613                    }
614                    "Map" | "HashMap" => {
615                        let key = type_args
616                            .first()
617                            .map(|t| LogosType::from_type_expr(t, interner))
618                            .unwrap_or(LogosType::String);
619                        let val = type_args
620                            .get(1)
621                            .map(|t| LogosType::from_type_expr(t, interner))
622                            .unwrap_or(LogosType::String);
623                        LogosType::Map(Box::new(key), Box::new(val))
624                    }
625                    "Set" | "HashSet" => {
626                        let elem = type_args
627                            .first()
628                            .map(|t| LogosType::from_type_expr(t, interner))
629                            .unwrap_or(LogosType::Unit);
630                        LogosType::Set(Box::new(elem))
631                    }
632                    _ => LogosType::Unknown,
633                }
634            }
635
636            Expr::FieldAccess { .. } => LogosType::Unknown,
637
638            Expr::OptionSome { value } => {
639                let inner = self.infer_expr(value, interner);
640                LogosType::Option(Box::new(inner))
641            }
642
643            Expr::OptionNone => LogosType::Option(Box::new(LogosType::Unknown)),
644
645            Expr::Range { .. } => LogosType::Seq(Box::new(LogosType::Int)),
646
647            Expr::Contains { .. } => LogosType::Bool,
648
649            Expr::Copy { expr: inner } | Expr::Give { value: inner } => {
650                self.infer_expr(inner, interner)
651            }
652
653            Expr::WithCapacity { value, .. } => self.infer_expr(value, interner),
654
655            _ => LogosType::Unknown,
656        }
657    }
658
659    /// Build a TypeEnv from a program's statements via a single forward pass.
660    ///
661    /// For each statement, registers variable types and function signatures
662    /// into the environment. Since LOGOS programs are forward-declared,
663    /// no fixpoint iteration is needed.
664    pub fn infer_program(stmts: &[Stmt], interner: &Interner, _registry: &TypeRegistry) -> Self {
665        let mut env = Self::new();
666        env.infer_stmts(stmts, interner);
667        env
668    }
669
670    /// Walk a slice of statements, registering types.
671    fn infer_stmts(&mut self, stmts: &[Stmt], interner: &Interner) {
672        for stmt in stmts {
673            self.infer_stmt(stmt, interner);
674        }
675    }
676
677    /// Infer types from a single statement.
678    fn infer_stmt(&mut self, stmt: &Stmt, interner: &Interner) {
679        match stmt {
680            Stmt::Let { var, ty, value, .. } => {
681                let inferred = if let Some(type_expr) = ty {
682                    // Explicit type annotation takes priority
683                    let base_ty = LogosType::from_type_expr(type_expr, interner);
684                    if base_ty != LogosType::Unknown {
685                        base_ty
686                    } else {
687                        self.infer_expr(value, interner)
688                    }
689                } else {
690                    self.infer_expr(value, interner)
691                };
692                self.register(*var, inferred);
693            }
694
695            Stmt::Set { target, value } => {
696                // If we don't already have a type for target, infer from value
697                if self.lookup(*target) == &LogosType::Unknown {
698                    let inferred = self.infer_expr(value, interner);
699                    if inferred != LogosType::Unknown {
700                        self.register(*target, inferred);
701                    }
702                }
703            }
704
705            Stmt::FunctionDef {
706                name,
707                params,
708                body,
709                return_type,
710                ..
711            } => {
712                // Register parameter types
713                let param_types: Vec<(Symbol, LogosType)> = params
714                    .iter()
715                    .map(|(sym, ty)| (*sym, LogosType::from_type_expr(ty, interner)))
716                    .collect();
717
718                // Register params in the env for body inference
719                for (sym, ty) in &param_types {
720                    self.register(*sym, ty.clone());
721                }
722
723                // Infer body
724                self.infer_stmts(body, interner);
725
726                // Determine return type
727                let ret_ty = if let Some(rt) = return_type {
728                    LogosType::from_type_expr(rt, interner)
729                } else {
730                    self.infer_return_type(body, interner)
731                };
732
733                self.register_fn(
734                    *name,
735                    FnSig {
736                        params: param_types,
737                        return_type: ret_ty,
738                    },
739                );
740            }
741
742            Stmt::Repeat { pattern, iterable, body } => {
743                // Infer the element type from the iterable
744                let iterable_ty = self.infer_expr(iterable, interner);
745                let elem_ty = match &iterable_ty {
746                    LogosType::Seq(inner) => (**inner).clone(),
747                    LogosType::Set(inner) => (**inner).clone(),
748                    LogosType::Map(k, _v) => (**k).clone(),
749                    _ => LogosType::Unknown,
750                };
751                match pattern {
752                    crate::ast::stmt::Pattern::Identifier(sym) => {
753                        self.register(*sym, elem_ty);
754                    }
755                    crate::ast::stmt::Pattern::Tuple(syms) => {
756                        // For tuple destructuring, we don't have enough info
757                        for sym in syms {
758                            self.register(*sym, LogosType::Unknown);
759                        }
760                    }
761                }
762                self.infer_stmts(body, interner);
763            }
764
765            Stmt::If { then_block, else_block, .. } => {
766                self.infer_stmts(then_block, interner);
767                if let Some(else_b) = else_block {
768                    self.infer_stmts(else_b, interner);
769                }
770            }
771
772            Stmt::While { body, .. } => {
773                self.infer_stmts(body, interner);
774            }
775
776            Stmt::Inspect { arms, .. } => {
777                for arm in arms {
778                    // Register bindings from pattern match as Unknown
779                    for (_field, binding) in &arm.bindings {
780                        self.register(*binding, LogosType::Unknown);
781                    }
782                    self.infer_stmts(arm.body, interner);
783                }
784            }
785
786            Stmt::Zone { body, .. } => {
787                self.infer_stmts(body, interner);
788            }
789
790            Stmt::ReadFrom { var, .. } => {
791                // ReadFrom always gives String
792                self.register(*var, LogosType::String);
793            }
794
795            Stmt::CreatePipe { var, element_type, .. } => {
796                let elem = LogosType::from_type_name(interner.resolve(*element_type));
797                self.register(*var, elem);
798            }
799
800            Stmt::ReceivePipe { var, .. } | Stmt::TryReceivePipe { var, .. } => {
801                // Pipe receive type depends on pipe type, default to Unknown
802                self.register(*var, LogosType::Unknown);
803            }
804
805            Stmt::Pop { into: Some(var), collection } => {
806                let coll_ty = self.infer_expr(collection, interner);
807                let elem_ty = coll_ty
808                    .element_type()
809                    .cloned()
810                    .unwrap_or(LogosType::Unknown);
811                self.register(*var, elem_ty);
812            }
813
814            Stmt::AwaitMessage { into, .. } => {
815                self.register(*into, LogosType::Unknown);
816            }
817
818            Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
819                self.infer_stmts(tasks, interner);
820            }
821
822            _ => {}
823        }
824    }
825
826    /// Infer the return type from a function body by scanning Return statements.
827    fn infer_return_type(&self, body: &[Stmt], interner: &Interner) -> LogosType {
828        for stmt in body {
829            if let Stmt::Return { value: Some(expr) } = stmt {
830                return self.infer_expr(expr, interner);
831            }
832        }
833        LogosType::Unit
834    }
835
836    /// Export the type environment as a LogosType map for RefinementContext seeding.
837    pub fn to_logos_type_map(&self) -> HashMap<Symbol, LogosType> {
838        self.variables
839            .iter()
840            .filter(|(_, ty)| **ty != LogosType::Unknown)
841            .map(|(sym, ty)| (*sym, ty.clone()))
842            .collect()
843    }
844
845    /// Convert the type environment to the legacy string-based format.
846    pub fn to_legacy_variable_types(&self) -> HashMap<Symbol, std::string::String> {
847        self.variables
848            .iter()
849            .filter(|(_, ty)| **ty != LogosType::Unknown)
850            .map(|(sym, ty)| (*sym, ty.to_rust_type()))
851            .collect()
852    }
853
854    /// Convert the type environment to the legacy string var set.
855    pub fn to_legacy_string_vars(&self) -> std::collections::HashSet<Symbol> {
856        self.variables
857            .iter()
858            .filter(|(_, ty)| ty.is_string())
859            .map(|(sym, _)| *sym)
860            .collect()
861    }
862}
863
864/// Centralized name resolution for Rust identifier output.
865///
866/// Wraps the interner and ensures all identifier output goes through
867/// keyword escaping. Makes it impossible to forget escaping.
868pub struct RustNames<'a> {
869    interner: &'a Interner,
870}
871
872impl<'a> RustNames<'a> {
873    pub fn new(interner: &'a Interner) -> Self {
874        Self { interner }
875    }
876
877    /// Resolve a symbol to a Rust-safe identifier (always escapes keywords).
878    pub fn ident(&self, sym: Symbol) -> std::string::String {
879        crate::codegen::escape_rust_ident(self.interner.resolve(sym))
880    }
881
882    /// Get the raw name for pattern matching (native functions, type names, etc.).
883    pub fn raw(&self, sym: Symbol) -> &str {
884        self.interner.resolve(sym)
885    }
886}
887
888#[cfg(test)]
889mod tests {
890    use super::*;
891
892    // =========================================================================
893    // LogosType::is_copy tests
894    // =========================================================================
895
896    #[test]
897    fn copy_types_are_copy() {
898        assert!(LogosType::Int.is_copy());
899        assert!(LogosType::Float.is_copy());
900        assert!(LogosType::Bool.is_copy());
901        assert!(LogosType::Char.is_copy());
902        assert!(LogosType::Byte.is_copy());
903        assert!(LogosType::Unit.is_copy());
904        assert!(LogosType::Nat.is_copy());
905    }
906
907    #[test]
908    fn non_copy_types_are_not_copy() {
909        assert!(!LogosType::String.is_copy());
910        assert!(!LogosType::Seq(Box::new(LogosType::Int)).is_copy());
911        assert!(!LogosType::Map(Box::new(LogosType::String), Box::new(LogosType::Int)).is_copy());
912        assert!(!LogosType::Set(Box::new(LogosType::Int)).is_copy());
913        assert!(!LogosType::Option(Box::new(LogosType::Int)).is_copy());
914        assert!(!LogosType::Duration.is_copy());
915        assert!(!LogosType::Unknown.is_copy());
916    }
917
918    // =========================================================================
919    // LogosType::is_numeric / is_string / is_float
920    // =========================================================================
921
922    #[test]
923    fn numeric_types() {
924        assert!(LogosType::Int.is_numeric());
925        assert!(LogosType::Float.is_numeric());
926        assert!(LogosType::Nat.is_numeric());
927        assert!(!LogosType::String.is_numeric());
928        assert!(!LogosType::Bool.is_numeric());
929    }
930
931    #[test]
932    fn string_type() {
933        assert!(LogosType::String.is_string());
934        assert!(!LogosType::Int.is_string());
935        assert!(!LogosType::Unknown.is_string());
936    }
937
938    #[test]
939    fn float_type() {
940        assert!(LogosType::Float.is_float());
941        assert!(!LogosType::Int.is_float());
942    }
943
944    // =========================================================================
945    // LogosType::element_type / value_type / key_type
946    // =========================================================================
947
948    #[test]
949    fn seq_element_type() {
950        let seq = LogosType::Seq(Box::new(LogosType::Int));
951        assert_eq!(seq.element_type(), Some(&LogosType::Int));
952    }
953
954    #[test]
955    fn set_element_type() {
956        let set = LogosType::Set(Box::new(LogosType::String));
957        assert_eq!(set.element_type(), Some(&LogosType::String));
958    }
959
960    #[test]
961    fn map_key_value_types() {
962        let map = LogosType::Map(Box::new(LogosType::String), Box::new(LogosType::Int));
963        assert_eq!(map.key_type(), Some(&LogosType::String));
964        assert_eq!(map.value_type(), Some(&LogosType::Int));
965    }
966
967    #[test]
968    fn non_collection_element_type() {
969        assert_eq!(LogosType::Int.element_type(), None);
970        assert_eq!(LogosType::String.element_type(), None);
971    }
972
973    // =========================================================================
974    // LogosType::numeric_promotion
975    // =========================================================================
976
977    #[test]
978    fn numeric_promotion_int_int() {
979        assert_eq!(
980            LogosType::numeric_promotion(&LogosType::Int, &LogosType::Int),
981            LogosType::Int
982        );
983    }
984
985    #[test]
986    fn numeric_promotion_int_float() {
987        assert_eq!(
988            LogosType::numeric_promotion(&LogosType::Int, &LogosType::Float),
989            LogosType::Float
990        );
991    }
992
993    #[test]
994    fn numeric_promotion_float_int() {
995        assert_eq!(
996            LogosType::numeric_promotion(&LogosType::Float, &LogosType::Int),
997            LogosType::Float
998        );
999    }
1000
1001    #[test]
1002    fn numeric_promotion_float_float() {
1003        assert_eq!(
1004            LogosType::numeric_promotion(&LogosType::Float, &LogosType::Float),
1005            LogosType::Float
1006        );
1007    }
1008
1009    #[test]
1010    fn numeric_promotion_non_numeric() {
1011        assert_eq!(
1012            LogosType::numeric_promotion(&LogosType::String, &LogosType::Int),
1013            LogosType::Unknown
1014        );
1015    }
1016
1017    // =========================================================================
1018    // LogosType::to_rust_type
1019    // =========================================================================
1020
1021    #[test]
1022    fn to_rust_type_primitives() {
1023        assert_eq!(LogosType::Int.to_rust_type(), "i64");
1024        assert_eq!(LogosType::Float.to_rust_type(), "f64");
1025        assert_eq!(LogosType::Bool.to_rust_type(), "bool");
1026        assert_eq!(LogosType::Char.to_rust_type(), "char");
1027        assert_eq!(LogosType::Byte.to_rust_type(), "u8");
1028        assert_eq!(LogosType::String.to_rust_type(), "String");
1029        assert_eq!(LogosType::Unit.to_rust_type(), "()");
1030        assert_eq!(LogosType::Nat.to_rust_type(), "u64");
1031    }
1032
1033    #[test]
1034    fn to_rust_type_temporal() {
1035        assert_eq!(LogosType::Duration.to_rust_type(), "std::time::Duration");
1036        assert_eq!(LogosType::Date.to_rust_type(), "LogosDate");
1037        assert_eq!(LogosType::Moment.to_rust_type(), "LogosMoment");
1038        assert_eq!(LogosType::Time.to_rust_type(), "LogosTime");
1039        assert_eq!(LogosType::Span.to_rust_type(), "LogosSpan");
1040    }
1041
1042    #[test]
1043    fn to_rust_type_collections() {
1044        assert_eq!(
1045            LogosType::Seq(Box::new(LogosType::Int)).to_rust_type(),
1046            "LogosSeq<i64>"
1047        );
1048        assert_eq!(
1049            LogosType::Map(Box::new(LogosType::String), Box::new(LogosType::Int)).to_rust_type(),
1050            "LogosMap<String, i64>"
1051        );
1052        assert_eq!(
1053            LogosType::Set(Box::new(LogosType::Int)).to_rust_type(),
1054            "Set<i64>"
1055        );
1056        assert_eq!(
1057            LogosType::Option(Box::new(LogosType::String)).to_rust_type(),
1058            "Option<String>"
1059        );
1060    }
1061
1062    #[test]
1063    fn to_rust_type_nested() {
1064        let nested = LogosType::Seq(Box::new(LogosType::Seq(Box::new(LogosType::Float))));
1065        assert_eq!(nested.to_rust_type(), "LogosSeq<LogosSeq<f64>>");
1066    }
1067
1068    // =========================================================================
1069    // LogosType::from_literal
1070    // =========================================================================
1071
1072    #[test]
1073    fn from_literal_number() {
1074        assert_eq!(LogosType::from_literal(&Literal::Number(42)), LogosType::Int);
1075    }
1076
1077    #[test]
1078    fn from_literal_float() {
1079        assert_eq!(
1080            LogosType::from_literal(&Literal::Float(3.14)),
1081            LogosType::Float
1082        );
1083    }
1084
1085    #[test]
1086    fn from_literal_text() {
1087        assert_eq!(
1088            LogosType::from_literal(&Literal::Text(Symbol::EMPTY)),
1089            LogosType::String
1090        );
1091    }
1092
1093    #[test]
1094    fn from_literal_bool() {
1095        assert_eq!(
1096            LogosType::from_literal(&Literal::Boolean(true)),
1097            LogosType::Bool
1098        );
1099    }
1100
1101    #[test]
1102    fn from_literal_nothing() {
1103        assert_eq!(LogosType::from_literal(&Literal::Nothing), LogosType::Unit);
1104    }
1105
1106    // =========================================================================
1107    // LogosType::from_type_expr
1108    // =========================================================================
1109
1110    #[test]
1111    fn from_type_expr_primitive_int() {
1112        let mut interner = Interner::new();
1113        let sym = interner.intern("Int");
1114        let ty = TypeExpr::Primitive(sym);
1115        assert_eq!(LogosType::from_type_expr(&ty, &interner), LogosType::Int);
1116    }
1117
1118    #[test]
1119    fn from_type_expr_named_text() {
1120        let mut interner = Interner::new();
1121        let sym = interner.intern("Text");
1122        let ty = TypeExpr::Named(sym);
1123        assert_eq!(LogosType::from_type_expr(&ty, &interner), LogosType::String);
1124    }
1125
1126    // =========================================================================
1127    // TypeEnv basics
1128    // =========================================================================
1129
1130    #[test]
1131    fn env_lookup_unknown_returns_unknown() {
1132        let env = TypeEnv::new();
1133        assert_eq!(env.lookup(Symbol::EMPTY), &LogosType::Unknown);
1134    }
1135
1136    #[test]
1137    fn env_register_and_lookup() {
1138        let mut env = TypeEnv::new();
1139        let mut interner = Interner::new();
1140        let x = interner.intern("x");
1141        env.register(x, LogosType::Int);
1142        assert_eq!(env.lookup(x), &LogosType::Int);
1143    }
1144
1145    #[test]
1146    fn env_register_fn_and_lookup() {
1147        let mut env = TypeEnv::new();
1148        let mut interner = Interner::new();
1149        let f = interner.intern("add");
1150        let a = interner.intern("a");
1151        let b = interner.intern("b");
1152        env.register_fn(
1153            f,
1154            FnSig {
1155                params: vec![(a, LogosType::Int), (b, LogosType::Int)],
1156                return_type: LogosType::Int,
1157            },
1158        );
1159        let sig = env.lookup_fn(f).unwrap();
1160        assert_eq!(sig.return_type, LogosType::Int);
1161        assert_eq!(sig.params.len(), 2);
1162    }
1163
1164    // =========================================================================
1165    // TypeEnv::infer_expr
1166    // =========================================================================
1167
1168    #[test]
1169    fn infer_expr_literal_number() {
1170        let env = TypeEnv::new();
1171        let interner = Interner::new();
1172        let expr = Expr::Literal(Literal::Number(42));
1173        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Int);
1174    }
1175
1176    #[test]
1177    fn infer_expr_literal_float() {
1178        let env = TypeEnv::new();
1179        let interner = Interner::new();
1180        let expr = Expr::Literal(Literal::Float(3.14));
1181        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Float);
1182    }
1183
1184    #[test]
1185    fn infer_expr_literal_text() {
1186        let env = TypeEnv::new();
1187        let interner = Interner::new();
1188        let expr = Expr::Literal(Literal::Text(Symbol::EMPTY));
1189        assert_eq!(env.infer_expr(&expr, &interner), LogosType::String);
1190    }
1191
1192    #[test]
1193    fn infer_expr_identifier_registered() {
1194        let mut env = TypeEnv::new();
1195        let mut interner = Interner::new();
1196        let x = interner.intern("x");
1197        env.register(x, LogosType::Float);
1198        let expr = Expr::Identifier(x);
1199        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Float);
1200    }
1201
1202    #[test]
1203    fn infer_expr_identifier_unknown() {
1204        let env = TypeEnv::new();
1205        let mut interner = Interner::new();
1206        let x = interner.intern("x");
1207        let expr = Expr::Identifier(x);
1208        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Unknown);
1209    }
1210
1211    #[test]
1212    fn infer_expr_add_int_int() {
1213        let env = TypeEnv::new();
1214        let interner = Interner::new();
1215        let left = Expr::Literal(Literal::Number(1));
1216        let right = Expr::Literal(Literal::Number(2));
1217        let expr = Expr::BinaryOp {
1218            op: BinaryOpKind::Add,
1219            left: &left,
1220            right: &right,
1221        };
1222        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Int);
1223    }
1224
1225    #[test]
1226    fn infer_expr_add_int_float_promotes() {
1227        let env = TypeEnv::new();
1228        let interner = Interner::new();
1229        let left = Expr::Literal(Literal::Number(1));
1230        let right = Expr::Literal(Literal::Float(2.0));
1231        let expr = Expr::BinaryOp {
1232            op: BinaryOpKind::Add,
1233            left: &left,
1234            right: &right,
1235        };
1236        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Float);
1237    }
1238
1239    #[test]
1240    fn infer_expr_add_string_int_is_string() {
1241        let mut env = TypeEnv::new();
1242        let mut interner = Interner::new();
1243        let s = interner.intern("s");
1244        env.register(s, LogosType::String);
1245        let left = Expr::Identifier(s);
1246        let right = Expr::Literal(Literal::Number(42));
1247        let expr = Expr::BinaryOp {
1248            op: BinaryOpKind::Add,
1249            left: &left,
1250            right: &right,
1251        };
1252        assert_eq!(env.infer_expr(&expr, &interner), LogosType::String);
1253    }
1254
1255    #[test]
1256    fn infer_expr_comparison_is_bool() {
1257        let env = TypeEnv::new();
1258        let interner = Interner::new();
1259        let left = Expr::Literal(Literal::Number(1));
1260        let right = Expr::Literal(Literal::Number(2));
1261        let expr = Expr::BinaryOp {
1262            op: BinaryOpKind::Lt,
1263            left: &left,
1264            right: &right,
1265        };
1266        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Bool);
1267    }
1268
1269    #[test]
1270    fn infer_expr_length_is_int() {
1271        let env = TypeEnv::new();
1272        let interner = Interner::new();
1273        let inner = Expr::Literal(Literal::Text(Symbol::EMPTY));
1274        let expr = Expr::Length { collection: &inner };
1275        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Int);
1276    }
1277
1278    #[test]
1279    fn infer_expr_call_sqrt_is_float() {
1280        let env = TypeEnv::new();
1281        let mut interner = Interner::new();
1282        let sqrt = interner.intern("sqrt");
1283        let arg = Expr::Literal(Literal::Number(4));
1284        let expr = Expr::Call {
1285            function: sqrt,
1286            args: vec![&arg],
1287        };
1288        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Float);
1289    }
1290
1291    #[test]
1292    fn infer_expr_call_parseint_is_int() {
1293        let env = TypeEnv::new();
1294        let mut interner = Interner::new();
1295        let pi = interner.intern("parseInt");
1296        let arg = Expr::Literal(Literal::Text(Symbol::EMPTY));
1297        let expr = Expr::Call {
1298            function: pi,
1299            args: vec![&arg],
1300        };
1301        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Int);
1302    }
1303
1304    #[test]
1305    fn infer_expr_call_user_function() {
1306        let mut env = TypeEnv::new();
1307        let mut interner = Interner::new();
1308        let f = interner.intern("compute");
1309        env.register_fn(
1310            f,
1311            FnSig {
1312                params: vec![],
1313                return_type: LogosType::Float,
1314            },
1315        );
1316        let expr = Expr::Call {
1317            function: f,
1318            args: vec![],
1319        };
1320        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Float);
1321    }
1322
1323    #[test]
1324    fn infer_expr_index_vec() {
1325        let mut env = TypeEnv::new();
1326        let mut interner = Interner::new();
1327        let items = interner.intern("items");
1328        env.register(items, LogosType::Seq(Box::new(LogosType::Int)));
1329        let coll = Expr::Identifier(items);
1330        let idx = Expr::Literal(Literal::Number(1));
1331        let expr = Expr::Index {
1332            collection: &coll,
1333            index: &idx,
1334        };
1335        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Int);
1336    }
1337
1338    #[test]
1339    fn infer_expr_index_map() {
1340        let mut env = TypeEnv::new();
1341        let mut interner = Interner::new();
1342        let data = interner.intern("data");
1343        env.register(
1344            data,
1345            LogosType::Map(Box::new(LogosType::String), Box::new(LogosType::Float)),
1346        );
1347        let coll = Expr::Identifier(data);
1348        let key = Expr::Literal(Literal::Text(Symbol::EMPTY));
1349        let expr = Expr::Index {
1350            collection: &coll,
1351            index: &key,
1352        };
1353        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Float);
1354    }
1355
1356    #[test]
1357    fn infer_expr_list_literal() {
1358        let env = TypeEnv::new();
1359        let interner = Interner::new();
1360        let a = Expr::Literal(Literal::Number(1));
1361        let b = Expr::Literal(Literal::Number(2));
1362        let expr = Expr::List(vec![&a, &b]);
1363        assert_eq!(
1364            env.infer_expr(&expr, &interner),
1365            LogosType::Seq(Box::new(LogosType::Int))
1366        );
1367    }
1368
1369    #[test]
1370    fn infer_expr_option_some() {
1371        let env = TypeEnv::new();
1372        let interner = Interner::new();
1373        let inner = Expr::Literal(Literal::Number(42));
1374        let expr = Expr::OptionSome { value: &inner };
1375        assert_eq!(
1376            env.infer_expr(&expr, &interner),
1377            LogosType::Option(Box::new(LogosType::Int))
1378        );
1379    }
1380
1381    #[test]
1382    fn infer_expr_contains_is_bool() {
1383        let env = TypeEnv::new();
1384        let interner = Interner::new();
1385        let coll = Expr::Literal(Literal::Text(Symbol::EMPTY));
1386        let val = Expr::Literal(Literal::Number(1));
1387        let expr = Expr::Contains {
1388            collection: &coll,
1389            value: &val,
1390        };
1391        assert_eq!(env.infer_expr(&expr, &interner), LogosType::Bool);
1392    }
1393
1394    // =========================================================================
1395    // TypeEnv::to_legacy_variable_types / to_legacy_string_vars
1396    // =========================================================================
1397
1398    #[test]
1399    fn legacy_variable_types_conversion() {
1400        let mut env = TypeEnv::new();
1401        let mut interner = Interner::new();
1402        let x = interner.intern("x");
1403        let y = interner.intern("y");
1404        let z = interner.intern("z");
1405        env.register(x, LogosType::Int);
1406        env.register(y, LogosType::Seq(Box::new(LogosType::Float)));
1407        env.register(z, LogosType::Unknown); // Should be filtered out
1408        let legacy = env.to_legacy_variable_types();
1409        assert_eq!(legacy.get(&x).unwrap(), "i64");
1410        assert_eq!(legacy.get(&y).unwrap(), "LogosSeq<f64>");
1411        assert!(!legacy.contains_key(&z));
1412    }
1413
1414    #[test]
1415    fn legacy_string_vars_conversion() {
1416        let mut env = TypeEnv::new();
1417        let mut interner = Interner::new();
1418        let s = interner.intern("s");
1419        let n = interner.intern("n");
1420        env.register(s, LogosType::String);
1421        env.register(n, LogosType::Int);
1422        let string_vars = env.to_legacy_string_vars();
1423        assert!(string_vars.contains(&s));
1424        assert!(!string_vars.contains(&n));
1425    }
1426
1427    // =========================================================================
1428    // TypeEnv::infer_program — integration tests via compile pipeline
1429    // =========================================================================
1430
1431    #[test]
1432    fn infer_program_let_literal() {
1433        let mut interner = Interner::new();
1434        let x = interner.intern("x");
1435        let val = Expr::Literal(Literal::Number(42));
1436        let stmts = [Stmt::Let {
1437            var: x,
1438            ty: None,
1439            value: &val,
1440            mutable: false,
1441        }];
1442        let registry = TypeRegistry::new();
1443        let env = TypeEnv::infer_program(&stmts, &interner, &registry);
1444        assert_eq!(env.lookup(x), &LogosType::Int);
1445    }
1446
1447    #[test]
1448    fn infer_program_let_with_type_annotation() {
1449        let mut interner = Interner::new();
1450        let x = interner.intern("x");
1451        let float_sym = interner.intern("Real");
1452        let val = Expr::Literal(Literal::Number(5));
1453        let ty = TypeExpr::Primitive(float_sym);
1454        let stmts = [Stmt::Let {
1455            var: x,
1456            ty: Some(&ty),
1457            value: &val,
1458            mutable: false,
1459        }];
1460        let registry = TypeRegistry::new();
1461        let env = TypeEnv::infer_program(&stmts, &interner, &registry);
1462        assert_eq!(env.lookup(x), &LogosType::Float);
1463    }
1464
1465    #[test]
1466    fn infer_program_let_string_tracked() {
1467        let mut interner = Interner::new();
1468        let s = interner.intern("name");
1469        let hello = interner.intern("hello");
1470        let val = Expr::Literal(Literal::Text(hello));
1471        let stmts = [Stmt::Let {
1472            var: s,
1473            ty: None,
1474            value: &val,
1475            mutable: false,
1476        }];
1477        let registry = TypeRegistry::new();
1478        let env = TypeEnv::infer_program(&stmts, &interner, &registry);
1479        assert!(env.lookup(s).is_string());
1480        let string_vars = env.to_legacy_string_vars();
1481        assert!(string_vars.contains(&s));
1482    }
1483
1484    #[test]
1485    fn infer_program_readfrom_is_string() {
1486        let mut interner = Interner::new();
1487        let input = interner.intern("input");
1488        let stmts = [Stmt::ReadFrom {
1489            var: input,
1490            source: crate::ast::stmt::ReadSource::Console,
1491        }];
1492        let registry = TypeRegistry::new();
1493        let env = TypeEnv::infer_program(&stmts, &interner, &registry);
1494        assert_eq!(env.lookup(input), &LogosType::String);
1495    }
1496
1497    // =========================================================================
1498    // RustNames tests
1499    // =========================================================================
1500
1501    #[test]
1502    fn rust_names_escapes_keywords() {
1503        let mut interner = Interner::new();
1504        let r#move = interner.intern("move");
1505        let names = RustNames::new(&interner);
1506        assert_eq!(names.ident(r#move), "r#move");
1507    }
1508
1509    #[test]
1510    fn rust_names_preserves_non_keywords() {
1511        let mut interner = Interner::new();
1512        let foo = interner.intern("foo");
1513        let names = RustNames::new(&interner);
1514        assert_eq!(names.ident(foo), "foo");
1515    }
1516
1517    #[test]
1518    fn rust_names_raw_returns_original() {
1519        let mut interner = Interner::new();
1520        let r#move = interner.intern("move");
1521        let names = RustNames::new(&interner);
1522        assert_eq!(names.raw(r#move), "move");
1523    }
1524
1525    // =========================================================================
1526    // Phase 5: LogosType::Function
1527    // =========================================================================
1528
1529    #[test]
1530    fn function_type_is_not_copy() {
1531        let fn_ty = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool));
1532        assert!(!fn_ty.is_copy());
1533    }
1534
1535    #[test]
1536    fn function_type_zero_params_is_not_copy() {
1537        let fn_ty = LogosType::Function(vec![], Box::new(LogosType::Unit));
1538        assert!(!fn_ty.is_copy());
1539    }
1540
1541    #[test]
1542    fn to_rust_type_function_single_param() {
1543        let fn_ty = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool));
1544        assert_eq!(fn_ty.to_rust_type(), "impl Fn(i64) -> bool");
1545    }
1546
1547    #[test]
1548    fn to_rust_type_function_two_params() {
1549        let fn_ty = LogosType::Function(
1550            vec![LogosType::Int, LogosType::String],
1551            Box::new(LogosType::Bool),
1552        );
1553        assert_eq!(fn_ty.to_rust_type(), "impl Fn(i64, String) -> bool");
1554    }
1555
1556    #[test]
1557    fn to_rust_type_function_zero_params() {
1558        let fn_ty = LogosType::Function(vec![], Box::new(LogosType::Int));
1559        assert_eq!(fn_ty.to_rust_type(), "impl Fn() -> i64");
1560    }
1561
1562    #[test]
1563    fn to_rust_type_function_nested_param() {
1564        // fn(fn(Int) -> Bool) -> String
1565        let inner_fn = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool));
1566        let fn_ty = LogosType::Function(vec![inner_fn], Box::new(LogosType::String));
1567        assert_eq!(
1568            fn_ty.to_rust_type(),
1569            "impl Fn(impl Fn(i64) -> bool) -> String"
1570        );
1571    }
1572
1573    #[test]
1574    fn from_type_expr_function_one_param() {
1575        use crate::ast::stmt::TypeExpr;
1576        let mut interner = Interner::new();
1577        let int_sym = interner.intern("Int");
1578        let bool_sym = interner.intern("Bool");
1579        let int_ty = TypeExpr::Primitive(int_sym);
1580        let bool_ty = TypeExpr::Primitive(bool_sym);
1581        let fn_ty = TypeExpr::Function {
1582            inputs: std::slice::from_ref(&int_ty),
1583            output: &bool_ty,
1584        };
1585        assert_eq!(
1586            LogosType::from_type_expr(&fn_ty, &interner),
1587            LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool))
1588        );
1589    }
1590
1591    #[test]
1592    fn from_type_expr_function_zero_params() {
1593        use crate::ast::stmt::TypeExpr;
1594        let mut interner = Interner::new();
1595        let unit_sym = interner.intern("Unit");
1596        let unit_ty = TypeExpr::Primitive(unit_sym);
1597        let fn_ty = TypeExpr::Function {
1598            inputs: &[],
1599            output: &unit_ty,
1600        };
1601        assert_eq!(
1602            LogosType::from_type_expr(&fn_ty, &interner),
1603            LogosType::Function(vec![], Box::new(LogosType::Unit))
1604        );
1605    }
1606
1607    #[test]
1608    fn from_type_expr_function_two_params() {
1609        use crate::ast::stmt::TypeExpr;
1610        let mut interner = Interner::new();
1611        let int_sym = interner.intern("Int");
1612        let text_sym = interner.intern("Text");
1613        let bool_sym = interner.intern("Bool");
1614        let int_ty = TypeExpr::Primitive(int_sym);
1615        let text_ty = TypeExpr::Primitive(text_sym);
1616        let bool_ty = TypeExpr::Primitive(bool_sym);
1617        let inputs = [int_ty, text_ty];
1618        let fn_ty = TypeExpr::Function {
1619            inputs: &inputs,
1620            output: &bool_ty,
1621        };
1622        assert_eq!(
1623            LogosType::from_type_expr(&fn_ty, &interner),
1624            LogosType::Function(
1625                vec![LogosType::Int, LogosType::String],
1626                Box::new(LogosType::Bool)
1627            )
1628        );
1629    }
1630
1631    #[test]
1632    fn from_type_expr_function_nested() {
1633        // fn(fn(Int) -> Bool) -> Text should parse correctly
1634        use crate::ast::stmt::TypeExpr;
1635        let mut interner = Interner::new();
1636        let int_sym = interner.intern("Int");
1637        let bool_sym = interner.intern("Bool");
1638        let text_sym = interner.intern("Text");
1639        let int_ty = TypeExpr::Primitive(int_sym);
1640        let bool_ty = TypeExpr::Primitive(bool_sym);
1641        let text_ty = TypeExpr::Primitive(text_sym);
1642        let inner_fn = TypeExpr::Function {
1643            inputs: std::slice::from_ref(&int_ty),
1644            output: &bool_ty,
1645        };
1646        let outer_fn = TypeExpr::Function {
1647            inputs: std::slice::from_ref(&inner_fn),
1648            output: &text_ty,
1649        };
1650        let inner_logos = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool));
1651        let expected = LogosType::Function(vec![inner_logos], Box::new(LogosType::String));
1652        assert_eq!(LogosType::from_type_expr(&outer_fn, &interner), expected);
1653    }
1654
1655    #[test]
1656    fn function_type_equality_same() {
1657        let a = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool));
1658        let b = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool));
1659        assert_eq!(a, b);
1660    }
1661
1662    #[test]
1663    fn function_type_equality_different_params() {
1664        let a = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool));
1665        let b = LogosType::Function(vec![LogosType::String], Box::new(LogosType::Bool));
1666        assert_ne!(a, b);
1667    }
1668
1669    #[test]
1670    fn function_type_equality_different_return() {
1671        let a = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool));
1672        let b = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::String));
1673        assert_ne!(a, b);
1674    }
1675
1676    #[test]
1677    fn function_type_not_numeric_not_string_not_float() {
1678        let fn_ty = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool));
1679        assert!(!fn_ty.is_numeric());
1680        assert!(!fn_ty.is_string());
1681        assert!(!fn_ty.is_float());
1682    }
1683
1684    #[test]
1685    fn function_type_element_type_is_none() {
1686        let fn_ty = LogosType::Function(vec![LogosType::Int], Box::new(LogosType::Bool));
1687        assert!(fn_ty.element_type().is_none());
1688        assert!(fn_ty.key_type().is_none());
1689        assert!(fn_ty.value_type().is_none());
1690    }
1691}