1use crate::compiler::ast::*;
4use crate::compiler::resolve::SymbolTable;
5
6use std::collections::HashMap;
7use thiserror::Error;
8
9fn is_builtin_function(name: &str) -> bool {
11 matches!(
12 name,
13 "print"
14 | "len"
15 | "length"
16 | "append"
17 | "range"
18 | "to_string"
19 | "str"
20 | "to_int"
21 | "int"
22 | "to_float"
23 | "float"
24 | "type_of"
25 | "keys"
26 | "values"
27 | "contains"
28 | "join"
29 | "split"
30 | "trim"
31 | "upper"
32 | "lower"
33 | "replace"
34 | "abs"
35 | "min"
36 | "max"
37 | "hash"
38 | "not"
39 | "count"
40 | "matches"
41 | "slice"
42 | "sort"
43 | "reverse"
44 | "map"
45 | "filter"
46 | "reduce"
47 | "parallel"
48 | "race"
49 | "vote"
50 | "select"
51 | "timeout"
52 | "spawn"
53 | "resume"
54 )
55}
56
57fn is_doc_placeholder_var(name: &str) -> bool {
61 !name.is_empty() && !name.starts_with("__")
64}
65
66fn desugar_pipe_application(
67 input: &Expr,
68 stage: &Expr,
69 span: crate::compiler::tokens::Span,
70) -> Expr {
71 match stage {
72 Expr::Call(callee, args, call_span) => {
73 let mut call_args = Vec::with_capacity(args.len() + 1);
74 call_args.push(CallArg::Positional(input.clone()));
75 call_args.extend(args.clone());
76 Expr::Call(callee.clone(), call_args, *call_span)
77 }
78 Expr::ToolCall(callee, args, call_span) => {
79 let mut call_args = Vec::with_capacity(args.len() + 1);
80 call_args.push(CallArg::Positional(input.clone()));
81 call_args.extend(args.clone());
82 Expr::ToolCall(callee.clone(), call_args, *call_span)
83 }
84 _ => Expr::Call(
85 Box::new(stage.clone()),
86 vec![CallArg::Positional(input.clone())],
87 span,
88 ),
89 }
90}
91
92fn type_contains_any(ty: &Type) -> bool {
93 match ty {
94 Type::Any => true,
95 Type::List(inner) | Type::Set(inner) => type_contains_any(inner),
96 Type::Map(k, v) | Type::Result(k, v) => type_contains_any(k) || type_contains_any(v),
97 Type::Fn(params, ret) => params.iter().any(type_contains_any) || type_contains_any(ret),
98 Type::Union(types) | Type::Tuple(types) => types.iter().any(type_contains_any),
99 Type::TypeRef(_, args) => args.iter().any(type_contains_any),
100 _ => false,
101 }
102}
103
104fn edit_distance(a: &str, b: &str) -> usize {
106 let a_chars: Vec<char> = a.chars().collect();
107 let b_chars: Vec<char> = b.chars().collect();
108 let a_len = a_chars.len();
109 let b_len = b_chars.len();
110
111 if a_len == 0 {
112 return b_len;
113 }
114 if b_len == 0 {
115 return a_len;
116 }
117
118 let mut matrix = vec![vec![0; b_len + 1]; a_len + 1];
119
120 for (i, row) in matrix.iter_mut().enumerate() {
121 row[0] = i;
122 }
123 #[allow(clippy::needless_range_loop)]
124 for j in 0..=b_len {
125 matrix[0][j] = j;
126 }
127
128 for i in 1..=a_len {
129 for j in 1..=b_len {
130 let cost = if a_chars[i - 1] == b_chars[j - 1] {
131 0
132 } else {
133 1
134 };
135 matrix[i][j] = (matrix[i - 1][j] + 1)
136 .min(matrix[i][j - 1] + 1)
137 .min(matrix[i - 1][j - 1] + cost);
138 }
139 }
140
141 matrix[a_len][b_len]
142}
143
144fn suggest_similar(name: &str, candidates: &[&str], max_distance: usize) -> Vec<String> {
146 let mut matches: Vec<(usize, String)> = candidates
147 .iter()
148 .filter_map(|c| {
149 let d = edit_distance(name, c);
150 if d <= max_distance && d < name.len() {
151 Some((d, c.to_string()))
152 } else {
153 None
154 }
155 })
156 .collect();
157
158 matches.sort_by_key(|(d, _)| *d);
159 matches.into_iter().map(|(_, s)| s).take(3).collect()
160}
161
162#[derive(Debug, Error)]
163pub enum TypeError {
164 #[error("type mismatch at line {line}: expected {expected}, got {actual}")]
165 Mismatch {
166 expected: String,
167 actual: String,
168 line: usize,
169 },
170 #[error("undefined variable '{name}' at line {line}")]
171 UndefinedVar { name: String, line: usize },
172 #[error("not callable at line {line}")]
173 NotCallable { line: usize },
174 #[error("wrong number of arguments at line {line}: expected {expected}, got {actual}")]
175 ArgCount {
176 expected: usize,
177 actual: usize,
178 line: usize,
179 },
180 #[error("unknown field '{field}' on type '{ty}' at line {line}")]
181 UnknownField {
182 field: String,
183 ty: String,
184 line: usize,
185 suggestions: Vec<String>,
186 },
187 #[error("undefined type '{name}' at line {line}")]
188 UndefinedType { name: String, line: usize },
189 #[error("missing return in cell '{name}' at line {line}")]
190 MissingReturn { name: String, line: usize },
191 #[error("cannot assign to immutable variable '{name}' at line {line}")]
192 ImmutableAssign { name: String, line: usize },
193 #[error("incomplete match at line {line}: missing variants {missing:?}")]
194 IncompleteMatch {
195 enum_name: String,
196 missing: Vec<String>,
197 line: usize,
198 },
199}
200
201#[derive(Debug, Clone, PartialEq)]
203pub enum Type {
204 String,
205 Int,
206 Float,
207 Bool,
208 Bytes,
209 Json,
210 Null,
211 List(Box<Type>),
212 Map(Box<Type>, Box<Type>),
213 Record(String),
214 Enum(String),
215 Result(Box<Type>, Box<Type>),
216 Union(Vec<Type>),
217 Tuple(Vec<Type>),
218 Set(Box<Type>),
219 Fn(Vec<Type>, Box<Type>),
220 Generic(String),
221 TypeRef(String, Vec<Type>),
222 Any, }
224
225impl std::fmt::Display for Type {
226 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227 match self {
228 Type::String => write!(f, "String"),
229 Type::Int => write!(f, "Int"),
230 Type::Float => write!(f, "Float"),
231 Type::Bool => write!(f, "Bool"),
232 Type::Bytes => write!(f, "Bytes"),
233 Type::Json => write!(f, "Json"),
234 Type::Null => write!(f, "Null"),
235 Type::Any => write!(f, "Any"),
236 Type::List(t) => write!(f, "list[{}]", t),
237 Type::Map(k, v) => write!(f, "map[{}, {}]", k, v),
238 Type::Record(n) => write!(f, "{}", n),
239 Type::Enum(n) => write!(f, "{}", n),
240 Type::Result(o, e) => write!(f, "result[{}, {}]", o, e),
241 Type::Union(ts) => {
242 let parts: Vec<_> = ts.iter().map(|t| format!("{}", t)).collect();
243 write!(f, "{}", parts.join(" | "))
244 }
245 Type::Tuple(ts) => {
246 let parts: Vec<_> = ts.iter().map(|t| format!("{}", t)).collect();
247 write!(f, "({})", parts.join(", "))
248 }
249 Type::Set(t) => write!(f, "set[{}]", t),
250 Type::Fn(params, ret) => {
251 let ps: Vec<_> = params.iter().map(|t| format!("{}", t)).collect();
252 write!(f, "fn({}) -> {}", ps.join(", "), ret)
253 }
254 Type::Generic(n) => write!(f, "{}", n),
255 Type::TypeRef(n, args) => {
256 let as_: Vec<_> = args.iter().map(|t| format!("{}", t)).collect();
257 write!(f, "{}[{}]", n, as_.join(", "))
258 }
259 }
260 }
261}
262
263pub type TypeSubst = HashMap<String, Type>;
265
266fn build_subst(params: &[String], args: &[Type]) -> TypeSubst {
268 params
269 .iter()
270 .zip(args.iter())
271 .map(|(p, a)| (p.clone(), a.clone()))
272 .collect()
273}
274
275fn infer_generic_args_from_fields(
277 generic_params: &[String],
278 field_defs: &[FieldDef],
279 field_values: &[(String, Expr)],
280 symbols: &SymbolTable,
281 checker: &mut TypeChecker,
282) -> Vec<Type> {
283 let mut inferred: HashMap<String, Type> = HashMap::new();
284
285 for (fname, fval) in field_values {
286 let val_type = checker.infer_expr(fval);
287 if let Some(field_def) = field_defs.iter().find(|f| f.name == *fname) {
288 unify_for_inference(&field_def.ty, &val_type, symbols, &mut inferred);
290 }
291 }
292
293 generic_params
295 .iter()
296 .map(|p| inferred.get(p).cloned().unwrap_or(Type::Any))
297 .collect()
298}
299
300fn unify_for_inference(
302 type_expr: &TypeExpr,
303 concrete: &Type,
304 _symbols: &SymbolTable,
305 inferred: &mut HashMap<String, Type>,
306) {
307 match (type_expr, concrete) {
308 (TypeExpr::Named(name, _), ty) => {
309 if name.len() == 1 && name.chars().next().unwrap().is_uppercase() {
312 inferred.entry(name.clone()).or_insert_with(|| ty.clone());
313 }
314 }
315 (TypeExpr::List(inner, _), Type::List(inner_ty)) => {
316 unify_for_inference(inner, inner_ty, _symbols, inferred);
317 }
318 (TypeExpr::Map(k, v, _), Type::Map(kt, vt)) => {
319 unify_for_inference(k, kt, _symbols, inferred);
320 unify_for_inference(v, vt, _symbols, inferred);
321 }
322 (TypeExpr::Set(inner, _), Type::Set(inner_ty)) => {
323 unify_for_inference(inner, inner_ty, _symbols, inferred);
324 }
325 (TypeExpr::Result(ok, err, _), Type::Result(ok_ty, err_ty)) => {
326 unify_for_inference(ok, ok_ty, _symbols, inferred);
327 unify_for_inference(err, err_ty, _symbols, inferred);
328 }
329 (TypeExpr::Tuple(exprs, _), Type::Tuple(types)) => {
330 for (expr, ty) in exprs.iter().zip(types.iter()) {
331 unify_for_inference(expr, ty, _symbols, inferred);
332 }
333 }
334 _ => {
335 }
337 }
338}
339
340pub fn resolve_type_expr(ty: &TypeExpr, symbols: &SymbolTable) -> Type {
342 resolve_type_expr_with_subst(ty, symbols, &HashMap::new())
343}
344
345fn resolve_type_expr_with_subst(ty: &TypeExpr, symbols: &SymbolTable, subst: &TypeSubst) -> Type {
347 match ty {
348 TypeExpr::Named(name, _) => {
349 if let Some(concrete_type) = subst.get(name) {
351 return concrete_type.clone();
352 }
353 match name.as_str() {
354 "String" => Type::String,
355 "Int" => Type::Int,
356 "Float" => Type::Float,
357 "Bool" => Type::Bool,
358 "Bytes" => Type::Bytes,
359 "Json" => Type::Json,
360 _ => {
361 if symbols.types.contains_key(name) {
362 use crate::compiler::resolve::TypeInfoKind;
363 match &symbols.types[name].kind {
364 TypeInfoKind::Record(_) => Type::Record(name.clone()),
365 TypeInfoKind::Enum(_) => Type::Enum(name.clone()),
366 TypeInfoKind::Builtin => Type::Record(name.clone()),
367 }
368 } else if let Some(alias_target) = symbols.type_aliases.get(name) {
369 resolve_type_expr_with_subst(alias_target, symbols, subst)
370 } else {
371 Type::Any
372 }
373 }
374 }
375 }
376 TypeExpr::List(inner, _) => Type::List(Box::new(resolve_type_expr_with_subst(
377 inner, symbols, subst,
378 ))),
379 TypeExpr::Map(k, v, _) => Type::Map(
380 Box::new(resolve_type_expr_with_subst(k, symbols, subst)),
381 Box::new(resolve_type_expr_with_subst(v, symbols, subst)),
382 ),
383 TypeExpr::Result(ok, err, _) => Type::Result(
384 Box::new(resolve_type_expr_with_subst(ok, symbols, subst)),
385 Box::new(resolve_type_expr_with_subst(err, symbols, subst)),
386 ),
387 TypeExpr::Union(types, _) => Type::Union(
388 types
389 .iter()
390 .map(|t| resolve_type_expr_with_subst(t, symbols, subst))
391 .collect(),
392 ),
393 TypeExpr::Null(_) => Type::Null,
394 TypeExpr::Tuple(types, _) => Type::Tuple(
395 types
396 .iter()
397 .map(|t| resolve_type_expr_with_subst(t, symbols, subst))
398 .collect(),
399 ),
400 TypeExpr::Set(inner, _) => Type::Set(Box::new(resolve_type_expr_with_subst(
401 inner, symbols, subst,
402 ))),
403 TypeExpr::Fn(params, ret, _, _) => {
404 let param_types = params
405 .iter()
406 .map(|t| resolve_type_expr_with_subst(t, symbols, subst))
407 .collect();
408 let ret_type = resolve_type_expr_with_subst(ret, symbols, subst);
409 Type::Fn(param_types, Box::new(ret_type))
410 }
411 TypeExpr::Generic(name, args, _) => {
412 if let Some(type_info) = symbols.types.get(name) {
414 let generic_params = &type_info.generic_params;
415
416 let arg_types: Vec<_> = args
418 .iter()
419 .map(|t| resolve_type_expr_with_subst(t, symbols, subst))
420 .collect();
421
422 if generic_params.len() == arg_types.len() {
424 Type::TypeRef(name.clone(), arg_types)
425 } else {
426 Type::TypeRef(name.clone(), arg_types)
428 }
429 } else {
430 let arg_types: Vec<_> = args
432 .iter()
433 .map(|t| resolve_type_expr_with_subst(t, symbols, subst))
434 .collect();
435 Type::TypeRef(name.clone(), arg_types)
436 }
437 }
438 }
439}
440
441struct TypeChecker<'a> {
442 symbols: &'a SymbolTable,
443 allow_placeholders: bool,
444 locals: HashMap<String, Type>,
445 mutables: HashMap<String, bool>,
446 errors: Vec<TypeError>,
447}
448
449#[derive(Debug)]
450enum CheckedCallArg {
451 Positional(Type, usize),
452 Named(String, Type, usize),
453}
454
455impl<'a> TypeChecker<'a> {
456 fn new(symbols: &'a SymbolTable, allow_placeholders: bool) -> Self {
457 Self {
458 symbols,
459 allow_placeholders,
460 locals: HashMap::new(),
461 mutables: HashMap::new(),
462 errors: Vec::new(),
463 }
464 }
465
466 fn check_cell(&mut self, cell: &CellDef) {
467 self.locals.clear();
468 self.mutables.clear();
469 for p in &cell.params {
470 let ty = resolve_type_expr(&p.ty, self.symbols);
471 self.locals.insert(p.name.clone(), ty);
472 self.mutables.insert(p.name.clone(), true); }
474 let return_type = if let Some(ref rt) = cell.return_type {
475 Some(resolve_type_expr(rt, self.symbols))
476 } else {
477 None
478 };
479
480 for stmt in &cell.body {
481 self.check_stmt(stmt, return_type.as_ref());
482 }
483 }
484
485 fn check_agent_cell(&mut self, cell: &CellDef) {
486 self.locals.clear();
487 self.mutables.clear();
488 self.locals.insert("self".into(), Type::Any);
489 self.mutables.insert("self".into(), true);
490 for p in &cell.params {
491 if p.name == "self" {
492 continue;
493 }
494 let ty = resolve_type_expr(&p.ty, self.symbols);
495 self.locals.insert(p.name.clone(), ty);
496 self.mutables.insert(p.name.clone(), true);
497 }
498 let return_type = if let Some(ref rt) = cell.return_type {
499 Some(resolve_type_expr(rt, self.symbols))
500 } else {
501 None
502 };
503 for stmt in &cell.body {
504 self.check_stmt(stmt, return_type.as_ref());
505 }
506 }
507
508 fn check_call_against_signature(
509 &mut self,
510 params: &[(String, TypeExpr)],
511 args: &[CheckedCallArg],
512 line: usize,
513 ) {
514 if args.len() > params.len() {
515 self.errors.push(TypeError::ArgCount {
516 expected: params.len(),
517 actual: args.len(),
518 line,
519 });
520 }
521
522 let mut positional_idx = 0usize;
523 for arg in args {
524 match arg {
525 CheckedCallArg::Positional(actual_ty, arg_line) => {
526 if let Some((_, expected_expr)) = params.get(positional_idx) {
527 let expected_ty = resolve_type_expr(expected_expr, self.symbols);
528 self.check_compat(&expected_ty, actual_ty, *arg_line);
529 }
530 positional_idx += 1;
531 }
532 CheckedCallArg::Named(name, actual_ty, arg_line) => {
533 if let Some((_, expected_expr)) = params.iter().find(|(p, _)| p == name) {
534 let expected_ty = resolve_type_expr(expected_expr, self.symbols);
535 self.check_compat(&expected_ty, actual_ty, *arg_line);
536 } else {
537 self.errors.push(TypeError::Mismatch {
538 expected: format!("parameter '{}'", name),
539 actual: "unknown named argument".to_string(),
540 line: *arg_line,
541 });
542 }
543 }
544 }
545 }
546 }
547
548 fn check_stmt(&mut self, stmt: &Stmt, expected_return: Option<&Type>) {
549 match stmt {
550 Stmt::Let(ls) => {
551 let val_type = self.infer_expr(&ls.value);
552 if let Some(ref ann) = ls.ty {
553 let expected = resolve_type_expr(ann, self.symbols);
554 self.check_compat(&expected, &val_type, ls.span.line);
555 }
556 self.locals.insert(ls.name.clone(), val_type);
557 self.mutables.insert(ls.name.clone(), true);
560 }
561 Stmt::If(ifs) => {
562 let ct = self.infer_expr(&ifs.condition);
563 self.check_compat(&Type::Bool, &ct, ifs.span.line);
564 for s in &ifs.then_body {
565 self.check_stmt(s, expected_return);
566 }
567 if let Some(ref eb) = ifs.else_body {
568 for s in eb {
569 self.check_stmt(s, expected_return);
570 }
571 }
572 }
573 Stmt::For(fs) => {
574 let iter_type = self.infer_expr(&fs.iter);
575 let elem_type = match &iter_type {
576 Type::List(inner) => *inner.clone(),
577 Type::Set(inner) => *inner.clone(),
578 Type::Map(k, _) => *k.clone(),
579 Type::Any => Type::Any,
580 _ => {
581 self.errors.push(TypeError::Mismatch {
582 expected: "iterable".into(),
583 actual: format!("{}", iter_type),
584 line: fs.span.line,
585 });
586 Type::Any
587 }
588 };
589 self.locals.insert(fs.var.clone(), elem_type);
590 if let Some(filter) = &fs.filter {
591 self.infer_expr(filter);
592 }
593 for s in &fs.body {
594 self.check_stmt(s, expected_return);
595 }
596 }
597 Stmt::Match(ms) => {
598 let subject_type = self.infer_expr(&ms.subject);
599 let mut covered_variants = Vec::new();
600 let mut has_catchall = false;
601
602 for arm in &ms.arms {
603 self.bind_match_pattern(
604 &arm.pattern,
605 &subject_type,
606 &mut covered_variants,
607 &mut has_catchall,
608 arm.span.line,
609 );
610 for s in &arm.body {
611 self.check_stmt(s, expected_return);
612 }
613 }
614
615 if let Type::Enum(ref name) = subject_type {
617 if !has_catchall {
618 if let Some(ti) = self.symbols.types.get(name) {
619 if let crate::compiler::resolve::TypeInfoKind::Enum(def) = &ti.kind {
620 let missing: Vec<_> = def
621 .variants
622 .iter()
623 .filter(|v| !covered_variants.contains(&v.name))
624 .map(|v| v.name.clone())
625 .collect();
626 if !missing.is_empty() {
627 self.errors.push(TypeError::IncompleteMatch {
628 enum_name: name.clone(),
629 missing,
630 line: ms.span.line,
631 });
632 }
633 }
634 }
635 }
636 }
637 }
638 Stmt::Return(rs) => {
639 let val_type = self.infer_expr(&rs.value);
640 if let Some(expected) = expected_return {
641 self.check_compat(expected, &val_type, rs.span.line);
642 }
643 }
644 Stmt::Halt(hs) => {
645 self.infer_expr(&hs.message);
646 }
647 Stmt::Assign(asgn) => {
648 let val_type = self.infer_expr(&asgn.value);
649 if let Some(&is_mut) = self.mutables.get(&asgn.target) {
651 if !is_mut {
652 self.errors.push(TypeError::ImmutableAssign {
653 name: asgn.target.clone(),
654 line: asgn.span.line,
655 });
656 }
657 }
658 self.locals.insert(asgn.target.clone(), val_type);
659 }
660 Stmt::Expr(es) => {
661 self.infer_expr(&es.expr);
662 }
663 Stmt::While(ws) => {
664 let ct = self.infer_expr(&ws.condition);
665 self.check_compat(&Type::Bool, &ct, ws.span.line);
666 for s in &ws.body {
667 self.check_stmt(s, expected_return);
668 }
669 }
670 Stmt::Loop(ls) => {
671 for s in &ls.body {
672 self.check_stmt(s, expected_return);
673 }
674 }
675 Stmt::Break(_) | Stmt::Continue(_) => {}
676 Stmt::Defer(ds) => {
677 for s in &ds.body {
678 self.check_stmt(s, expected_return);
679 }
680 }
681 Stmt::Emit(es) => {
682 self.infer_expr(&es.value);
683 }
684 Stmt::CompoundAssign(ca) => {
685 let val_type = self.infer_expr(&ca.value);
686 if let Some(&is_mut) = self.mutables.get(&ca.target) {
688 if !is_mut {
689 self.errors.push(TypeError::ImmutableAssign {
690 name: ca.target.clone(),
691 line: ca.span.line,
692 });
693 }
694 }
695 if let Some(existing) = self.locals.get(&ca.target).cloned() {
696 match ca.op {
698 CompoundOp::BitAndAssign
699 | CompoundOp::BitOrAssign
700 | CompoundOp::BitXorAssign => {
701 if existing != Type::Any && existing != Type::Int {
702 self.errors.push(TypeError::Mismatch {
703 expected: "Int".into(),
704 actual: format!("{}", existing),
705 line: ca.span.line,
706 });
707 }
708 if val_type != Type::Any && val_type != Type::Int {
709 self.errors.push(TypeError::Mismatch {
710 expected: "Int".into(),
711 actual: format!("{}", val_type),
712 line: ca.span.line,
713 });
714 }
715 }
716 _ => {
717 self.check_compat(&existing, &val_type, ca.span.line);
718 }
719 }
720 }
721 }
722 }
723 }
724
725 fn bind_match_pattern(
726 &mut self,
727 pattern: &Pattern,
728 subject_type: &Type,
729 covered_variants: &mut Vec<String>,
730 has_catchall: &mut bool,
731 line: usize,
732 ) {
733 match pattern {
734 Pattern::Variant(tag, binding, _) => {
735 let mut valid_variant = false;
736 let mut payload_type = Type::Any;
737 let mut expects_payload = false;
738
739 if let Type::Enum(ref name) = subject_type {
740 if let Some(ti) = self.symbols.types.get(name) {
741 if let crate::compiler::resolve::TypeInfoKind::Enum(def) = &ti.kind {
742 if let Some(variant) = def.variants.iter().find(|v| v.name == *tag) {
743 valid_variant = true;
744 covered_variants.push(tag.clone());
745 if let Some(payload) = &variant.payload {
746 expects_payload = true;
747 payload_type = resolve_type_expr(payload, self.symbols);
748 } else {
749 payload_type = Type::Null;
750 }
751 }
752 }
753 }
754 if !valid_variant {
755 self.errors.push(TypeError::Mismatch {
756 expected: format!("variant of {}", name),
757 actual: tag.clone(),
758 line,
759 });
760 }
761 } else if let Type::Result(ref ok, ref err) = subject_type {
762 if tag == "ok" {
763 valid_variant = true;
764 expects_payload = true;
765 payload_type = *ok.clone();
766 } else if tag == "err" {
767 valid_variant = true;
768 expects_payload = true;
769 payload_type = *err.clone();
770 }
771 if !valid_variant {
772 self.errors.push(TypeError::Mismatch {
773 expected: "ok or err".into(),
774 actual: tag.clone(),
775 line,
776 });
777 }
778 }
779
780 if let Some(inner_pattern) = binding {
781 if valid_variant && !expects_payload {
782 self.errors.push(TypeError::Mismatch {
783 expected: format!("{} without payload", tag),
784 actual: format!("{}(...)", tag),
785 line,
786 });
787 }
788 self.bind_match_pattern(
789 inner_pattern,
790 &payload_type,
791 covered_variants,
792 has_catchall,
793 line,
794 );
795 }
796 }
797 Pattern::Ident(name, _) => {
798 let mut is_variant = false;
800 if let Type::Enum(ref enum_name) = subject_type {
801 if let Some(ti) = self.symbols.types.get(enum_name) {
802 if let crate::compiler::resolve::TypeInfoKind::Enum(def) = &ti.kind {
803 if def.variants.iter().any(|v| v.name == *name) {
804 covered_variants.push(name.clone());
806 is_variant = true;
807 }
808 }
809 }
810 }
811
812 if !is_variant {
813 self.locals.insert(name.clone(), subject_type.clone());
815 *has_catchall = true;
816 }
817 }
818 Pattern::Wildcard(_) => {
819 *has_catchall = true;
820 }
821 Pattern::Guard {
822 inner, condition, ..
823 } => {
824 let mut _guarded_variants = Vec::new();
828 let mut _guarded_catchall = false;
829 self.bind_match_pattern(
830 inner,
831 subject_type,
832 &mut _guarded_variants,
833 &mut _guarded_catchall,
834 line,
835 );
836 let guard_ty = self.infer_expr(condition);
837 self.check_compat(&Type::Bool, &guard_ty, line);
838 }
839 Pattern::Or { patterns, .. } => {
840 for p in patterns {
841 self.bind_match_pattern(p, subject_type, covered_variants, has_catchall, line);
842 }
843 }
844 Pattern::ListDestructure { elements, rest, .. } => {
845 let elem_type = match subject_type {
846 Type::List(inner) => *inner.clone(),
847 Type::Any => Type::Any,
848 other => {
849 self.errors.push(TypeError::Mismatch {
850 expected: "List".into(),
851 actual: format!("{}", other),
852 line,
853 });
854 Type::Any
855 }
856 };
857 for p in elements {
858 self.bind_match_pattern(p, &elem_type, covered_variants, has_catchall, line);
859 }
860 if let Some(rest_name) = rest {
861 self.locals
862 .insert(rest_name.clone(), Type::List(Box::new(elem_type)));
863 }
864 }
865 Pattern::TupleDestructure { elements, .. } => match subject_type {
866 Type::Tuple(types) => {
867 for (idx, p) in elements.iter().enumerate() {
868 let ty = types.get(idx).cloned().unwrap_or(Type::Any);
869 self.bind_match_pattern(p, &ty, covered_variants, has_catchall, line);
870 }
871 }
872 Type::Any => {
873 for p in elements {
874 self.bind_match_pattern(
875 p,
876 &Type::Any,
877 covered_variants,
878 has_catchall,
879 line,
880 );
881 }
882 }
883 other => {
884 self.errors.push(TypeError::Mismatch {
885 expected: "Tuple".into(),
886 actual: format!("{}", other),
887 line,
888 });
889 }
890 },
891 Pattern::RecordDestructure {
892 type_name,
893 fields,
894 open: _,
895 ..
896 } => {
897 if let Type::Record(actual_name) = subject_type {
898 if actual_name != type_name {
899 self.errors.push(TypeError::Mismatch {
900 expected: type_name.clone(),
901 actual: actual_name.clone(),
902 line,
903 });
904 }
905 }
906 for (field_name, field_pat) in fields {
907 let field_ty = if let Some(ti) = self.symbols.types.get(type_name) {
908 if let crate::compiler::resolve::TypeInfoKind::Record(def) = &ti.kind {
909 if let Some(field) = def.fields.iter().find(|f| f.name == *field_name) {
910 resolve_type_expr(&field.ty, self.symbols)
911 } else {
912 Type::Any
913 }
914 } else {
915 Type::Any
916 }
917 } else {
918 Type::Any
919 };
920 if let Some(p) = field_pat {
921 self.bind_match_pattern(p, &field_ty, covered_variants, has_catchall, line);
922 } else {
923 self.locals.insert(field_name.clone(), field_ty);
924 }
925 }
926 }
927 Pattern::TypeCheck {
928 name, type_expr, ..
929 } => {
930 let expected = resolve_type_expr(type_expr, self.symbols);
931 self.check_compat(&expected, subject_type, line);
932 self.locals.insert(name.clone(), expected);
933 }
934 Pattern::Literal(_) => {}
935 }
936 }
937
938 fn infer_expr(&mut self, expr: &Expr) -> Type {
939 match expr {
940 Expr::IntLit(_, _) => Type::Int,
941 Expr::FloatLit(_, _) => Type::Float,
942 Expr::StringLit(_, _) => Type::String,
943 Expr::StringInterp(_, _) => Type::String,
944 Expr::BoolLit(_, _) => Type::Bool,
945 Expr::NullLit(_) => Type::Null,
946 Expr::Ident(name, span) => {
947 if let Some(ty) = self.locals.get(name) {
948 ty.clone()
949 } else if let Some(const_info) = self.symbols.consts.get(name) {
950 if let Some(ref ty) = const_info.ty {
951 resolve_type_expr(ty, self.symbols)
952 } else if let Some(ref val) = const_info.value {
953 match val {
954 Expr::IntLit(_, _) => Type::Int,
955 Expr::FloatLit(_, _) => Type::Float,
956 Expr::StringLit(_, _) | Expr::StringInterp(_, _) => Type::String,
957 Expr::BoolLit(_, _) => Type::Bool,
958 Expr::NullLit(_) => Type::Null,
959 Expr::ListLit(_, _) => Type::List(Box::new(Type::Any)),
960 Expr::MapLit(_, _) => {
961 Type::Map(Box::new(Type::String), Box::new(Type::Any))
962 }
963 _ => Type::Any,
964 }
965 } else {
966 Type::Any
967 }
968 }
969 else if self.symbols.cells.contains_key(name)
971 || self.symbols.tools.contains_key(name)
972 || self.symbols.agents.contains_key(name)
973 || self
974 .symbols
975 .addons
976 .iter()
977 .any(|a| a.name.as_deref() == Some(name.as_str()))
978 || self.symbols.types.contains_key(name)
979 || self.symbols.type_aliases.contains_key(name)
980 || is_builtin_function(name)
981 {
982 Type::Any
983 }
984 else if name == "null" {
986 Type::Null
987 } else if self.allow_placeholders && is_doc_placeholder_var(name) {
988 Type::Any
989 } else {
990 let mut found_enum = None;
992 for (type_name, type_info) in &self.symbols.types {
993 if let crate::compiler::resolve::TypeInfoKind::Enum(def) = &type_info.kind {
994 if def.variants.iter().any(|v| v.name == *name) {
995 found_enum = Some(Type::Enum(type_name.clone()));
996 break;
997 }
998 }
999 }
1000 if let Some(ty) = found_enum {
1001 ty
1002 } else {
1003 self.errors.push(TypeError::UndefinedVar {
1004 name: name.clone(),
1005 line: span.line,
1006 });
1007 Type::Any
1008 }
1009 }
1010 }
1011 Expr::ListLit(elems, _) => {
1012 if elems.is_empty() {
1013 Type::List(Box::new(Type::Any))
1014 } else {
1015 let first = self.infer_expr(&elems[0]);
1016 for e in &elems[1..] {
1017 self.infer_expr(e);
1018 }
1019 Type::List(Box::new(first))
1020 }
1021 }
1022 Expr::MapLit(pairs, _) => {
1023 if pairs.is_empty() {
1024 Type::Map(Box::new(Type::String), Box::new(Type::Any))
1025 } else {
1026 let kt = self.infer_expr(&pairs[0].0);
1027 let vt = self.infer_expr(&pairs[0].1);
1028 for (k, v) in &pairs[1..] {
1029 self.infer_expr(k);
1030 self.infer_expr(v);
1031 }
1032 Type::Map(Box::new(kt), Box::new(vt))
1033 }
1034 }
1035 Expr::RecordLit(name, fields, span) => {
1036 if let Some(ti) = self.symbols.types.get(name) {
1037 if let crate::compiler::resolve::TypeInfoKind::Record(def) = &ti.kind {
1038 let generic_args = if !ti.generic_params.is_empty() {
1040 infer_generic_args_from_fields(
1041 &ti.generic_params,
1042 &def.fields,
1043 fields,
1044 self.symbols,
1045 self,
1046 )
1047 } else {
1048 vec![]
1049 };
1050
1051 let subst = build_subst(&ti.generic_params, &generic_args);
1053
1054 for (fname, fval) in fields {
1056 let val_type = self.infer_expr(fval);
1057 if let Some(field_def) = def.fields.iter().find(|f| f.name == *fname) {
1058 let expected = resolve_type_expr_with_subst(
1059 &field_def.ty,
1060 self.symbols,
1061 &subst,
1062 );
1063 self.check_compat(&expected, &val_type, span.line);
1064 } else {
1065 let field_names: Vec<&str> =
1066 def.fields.iter().map(|f| f.name.as_str()).collect();
1067 let suggestions = suggest_similar(fname, &field_names, 2);
1068 self.errors.push(TypeError::UnknownField {
1069 field: fname.clone(),
1070 ty: name.clone(),
1071 line: span.line,
1072 suggestions,
1073 });
1074 }
1075 }
1076 for field_def in &def.fields {
1078 if field_def.default_value.is_none()
1079 && !fields.iter().any(|(fname, _)| fname == &field_def.name)
1080 {
1081 self.errors.push(TypeError::Mismatch {
1082 expected: format!("field '{}'", field_def.name),
1083 actual: "missing".into(),
1084 line: span.line,
1085 });
1086 }
1087 }
1088
1089 if !generic_args.is_empty() {
1091 Type::TypeRef(name.clone(), generic_args)
1092 } else {
1093 Type::Record(name.clone())
1094 }
1095 } else {
1096 Type::Record(name.clone())
1097 }
1098 } else {
1099 self.errors.push(TypeError::UndefinedType {
1100 name: name.clone(),
1101 line: span.line,
1102 });
1103 Type::Record(name.clone())
1104 }
1105 }
1106 Expr::BinOp(lhs, op, rhs, _span) => {
1107 let lt = self.infer_expr(lhs);
1108 let rt = self.infer_expr(rhs);
1109 match op {
1110 BinOp::Add
1111 | BinOp::Sub
1112 | BinOp::Mul
1113 | BinOp::Div
1114 | BinOp::FloorDiv
1115 | BinOp::Mod => {
1116 if lt == Type::Any || rt == Type::Any {
1117 Type::Any
1118 } else if (lt == Type::String || rt == Type::String) && *op == BinOp::Add {
1119 Type::String
1120 } else if lt == Type::Float || rt == Type::Float {
1121 Type::Float
1122 } else {
1123 Type::Int
1124 }
1125 }
1126 BinOp::Eq
1127 | BinOp::NotEq
1128 | BinOp::Lt
1129 | BinOp::LtEq
1130 | BinOp::Gt
1131 | BinOp::GtEq => Type::Bool,
1132 BinOp::And | BinOp::Or => Type::Bool,
1133 BinOp::Pow => {
1134 if lt == Type::Float || rt == Type::Float {
1135 Type::Float
1136 } else {
1137 Type::Int
1138 }
1139 }
1140 BinOp::PipeForward => rt,
1141 BinOp::Concat => lt,
1142 BinOp::In => Type::Bool,
1143 BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor => Type::Int,
1144 BinOp::Shl | BinOp::Shr => {
1145 if lt != Type::Any && lt != Type::Int {
1146 self.errors.push(TypeError::Mismatch {
1147 expected: "Int".into(),
1148 actual: format!("{}", lt),
1149 line: _span.line,
1150 });
1151 }
1152 if rt != Type::Any && rt != Type::Int {
1153 self.errors.push(TypeError::Mismatch {
1154 expected: "Int".into(),
1155 actual: format!("{}", rt),
1156 line: _span.line,
1157 });
1158 }
1159 Type::Int
1160 }
1161 }
1162 }
1163 Expr::Pipe { left, right, span } => {
1164 let call_expr = desugar_pipe_application(left, right, *span);
1165 self.infer_expr(&call_expr)
1166 }
1167 Expr::Illuminate {
1168 input,
1169 transform,
1170 span,
1171 } => {
1172 let call_expr = desugar_pipe_application(input, transform, *span);
1173 self.infer_expr(&call_expr)
1174 }
1175 Expr::UnaryOp(op, inner, _) => {
1176 let t = self.infer_expr(inner);
1177 match op {
1178 UnaryOp::Neg => t,
1179 UnaryOp::Not => Type::Bool,
1180 UnaryOp::BitNot => Type::Int,
1181 }
1182 }
1183 Expr::Call(callee, args, span) => {
1184 let mut checked_args = Vec::new();
1185 for arg in args {
1186 match arg {
1187 CallArg::Positional(e) => {
1188 let ty = self.infer_expr(e);
1189 checked_args.push(CheckedCallArg::Positional(ty, e.span().line));
1190 }
1191 CallArg::Named(name, e, _) => {
1192 let ty = self.infer_expr(e);
1193 checked_args.push(CheckedCallArg::Named(
1194 name.clone(),
1195 ty,
1196 e.span().line,
1197 ));
1198 }
1199 CallArg::Role(_, _, _) => {}
1200 }
1201 }
1202 if let Expr::Ident(name, _) = callee.as_ref() {
1204 if let Some(ci) = self.symbols.cells.get(name) {
1206 self.check_call_against_signature(&ci.params, &checked_args, span.line);
1207 if let Some(ref rt) = ci.return_type {
1208 return resolve_type_expr(rt, self.symbols);
1209 }
1210 }
1211 else if let Some(ti) = self.symbols.types.get(name) {
1213 if let crate::compiler::resolve::TypeInfoKind::Record(def) = &ti.kind {
1214 let generic_args = if !ti.generic_params.is_empty() {
1216 let mut inferred: HashMap<String, Type> = HashMap::new();
1218 for checked_arg in &checked_args {
1219 if let CheckedCallArg::Named(fname, arg_ty, _) = checked_arg {
1220 if let Some(field_def) =
1221 def.fields.iter().find(|f| f.name == *fname)
1222 {
1223 unify_for_inference(
1224 &field_def.ty,
1225 arg_ty,
1226 self.symbols,
1227 &mut inferred,
1228 );
1229 }
1230 }
1231 }
1232
1233 ti.generic_params
1234 .iter()
1235 .map(|p| inferred.get(p).cloned().unwrap_or(Type::Any))
1236 .collect()
1237 } else {
1238 vec![]
1239 };
1240
1241 let subst = build_subst(&ti.generic_params, &generic_args);
1243
1244 for checked_arg in &checked_args {
1246 if let CheckedCallArg::Named(fname, arg_ty, line) = checked_arg {
1247 if let Some(field_def) =
1248 def.fields.iter().find(|f| f.name == *fname)
1249 {
1250 let expected = resolve_type_expr_with_subst(
1251 &field_def.ty,
1252 self.symbols,
1253 &subst,
1254 );
1255 self.check_compat(&expected, arg_ty, *line);
1256 } else {
1257 let field_names: Vec<&str> =
1258 def.fields.iter().map(|f| f.name.as_str()).collect();
1259 let suggestions = suggest_similar(fname, &field_names, 2);
1260 self.errors.push(TypeError::UnknownField {
1261 field: fname.clone(),
1262 ty: name.clone(),
1263 line: *line,
1264 suggestions,
1265 });
1266 }
1267 }
1268 }
1269
1270 return if !generic_args.is_empty() {
1272 Type::TypeRef(name.clone(), generic_args)
1273 } else {
1274 Type::Record(name.clone())
1275 };
1276 }
1277 }
1278 }
1279 Type::Any
1280 }
1281 Expr::ToolCall(_, args, _) => {
1282 for arg in args {
1283 match arg {
1284 CallArg::Positional(e) | CallArg::Named(_, e, _) => {
1285 self.infer_expr(e);
1286 }
1287 _ => {}
1288 }
1289 }
1290 Type::Any
1291 }
1292 Expr::DotAccess(obj, field, _span) => {
1293 let ot = self.infer_expr(obj);
1294 match &ot {
1295 Type::Record(ref name) => {
1296 if let Some(ti) = self.symbols.types.get(name) {
1297 if let crate::compiler::resolve::TypeInfoKind::Record(ref rd) = ti.kind
1298 {
1299 if let Some(f) = rd.fields.iter().find(|f| f.name == *field) {
1300 return resolve_type_expr(&f.ty, self.symbols);
1301 }
1302 }
1303 }
1304 }
1305 Type::TypeRef(ref name, ref args) => {
1306 if let Some(ti) = self.symbols.types.get(name) {
1308 let subst = build_subst(&ti.generic_params, args);
1309 if let crate::compiler::resolve::TypeInfoKind::Record(ref rd) = ti.kind
1310 {
1311 if let Some(f) = rd.fields.iter().find(|f| f.name == *field) {
1312 return resolve_type_expr_with_subst(
1313 &f.ty,
1314 self.symbols,
1315 &subst,
1316 );
1317 }
1318 }
1319 }
1320 }
1321 _ => {}
1322 }
1323 Type::Any
1324 }
1325 Expr::IndexAccess(obj, idx, _) => {
1326 let ot = self.infer_expr(obj);
1327 self.infer_expr(idx);
1328 match ot {
1329 Type::List(inner) => *inner,
1330 Type::Map(_, v) => *v,
1331 _ => Type::Any,
1332 }
1333 }
1334 Expr::RoleBlock(_, content, _) => {
1335 self.infer_expr(content);
1336 Type::String
1337 }
1338 Expr::ExpectSchema(inner, schema_name, _) => {
1339 self.infer_expr(inner);
1340 if self.symbols.types.contains_key(schema_name) {
1341 Type::Record(schema_name.clone())
1342 } else {
1343 Type::Any
1344 }
1345 }
1346 Expr::RawStringLit(_, _) => Type::String,
1347 Expr::BytesLit(_, _) => Type::Bytes,
1348 Expr::Lambda {
1349 params,
1350 return_type,
1351 body,
1352 ..
1353 } => {
1354 let saved_locals = self.locals.clone();
1355 let saved_mutables = self.mutables.clone();
1356 let mut param_types = Vec::new();
1357 for p in params {
1358 let pt = resolve_type_expr(&p.ty, self.symbols);
1359 if let Some(ref def) = p.default_value {
1360 self.infer_expr(def);
1361 }
1362 self.locals.insert(p.name.clone(), pt.clone());
1363 self.mutables.insert(p.name.clone(), true);
1364 param_types.push(pt);
1365 }
1366 let ret = if let Some(ref rt) = return_type {
1367 resolve_type_expr(rt, self.symbols)
1368 } else {
1369 match body {
1370 LambdaBody::Expr(e) => self.infer_expr(e),
1371 LambdaBody::Block(stmts) => {
1372 for s in stmts {
1373 self.check_stmt(s, None);
1374 }
1375 Type::Any
1376 }
1377 }
1378 };
1379 self.locals = saved_locals;
1380 self.mutables = saved_mutables;
1381 Type::Fn(param_types, Box::new(ret))
1382 }
1383 Expr::TupleLit(elems, _) => {
1384 let types: Vec<_> = elems.iter().map(|e| self.infer_expr(e)).collect();
1385 Type::Tuple(types)
1386 }
1387 Expr::SetLit(elems, _) => {
1388 if elems.is_empty() {
1389 Type::Set(Box::new(Type::Any))
1390 } else {
1391 let first = self.infer_expr(&elems[0]);
1392 for e in &elems[1..] {
1393 self.infer_expr(e);
1394 }
1395 Type::Set(Box::new(first))
1396 }
1397 }
1398 Expr::RangeExpr {
1399 start, end, step, ..
1400 } => {
1401 if let Some(ref s) = start {
1402 self.infer_expr(s);
1403 }
1404 if let Some(ref e) = end {
1405 self.infer_expr(e);
1406 }
1407 if let Some(ref st) = step {
1408 self.infer_expr(st);
1409 }
1410 Type::List(Box::new(Type::Int))
1411 }
1412 Expr::TryExpr(inner, _) => {
1413 let t = self.infer_expr(inner);
1414 if let Type::Result(ok, _) = t {
1416 *ok
1417 } else {
1418 t
1419 }
1420 }
1421 Expr::NullCoalesce(lhs, rhs, _) => {
1422 let lt = self.infer_expr(lhs);
1423 let rt = self.infer_expr(rhs);
1424 match lt {
1426 Type::Union(ref types) => {
1427 let non_null: Vec<_> = types
1428 .iter()
1429 .filter(|t| **t != Type::Null)
1430 .cloned()
1431 .collect();
1432 if non_null.len() == 1 {
1433 non_null.into_iter().next().unwrap()
1434 } else if non_null.is_empty() {
1435 rt
1436 } else {
1437 Type::Union(non_null)
1438 }
1439 }
1440 Type::Null => rt,
1441 _ => lt,
1442 }
1443 }
1444 Expr::NullSafeAccess(obj, field, _span) => {
1445 let ot = self.infer_expr(obj);
1446 let field_type = if let Type::Record(ref name) = ot {
1448 if let Some(ti) = self.symbols.types.get(name) {
1449 if let crate::compiler::resolve::TypeInfoKind::Record(ref rd) = ti.kind {
1450 if let Some(f) = rd.fields.iter().find(|f| f.name == *field) {
1451 resolve_type_expr(&f.ty, self.symbols)
1452 } else {
1453 Type::Any
1454 }
1455 } else {
1456 Type::Any
1457 }
1458 } else {
1459 Type::Any
1460 }
1461 } else {
1462 Type::Any
1463 };
1464 Type::Union(vec![field_type, Type::Null])
1465 }
1466 Expr::NullSafeIndex(obj, idx, _) => {
1467 let ot = self.infer_expr(obj);
1468 self.infer_expr(idx);
1469 let elem_type = match ot {
1470 Type::List(inner) => *inner,
1471 _ => Type::Any,
1472 };
1473 Type::Union(vec![elem_type, Type::Null])
1474 }
1475 Expr::NullAssert(inner, _) => {
1476 let t = self.infer_expr(inner);
1477 match t {
1479 Type::Union(ref types) => {
1480 let non_null: Vec<_> = types
1481 .iter()
1482 .filter(|t| **t != Type::Null)
1483 .cloned()
1484 .collect();
1485 if non_null.len() == 1 {
1486 non_null.into_iter().next().unwrap()
1487 } else if non_null.is_empty() {
1488 Type::Any
1489 } else {
1490 Type::Union(non_null)
1491 }
1492 }
1493 _ => t,
1494 }
1495 }
1496 Expr::SpreadExpr(inner, _) => self.infer_expr(inner),
1497 Expr::IsType {
1498 expr: inner,
1499 type_name,
1500 span,
1501 } => {
1502 self.infer_expr(inner);
1503 let is_known_type = matches!(
1505 type_name.as_str(),
1506 "Int" | "Float" | "String" | "Bool" | "Bytes" | "Json" | "Null"
1507 ) || self.symbols.types.contains_key(type_name)
1508 || self.symbols.type_aliases.contains_key(type_name);
1509 if !is_known_type && !self.allow_placeholders {
1510 self.errors.push(TypeError::UndefinedType {
1511 name: type_name.clone(),
1512 line: span.line,
1513 });
1514 }
1515 Type::Bool
1516 }
1517 Expr::TypeCast {
1518 expr: inner,
1519 target_type,
1520 span,
1521 } => {
1522 self.infer_expr(inner);
1523 match target_type.as_str() {
1524 "Int" => Type::Int,
1525 "Float" => Type::Float,
1526 "String" => Type::String,
1527 "Bool" => Type::Bool,
1528 "Bytes" => Type::Bytes,
1529 "Json" => Type::Json,
1530 _ => {
1531 if let Some(ti) = self.symbols.types.get(target_type) {
1532 use crate::compiler::resolve::TypeInfoKind;
1533 match &ti.kind {
1534 TypeInfoKind::Record(_) => Type::Record(target_type.clone()),
1535 TypeInfoKind::Enum(_) => Type::Enum(target_type.clone()),
1536 TypeInfoKind::Builtin => Type::Record(target_type.clone()),
1537 }
1538 } else if self.allow_placeholders {
1539 Type::Any
1540 } else {
1541 self.errors.push(TypeError::UndefinedType {
1542 name: target_type.clone(),
1543 line: span.line,
1544 });
1545 Type::Any
1546 }
1547 }
1548 }
1549 }
1550 Expr::IfExpr {
1551 cond,
1552 then_val,
1553 else_val,
1554 ..
1555 } => {
1556 let ct = self.infer_expr(cond);
1557 self.check_compat(&Type::Bool, &ct, cond.span().line);
1558 let tt = self.infer_expr(then_val);
1559 self.infer_expr(else_val);
1560 tt
1561 }
1562 Expr::AwaitExpr(inner, _) => self.infer_expr(inner),
1563 Expr::Comprehension {
1564 body,
1565 var,
1566 iter,
1567 condition,
1568 kind,
1569 span: _,
1570 } => {
1571 let iter_type = self.infer_expr(iter);
1572 let elem_type = match &iter_type {
1573 Type::List(inner) => *inner.clone(),
1574 Type::Set(inner) => *inner.clone(),
1575 _ => Type::Any,
1576 };
1577 self.locals.insert(var.clone(), elem_type);
1578 if let Some(ref cond) = condition {
1579 let ct = self.infer_expr(cond);
1580 self.check_compat(&Type::Bool, &ct, cond.span().line);
1581 }
1582 let body_type = self.infer_expr(body);
1583 match kind {
1584 ComprehensionKind::List => Type::List(Box::new(body_type)),
1585 ComprehensionKind::Set => Type::Set(Box::new(body_type)),
1586 ComprehensionKind::Map => Type::Any, }
1588 }
1589 Expr::MatchExpr {
1590 subject,
1591 arms,
1592 span,
1593 } => {
1594 let subject_type = self.infer_expr(subject);
1595 let mut covered_variants = Vec::new();
1596 let mut has_catchall = false;
1597 let mut result_type = Type::Any;
1598
1599 for arm in arms {
1600 self.bind_match_pattern(
1601 &arm.pattern,
1602 &subject_type,
1603 &mut covered_variants,
1604 &mut has_catchall,
1605 arm.span.line,
1606 );
1607 for s in &arm.body {
1608 self.check_stmt(s, None);
1609 }
1610 if let Some(Stmt::Expr(es)) = arm.body.last() {
1612 result_type = self.infer_expr(&es.expr);
1613 } else if let Some(Stmt::Return(rs)) = arm.body.last() {
1614 result_type = self.infer_expr(&rs.value);
1615 }
1616 }
1617
1618 if let Type::Enum(ref name) = subject_type {
1620 if !has_catchall {
1621 if let Some(ti) = self.symbols.types.get(name) {
1622 if let crate::compiler::resolve::TypeInfoKind::Enum(def) = &ti.kind {
1623 let missing: Vec<_> = def
1624 .variants
1625 .iter()
1626 .filter(|v| !covered_variants.contains(&v.name))
1627 .map(|v| v.name.clone())
1628 .collect();
1629 if !missing.is_empty() {
1630 self.errors.push(TypeError::IncompleteMatch {
1631 enum_name: name.clone(),
1632 missing,
1633 line: span.line,
1634 });
1635 }
1636 }
1637 }
1638 }
1639 }
1640
1641 result_type
1642 }
1643 Expr::BlockExpr(stmts, _) => {
1644 for s in stmts {
1645 self.check_stmt(s, None);
1646 }
1647 if let Some(Stmt::Expr(es)) = stmts.last() {
1649 self.infer_expr(&es.expr)
1650 } else {
1651 Type::Any
1652 }
1653 }
1654 }
1655 }
1656
1657 fn check_compat(&mut self, expected: &Type, actual: &Type, line: usize) {
1658 if *expected == Type::Any || *actual == Type::Any {
1659 return;
1660 }
1661 if type_contains_any(expected) || type_contains_any(actual) {
1662 return;
1663 }
1664 if expected == actual {
1665 return;
1666 }
1667
1668 if let Type::Union(ref types) = expected {
1670 if types.iter().any(|t| t == actual || *t == Type::Any) {
1671 return;
1672 }
1673 }
1674 if let Type::Union(ref types) = actual {
1676 if types.iter().any(|t| t == expected || *t == Type::Any) {
1677 return;
1678 }
1679 }
1680
1681 if *actual == Type::Null {
1683 if let Type::Union(ref types) = expected {
1684 if types.contains(&Type::Null) {
1685 return;
1686 }
1687 }
1688 }
1689
1690 if *expected == Type::Float && *actual == Type::Int {
1691 return;
1692 }
1693
1694 if let Type::Result(ok, _) = expected {
1697 if **ok == *actual || **ok == Type::Any || *actual == Type::Any {
1698 return;
1699 }
1700 }
1701 if let (Type::TypeRef(n1, args1), Type::TypeRef(n2, args2)) = (expected, actual) {
1703 if n1 == n2 {
1704 if args1.len() == args2.len() {
1706 let all_compat = args1
1707 .iter()
1708 .zip(args2.iter())
1709 .all(|(a1, a2)| a1 == a2 || *a1 == Type::Any || *a2 == Type::Any);
1710 if all_compat {
1711 return;
1712 }
1713 } else {
1714 return; }
1716 }
1717 }
1718
1719 if let (Type::Record(name1), Type::TypeRef(name2, _)) = (expected, actual) {
1721 if name1 == name2 {
1722 return;
1723 }
1724 }
1725 if let (Type::TypeRef(name1, _), Type::Record(name2)) = (expected, actual) {
1726 if name1 == name2 {
1727 return;
1728 }
1729 }
1730
1731 self.errors.push(TypeError::Mismatch {
1732 expected: format!("{}", expected),
1733 actual: format!("{}", actual),
1734 line,
1735 });
1736 }
1737}
1738
1739fn parse_directive_bool(program: &Program, name: &str) -> Option<bool> {
1740 if let Some(directive) = program
1741 .directives
1742 .iter()
1743 .find(|d| d.name.eq_ignore_ascii_case(name))
1744 {
1745 let raw = directive
1746 .value
1747 .as_deref()
1748 .unwrap_or("true")
1749 .trim()
1750 .to_ascii_lowercase();
1751 return match raw.as_str() {
1752 "1" | "true" | "yes" | "on" => Some(true),
1753 "0" | "false" | "no" | "off" => Some(false),
1754 _ => None,
1755 };
1756 }
1757
1758 let has_attr = program.items.iter().any(|item| {
1760 matches!(
1761 item,
1762 Item::Addon(AddonDecl {
1763 kind,
1764 name: Some(attr_name),
1765 ..
1766 }) if kind == "attribute" && attr_name.eq_ignore_ascii_case(name)
1767 )
1768 });
1769 if has_attr {
1770 Some(true)
1771 } else {
1772 None
1773 }
1774}
1775
1776pub fn typecheck(program: &Program, symbols: &SymbolTable) -> Result<(), Vec<TypeError>> {
1778 let strict = parse_directive_bool(program, "strict").unwrap_or(true);
1779 let doc_mode = parse_directive_bool(program, "doc_mode").unwrap_or(false);
1780 let allow_placeholders = doc_mode || !strict;
1781 let mut checker = TypeChecker::new(symbols, allow_placeholders);
1782 for item in &program.items {
1783 match item {
1784 Item::Cell(c) => checker.check_cell(c),
1785 Item::Agent(a) => {
1786 for cell in &a.cells {
1787 checker.check_agent_cell(cell);
1788 }
1789 }
1790 Item::Process(p) => {
1791 for cell in &p.cells {
1792 checker.check_cell(cell);
1793 }
1794 }
1795 Item::Effect(e) => {
1796 for op in &e.operations {
1797 checker.check_cell(op);
1798 }
1799 }
1800 Item::Handler(h) => {
1801 for handle in &h.handles {
1802 checker.check_cell(handle);
1803 }
1804 }
1805 _ => {}
1806 }
1807 }
1808 if checker.errors.is_empty() {
1809 Ok(())
1810 } else {
1811 Err(checker.errors)
1812 }
1813}
1814
1815#[cfg(test)]
1816mod tests {
1817 use super::*;
1818 use crate::compiler::lexer::Lexer;
1819 use crate::compiler::parser::Parser;
1820 use crate::compiler::resolve;
1821
1822 fn typecheck_src(src: &str) -> Result<(), Vec<TypeError>> {
1823 let mut lexer = Lexer::new(src, 1, 0);
1824 let tokens = lexer.tokenize().unwrap();
1825 let mut parser = Parser::new(tokens);
1826 let prog = parser.parse_program(vec![]).unwrap();
1827 let symbols = resolve::resolve(&prog).unwrap();
1828 typecheck(&prog, &symbols)
1829 }
1830
1831 #[test]
1832 fn test_typecheck_basic() {
1833 typecheck_src("cell add(a: Int, b: Int) -> Int\n return a + b\nend").unwrap();
1834 }
1835
1836 #[test]
1837 fn test_typecheck_undefined_var() {
1838 let err = typecheck_src("cell bad() -> Int\n return missing_var\nend").unwrap_err();
1839 assert!(!err.is_empty());
1840 }
1841
1842 #[test]
1843 fn test_type_alias_resolves_in_typecheck() {
1844 typecheck_src("type UserId = String\n\ncell greet(id: UserId) -> String\n return id\nend")
1846 .unwrap();
1847 }
1848
1849 #[test]
1850 fn test_doc_mode_allows_any_undefined_var() {
1851 typecheck_src(
1853 "@doc_mode true\n\ncell example() -> Int\n return completely_unknown_var_xyz\nend",
1854 )
1855 .unwrap();
1856 }
1857
1858 #[test]
1859 fn test_strict_mode_catches_undefined_var() {
1860 let err = typecheck_src("cell example() -> Int\n return completely_unknown_var_xyz\nend")
1862 .unwrap_err();
1863 assert!(err.iter().any(|e| matches!(e, TypeError::UndefinedVar { name, .. } if name == "completely_unknown_var_xyz")));
1864 }
1865
1866 #[test]
1867 fn test_is_doc_placeholder_var_rejects_dunder_names() {
1868 assert!(!is_doc_placeholder_var("__pattern"));
1870 assert!(!is_doc_placeholder_var("__tuple"));
1871 assert!(is_doc_placeholder_var("x"));
1873 assert!(is_doc_placeholder_var("my_variable"));
1874 }
1875
1876 #[test]
1877 fn test_type_alias_basic() {
1878 typecheck_src("type UserId = String\n\ncell greet(id: UserId) -> UserId\n return id\nend")
1880 .unwrap();
1881 }
1882
1883 #[test]
1884 fn test_type_alias_complex() {
1885 typecheck_src(
1887 "type StringList = list[String]\n\ncell make_list() -> StringList\n return [\"a\", \"b\"]\nend",
1888 )
1889 .unwrap();
1890 }
1891
1892 #[test]
1893 fn test_type_alias_in_record() {
1894 typecheck_src(
1896 "type Email = String\n\nrecord User\n email: Email\nend\n\ncell get_email(u: User) -> Email\n return u.email\nend",
1897 )
1898 .unwrap();
1899 }
1900
1901 #[test]
1902 fn test_type_alias_chained() {
1903 typecheck_src(
1905 "type UserId = String\ntype Id = UserId\n\ncell make_id() -> Id\n return \"123\"\nend",
1906 )
1907 .unwrap();
1908 }
1909
1910 #[test]
1911 fn test_is_type_returns_bool() {
1912 typecheck_src("cell check(x: Int) -> Bool\n return x is Int\nend").unwrap();
1914 }
1915
1916 #[test]
1917 fn test_type_cast_returns_target_type() {
1918 typecheck_src("cell convert(x: Float) -> Int\n return x as Int\nend").unwrap();
1920 }
1921
1922 #[test]
1923 fn test_compound_assign_bitwise_requires_int() {
1924 let err =
1926 typecheck_src("cell bad() -> String\n let x = \"hello\"\n x &= 1\n return x\nend")
1927 .unwrap_err();
1928 assert!(err
1929 .iter()
1930 .any(|e| matches!(e, TypeError::Mismatch { expected, .. } if expected == "Int")));
1931 }
1932
1933 #[test]
1934 fn test_compound_assign_add_is_valid() {
1935 typecheck_src("cell inc() -> Int\n let x = 1\n x += 2\n return x\nend").unwrap();
1937 }
1938
1939 #[test]
1940 fn test_shift_operators_return_int() {
1941 typecheck_src("cell shift(a: Int, b: Int) -> Int\n return a << b\nend").unwrap();
1943 }
1944
1945 #[test]
1946 fn test_shift_operators_require_int_operands() {
1947 let err =
1949 typecheck_src("cell bad(a: String, b: Int) -> Int\n return a << b\nend").unwrap_err();
1950 assert!(err
1951 .iter()
1952 .any(|e| matches!(e, TypeError::Mismatch { expected, .. } if expected == "Int")));
1953 }
1954
1955 #[test]
1956 fn test_validation_error_not_hardcoded() {
1957 let err =
1960 typecheck_src("cell test() -> Int\n let x: ValidationError = null\n return 1\nend");
1961 let _ = err;
1965 }
1966}