Skip to main content

tl_types/
checker.rs

1// ThinkingLanguage — Type Checker
2// Licensed under MIT OR Apache-2.0
3//
4// Walks the AST, builds type environment, infers types from expressions,
5// and checks annotations. Gradual: unannotated code = `any`, always passes.
6
7use std::collections::HashSet;
8use tl_ast::{Expr, MatchArm, Pattern, Program, Stmt, StmtKind};
9use tl_errors::Span;
10
11use crate::convert::{convert_type_expr, convert_type_expr_with_params};
12use crate::infer::infer_expr;
13use crate::{FnSig, TraitInfo, Type, TypeEnv, is_compatible};
14
15/// A type error with source location.
16#[derive(Debug, Clone)]
17pub struct TypeError {
18    pub message: String,
19    pub span: Span,
20    pub expected: Option<String>,
21    pub found: Option<String>,
22    pub hint: Option<String>,
23}
24
25impl std::fmt::Display for TypeError {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        write!(f, "{}", self.message)?;
28        if let (Some(expected), Some(found)) = (&self.expected, &self.found) {
29            write!(f, " (expected `{expected}`, found `{found}`)")?;
30        }
31        Ok(())
32    }
33}
34
35/// Configuration for the type checker.
36#[derive(Default)]
37pub struct CheckerConfig {
38    /// If true, require type annotations on function parameters.
39    pub strict: bool,
40}
41
42/// Result of type checking a program.
43pub struct CheckResult {
44    pub errors: Vec<TypeError>,
45    pub warnings: Vec<TypeError>,
46}
47
48impl CheckResult {
49    pub fn has_errors(&self) -> bool {
50        !self.errors.is_empty()
51    }
52}
53
54/// Type check a program. Returns errors and warnings.
55pub fn check_program(program: &Program, config: &CheckerConfig) -> CheckResult {
56    let mut checker = TypeChecker {
57        env: TypeEnv::new(),
58        errors: Vec::new(),
59        warnings: Vec::new(),
60        config,
61        current_fn_return: None,
62        defined_vars: Vec::new(),
63        used_vars: HashSet::new(),
64        imported_names: Vec::new(),
65        used_imports: HashSet::new(),
66        in_async_fn: false,
67        consumed_vars: std::collections::HashMap::new(),
68    };
69
70    // First pass: register all top-level functions and types
71    for stmt in &program.statements {
72        checker.register_top_level(stmt);
73    }
74
75    // Second pass: check all statements
76    checker.check_body(&program.statements);
77
78    // Check for unused variables at top-level scope
79    checker.check_unused_vars();
80
81    // Check for unused imports
82    checker.check_unused_imports();
83
84    CheckResult {
85        errors: checker.errors,
86        warnings: checker.warnings,
87    }
88}
89
90struct TypeChecker<'a> {
91    env: TypeEnv,
92    errors: Vec<TypeError>,
93    warnings: Vec<TypeError>,
94    config: &'a CheckerConfig,
95    /// The return type of the current function being checked (None if top-level).
96    current_fn_return: Option<Type>,
97    /// Variables defined in the current scope: (name, span, scope_depth)
98    defined_vars: Vec<(String, Span, u32)>,
99    /// Variables that have been used/referenced
100    used_vars: HashSet<String>,
101    /// Names imported via `use` statements: (name, span)
102    imported_names: Vec<(String, Span)>,
103    /// Import names that have been referenced
104    used_imports: HashSet<String>,
105    /// Whether the current function is async (for await checking)
106    in_async_fn: bool,
107    /// Variables consumed by pipe-move: name -> span where consumed
108    consumed_vars: std::collections::HashMap<String, Span>,
109}
110
111/// Check if a name follows snake_case convention.
112pub fn is_snake_case(s: &str) -> bool {
113    if s.is_empty() || s.starts_with('_') {
114        return true; // _-prefixed names are always ok
115    }
116    s.chars()
117        .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
118}
119
120/// Check if a name follows PascalCase convention.
121pub fn is_pascal_case(s: &str) -> bool {
122    if s.is_empty() {
123        return true;
124    }
125    let first = s.chars().next().unwrap();
126    first.is_ascii_uppercase() && !s.contains('_')
127}
128
129/// Check if a statement is a control flow terminator (return/break/continue/throw).
130fn is_terminator(kind: &StmtKind) -> bool {
131    matches!(
132        kind,
133        StmtKind::Return(_) | StmtKind::Break | StmtKind::Continue | StmtKind::Throw(_)
134    )
135}
136
137impl<'a> TypeChecker<'a> {
138    fn current_scope_depth(&self) -> u32 {
139        self.env.scope_depth()
140    }
141
142    fn register_top_level(&mut self, stmt: &Stmt) {
143        match &stmt.kind {
144            StmtKind::FnDecl {
145                name,
146                type_params,
147                params,
148                return_type,
149                ..
150            } => {
151                let param_types: Vec<(String, Type)> = params
152                    .iter()
153                    .map(|p| {
154                        let ty = p
155                            .type_ann
156                            .as_ref()
157                            .map(|t| convert_type_expr_with_params(t, type_params))
158                            .unwrap_or(Type::Any);
159                        (p.name.clone(), ty)
160                    })
161                    .collect();
162                let ret = return_type
163                    .as_ref()
164                    .map(|t| convert_type_expr_with_params(t, type_params))
165                    .unwrap_or(Type::Any);
166                self.env.define_fn(
167                    name.clone(),
168                    FnSig {
169                        params: param_types,
170                        ret,
171                    },
172                );
173                // Also define the function name as a variable of function type
174                let fn_type = Type::Function {
175                    params: params
176                        .iter()
177                        .map(|p| {
178                            p.type_ann
179                                .as_ref()
180                                .map(|t| convert_type_expr_with_params(t, type_params))
181                                .unwrap_or(Type::Any)
182                        })
183                        .collect(),
184                    ret: Box::new(
185                        return_type
186                            .as_ref()
187                            .map(|t| convert_type_expr_with_params(t, type_params))
188                            .unwrap_or(Type::Any),
189                    ),
190                };
191                self.env.define(name.clone(), fn_type);
192                // Mark function names as "used" — they are declarations, not unused vars
193                self.used_vars.insert(name.clone());
194            }
195            StmtKind::StructDecl { name, fields, .. } => {
196                let field_types: Vec<(String, Type)> = fields
197                    .iter()
198                    .map(|f| (f.name.clone(), convert_type_expr(&f.type_ann)))
199                    .collect();
200                self.env.define_struct(name.clone(), field_types);
201                self.env.define(name.clone(), Type::Struct(name.clone()));
202                self.used_vars.insert(name.clone());
203            }
204            StmtKind::EnumDecl { name, variants, .. } => {
205                let variant_types: Vec<(String, Vec<Type>)> = variants
206                    .iter()
207                    .map(|v| {
208                        (
209                            v.name.clone(),
210                            v.fields.iter().map(convert_type_expr).collect(),
211                        )
212                    })
213                    .collect();
214                self.env.define_enum(name.clone(), variant_types);
215                self.env.define(name.clone(), Type::Enum(name.clone()));
216                self.used_vars.insert(name.clone());
217            }
218            StmtKind::TraitDef { name, methods, .. } => {
219                let method_sigs: Vec<(String, Vec<Type>, Type)> = methods
220                    .iter()
221                    .map(|m| {
222                        let param_types: Vec<Type> = m
223                            .params
224                            .iter()
225                            .map(|p| {
226                                p.type_ann
227                                    .as_ref()
228                                    .map(convert_type_expr)
229                                    .unwrap_or(Type::Any)
230                            })
231                            .collect();
232                        let ret = m
233                            .return_type
234                            .as_ref()
235                            .map(convert_type_expr)
236                            .unwrap_or(Type::Any);
237                        (m.name.clone(), param_types, ret)
238                    })
239                    .collect();
240                self.env.define_trait(
241                    name.clone(),
242                    TraitInfo {
243                        name: name.clone(),
244                        methods: method_sigs,
245                        supertrait: None,
246                    },
247                );
248                self.used_vars.insert(name.clone());
249            }
250            _ => {}
251        }
252    }
253
254    /// Check a body of statements, tracking unreachable code.
255    fn check_body(&mut self, stmts: &[Stmt]) {
256        let mut terminated = false;
257        for stmt in stmts {
258            if terminated {
259                self.warnings.push(TypeError {
260                    message: "Unreachable code".to_string(),
261                    span: stmt.span,
262                    expected: None,
263                    found: None,
264                    hint: Some("This code will never be executed".to_string()),
265                });
266                // Only warn once per block
267                return;
268            }
269            self.check_stmt(stmt);
270            if is_terminator(&stmt.kind) {
271                terminated = true;
272            }
273        }
274    }
275
276    /// Check a body and return whether ALL paths terminate.
277    #[allow(dead_code)]
278    fn check_body_terminates(&mut self, stmts: &[Stmt]) -> bool {
279        let mut terminated = false;
280        for stmt in stmts {
281            if terminated {
282                self.warnings.push(TypeError {
283                    message: "Unreachable code".to_string(),
284                    span: stmt.span,
285                    expected: None,
286                    found: None,
287                    hint: Some("This code will never be executed".to_string()),
288                });
289                return true;
290            }
291            self.check_stmt(stmt);
292            if is_terminator(&stmt.kind) {
293                terminated = true;
294            }
295        }
296        terminated
297    }
298
299    /// Mark a variable name as used (for unused variable tracking).
300    fn mark_used_in_expr(&mut self, expr: &Expr) {
301        match expr {
302            Expr::Ident(name) => {
303                self.used_vars.insert(name.clone());
304                if self.imported_names.iter().any(|(n, _)| n == name) {
305                    self.used_imports.insert(name.clone());
306                }
307                // Check for use-after-move
308                if self.consumed_vars.contains_key(name) {
309                    self.errors.push(TypeError {
310                        message: format!("Use of moved value `{name}`. It was consumed by a pipe (|>) operation. Use .clone() to keep a copy."),
311                        span: Span::new(0, 0),
312                        expected: None,
313                        found: None,
314                        hint: Some(format!("Use `{name}.clone() |> ...` to keep a copy")),
315                    });
316                }
317            }
318            Expr::BinOp { left, right, .. } => {
319                self.mark_used_in_expr(left);
320                self.mark_used_in_expr(right);
321            }
322            Expr::UnaryOp { expr, .. } => self.mark_used_in_expr(expr),
323            Expr::Call { function, args } => {
324                self.mark_used_in_expr(function);
325                for a in args {
326                    self.mark_used_in_expr(a);
327                }
328            }
329            Expr::Member { object, .. } => self.mark_used_in_expr(object),
330            Expr::Index { object, index } => {
331                self.mark_used_in_expr(object);
332                self.mark_used_in_expr(index);
333            }
334            Expr::List(elems) => {
335                for e in elems {
336                    self.mark_used_in_expr(e);
337                }
338            }
339            Expr::Map(entries) => {
340                for (k, v) in entries {
341                    self.mark_used_in_expr(k);
342                    self.mark_used_in_expr(v);
343                }
344            }
345            Expr::Pipe { left, right } => {
346                self.mark_used_in_expr(left);
347                // Mark left-side identifier as consumed by pipe-move
348                if let Expr::Ident(name) = left.as_ref() {
349                    self.consumed_vars.insert(name.clone(), Span::new(0, 0));
350                }
351                self.mark_used_in_expr(right);
352            }
353            Expr::Closure { body, .. } => match body {
354                tl_ast::ClosureBody::Expr(e) => self.mark_used_in_expr(e),
355                tl_ast::ClosureBody::Block { stmts, expr } => {
356                    for s in stmts {
357                        self.mark_used_in_stmt(s);
358                    }
359                    if let Some(e) = expr {
360                        self.mark_used_in_expr(e);
361                    }
362                }
363            },
364            Expr::NullCoalesce { expr, default } => {
365                self.mark_used_in_expr(expr);
366                self.mark_used_in_expr(default);
367            }
368            Expr::Assign { target, value } => {
369                self.mark_used_in_expr(target);
370                self.mark_used_in_expr(value);
371                // Clear move state on reassignment
372                if let Expr::Ident(name) = target.as_ref() {
373                    self.consumed_vars.remove(name);
374                }
375            }
376            Expr::StructInit { name, fields } => {
377                self.used_vars.insert(name.clone());
378                for (_, v) in fields {
379                    self.mark_used_in_expr(v);
380                }
381            }
382            Expr::EnumVariant {
383                enum_name, args, ..
384            } => {
385                self.used_vars.insert(enum_name.clone());
386                for a in args {
387                    self.mark_used_in_expr(a);
388                }
389            }
390            Expr::Range { start, end } => {
391                self.mark_used_in_expr(start);
392                self.mark_used_in_expr(end);
393            }
394            Expr::Block { stmts, expr } => {
395                for s in stmts {
396                    self.mark_used_in_stmt(s);
397                }
398                if let Some(e) = expr {
399                    self.mark_used_in_expr(e);
400                }
401            }
402            Expr::Match { subject, arms } => {
403                self.mark_used_in_expr(subject);
404                for arm in arms {
405                    self.mark_used_in_pattern(&arm.pattern);
406                    if let Some(guard) = &arm.guard {
407                        self.mark_used_in_expr(guard);
408                    }
409                    self.mark_used_in_expr(&arm.body);
410                }
411            }
412            Expr::Case { arms } => {
413                for arm in arms {
414                    self.mark_used_in_pattern(&arm.pattern);
415                    if let Some(guard) = &arm.guard {
416                        self.mark_used_in_expr(guard);
417                    }
418                    self.mark_used_in_expr(&arm.body);
419                }
420            }
421            Expr::Await(inner) => {
422                self.mark_used_in_expr(inner);
423                if !self.in_async_fn {
424                    self.warnings.push(TypeError {
425                        message: "await used outside of async function".to_string(),
426                        span: Span::new(0, 0),
427                        expected: None,
428                        found: None,
429                        hint: Some("Use `async fn` to declare an async function".to_string()),
430                    });
431                }
432            }
433            Expr::Try(inner) => self.mark_used_in_expr(inner),
434            Expr::Yield(Some(inner)) => self.mark_used_in_expr(inner),
435            Expr::NamedArg { value, .. } => self.mark_used_in_expr(value),
436            _ => {}
437        }
438    }
439
440    fn mark_used_in_pattern(&mut self, pattern: &Pattern) {
441        match pattern {
442            Pattern::Literal(expr) => self.mark_used_in_expr(expr),
443            Pattern::Enum { args, .. } => {
444                for arg in args {
445                    self.mark_used_in_pattern(arg);
446                }
447            }
448            Pattern::Struct { fields, .. } => {
449                for f in fields {
450                    if let Some(p) = &f.pattern {
451                        self.mark_used_in_pattern(p);
452                    }
453                }
454            }
455            Pattern::List { elements, .. } => {
456                for e in elements {
457                    self.mark_used_in_pattern(e);
458                }
459            }
460            Pattern::Or(pats) => {
461                for p in pats {
462                    self.mark_used_in_pattern(p);
463                }
464            }
465            Pattern::Wildcard | Pattern::Binding(_) => {}
466        }
467    }
468
469    fn mark_used_in_stmt(&mut self, stmt: &Stmt) {
470        match &stmt.kind {
471            StmtKind::Expr(e) | StmtKind::Throw(e) | StmtKind::Return(Some(e)) => {
472                self.mark_used_in_expr(e);
473            }
474            StmtKind::Let { value, .. } | StmtKind::LetDestructure { value, .. } => {
475                self.mark_used_in_expr(value)
476            }
477            _ => {}
478        }
479    }
480
481    fn check_stmt(&mut self, stmt: &Stmt) {
482        match &stmt.kind {
483            StmtKind::Let {
484                name,
485                type_ann,
486                value,
487                ..
488            } => {
489                // Mark all variables used in the value expression
490                self.mark_used_in_expr(value);
491
492                // Check struct init if the value is a struct init expression
493                if let Expr::StructInit {
494                    name: sname,
495                    fields,
496                } = value
497                {
498                    self.check_struct_init(sname, fields, stmt.span);
499                }
500
501                // Check match exhaustiveness in value expression
502                self.check_match_exhaustiveness_in_expr(value, stmt.span);
503
504                // Check closure return type annotation matches body
505                self.check_closure_return_type(value, stmt.span);
506
507                let inferred = infer_expr(value, &self.env);
508                if let Some(ann) = type_ann {
509                    let expected = convert_type_expr(ann);
510                    if !is_compatible(&expected, &inferred) {
511                        self.errors.push(TypeError {
512                            message: format!("Type mismatch in let binding `{name}`"),
513                            span: stmt.span,
514                            expected: Some(expected.to_string()),
515                            found: Some(inferred.to_string()),
516                            hint: None,
517                        });
518                    }
519                    self.env.define(name.clone(), expected);
520                } else {
521                    self.env.define(name.clone(), inferred);
522                }
523
524                // Clear move state on reassignment
525                self.consumed_vars.remove(name);
526
527                // Lint: naming convention — variables should be snake_case
528                if !is_snake_case(name) {
529                    self.warnings.push(TypeError {
530                        message: format!("Variable `{name}` should be snake_case"),
531                        span: stmt.span,
532                        expected: None,
533                        found: None,
534                        hint: Some("Use lowercase with underscores for variable names".to_string()),
535                    });
536                }
537
538                // Lint: shadowing warning — check if variable already exists in outer scope
539                if !name.starts_with('_') && self.defined_vars.iter().any(|(n, _, _)| n == name) {
540                    self.warnings.push(TypeError {
541                        message: format!("Variable `{name}` shadows a previous definition"),
542                        span: stmt.span,
543                        expected: None,
544                        found: None,
545                        hint: Some("Consider using a different name".to_string()),
546                    });
547                }
548
549                // Track defined variable for unused-var checking
550                let depth = self.current_scope_depth();
551                self.defined_vars.push((name.clone(), stmt.span, depth));
552            }
553
554            StmtKind::LetDestructure { value, pattern, .. } => {
555                self.mark_used_in_expr(value);
556                self.mark_used_in_pattern(pattern);
557            }
558
559            StmtKind::FnDecl {
560                name,
561                type_params,
562                params,
563                return_type,
564                bounds,
565                body,
566                ..
567            } => {
568                // Lint: function naming convention — should be snake_case
569                if !is_snake_case(name) {
570                    self.warnings.push(TypeError {
571                        message: format!("Function `{name}` should be snake_case"),
572                        span: stmt.span,
573                        expected: None,
574                        found: None,
575                        hint: Some("Use lowercase with underscores for function names".to_string()),
576                    });
577                }
578
579                // Lint: empty function body
580                if body.is_empty() {
581                    self.warnings.push(TypeError {
582                        message: format!("Empty function body in `{name}`"),
583                        span: stmt.span,
584                        expected: None,
585                        found: None,
586                        hint: Some(
587                            "Consider adding an implementation or removing the function"
588                                .to_string(),
589                        ),
590                    });
591                }
592
593                // Save outer unused-var state
594                let outer_defined = std::mem::take(&mut self.defined_vars);
595                let outer_used = std::mem::take(&mut self.used_vars);
596
597                self.env.push_scope();
598
599                // Define type parameters in scope
600                for tp in type_params {
601                    self.env.define(tp.clone(), Type::TypeParam(tp.clone()));
602                }
603
604                // Validate trait bounds reference existing traits
605                for bound in bounds {
606                    for trait_name in &bound.traits {
607                        if self.env.lookup_trait(trait_name).is_none() {
608                            self.errors.push(TypeError {
609                                message: format!("Unknown trait `{trait_name}` in bound for `{}`", bound.type_param),
610                                span: stmt.span,
611                                expected: None,
612                                found: None,
613                                hint: Some("Available built-in traits: Numeric, Comparable, Hashable, Displayable, Serializable, Default".to_string()),
614                            });
615                        }
616                    }
617                    // Verify the bound references a declared type param
618                    if !type_params.contains(&bound.type_param) {
619                        self.errors.push(TypeError {
620                            message: format!(
621                                "Trait bound on undeclared type parameter `{}`",
622                                bound.type_param
623                            ),
624                            span: stmt.span,
625                            expected: None,
626                            found: None,
627                            hint: Some(format!(
628                                "Declare it in the type parameter list: `fn {}<{}, ...>(...)`",
629                                name, bound.type_param
630                            )),
631                        });
632                    }
633                }
634
635                // Bind parameters
636                let fn_depth = self.current_scope_depth();
637                for p in params {
638                    let ty = p
639                        .type_ann
640                        .as_ref()
641                        .map(|t| convert_type_expr_with_params(t, type_params))
642                        .unwrap_or(Type::Any);
643                    self.env.define(p.name.clone(), ty);
644                    // Track param as defined (for unused checking)
645                    self.defined_vars
646                        .push((p.name.clone(), stmt.span, fn_depth));
647
648                    // In strict mode, require type annotations on params
649                    if self.config.strict && p.type_ann.is_none() {
650                        self.errors.push(TypeError {
651                            message: format!(
652                                "Parameter `{}` of function `{name}` requires a type annotation in strict mode",
653                                p.name
654                            ),
655                            span: stmt.span,
656                            expected: None,
657                            found: None,
658                            hint: Some(format!("Add a type annotation: `{}: <type>`", p.name)),
659                        });
660                    }
661                }
662
663                // Set return type for checking returns
664                let prev_return = self.current_fn_return.take();
665                self.current_fn_return = return_type
666                    .as_ref()
667                    .map(|t| convert_type_expr_with_params(t, type_params));
668
669                // Track async state (Phase 24)
670                let prev_async = self.in_async_fn;
671                if let StmtKind::FnDecl { is_async, .. } = &stmt.kind
672                    && *is_async
673                {
674                    self.in_async_fn = true;
675                }
676
677                // Check body with unreachable code detection
678                self.check_body(body);
679
680                // Check unused vars in this function
681                self.check_unused_vars();
682
683                self.current_fn_return = prev_return;
684                self.in_async_fn = prev_async;
685                self.env.pop_scope();
686
687                // Restore outer unused-var state
688                self.defined_vars = outer_defined;
689                self.used_vars = outer_used;
690
691                // Mark the function name itself as used in the outer scope
692                self.used_vars.insert(name.clone());
693            }
694
695            StmtKind::Return(Some(expr)) => {
696                self.mark_used_in_expr(expr);
697                if let Some(expected_ret) = &self.current_fn_return {
698                    let inferred = infer_expr(expr, &self.env);
699                    if !is_compatible(expected_ret, &inferred) {
700                        self.errors.push(TypeError {
701                            message: "Return type mismatch".to_string(),
702                            span: stmt.span,
703                            expected: Some(expected_ret.to_string()),
704                            found: Some(inferred.to_string()),
705                            hint: None,
706                        });
707                    }
708                }
709            }
710
711            StmtKind::If {
712                condition,
713                then_body,
714                else_ifs,
715                else_body,
716            } => {
717                self.mark_used_in_expr(condition);
718                let cond_ty = infer_expr(condition, &self.env);
719                if !is_compatible(&Type::Bool, &cond_ty)
720                    && !matches!(cond_ty, Type::Any | Type::Error)
721                {
722                    self.warnings.push(TypeError {
723                        message: "Condition should be a bool".to_string(),
724                        span: stmt.span,
725                        expected: Some("bool".to_string()),
726                        found: Some(cond_ty.to_string()),
727                        hint: None,
728                    });
729                }
730                self.env.push_scope();
731                self.check_body(then_body);
732                self.env.pop_scope();
733
734                for (cond, body) in else_ifs {
735                    self.mark_used_in_expr(cond);
736                    let _ = infer_expr(cond, &self.env);
737                    self.env.push_scope();
738                    self.check_body(body);
739                    self.env.pop_scope();
740                }
741
742                if let Some(body) = else_body {
743                    self.env.push_scope();
744                    self.check_body(body);
745                    self.env.pop_scope();
746                }
747            }
748
749            StmtKind::While { condition, body } => {
750                self.mark_used_in_expr(condition);
751                let _ = infer_expr(condition, &self.env);
752                self.env.push_scope();
753                self.check_body(body);
754                self.env.pop_scope();
755            }
756
757            StmtKind::For { name, iter, body } | StmtKind::ParallelFor { name, iter, body } => {
758                self.mark_used_in_expr(iter);
759                let iter_ty = infer_expr(iter, &self.env);
760                let elem_ty = match &iter_ty {
761                    Type::List(inner) => *inner.clone(),
762                    Type::Set(inner) => *inner.clone(),
763                    Type::Generator(inner) => *inner.clone(),
764                    Type::Map(_) => Type::Any,
765                    Type::String => Type::String,
766                    Type::Any => Type::Any,
767                    _ => {
768                        self.warnings.push(TypeError {
769                            message: format!(
770                                "For-loop iterating over non-iterable type `{iter_ty}`"
771                            ),
772                            span: stmt.span,
773                            expected: Some("list, set, generator, map, or string".to_string()),
774                            found: Some(iter_ty.to_string()),
775                            hint: None,
776                        });
777                        Type::Any
778                    }
779                };
780                self.env.push_scope();
781                self.env.define(name.clone(), elem_ty);
782                // Mark loop variable as used — it's defined by the for-loop, not the user
783                self.used_vars.insert(name.clone());
784                self.check_body(body);
785                self.env.pop_scope();
786            }
787
788            StmtKind::Expr(expr) => {
789                self.mark_used_in_expr(expr);
790
791                // Check struct init field validation
792                if let Expr::StructInit { name, fields } = expr {
793                    self.check_struct_init(name, fields, stmt.span);
794                }
795
796                // Check assignment type compatibility
797                if let Expr::Assign { target, value } = expr {
798                    self.check_assignment(target, value, stmt.span);
799                }
800
801                // Check match exhaustiveness
802                self.check_match_exhaustiveness_in_expr(expr, stmt.span);
803
804                let _ = infer_expr(expr, &self.env);
805            }
806
807            StmtKind::TryCatch {
808                try_body,
809                catch_var,
810                catch_body,
811            } => {
812                self.env.push_scope();
813                self.check_body(try_body);
814                self.env.pop_scope();
815
816                self.env.push_scope();
817                self.env.define(catch_var.clone(), Type::Any);
818                self.used_vars.insert(catch_var.clone()); // catch vars are implicitly used
819                self.check_body(catch_body);
820                self.env.pop_scope();
821            }
822
823            StmtKind::Throw(expr) => {
824                self.mark_used_in_expr(expr);
825                let _ = infer_expr(expr, &self.env);
826            }
827
828            StmtKind::ImplBlock { methods, .. } => {
829                for method in methods {
830                    self.check_stmt(method);
831                }
832            }
833
834            StmtKind::Test { body, .. } => {
835                self.env.push_scope();
836                self.check_body(body);
837                self.env.pop_scope();
838            }
839
840            StmtKind::Use { item, .. } => {
841                // Track imported names for unused import checking
842                match item {
843                    tl_ast::UseItem::Single(path) => {
844                        if let Some(last) = path.last() {
845                            self.imported_names.push((last.clone(), stmt.span));
846                        }
847                    }
848                    tl_ast::UseItem::Group(_, names) => {
849                        for name in names {
850                            self.imported_names.push((name.clone(), stmt.span));
851                        }
852                    }
853                    tl_ast::UseItem::Aliased(_, alias) => {
854                        self.imported_names.push((alias.clone(), stmt.span));
855                    }
856                    tl_ast::UseItem::Wildcard(_) => {} // can't check wildcard imports
857                }
858            }
859
860            StmtKind::StructDecl { name, .. } => {
861                // Lint: struct naming convention — should be PascalCase
862                if !is_pascal_case(name) {
863                    self.warnings.push(TypeError {
864                        message: format!("Struct `{name}` should be PascalCase"),
865                        span: stmt.span,
866                        expected: None,
867                        found: None,
868                        hint: Some("Use PascalCase for struct names".to_string()),
869                    });
870                }
871            }
872
873            StmtKind::EnumDecl { name, .. } => {
874                // Lint: enum naming convention — should be PascalCase
875                if !is_pascal_case(name) {
876                    self.warnings.push(TypeError {
877                        message: format!("Enum `{name}` should be PascalCase"),
878                        span: stmt.span,
879                        expected: None,
880                        found: None,
881                        hint: Some("Use PascalCase for enum names".to_string()),
882                    });
883                }
884            }
885
886            // Pass-through for statements we don't type check yet
887            StmtKind::Return(None)
888            | StmtKind::Break
889            | StmtKind::Continue
890            | StmtKind::Import { .. }
891            | StmtKind::Schema { .. }
892            | StmtKind::Train { .. }
893            | StmtKind::Pipeline { .. }
894            | StmtKind::StreamDecl { .. }
895            | StmtKind::SourceDecl { .. }
896            | StmtKind::SinkDecl { .. }
897            | StmtKind::ModDecl { .. }
898            | StmtKind::Migrate { .. } => {}
899
900            StmtKind::TraitDef {
901                name,
902                type_params: _,
903                methods,
904                ..
905            } => {
906                // Lint: trait naming convention — should be PascalCase
907                if !is_pascal_case(name) {
908                    self.warnings.push(TypeError {
909                        message: format!("Trait `{name}` should be PascalCase"),
910                        span: stmt.span,
911                        expected: None,
912                        found: None,
913                        hint: Some("Use PascalCase for trait names".to_string()),
914                    });
915                }
916
917                // Register the trait
918                let method_sigs: Vec<(String, Vec<Type>, Type)> = methods
919                    .iter()
920                    .map(|m| {
921                        let param_types: Vec<Type> = m
922                            .params
923                            .iter()
924                            .map(|p| {
925                                p.type_ann
926                                    .as_ref()
927                                    .map(convert_type_expr)
928                                    .unwrap_or(Type::Any)
929                            })
930                            .collect();
931                        let ret = m
932                            .return_type
933                            .as_ref()
934                            .map(convert_type_expr)
935                            .unwrap_or(Type::Any);
936                        (m.name.clone(), param_types, ret)
937                    })
938                    .collect();
939                self.env.define_trait(
940                    name.clone(),
941                    TraitInfo {
942                        name: name.clone(),
943                        methods: method_sigs,
944                        supertrait: None,
945                    },
946                );
947            }
948
949            StmtKind::TraitImpl {
950                trait_name,
951                type_name,
952                methods,
953                ..
954            } => {
955                // Validate the trait exists
956                if let Some(trait_info) = self.env.lookup_trait(trait_name).cloned() {
957                    // Check all required methods are provided
958                    let provided: Vec<String> = methods
959                        .iter()
960                        .filter_map(|m| {
961                            if let StmtKind::FnDecl { name, .. } = &m.kind {
962                                Some(name.clone())
963                            } else {
964                                None
965                            }
966                        })
967                        .collect();
968
969                    for (required_method, _, _) in &trait_info.methods {
970                        if !provided.contains(required_method) {
971                            self.errors.push(TypeError {
972                                message: format!(
973                                    "Missing method `{required_method}` in impl `{trait_name}` for `{type_name}`"
974                                ),
975                                span: stmt.span,
976                                expected: None,
977                                found: None,
978                                hint: Some(format!("Trait `{trait_name}` requires method `{required_method}`")),
979                            });
980                        }
981                    }
982
983                    // Register the trait impl
984                    self.env
985                        .register_trait_impl(trait_name.clone(), type_name.clone(), provided);
986                } else {
987                    self.errors.push(TypeError {
988                        message: format!("Unknown trait `{trait_name}`"),
989                        span: stmt.span,
990                        expected: None,
991                        found: None,
992                        hint: None,
993                    });
994                }
995
996                // Check method bodies
997                for method in methods {
998                    self.check_stmt(method);
999                }
1000            }
1001
1002            StmtKind::TypeAlias {
1003                name,
1004                type_params,
1005                value,
1006                ..
1007            } => {
1008                // Register the type alias in the type environment
1009                self.env
1010                    .register_type_alias(name.clone(), type_params.clone(), value.clone());
1011            }
1012        }
1013    }
1014
1015    /// Validate struct initialization fields.
1016    fn check_struct_init(&mut self, name: &str, fields: &[(String, Expr)], span: Span) {
1017        if let Some(declared_fields) = self.env.lookup_struct(name).cloned() {
1018            // Check for unknown fields
1019            for (field_name, _) in fields {
1020                if !declared_fields.iter().any(|(f, _)| f == field_name) {
1021                    self.errors.push(TypeError {
1022                        message: format!("Unknown field `{field_name}` in struct `{name}`"),
1023                        span,
1024                        expected: None,
1025                        found: None,
1026                        hint: Some(format!(
1027                            "Available fields: {}",
1028                            declared_fields
1029                                .iter()
1030                                .map(|(f, _)| f.as_str())
1031                                .collect::<Vec<_>>()
1032                                .join(", ")
1033                        )),
1034                    });
1035                }
1036            }
1037
1038            // Check field types match
1039            for (field_name, field_value) in fields {
1040                if let Some((_, expected_ty)) =
1041                    declared_fields.iter().find(|(f, _)| f == field_name)
1042                {
1043                    let inferred = infer_expr(field_value, &self.env);
1044                    if !is_compatible(expected_ty, &inferred) {
1045                        self.errors.push(TypeError {
1046                            message: format!(
1047                                "Type mismatch for field `{field_name}` in struct `{name}`"
1048                            ),
1049                            span,
1050                            expected: Some(expected_ty.to_string()),
1051                            found: Some(inferred.to_string()),
1052                            hint: None,
1053                        });
1054                    }
1055                }
1056            }
1057        }
1058    }
1059
1060    /// Check assignment type compatibility.
1061    fn check_assignment(&mut self, target: &Expr, value: &Expr, span: Span) {
1062        let target_ty = infer_expr(target, &self.env);
1063        let value_ty = infer_expr(value, &self.env);
1064        // Only check if target has a known (non-Any) type
1065        if !matches!(target_ty, Type::Any | Type::Error) && !is_compatible(&target_ty, &value_ty) {
1066            self.warnings.push(TypeError {
1067                message: "Assignment type mismatch".to_string(),
1068                span,
1069                expected: Some(target_ty.to_string()),
1070                found: Some(value_ty.to_string()),
1071                hint: None,
1072            });
1073        }
1074    }
1075
1076    /// Check match expressions for exhaustiveness warnings.
1077    /// Check that a closure's return type annotation matches its body type.
1078    fn check_closure_return_type(&mut self, expr: &Expr, span: Span) {
1079        if let Expr::Closure {
1080            return_type: Some(rt),
1081            body,
1082            params,
1083            ..
1084        } = expr
1085        {
1086            let declared = convert_type_expr(rt);
1087            let body_type = match body {
1088                tl_ast::ClosureBody::Expr(e) => infer_expr(e, &self.env),
1089                tl_ast::ClosureBody::Block { expr: Some(e), .. } => infer_expr(e, &self.env),
1090                tl_ast::ClosureBody::Block { expr: None, .. } => Type::None,
1091            };
1092            if !is_compatible(&declared, &body_type) && !matches!(body_type, Type::Any) {
1093                self.warnings.push(TypeError {
1094                    message: "Closure return type mismatch".to_string(),
1095                    span,
1096                    expected: Some(declared.to_string()),
1097                    found: Some(body_type.to_string()),
1098                    hint: Some(
1099                        "The declared return type does not match the body expression type"
1100                            .to_string(),
1101                    ),
1102                });
1103            }
1104            // Warn on unused closure parameters (except _-prefixed)
1105            for p in params {
1106                if !p.name.starts_with('_') && p.name != "self" {
1107                    let is_used = match body {
1108                        tl_ast::ClosureBody::Expr(e) => self.expr_references_name(e, &p.name),
1109                        tl_ast::ClosureBody::Block { stmts, expr } => {
1110                            stmts.iter().any(|s| self.stmt_references_name(s, &p.name))
1111                                || expr
1112                                    .as_ref()
1113                                    .is_some_and(|e| self.expr_references_name(e, &p.name))
1114                        }
1115                    };
1116                    if !is_used {
1117                        self.warnings.push(TypeError {
1118                            message: format!("Unused closure parameter `{}`", p.name),
1119                            span,
1120                            expected: None,
1121                            found: None,
1122                            hint: Some(format!("Prefix with `_` to suppress: `_{}`", p.name)),
1123                        });
1124                    }
1125                }
1126            }
1127        }
1128    }
1129
1130    /// Check if an expression references a name (simple identifier check).
1131    fn expr_references_name(&self, expr: &Expr, name: &str) -> bool {
1132        match expr {
1133            Expr::Ident(n) => n == name,
1134            Expr::BinOp { left, right, .. } => {
1135                self.expr_references_name(left, name) || self.expr_references_name(right, name)
1136            }
1137            Expr::UnaryOp { expr: e, .. } => self.expr_references_name(e, name),
1138            Expr::Call { function, args } => {
1139                self.expr_references_name(function, name)
1140                    || args.iter().any(|a| self.expr_references_name(a, name))
1141            }
1142            Expr::Pipe { left, right } => {
1143                self.expr_references_name(left, name) || self.expr_references_name(right, name)
1144            }
1145            Expr::Member { object, .. } => self.expr_references_name(object, name),
1146            Expr::Index { object, index } => {
1147                self.expr_references_name(object, name) || self.expr_references_name(index, name)
1148            }
1149            Expr::List(items) => items.iter().any(|i| self.expr_references_name(i, name)),
1150            Expr::Map(pairs) => pairs.iter().any(|(k, v)| {
1151                self.expr_references_name(k, name) || self.expr_references_name(v, name)
1152            }),
1153            Expr::Block { stmts, expr } => {
1154                stmts.iter().any(|s| self.stmt_references_name(s, name))
1155                    || expr
1156                        .as_ref()
1157                        .is_some_and(|e| self.expr_references_name(e, name))
1158            }
1159            Expr::Assign { target, value } => {
1160                self.expr_references_name(target, name) || self.expr_references_name(value, name)
1161            }
1162            Expr::NullCoalesce { expr: e, default } => {
1163                self.expr_references_name(e, name) || self.expr_references_name(default, name)
1164            }
1165            Expr::Range { start, end } => {
1166                self.expr_references_name(start, name) || self.expr_references_name(end, name)
1167            }
1168            Expr::Await(e) | Expr::Try(e) => self.expr_references_name(e, name),
1169            Expr::NamedArg { value, .. } => self.expr_references_name(value, name),
1170            Expr::StructInit { fields, .. } => fields
1171                .iter()
1172                .any(|(_, e)| self.expr_references_name(e, name)),
1173            Expr::EnumVariant { args, .. } => {
1174                args.iter().any(|a| self.expr_references_name(a, name))
1175            }
1176            _ => false,
1177        }
1178    }
1179
1180    /// Check if a statement references a name.
1181    fn stmt_references_name(&self, stmt: &Stmt, name: &str) -> bool {
1182        match &stmt.kind {
1183            StmtKind::Expr(e) | StmtKind::Return(Some(e)) | StmtKind::Throw(e) => {
1184                self.expr_references_name(e, name)
1185            }
1186            StmtKind::Let { value, .. } | StmtKind::LetDestructure { value, .. } => {
1187                self.expr_references_name(value, name)
1188            }
1189            StmtKind::If {
1190                condition,
1191                then_body,
1192                else_ifs,
1193                else_body,
1194            } => {
1195                self.expr_references_name(condition, name)
1196                    || then_body.iter().any(|s| self.stmt_references_name(s, name))
1197                    || else_ifs.iter().any(|(c, b)| {
1198                        self.expr_references_name(c, name)
1199                            || b.iter().any(|s| self.stmt_references_name(s, name))
1200                    })
1201                    || else_body
1202                        .as_ref()
1203                        .is_some_and(|b| b.iter().any(|s| self.stmt_references_name(s, name)))
1204            }
1205            StmtKind::While { condition, body } => {
1206                self.expr_references_name(condition, name)
1207                    || body.iter().any(|s| self.stmt_references_name(s, name))
1208            }
1209            StmtKind::For { iter, body, .. } => {
1210                self.expr_references_name(iter, name)
1211                    || body.iter().any(|s| self.stmt_references_name(s, name))
1212            }
1213            _ => false,
1214        }
1215    }
1216
1217    fn check_match_exhaustiveness_in_expr(&mut self, expr: &Expr, span: Span) {
1218        if let Expr::Match { subject, arms } = expr {
1219            let subject_ty = infer_expr(subject, &self.env);
1220            let missing = check_match_exhaustiveness_patterns(&subject_ty, arms, &self.env);
1221            if !missing.is_empty() {
1222                self.warnings.push(TypeError {
1223                    message: format!("Non-exhaustive match: missing {}", missing.join(", ")),
1224                    span,
1225                    expected: None,
1226                    found: None,
1227                    hint: Some("Add missing patterns or a wildcard `_` arm".to_string()),
1228                });
1229            }
1230        }
1231    }
1232
1233    /// Emit warnings for unused variables.
1234    fn check_unused_vars(&mut self) {
1235        for (name, span, _depth) in &self.defined_vars {
1236            // Skip variables starting with _ (convention for intentionally unused)
1237            if name.starts_with('_') {
1238                continue;
1239            }
1240            if !self.used_vars.contains(name) {
1241                self.warnings.push(TypeError {
1242                    message: format!("Unused variable `{name}`"),
1243                    span: *span,
1244                    expected: None,
1245                    found: None,
1246                    hint: Some(format!("Prefix with `_` to suppress: `_{name}`")),
1247                });
1248            }
1249        }
1250    }
1251
1252    /// Emit warnings for unused imports.
1253    fn check_unused_imports(&mut self) {
1254        for (name, span) in &self.imported_names {
1255            if !self.used_imports.contains(name) {
1256                self.warnings.push(TypeError {
1257                    message: format!("Unused import `{name}`"),
1258                    span: *span,
1259                    expected: None,
1260                    found: None,
1261                    hint: Some("Remove unused import".to_string()),
1262                });
1263            }
1264        }
1265    }
1266}
1267
1268/// Check match arms for exhaustiveness on typed enums/result/option.
1269pub fn check_match_exhaustiveness(
1270    subject_type: &Type,
1271    arm_patterns: &[&str],
1272    env: &TypeEnv,
1273) -> Vec<String> {
1274    let mut missing = Vec::new();
1275
1276    match subject_type {
1277        Type::Result(_, _) => {
1278            if !arm_patterns.contains(&"Ok") {
1279                missing.push("Ok".to_string());
1280            }
1281            if !arm_patterns.contains(&"Err") {
1282                missing.push("Err".to_string());
1283            }
1284        }
1285        Type::Option(_) => {
1286            if !arm_patterns.iter().any(|p| *p == "none" || *p == "_") {
1287                missing.push("none".to_string());
1288            }
1289        }
1290        Type::Enum(name) => {
1291            if let Some(variants) = env.lookup_enum(name) {
1292                for (variant_name, _) in variants {
1293                    if !arm_patterns.iter().any(|p| p == variant_name || *p == "_") {
1294                        missing.push(variant_name.clone());
1295                    }
1296                }
1297            }
1298        }
1299        _ => {}
1300    }
1301
1302    missing
1303}
1304
1305/// Check match arms for exhaustiveness using Pattern types.
1306/// Returns a list of missing variant names, or empty if exhaustive.
1307pub fn check_match_exhaustiveness_patterns(
1308    subject_type: &Type,
1309    arms: &[MatchArm],
1310    env: &TypeEnv,
1311) -> Vec<String> {
1312    // If any arm is a wildcard or binding without guard, it's a catch-all
1313    let has_catch_all = arms.iter().any(|arm| {
1314        arm.guard.is_none() && matches!(arm.pattern, Pattern::Wildcard | Pattern::Binding(_))
1315    });
1316    if has_catch_all {
1317        return vec![];
1318    }
1319
1320    // Extract variant names from patterns
1321    let mut covered: Vec<&str> = Vec::new();
1322    for arm in arms {
1323        collect_pattern_variants(&arm.pattern, &mut covered);
1324    }
1325
1326    check_match_exhaustiveness(subject_type, &covered, env)
1327}
1328
1329/// Collect variant names covered by a pattern.
1330fn collect_pattern_variants<'a>(pattern: &'a Pattern, variants: &mut Vec<&'a str>) {
1331    match pattern {
1332        Pattern::Wildcard | Pattern::Binding(_) => {
1333            variants.push("_");
1334        }
1335        Pattern::Literal(Expr::None) => {
1336            variants.push("none");
1337        }
1338        Pattern::Enum { variant, .. } => {
1339            variants.push(variant.as_str());
1340        }
1341        Pattern::Or(pats) => {
1342            for p in pats {
1343                collect_pattern_variants(p, variants);
1344            }
1345        }
1346        _ => {}
1347    }
1348}
1349
1350#[cfg(test)]
1351mod tests {
1352    use super::*;
1353
1354    fn parse_and_check(source: &str) -> CheckResult {
1355        let program = tl_parser::parse(source).unwrap();
1356        check_program(&program, &CheckerConfig::default())
1357    }
1358
1359    fn parse_and_check_strict(source: &str) -> CheckResult {
1360        let program = tl_parser::parse(source).unwrap();
1361        check_program(&program, &CheckerConfig { strict: true })
1362    }
1363
1364    #[test]
1365    fn test_correct_let_int() {
1366        let result = parse_and_check("let x: int = 42\nprint(x)");
1367        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1368    }
1369
1370    #[test]
1371    fn test_correct_let_string() {
1372        let result = parse_and_check("let s: string = \"hello\"\nprint(s)");
1373        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1374    }
1375
1376    #[test]
1377    fn test_mismatch_let() {
1378        let result = parse_and_check("let x: int = \"hello\"\nprint(x)");
1379        assert!(result.has_errors());
1380        assert!(result.errors[0].message.contains("mismatch"));
1381    }
1382
1383    #[test]
1384    fn test_gradual_untyped() {
1385        // Untyped code should always pass
1386        let result = parse_and_check("let x = 42\nlet y = \"hello\"\nprint(x)\nprint(y)");
1387        assert!(!result.has_errors());
1388    }
1389
1390    #[test]
1391    fn test_function_return_type() {
1392        let result = parse_and_check("fn f() -> int { return \"hello\" }");
1393        assert!(result.has_errors());
1394        assert!(result.errors[0].message.contains("Return type"));
1395    }
1396
1397    #[test]
1398    fn test_function_correct_return() {
1399        let result = parse_and_check("fn f() -> int { return 42 }");
1400        assert!(!result.has_errors());
1401    }
1402
1403    #[test]
1404    fn test_gradual_function_no_annotations() {
1405        let result = parse_and_check("fn f(a, b) { return a + b }");
1406        assert!(!result.has_errors());
1407    }
1408
1409    #[test]
1410    fn test_strict_mode_requires_param_types() {
1411        let result = parse_and_check_strict("fn f(a, b) { return a + b }");
1412        assert!(result.has_errors());
1413        assert!(
1414            result.errors[0]
1415                .message
1416                .contains("requires a type annotation")
1417        );
1418    }
1419
1420    #[test]
1421    fn test_strict_mode_with_annotations() {
1422        let result = parse_and_check_strict("fn f(a: int, b: int) -> int { return a + b }");
1423        assert!(!result.has_errors());
1424    }
1425
1426    #[test]
1427    fn test_option_none_compatible() {
1428        let mut env = TypeEnv::new();
1429        env.define("x".into(), Type::Option(Box::new(Type::Int)));
1430        assert!(is_compatible(
1431            &Type::Option(Box::new(Type::Int)),
1432            &Type::None
1433        ));
1434    }
1435
1436    #[test]
1437    fn test_int_float_promotion() {
1438        let result = parse_and_check("let x: float = 42\nprint(x)");
1439        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1440    }
1441
1442    #[test]
1443    fn test_match_exhaustiveness_result() {
1444        let env = TypeEnv::new();
1445        let ty = Type::Result(Box::new(Type::Int), Box::new(Type::String));
1446        let missing = check_match_exhaustiveness(&ty, &["Ok"], &env);
1447        assert_eq!(missing, vec!["Err"]);
1448
1449        let missing = check_match_exhaustiveness(&ty, &["Ok", "Err"], &env);
1450        assert!(missing.is_empty());
1451    }
1452
1453    #[test]
1454    fn test_match_exhaustiveness_enum() {
1455        let mut env = TypeEnv::new();
1456        env.define_enum(
1457            "Color".into(),
1458            vec![
1459                ("Red".into(), vec![]),
1460                ("Green".into(), vec![]),
1461                ("Blue".into(), vec![]),
1462            ],
1463        );
1464        let ty = Type::Enum("Color".into());
1465        let missing = check_match_exhaustiveness(&ty, &["Red", "Green"], &env);
1466        assert_eq!(missing, vec!["Blue"]);
1467    }
1468
1469    // ── Phase 12: Generics & Traits ──────────────────────────
1470
1471    #[test]
1472    fn test_generic_fn_type_params() {
1473        let result = parse_and_check("fn identity<T>(x: T) -> T { return x }");
1474        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1475    }
1476
1477    #[test]
1478    fn test_generic_fn_no_errors_untyped() {
1479        // Gradual: untyped generic code always passes
1480        let result = parse_and_check("fn identity<T>(x) { return x }");
1481        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1482    }
1483
1484    #[test]
1485    fn test_trait_def_registered() {
1486        let result = parse_and_check("trait Display { fn show(self) -> string }");
1487        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1488    }
1489
1490    #[test]
1491    fn test_trait_impl_validates_methods() {
1492        let result = parse_and_check(
1493            "trait Display { fn show(self) -> string }\nimpl Display for Point { fn show(self) -> string { \"point\" } }",
1494        );
1495        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1496    }
1497
1498    #[test]
1499    fn test_trait_impl_missing_method() {
1500        let result = parse_and_check(
1501            "trait Display { fn show(self) -> string }\nimpl Display for Point { fn other(self) { 1 } }",
1502        );
1503        assert!(result.has_errors());
1504        assert!(result.errors[0].message.contains("Missing method"));
1505    }
1506
1507    #[test]
1508    fn test_unknown_trait_in_impl() {
1509        let result = parse_and_check("impl FooTrait for Point { fn bar(self) { 1 } }");
1510        assert!(result.has_errors());
1511        assert!(result.errors[0].message.contains("Unknown trait"));
1512    }
1513
1514    #[test]
1515    fn test_unknown_trait_in_bound() {
1516        let result = parse_and_check("fn foo<T: UnknownTrait>(x: T) { x }");
1517        assert!(result.has_errors());
1518        assert!(result.errors[0].message.contains("Unknown trait"));
1519    }
1520
1521    #[test]
1522    fn test_builtin_trait_bound_accepted() {
1523        let result = parse_and_check("fn foo<T: Comparable>(x: T) { x }");
1524        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1525    }
1526
1527    #[test]
1528    fn test_multiple_bounds() {
1529        let result = parse_and_check("fn foo<T: Comparable + Hashable>(x: T) { x }");
1530        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1531    }
1532
1533    #[test]
1534    fn test_where_clause_validation() {
1535        let result = parse_and_check("fn foo<T>(x: T) where T: Comparable { x }");
1536        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1537    }
1538
1539    #[test]
1540    fn test_undeclared_type_param_in_bound() {
1541        let result = parse_and_check("fn foo<T>(x: T) where U: Comparable { x }");
1542        assert!(result.has_errors());
1543        assert!(
1544            result.errors[0]
1545                .message
1546                .contains("undeclared type parameter")
1547        );
1548    }
1549
1550    #[test]
1551    fn test_builtin_traits_registered() {
1552        let env = TypeEnv::new();
1553        assert!(env.lookup_trait("Numeric").is_some());
1554        assert!(env.lookup_trait("Comparable").is_some());
1555        assert!(env.lookup_trait("Hashable").is_some());
1556        assert!(env.lookup_trait("Displayable").is_some());
1557        assert!(env.lookup_trait("Serializable").is_some());
1558        assert!(env.lookup_trait("Default").is_some());
1559    }
1560
1561    #[test]
1562    fn test_type_satisfies_numeric() {
1563        let env = TypeEnv::new();
1564        assert!(env.type_satisfies_trait(&Type::Int, "Numeric"));
1565        assert!(env.type_satisfies_trait(&Type::Float, "Numeric"));
1566        assert!(!env.type_satisfies_trait(&Type::String, "Numeric"));
1567    }
1568
1569    #[test]
1570    fn test_type_satisfies_comparable() {
1571        let env = TypeEnv::new();
1572        assert!(env.type_satisfies_trait(&Type::Int, "Comparable"));
1573        assert!(env.type_satisfies_trait(&Type::String, "Comparable"));
1574        assert!(!env.type_satisfies_trait(&Type::Bool, "Comparable"));
1575    }
1576
1577    #[test]
1578    fn test_strict_mode_with_generics() {
1579        // In strict mode, params still need annotations — type params count as annotations
1580        let result = parse_and_check_strict("fn identity<T>(x: T) -> T { return x }");
1581        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1582    }
1583
1584    // ── Phase 13: Enhanced Checking ──────────────────────────
1585
1586    #[test]
1587    fn test_struct_init_correct_fields() {
1588        let result = parse_and_check(
1589            "struct Point { x: int, y: int }\nlet p = Point { x: 1, y: 2 }\nprint(p)",
1590        );
1591        assert!(!result.has_errors(), "errors: {:?}", result.errors);
1592    }
1593
1594    #[test]
1595    fn test_struct_init_unknown_field() {
1596        let result = parse_and_check(
1597            "struct Point { x: int, y: int }\nlet p = Point { x: 1, z: 2 }\nprint(p)",
1598        );
1599        assert!(result.has_errors());
1600        assert!(result.errors[0].message.contains("Unknown field `z`"));
1601    }
1602
1603    #[test]
1604    fn test_struct_init_wrong_field_type() {
1605        let result = parse_and_check(
1606            "struct Point { x: int, y: int }\nlet p = Point { x: 1, y: \"hello\" }\nprint(p)",
1607        );
1608        assert!(result.has_errors());
1609        assert!(
1610            result.errors[0]
1611                .message
1612                .contains("Type mismatch for field `y`")
1613        );
1614    }
1615
1616    #[test]
1617    fn test_assignment_type_mismatch() {
1618        let result = parse_and_check("let mut x: int = 42\nx = \"hello\"");
1619        // Assignment type mismatch is a warning in gradual mode
1620        let assign_warnings: Vec<_> = result
1621            .warnings
1622            .iter()
1623            .filter(|w| w.message.contains("Assignment type mismatch"))
1624            .collect();
1625        assert!(
1626            !assign_warnings.is_empty(),
1627            "Expected assignment type mismatch warning. warnings: {:?}",
1628            result.warnings
1629        );
1630    }
1631
1632    #[test]
1633    fn test_unused_variable_warning() {
1634        let result = parse_and_check("let x = 42");
1635        let unused_warnings: Vec<_> = result
1636            .warnings
1637            .iter()
1638            .filter(|w| w.message.contains("Unused variable `x`"))
1639            .collect();
1640        assert!(
1641            !unused_warnings.is_empty(),
1642            "Expected unused variable warning. warnings: {:?}",
1643            result.warnings
1644        );
1645    }
1646
1647    #[test]
1648    fn test_underscore_prefix_no_warning() {
1649        let result = parse_and_check("let _x = 42");
1650        let unused_warnings: Vec<_> = result
1651            .warnings
1652            .iter()
1653            .filter(|w| w.message.contains("Unused variable"))
1654            .collect();
1655        assert!(
1656            unused_warnings.is_empty(),
1657            "Should not warn for _-prefixed variables. warnings: {:?}",
1658            result.warnings
1659        );
1660    }
1661
1662    #[test]
1663    fn test_used_variable_no_warning() {
1664        let result = parse_and_check("let x = 42\nprint(x)");
1665        let unused_warnings: Vec<_> = result
1666            .warnings
1667            .iter()
1668            .filter(|w| w.message.contains("Unused variable `x`"))
1669            .collect();
1670        assert!(
1671            unused_warnings.is_empty(),
1672            "Should not warn for used variables. warnings: {:?}",
1673            result.warnings
1674        );
1675    }
1676
1677    #[test]
1678    fn test_unreachable_code_after_return() {
1679        let result = parse_and_check("fn f() {\n  return 1\n  print(\"unreachable\")\n}");
1680        let unreachable: Vec<_> = result
1681            .warnings
1682            .iter()
1683            .filter(|w| w.message.contains("Unreachable code"))
1684            .collect();
1685        assert!(
1686            !unreachable.is_empty(),
1687            "Expected unreachable code warning. warnings: {:?}",
1688            result.warnings
1689        );
1690    }
1691
1692    #[test]
1693    fn test_unreachable_code_after_break() {
1694        let result =
1695            parse_and_check("fn f() {\n  while true {\n    break\n    print(\"x\")\n  }\n}");
1696        let unreachable: Vec<_> = result
1697            .warnings
1698            .iter()
1699            .filter(|w| w.message.contains("Unreachable code"))
1700            .collect();
1701        assert!(
1702            !unreachable.is_empty(),
1703            "Expected unreachable code warning after break. warnings: {:?}",
1704            result.warnings
1705        );
1706    }
1707
1708    #[test]
1709    fn test_for_loop_non_iterable_warning() {
1710        let result = parse_and_check("let x: int = 42\nfor i in x { print(i) }");
1711        let warnings: Vec<_> = result
1712            .warnings
1713            .iter()
1714            .filter(|w| w.message.contains("non-iterable"))
1715            .collect();
1716        assert!(
1717            !warnings.is_empty(),
1718            "Expected non-iterable warning. warnings: {:?}",
1719            result.warnings
1720        );
1721    }
1722
1723    #[test]
1724    fn test_multiple_warnings_accumulated() {
1725        let result = parse_and_check("let x = 42\nlet y = 43");
1726        let unused_warnings: Vec<_> = result
1727            .warnings
1728            .iter()
1729            .filter(|w| w.message.contains("Unused variable"))
1730            .collect();
1731        assert_eq!(
1732            unused_warnings.len(),
1733            2,
1734            "Expected 2 unused variable warnings. warnings: {:?}",
1735            result.warnings
1736        );
1737    }
1738
1739    #[test]
1740    fn test_all_existing_patterns_pass() {
1741        // Verify various existing patterns still work without errors
1742        let result = parse_and_check("fn f(a, b) { return a + b }");
1743        assert!(!result.has_errors());
1744
1745        let result = parse_and_check("let xs = [1, 2, 3]\nprint(xs)");
1746        assert!(!result.has_errors());
1747    }
1748
1749    // ── Phase 14: Lint Rules ──────────────────────────
1750
1751    #[test]
1752    fn test_snake_case_function_no_warning() {
1753        let result = parse_and_check("fn my_func() { 1 }");
1754        let naming: Vec<_> = result
1755            .warnings
1756            .iter()
1757            .filter(|w| w.message.contains("snake_case"))
1758            .collect();
1759        assert!(
1760            naming.is_empty(),
1761            "snake_case function should not produce naming warning"
1762        );
1763    }
1764
1765    #[test]
1766    fn test_camel_case_function_warning() {
1767        let result = parse_and_check("fn myFunc() { 1 }");
1768        let naming: Vec<_> = result
1769            .warnings
1770            .iter()
1771            .filter(|w| w.message.contains("snake_case"))
1772            .collect();
1773        assert!(
1774            !naming.is_empty(),
1775            "camelCase function should produce naming warning"
1776        );
1777    }
1778
1779    #[test]
1780    fn test_pascal_case_struct_no_warning() {
1781        let result = parse_and_check("struct MyStruct { x: int }");
1782        let naming: Vec<_> = result
1783            .warnings
1784            .iter()
1785            .filter(|w| w.message.contains("PascalCase"))
1786            .collect();
1787        assert!(
1788            naming.is_empty(),
1789            "PascalCase struct should not produce naming warning"
1790        );
1791    }
1792
1793    #[test]
1794    fn test_lowercase_struct_warning() {
1795        let result = parse_and_check("struct my_struct { x: int }");
1796        let naming: Vec<_> = result
1797            .warnings
1798            .iter()
1799            .filter(|w| w.message.contains("PascalCase"))
1800            .collect();
1801        assert!(
1802            !naming.is_empty(),
1803            "lowercase struct should produce naming warning"
1804        );
1805    }
1806
1807    #[test]
1808    fn test_variable_shadowing_warning() {
1809        let result = parse_and_check("let x = 1\nlet x = 2\nprint(x)");
1810        let shadow: Vec<_> = result
1811            .warnings
1812            .iter()
1813            .filter(|w| w.message.contains("shadows"))
1814            .collect();
1815        assert!(
1816            !shadow.is_empty(),
1817            "Shadowed variable should produce warning: {:?}",
1818            result.warnings
1819        );
1820    }
1821
1822    #[test]
1823    fn test_underscore_shadow_no_warning() {
1824        let result = parse_and_check("let _x = 1\nlet _x = 2");
1825        let shadow: Vec<_> = result
1826            .warnings
1827            .iter()
1828            .filter(|w| w.message.contains("shadows"))
1829            .collect();
1830        assert!(
1831            shadow.is_empty(),
1832            "_-prefixed shadow should not warn: {:?}",
1833            result.warnings
1834        );
1835    }
1836
1837    // ── Phase 17: Pattern Matching ──
1838
1839    #[test]
1840    fn test_match_exhaustiveness_patterns_with_wildcard() {
1841        let env = TypeEnv::new();
1842        let ty = Type::Result(Box::new(Type::Int), Box::new(Type::String));
1843        let arms = vec![
1844            MatchArm {
1845                pattern: Pattern::Enum {
1846                    type_name: "Result".into(),
1847                    variant: "Ok".into(),
1848                    args: vec![Pattern::Binding("v".into())],
1849                },
1850                guard: None,
1851                body: Expr::Ident("v".into()),
1852            },
1853            MatchArm {
1854                pattern: Pattern::Wildcard,
1855                guard: None,
1856                body: Expr::None,
1857            },
1858        ];
1859        let missing = check_match_exhaustiveness_patterns(&ty, &arms, &env);
1860        assert!(missing.is_empty(), "Wildcard should make match exhaustive");
1861    }
1862
1863    #[test]
1864    fn test_match_exhaustiveness_patterns_missing_variant() {
1865        let mut env = TypeEnv::new();
1866        env.define_enum(
1867            "Color".into(),
1868            vec![
1869                ("Red".into(), vec![]),
1870                ("Green".into(), vec![]),
1871                ("Blue".into(), vec![]),
1872            ],
1873        );
1874        let ty = Type::Enum("Color".into());
1875        let arms = vec![
1876            MatchArm {
1877                pattern: Pattern::Enum {
1878                    type_name: "Color".into(),
1879                    variant: "Red".into(),
1880                    args: vec![],
1881                },
1882                guard: None,
1883                body: Expr::Int(1),
1884            },
1885            MatchArm {
1886                pattern: Pattern::Enum {
1887                    type_name: "Color".into(),
1888                    variant: "Green".into(),
1889                    args: vec![],
1890                },
1891                guard: None,
1892                body: Expr::Int(2),
1893            },
1894        ];
1895        let missing = check_match_exhaustiveness_patterns(&ty, &arms, &env);
1896        assert_eq!(missing, vec!["Blue"]);
1897    }
1898
1899    #[test]
1900    fn test_match_exhaustiveness_patterns_all_variants() {
1901        let mut env = TypeEnv::new();
1902        env.define_enum(
1903            "Color".into(),
1904            vec![
1905                ("Red".into(), vec![]),
1906                ("Green".into(), vec![]),
1907                ("Blue".into(), vec![]),
1908            ],
1909        );
1910        let ty = Type::Enum("Color".into());
1911        let arms = vec![
1912            MatchArm {
1913                pattern: Pattern::Enum {
1914                    type_name: "Color".into(),
1915                    variant: "Red".into(),
1916                    args: vec![],
1917                },
1918                guard: None,
1919                body: Expr::Int(1),
1920            },
1921            MatchArm {
1922                pattern: Pattern::Enum {
1923                    type_name: "Color".into(),
1924                    variant: "Green".into(),
1925                    args: vec![],
1926                },
1927                guard: None,
1928                body: Expr::Int(2),
1929            },
1930            MatchArm {
1931                pattern: Pattern::Enum {
1932                    type_name: "Color".into(),
1933                    variant: "Blue".into(),
1934                    args: vec![],
1935                },
1936                guard: None,
1937                body: Expr::Int(3),
1938            },
1939        ];
1940        let missing = check_match_exhaustiveness_patterns(&ty, &arms, &env);
1941        assert!(missing.is_empty());
1942    }
1943
1944    #[test]
1945    fn test_match_exhaustiveness_binding_is_catchall() {
1946        let env = TypeEnv::new();
1947        let ty = Type::Result(Box::new(Type::Int), Box::new(Type::String));
1948        let arms = vec![MatchArm {
1949            pattern: Pattern::Binding("x".into()),
1950            guard: None,
1951            body: Expr::Ident("x".into()),
1952        }];
1953        let missing = check_match_exhaustiveness_patterns(&ty, &arms, &env);
1954        assert!(missing.is_empty(), "Binding pattern should be a catch-all");
1955    }
1956
1957    #[test]
1958    fn test_match_exhaustiveness_or_pattern() {
1959        let mut env = TypeEnv::new();
1960        env.define_enum(
1961            "Dir".into(),
1962            vec![
1963                ("North".into(), vec![]),
1964                ("South".into(), vec![]),
1965                ("East".into(), vec![]),
1966                ("West".into(), vec![]),
1967            ],
1968        );
1969        let ty = Type::Enum("Dir".into());
1970        let arms = vec![
1971            MatchArm {
1972                pattern: Pattern::Or(vec![
1973                    Pattern::Enum {
1974                        type_name: "Dir".into(),
1975                        variant: "North".into(),
1976                        args: vec![],
1977                    },
1978                    Pattern::Enum {
1979                        type_name: "Dir".into(),
1980                        variant: "South".into(),
1981                        args: vec![],
1982                    },
1983                ]),
1984                guard: None,
1985                body: Expr::String("vertical".into()),
1986            },
1987            MatchArm {
1988                pattern: Pattern::Or(vec![
1989                    Pattern::Enum {
1990                        type_name: "Dir".into(),
1991                        variant: "East".into(),
1992                        args: vec![],
1993                    },
1994                    Pattern::Enum {
1995                        type_name: "Dir".into(),
1996                        variant: "West".into(),
1997                        args: vec![],
1998                    },
1999                ]),
2000                guard: None,
2001                body: Expr::String("horizontal".into()),
2002            },
2003        ];
2004        let missing = check_match_exhaustiveness_patterns(&ty, &arms, &env);
2005        assert!(missing.is_empty(), "OR patterns should cover all variants");
2006    }
2007
2008    // ── Phase 18: Closure type checking ────────────────
2009
2010    #[test]
2011    fn test_checker_closure_annotated_params() {
2012        let src = "let _f = (x: int64) => x * 2";
2013        let program = tl_parser::parse(src).unwrap();
2014        let config = CheckerConfig::default();
2015        let result = check_program(&program, &config);
2016        assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
2017    }
2018
2019    #[test]
2020    fn test_checker_block_body_closure_type_inferred() {
2021        let src = "let _f = (x: int64) -> int64 { let _y = x * 2\n _y + 1 }";
2022        let program = tl_parser::parse(src).unwrap();
2023        let config = CheckerConfig::default();
2024        let result = check_program(&program, &config);
2025        assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
2026    }
2027
2028    #[test]
2029    fn test_checker_unused_closure_param_warning() {
2030        let src = "let _f = (x: int64) -> int64 { 42 }";
2031        let program = tl_parser::parse(src).unwrap();
2032        let config = CheckerConfig::default();
2033        let result = check_program(&program, &config);
2034        let has_unused_warning = result
2035            .warnings
2036            .iter()
2037            .any(|w| w.message.contains("Unused closure parameter"));
2038        assert!(
2039            has_unused_warning,
2040            "Expected unused closure parameter warning, got: {:?}",
2041            result.warnings
2042        );
2043    }
2044
2045    #[test]
2046    fn test_checker_closure_return_type_mismatch() {
2047        // Closure declares int64 return type but body returns a string
2048        let src = "let _f = (x: int64) -> int64 { \"hello\" }";
2049        let program = tl_parser::parse(src).unwrap();
2050        let config = CheckerConfig::default();
2051        let result = check_program(&program, &config);
2052        let has_mismatch = result
2053            .warnings
2054            .iter()
2055            .any(|w| w.message.contains("return type mismatch"));
2056        assert!(
2057            has_mismatch,
2058            "Expected return type mismatch warning, got: {:?}",
2059            result.warnings
2060        );
2061    }
2062
2063    // ── Phase 22-24 Checker Tests ──────────────────────────────────
2064
2065    #[test]
2066    fn test_checker_decimal_type_annotation() {
2067        let src = "let _x: decimal = 1.0d";
2068        let program = tl_parser::parse(src).unwrap();
2069        let config = CheckerConfig::default();
2070        let result = check_program(&program, &config);
2071        assert!(
2072            !result.has_errors(),
2073            "Decimal type annotation should be valid: {:?}",
2074            result.errors
2075        );
2076    }
2077
2078    #[test]
2079    fn test_checker_async_fn_parses() {
2080        let src = "async fn _fetch() { return 42 }";
2081        let program = tl_parser::parse(src).unwrap();
2082        let config = CheckerConfig::default();
2083        let result = check_program(&program, &config);
2084        assert!(
2085            !result.has_errors(),
2086            "async fn should type-check without errors: {:?}",
2087            result.errors
2088        );
2089    }
2090
2091    #[test]
2092    fn test_checker_await_outside_async_warns() {
2093        let src = r#"
2094fn _sync_fn() {
2095    let _t = spawn(() => 42)
2096    let _x = await _t
2097}
2098"#;
2099        let program = tl_parser::parse(src).unwrap();
2100        let config = CheckerConfig::default();
2101        let result = check_program(&program, &config);
2102        let has_await_warn = result
2103            .warnings
2104            .iter()
2105            .any(|w| w.message.contains("await") && w.message.contains("async"));
2106        assert!(
2107            has_await_warn,
2108            "Expected await-outside-async warning, got: {:?}",
2109            result.warnings
2110        );
2111    }
2112
2113    #[test]
2114    fn test_checker_await_inside_async_no_warn() {
2115        let src = r#"
2116async fn _async_fn() {
2117    let _t = spawn(() => 42)
2118    let _x = await _t
2119}
2120"#;
2121        let program = tl_parser::parse(src).unwrap();
2122        let config = CheckerConfig::default();
2123        let result = check_program(&program, &config);
2124        let has_await_warn = result
2125            .warnings
2126            .iter()
2127            .any(|w| w.message.contains("await") && w.message.contains("async"));
2128        assert!(
2129            !has_await_warn,
2130            "Should not warn about await inside async fn, but got: {:?}",
2131            result.warnings
2132        );
2133    }
2134}