1use std::collections::HashMap;
18
19use crate::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt, TypeExpr};
20use crate::intern::{Interner, Symbol};
21use crate::analysis::TypeRegistry;
22
23#[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 Rational,
49 Decimal,
52 Complex,
55 Modular,
58 Quantity,
63 Money,
67 Uuid,
70 UserDefined(Symbol),
71 Function(Vec<LogosType>, Box<LogosType>),
73 Unknown,
74}
75
76#[derive(Debug, Clone)]
78pub struct FnSig {
79 pub params: Vec<(Symbol, LogosType)>,
80 pub return_type: LogosType,
81}
82
83#[derive(Debug)]
89pub struct TypeEnv {
90 variables: HashMap<Symbol, LogosType>,
91 functions: HashMap<Symbol, FnSig>,
92}
93
94impl LogosType {
95 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 pub fn is_numeric(&self) -> bool {
114 matches!(self, LogosType::Int | LogosType::Float | LogosType::Nat)
115 }
116
117 pub fn is_string(&self) -> bool {
119 matches!(self, LogosType::String)
120 }
121
122 pub fn is_float(&self) -> bool {
124 matches!(self, LogosType::Float)
125 }
126
127 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 pub fn value_type(&self) -> Option<&LogosType> {
137 match self {
138 LogosType::Map(_, v) => Some(v),
139 _ => None,
140 }
141 }
142
143 pub fn key_type(&self) -> Option<&LogosType> {
145 match self {
146 LogosType::Map(k, _) => Some(k),
147 _ => None,
148 }
149 }
150
151 pub fn numeric_promotion(a: &LogosType, b: &LogosType) -> LogosType {
155 if matches!(a, LogosType::Quantity) || matches!(b, LogosType::Quantity) {
156 LogosType::Quantity
159 } else if matches!(a, LogosType::Money) || matches!(b, LogosType::Money) {
160 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 LogosType::Rational
169 } else if matches!(a, LogosType::Modular) && matches!(b, LogosType::Modular) {
170 LogosType::Modular
172 } else if matches!(a, LogosType::Complex) || matches!(b, LogosType::Complex) {
173 LogosType::Complex
176 } else if matches!(a, LogosType::Decimal) || matches!(b, LogosType::Decimal) {
177 LogosType::Decimal
179 } else if a.is_numeric() && b.is_numeric() {
180 LogosType::Int
181 } else {
182 LogosType::Unknown
183 }
184 }
185
186 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 pub fn from_type_expr(ty: &TypeExpr, interner: &Interner) -> LogosType {
238 match ty {
239 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 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 pub fn from_rust_type_str(s: &str) -> LogosType {
327 let s = s.split("|__hl:").next().unwrap_or(s);
329 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 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 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 pub fn lookup(&self, sym: Symbol) -> &LogosType {
423 self.variables.get(&sym).unwrap_or(&LogosType::Unknown)
424 }
425
426 pub fn lookup_fn(&self, sym: Symbol) -> Option<&FnSig> {
428 self.functions.get(&sym)
429 }
430
431 pub fn register(&mut self, sym: Symbol, ty: LogosType) {
433 self.variables.insert(sym, ty);
434 }
435
436 pub fn register_fn(&mut self, sym: Symbol, sig: FnSig) {
438 self.functions.insert(sym, sig);
439 }
440
441 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 BinaryOpKind::Eq
452 | BinaryOpKind::NotEq
453 | BinaryOpKind::Lt
454 | BinaryOpKind::Gt
455 | BinaryOpKind::LtEq
456 | BinaryOpKind::GtEq
457 | BinaryOpKind::ApproxEq => LogosType::Bool,
458
459 BinaryOpKind::And | BinaryOpKind::Or => LogosType::Bool,
461
462 BinaryOpKind::Concat => LogosType::String,
464
465 BinaryOpKind::SeqConcat => self.infer_expr(left, interner),
467
468 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 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(<, &rt)
485 }
486 }
487
488 BinaryOpKind::ExactDivide => LogosType::Rational,
491
492 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(<, &rt);
502 if matches!(op, BinaryOpKind::Divide) && promoted == LogosType::Decimal {
505 LogosType::Rational
506 } else {
507 promoted
508 }
509 }
510 }
511 }
512
513 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" | "convert" => LogosType::Quantity,
527 "money" => LogosType::Money,
528 "to_currency" => LogosType::Money,
530 "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 "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 if let Some(first) = args.first() {
556 self.infer_expr(first, interner)
557 } else {
558 LogosType::Unknown
559 }
560 }
561 "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 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 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 fn infer_stmts(&mut self, stmts: &[Stmt], interner: &Interner) {
672 for stmt in stmts {
673 self.infer_stmt(stmt, interner);
674 }
675 }
676
677 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 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 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 let param_types: Vec<(Symbol, LogosType)> = params
714 .iter()
715 .map(|(sym, ty)| (*sym, LogosType::from_type_expr(ty, interner)))
716 .collect();
717
718 for (sym, ty) in ¶m_types {
720 self.register(*sym, ty.clone());
721 }
722
723 self.infer_stmts(body, interner);
725
726 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 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 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 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 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 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 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 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 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 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
864pub 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 pub fn ident(&self, sym: Symbol) -> std::string::String {
879 crate::codegen::escape_rust_ident(self.interner.resolve(sym))
880 }
881
882 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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); 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 #[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, ®istry);
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, ®istry);
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, ®istry);
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, ®istry);
1494 assert_eq!(env.lookup(input), &LogosType::String);
1495 }
1496
1497 #[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 #[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 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 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}