tsz-checker 0.1.9

TypeScript type checker for the tsz compiler
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
//! Statement Type Checking
//!
//! Handles control flow statements and dispatches declarations.
//! This module separates statement checking logic from the monolithic `CheckerState`.

use tsz_parser::parser::NodeIndex;
use tsz_parser::parser::node::NodeArena;
use tsz_parser::parser::syntax_kind_ext;
use tsz_solver::TypeId;

/// Trait for statement checking callbacks.
///
/// This trait defines the interface that `CheckerState` must implement
/// to allow `StatementChecker` to delegate type checking and other operations.
pub trait StatementCheckCallbacks {
    /// Get access to the node arena for AST traversal.
    fn arena(&self) -> &NodeArena;

    /// Get the type of a node (expression or type annotation).
    fn get_type_of_node(&mut self, idx: NodeIndex) -> TypeId;

    /// Get the type of a node without flow narrowing.
    /// Used for switch discriminants where tsc uses the declared/widened type,
    /// not the flow-narrowed type (avoids false TS2678).
    fn get_type_of_node_no_narrowing(&mut self, idx: NodeIndex) -> TypeId;

    /// Check a variable statement.
    fn check_variable_statement(&mut self, stmt_idx: NodeIndex);

    /// Check a variable declaration list.
    fn check_variable_declaration_list(&mut self, list_idx: NodeIndex);

    /// Check a variable declaration.
    fn check_variable_declaration(&mut self, decl_idx: NodeIndex);

    /// Check a return statement.
    fn check_return_statement(&mut self, stmt_idx: NodeIndex);

    /// Check unreachable code in a block.
    /// Check function implementations in a block.
    fn check_function_implementations(&mut self, stmts: &[NodeIndex]);

    /// Check a function declaration.
    fn check_function_declaration(&mut self, func_idx: NodeIndex);

    /// Check a class declaration.
    fn check_class_declaration(&mut self, class_idx: NodeIndex);

    /// Check an interface declaration.
    fn check_interface_declaration(&mut self, iface_idx: NodeIndex);

    /// Check an import declaration.
    fn check_import_declaration(&mut self, import_idx: NodeIndex);

    /// Check an import equals declaration.
    fn check_import_equals_declaration(&mut self, import_idx: NodeIndex);

    /// Check an export declaration.
    fn check_export_declaration(&mut self, export_idx: NodeIndex);

    /// Check a type alias declaration.
    fn check_type_alias_declaration(&mut self, type_alias_idx: NodeIndex);

    /// Check enum duplicate members.
    fn check_enum_duplicate_members(&mut self, enum_idx: NodeIndex);

    /// Check a module declaration.
    fn check_module_declaration(&mut self, module_idx: NodeIndex);

    /// Check an await expression (TS1359: await outside async).
    fn check_await_expression(&mut self, expr_idx: NodeIndex);

    /// Check a for-await statement (TS1103/TS1432: for-await outside async or without proper module/target).
    fn check_for_await_statement(&mut self, stmt_idx: NodeIndex);

    /// Check if a condition expression is always truthy/falsy (TS2872/TS2873).
    fn check_truthy_or_falsy(&mut self, node_idx: NodeIndex);

    /// TS2774: Check if a non-nullable function type is tested for truthiness
    /// without being called in the body.
    fn check_callable_truthiness(&mut self, cond_expr: NodeIndex, body: Option<NodeIndex>);

    /// Check if a condition is statically true
    fn is_true_condition(&self, condition_idx: NodeIndex) -> bool;

    /// Check if a condition is statically false
    fn is_false_condition(&self, condition_idx: NodeIndex) -> bool;

    /// Report unreachable code directly for single statements
    fn report_unreachable_statement(&mut self, stmt_idx: NodeIndex);

    /// TS2407: Check that the right-hand side of a for-in statement is of type 'any',
    /// an object type, or a type parameter.
    fn check_for_in_expression_type(&mut self, expr_type: TypeId, expression: NodeIndex) {
        // Default: no check
        let _ = (expr_type, expression);
    }

    /// Assign types for for-in/for-of initializers.
    /// `is_for_in` should be true for for-in loops (to emit TS2404 on type annotations).
    fn assign_for_in_of_initializer_types(
        &mut self,
        decl_list_idx: NodeIndex,
        loop_var_type: TypeId,
        is_for_in: bool,
    );

    /// Get element type for for-of loop.
    fn for_of_element_type(&mut self, expr_type: TypeId) -> TypeId;

    /// Check for-of iterability.
    fn check_for_of_iterability(
        &mut self,
        expr_type: TypeId,
        expr_idx: NodeIndex,
        await_modifier: bool,
    );

    /// Check assignability for for-in/of expression initializer (non-declaration case).
    /// For `for (v of expr)` where `v` is a pre-declared variable (not `var v`/`let v`/`const v`),
    /// this checks:
    /// - TS2588: Cannot assign to const variable
    /// - TS2322: Element type not assignable to variable type
    fn check_for_in_of_expression_initializer(
        &mut self,
        initializer: NodeIndex,
        element_type: TypeId,
        is_for_of: bool,
        has_await_modifier: bool,
    );

    /// TS2491: Check if a for-in variable declaration uses a destructuring pattern.
    fn check_for_in_destructuring_pattern(&mut self, initializer: NodeIndex);

    /// TS2491: Check if a for-in expression initializer is an array/object literal.
    fn check_for_in_expression_destructuring(&mut self, initializer: NodeIndex);

    /// Recursively check a nested statement (callback to `check_statement`).
    fn check_statement(&mut self, stmt_idx: NodeIndex);

    /// Check switch statement exhaustiveness (Task 12: CFA Diagnostics).
    ///
    /// Called after all switch clauses have been checked to determine if
    /// the switch is exhaustive (handles all possible cases).
    ///
    /// Parameters:
    /// - `stmt_idx`: The switch statement node
    /// - `expression`: The discriminant expression
    /// - `case_block`: The case block containing all clauses
    /// - `has_default`: Whether the switch has a default clause
    ///
    /// Default implementation does nothing (exhaustiveness checking is optional).
    fn check_switch_exhaustiveness(
        &mut self,
        _stmt_idx: NodeIndex,
        _expression: NodeIndex,
        _case_block: NodeIndex,
        _has_default: bool,
    ) {
        // Default: no exhaustiveness checking
    }

    /// Check that a case expression type is comparable to the switch expression type.
    /// Emits TS2678 if the types have no overlap.
    fn check_switch_case_comparable(
        &mut self,
        switch_type: TypeId,
        case_type: TypeId,
        switch_expr: NodeIndex,
        case_expr: NodeIndex,
    ) {
        // Default: no comparability checking
        let _ = (switch_type, case_type, switch_expr, case_expr);
    }

    /// Check a break statement for validity.
    /// TS1105: A 'break' statement can only be used within an enclosing iteration statement.
    fn check_break_statement(&mut self, stmt_idx: NodeIndex);

    /// Check a continue statement for validity.
    /// TS1104: A 'continue' statement can only be used within an enclosing iteration statement.
    fn check_continue_statement(&mut self, stmt_idx: NodeIndex);

    /// Get current reachability state
    fn is_unreachable(&self) -> bool;

    /// Set current reachability state
    fn set_unreachable(&mut self, value: bool);

    /// Get current reported state
    fn has_reported_unreachable(&self) -> bool;

    /// Set current reported state
    fn set_reported_unreachable(&mut self, value: bool);

    /// Check if a statement falls through
    fn statement_falls_through(&mut self, stmt_idx: NodeIndex) -> bool;

    /// Enter an iteration statement (for/while/do-while/for-in/for-of).
    /// Increments `iteration_depth` for break/continue validation.
    fn enter_iteration_statement(&mut self);

    /// Leave an iteration statement.
    /// Decrements `iteration_depth`.
    fn leave_iteration_statement(&mut self);

    /// Enter a switch statement.
    /// Increments `switch_depth` for break validation.
    fn enter_switch_statement(&mut self);

    /// Leave a switch statement.
    /// Decrements `switch_depth`.
    fn leave_switch_statement(&mut self);

    /// Save current iteration/switch context and reset it.
    /// Used when entering a function body (function creates new context).
    /// Returns the saved (`iteration_depth`, `switch_depth`, `had_outer_loop`).
    fn save_and_reset_control_flow_context(&mut self) -> (u32, u32, bool);

    /// Restore previously saved iteration/switch context.
    /// Used when leaving a function body.
    fn restore_control_flow_context(&mut self, saved: (u32, u32, bool));

    /// Enter a labeled statement.
    /// Pushes a label onto the label stack for break/continue validation.
    /// `is_iteration` should be true if the labeled statement wraps an iteration statement.
    fn enter_labeled_statement(&mut self, label: String, is_iteration: bool);

    /// Leave a labeled statement.
    /// Pops the label from the label stack.
    fn leave_labeled_statement(&mut self);

    /// Get the text of a node (used for getting label names).
    fn get_node_text(&self, idx: NodeIndex) -> Option<String>;

    /// Check for declarations in single-statement position (TS1156).
    /// Called when a statement in a control flow construct (if/while/do/for) body
    /// is a declaration that requires a block context.
    fn check_declaration_in_statement_position(&mut self, stmt_idx: NodeIndex);

    /// TS1344: Check if a label is placed before a declaration that doesn't allow labels.
    /// Called when a labeled statement is found; `label_idx` is the label identifier,
    /// `statement_idx` is the inner statement.
    fn check_label_on_declaration(&mut self, label_idx: NodeIndex, statement_idx: NodeIndex);

    /// Check a with statement and emit TS2410.
    /// The 'with' statement is not supported in TypeScript.
    fn check_with_statement(&mut self, stmt_idx: NodeIndex);
}

/// Statement type checker that dispatches to specialized handlers.
///
/// This is a zero-sized struct that only provides the dispatching logic.
/// All state and type checking operations are delegated back to the
/// implementation of `StatementCheckCallbacks` (typically `CheckerState`).
pub struct StatementChecker;

impl StatementChecker {
    /// Create a new statement checker.
    pub const fn new() -> Self {
        Self
    }

    /// Check a statement node.
    ///
    /// This dispatches to specialized handlers based on statement kind.
    /// The `state` parameter provides both the arena for AST access and
    /// callbacks for type checking operations.
    pub fn check<S: StatementCheckCallbacks>(stmt_idx: NodeIndex, state: &mut S) {
        state.report_unreachable_statement(stmt_idx);

        // Get node kind and extract needed data before any mutable operations
        let node_data = {
            let arena = state.arena();
            let Some(node) = arena.get(stmt_idx) else {
                return;
            };
            (node.kind, node)
        };
        let kind = node_data.0;

        match kind {
            syntax_kind_ext::VARIABLE_STATEMENT => {
                state.check_variable_statement(stmt_idx);
            }
            syntax_kind_ext::EXPRESSION_STATEMENT => {
                // Extract expression index before mutable operations
                let expr_idx = {
                    let arena = state.arena();
                    arena
                        .get(stmt_idx)
                        .and_then(|node| arena.get_expression_statement(node))
                        .map(|e| e.expression)
                };
                if let Some(expression) = expr_idx {
                    // TS1359: Check for await expressions outside async function
                    state.check_await_expression(expression);
                    // Then get the type for normal type checking
                    state.get_type_of_node(expression);
                }
            }
            syntax_kind_ext::IF_STATEMENT => {
                // Extract all needed data before mutable operations
                let if_data = {
                    let arena = state.arena();
                    arena
                        .get(stmt_idx)
                        .and_then(|node| arena.get_if_statement(node))
                        .map(|if_stmt| {
                            (
                                if_stmt.expression,
                                if_stmt.then_statement,
                                if_stmt.else_statement,
                            )
                        })
                };
                if let Some((expression, then_stmt, else_stmt)) = if_data {
                    // Check condition
                    state.check_await_expression(expression);
                    state.get_type_of_node(expression);
                    // TS2872/TS2873: check if condition is always truthy/falsy
                    state.check_truthy_or_falsy(expression);
                    // TS2774: check for non-nullable callable tested for truthiness
                    state.check_callable_truthiness(expression, Some(then_stmt));

                    let condition_is_true = state.is_true_condition(expression);
                    let condition_is_false = state.is_false_condition(expression);

                    let prev_unreachable = state.is_unreachable();
                    let prev_reported = state.has_reported_unreachable();

                    // Check then branch
                    if condition_is_false {
                        state.set_unreachable(true);
                    }
                    state.check_declaration_in_statement_position(then_stmt);
                    state.check_statement(then_stmt);

                    state.set_unreachable(prev_unreachable);
                    state.set_reported_unreachable(prev_reported);

                    // Check else branch if present
                    if else_stmt.is_some() {
                        if condition_is_true {
                            state.set_unreachable(true);
                        }
                        state.check_declaration_in_statement_position(else_stmt);
                        state.check_statement(else_stmt);

                        state.set_unreachable(prev_unreachable);
                        state.set_reported_unreachable(prev_reported);
                    }
                }
            }
            syntax_kind_ext::RETURN_STATEMENT => {
                state.check_return_statement(stmt_idx);
            }
            syntax_kind_ext::BLOCK => {
                // Extract statements before mutable operations
                let stmts = {
                    let arena = state.arena();
                    arena
                        .get(stmt_idx)
                        .and_then(|node| arena.get_block(node))
                        .map(|b| b.statements.nodes.clone())
                };
                if let Some(stmts) = stmts {
                    let prev_unreachable = state.is_unreachable();
                    let prev_reported = state.has_reported_unreachable();
                    for inner_stmt in &stmts {
                        state.check_statement(*inner_stmt);
                        if !state.statement_falls_through(*inner_stmt) {
                            state.set_unreachable(true);
                        }
                    }
                    state.set_unreachable(prev_unreachable);
                    state.set_reported_unreachable(prev_reported);
                    // Check for function overload implementations in blocks
                    state.check_function_implementations(&stmts);
                }
            }
            syntax_kind_ext::FUNCTION_DECLARATION
            | syntax_kind_ext::FUNCTION_EXPRESSION
            | syntax_kind_ext::ARROW_FUNCTION => {
                state.check_function_declaration(stmt_idx);
            }
            syntax_kind_ext::WHILE_STATEMENT | syntax_kind_ext::DO_STATEMENT => {
                // Extract loop data before mutable operations
                let loop_data = {
                    let arena = state.arena();
                    arena
                        .get(stmt_idx)
                        .and_then(|node| arena.get_loop(node))
                        .map(|l| (l.condition, l.statement))
                };
                if let Some((condition, statement)) = loop_data {
                    state.get_type_of_node(condition);
                    state.check_truthy_or_falsy(condition);

                    let prev_unreachable = state.is_unreachable();
                    let prev_reported = state.has_reported_unreachable();

                    // Body is unreachable if it's a while loop with a false condition
                    if kind == syntax_kind_ext::WHILE_STATEMENT
                        && state.is_false_condition(condition)
                    {
                        state.set_unreachable(true);
                    }

                    state.enter_iteration_statement();
                    state.check_declaration_in_statement_position(statement);
                    state.check_statement(statement);
                    state.leave_iteration_statement();

                    state.set_unreachable(prev_unreachable);
                    state.set_reported_unreachable(prev_reported);
                }
            }
            syntax_kind_ext::FOR_STATEMENT => {
                // Extract loop data before mutable operations
                let loop_data = {
                    let arena = state.arena();
                    arena
                        .get(stmt_idx)
                        .and_then(|node| arena.get_loop(node))
                        .map(|l| (l.initializer, l.condition, l.incrementor, l.statement))
                };
                if let Some((initializer, condition, incrementor, statement)) = loop_data {
                    if initializer.is_some() {
                        // Check if initializer is a variable declaration list
                        let is_var_decl_list = {
                            let arena = state.arena();
                            arena.get(initializer).is_some_and(|n| {
                                n.kind == syntax_kind_ext::VARIABLE_DECLARATION_LIST
                            })
                        };
                        if is_var_decl_list {
                            state.check_variable_declaration_list(initializer);
                        } else {
                            state.get_type_of_node(initializer);
                        }
                    }
                    let mut condition_is_false = false;
                    if condition.is_some() {
                        state.get_type_of_node(condition);
                        state.check_truthy_or_falsy(condition);
                        condition_is_false = state.is_false_condition(condition);
                    }
                    if incrementor.is_some() {
                        state.get_type_of_node(incrementor);
                    }

                    let prev_unreachable = state.is_unreachable();
                    let prev_reported = state.has_reported_unreachable();

                    if condition_is_false {
                        state.set_unreachable(true);
                    }

                    state.enter_iteration_statement();
                    state.check_declaration_in_statement_position(statement);
                    state.check_statement(statement);
                    state.leave_iteration_statement();

                    state.set_unreachable(prev_unreachable);
                    state.set_reported_unreachable(prev_reported);
                }
            }
            syntax_kind_ext::FOR_IN_STATEMENT | syntax_kind_ext::FOR_OF_STATEMENT => {
                // Extract for-in/of data before mutable operations
                let for_data = {
                    let arena = state.arena();
                    arena
                        .get(stmt_idx)
                        .and_then(|node| arena.get_for_in_of(node))
                        .map(|f| (f.expression, f.initializer, f.await_modifier, f.statement))
                };
                let is_for_of = kind == syntax_kind_ext::FOR_OF_STATEMENT;

                if let Some((expression, initializer, await_modifier, statement)) = for_data {
                    // Bug #9: Check await_modifier is only used in async context
                    // for-await-of requires async function context
                    if await_modifier {
                        state.check_await_expression(expression);
                        state.check_for_await_statement(stmt_idx);
                    }

                    // Check if initializer is a variable declaration list and detect
                    // parser-level errors that should suppress semantic expression checks:
                    // - Empty decl list (TS1123 already reported by parser)
                    // - For-in variable with initializer (TS1189 will be reported)
                    let (is_var_decl_list, has_grammar_error) = {
                        let arena = state.arena();
                        if let Some(n) = arena.get(initializer) {
                            if n.kind == syntax_kind_ext::VARIABLE_DECLARATION_LIST {
                                let grammar_err = arena.get_variable(n).is_none_or(|v| {
                                    v.declarations.nodes.is_empty()
                                        || (!is_for_of
                                            && v.declarations.nodes.len() == 1
                                            && v.declarations.nodes.first().is_some_and(|&d| {
                                                arena.get(d).is_some_and(|dn| {
                                                    arena
                                                        .get_variable_declaration(dn)
                                                        .is_some_and(|vd| vd.initializer.is_some())
                                                })
                                            }))
                                });
                                (true, grammar_err)
                            } else {
                                (false, false)
                            }
                        } else {
                            (false, false)
                        }
                    };

                    // Determine the element type for the loop variable (for-of) or key type (for-in).
                    // When there are grammar errors, skip semantic checks (TS2407 etc.)
                    // but still evaluate the expression to catch TS2304 "cannot find name".
                    let loop_var_type = if has_grammar_error {
                        // Still type-check the expression for name resolution errors,
                        // but only for for-in. For for-of with grammar errors, the
                        // expression often involves the `of` keyword itself due to
                        // parsing ambiguity (e.g., `for (var of of)`).
                        if !is_for_of {
                            state.get_type_of_node(expression);
                        }
                        if is_for_of {
                            TypeId::ANY
                        } else {
                            TypeId::STRING
                        }
                    } else {
                        let expr_type = state.get_type_of_node(expression);
                        if is_for_of {
                            // Check if the expression is iterable and emit TS2488/TS2504 if not
                            state.check_for_of_iterability(expr_type, expression, await_modifier);
                            state.for_of_element_type(expr_type)
                        } else {
                            // TS2407: for-in expression must be any, object type, or type parameter
                            state.check_for_in_expression_type(expr_type, expression);
                            // `for (x in obj)` iterates keys (string in TS).
                            TypeId::STRING
                        }
                    };

                    if is_var_decl_list {
                        // TS2491: for-in cannot use destructuring patterns
                        if !is_for_of {
                            state.check_for_in_destructuring_pattern(initializer);
                        }
                        state.assign_for_in_of_initializer_types(
                            initializer,
                            loop_var_type,
                            !is_for_of,
                        );
                        state.check_variable_declaration_list(initializer);
                    } else {
                        // TS2491: for-in with expression initializer cannot be array/object literal
                        if !is_for_of {
                            state.check_for_in_expression_destructuring(initializer);
                        }
                        // Non-declaration initializer (e.g., `for (v of expr)` where v is pre-declared)
                        // Check assignability: element type must be assignable to the variable's type
                        // Also checks TS2588 (const assignment)
                        state.check_for_in_of_expression_initializer(
                            initializer,
                            loop_var_type,
                            is_for_of,
                            await_modifier,
                        );
                    }
                    state.enter_iteration_statement();
                    state.check_declaration_in_statement_position(statement);
                    state.check_statement(statement);
                    state.leave_iteration_statement();
                }
            }
            syntax_kind_ext::SWITCH_STATEMENT => {
                // Extract switch data before mutable operations
                let switch_data = {
                    let arena = state.arena();
                    arena
                        .get(stmt_idx)
                        .and_then(|node| arena.get_switch(node))
                        .map(|s| (s.expression, s.case_block))
                };

                if let Some((expression, case_block)) = switch_data {
                    // Use the declared/widened type (no flow narrowing) for the switch
                    // discriminant. tsc's checkExpression returns the non-narrowed type,
                    // preventing false TS2678 when flow narrows the discriminant
                    // (e.g., `const x: number = 0` narrowed to `0` would reject `case 1`).
                    let switch_type = state.get_type_of_node_no_narrowing(expression);

                    // Extract case clauses
                    let clauses = {
                        let arena = state.arena();
                        if let Some(cb_node) = arena.get(case_block) {
                            arena
                                .get_block(cb_node)
                                .map(|cb| cb.statements.nodes.clone())
                        } else {
                            None
                        }
                    };

                    if let Some(clauses) = clauses {
                        // Track if there's a default clause (for exhaustiveness checking)
                        let mut has_default = false;

                        // Enter switch context for break validation
                        state.enter_switch_statement();

                        for clause_idx in clauses {
                            // Extract clause data
                            let clause_data = {
                                let arena = state.arena();
                                if let Some(clause_node) = arena.get(clause_idx) {
                                    arena
                                        .get_case_clause(clause_node)
                                        .map(|c| (c.expression, c.statements.nodes.clone()))
                                } else {
                                    None
                                }
                            };

                            if let Some((clause_expr, clause_stmts)) = clause_data {
                                // Check if this is a default clause (expression is NONE)
                                if clause_expr.is_none() {
                                    has_default = true;
                                } else {
                                    // Check case expression and comparability with switch expression
                                    let case_type = state.get_type_of_node(clause_expr);
                                    state.check_switch_case_comparable(
                                        switch_type,
                                        case_type,
                                        expression,
                                        clause_expr,
                                    );
                                }
                                let prev_unreachable = state.is_unreachable();
                                let prev_reported = state.has_reported_unreachable();
                                for inner_stmt_idx in &clause_stmts {
                                    state.check_statement(*inner_stmt_idx);
                                    if !state.statement_falls_through(*inner_stmt_idx) {
                                        state.set_unreachable(true);
                                    }
                                }
                                state.set_unreachable(prev_unreachable);
                                state.set_reported_unreachable(prev_reported);
                            }
                        }

                        // Leave switch context
                        state.leave_switch_statement();

                        // Check exhaustiveness (Task 12: CFA Diagnostics)
                        state.check_switch_exhaustiveness(
                            stmt_idx,
                            expression,
                            case_block,
                            has_default,
                        );
                    }
                }
            }
            syntax_kind_ext::TRY_STATEMENT => {
                // Extract try data before mutable operations
                let try_data = {
                    let arena = state.arena();
                    arena
                        .get(stmt_idx)
                        .and_then(|node| arena.get_try(node))
                        .map(|t| (t.try_block, t.catch_clause, t.finally_block))
                };

                if let Some((try_block, catch_clause, finally_block)) = try_data {
                    state.check_statement(try_block);

                    if catch_clause.is_some() {
                        // Extract catch clause data
                        let catch_data = {
                            let arena = state.arena();
                            if let Some(catch_node) = arena.get(catch_clause) {
                                arena
                                    .get_catch_clause(catch_node)
                                    .map(|c| (c.variable_declaration, c.block))
                            } else {
                                None
                            }
                        };

                        if let Some((var_decl, block)) = catch_data {
                            if var_decl.is_some() {
                                state.check_variable_declaration(var_decl);
                            }
                            state.check_statement(block);
                        }
                    }
                    if finally_block.is_some() {
                        state.check_statement(finally_block);
                    }
                }
            }
            syntax_kind_ext::THROW_STATEMENT => {
                // Extract operand before mutable operations
                let operand = {
                    let arena = state.arena();
                    arena
                        .get(stmt_idx)
                        .and_then(|node| arena.get_unary_expr(node))
                        .map(|u| u.operand)
                };
                if let Some(operand) = operand {
                    state.get_type_of_node(operand);
                }
            }
            syntax_kind_ext::INTERFACE_DECLARATION => {
                state.check_interface_declaration(stmt_idx);
            }
            syntax_kind_ext::EXPORT_DECLARATION => {
                state.check_export_declaration(stmt_idx);
            }
            syntax_kind_ext::TYPE_ALIAS_DECLARATION => {
                state.check_type_alias_declaration(stmt_idx);
            }
            syntax_kind_ext::ENUM_DECLARATION => {
                state.check_enum_duplicate_members(stmt_idx);
                // Walk enum member initializer expressions for semantic checking
                // (name resolution, etc.). tsc resolves identifiers in initializers
                // and emits TS2304 for undefined names (e.g., `[e] = id++` → TS2304 for `id`).
                let initializers: Vec<NodeIndex> = {
                    let arena = state.arena();
                    if let Some(node) = arena.get(stmt_idx)
                        && let Some(enum_data) = arena.get_enum(node)
                    {
                        enum_data
                            .members
                            .nodes
                            .iter()
                            .filter_map(|&member_idx| {
                                let member_node = arena.get(member_idx)?;
                                let member_data = arena.get_enum_member(member_node)?;
                                if member_data.initializer.is_none() {
                                    None
                                } else {
                                    Some(member_data.initializer)
                                }
                            })
                            .collect()
                    } else {
                        Vec::new()
                    }
                };
                for init_idx in initializers {
                    state.get_type_of_node(init_idx);
                }
            }
            syntax_kind_ext::EMPTY_STATEMENT | syntax_kind_ext::DEBUGGER_STATEMENT => {
                // No action needed
            }
            syntax_kind_ext::BREAK_STATEMENT => {
                state.check_break_statement(stmt_idx);
            }
            syntax_kind_ext::CONTINUE_STATEMENT => {
                state.check_continue_statement(stmt_idx);
            }
            syntax_kind_ext::IMPORT_DECLARATION => {
                state.check_import_declaration(stmt_idx);
            }
            syntax_kind_ext::IMPORT_EQUALS_DECLARATION => {
                state.check_import_equals_declaration(stmt_idx);
            }
            syntax_kind_ext::MODULE_DECLARATION => {
                state.check_module_declaration(stmt_idx);
            }
            syntax_kind_ext::CLASS_DECLARATION | syntax_kind_ext::CLASS_EXPRESSION => {
                state.check_class_declaration(stmt_idx);
            }
            syntax_kind_ext::WITH_STATEMENT => {
                state.check_with_statement(stmt_idx);
            }
            syntax_kind_ext::LABELED_STATEMENT => {
                // Extract labeled statement data before mutable operations
                let labeled_data = {
                    let arena = state.arena();
                    arena
                        .get(stmt_idx)
                        .and_then(|node| arena.get_labeled_statement(node))
                        .map(|l| (l.label, l.statement))
                };

                if let Some((label_idx, statement_idx)) = labeled_data {
                    // TS1344: Check if label is placed before a non-labelable declaration
                    state.check_label_on_declaration(label_idx, statement_idx);

                    // Get the label name
                    let label_name = state.get_node_text(label_idx).unwrap_or_default();

                    // Determine if the labeled statement wraps an iteration statement
                    // This checks recursively through nested labels (e.g., target1: target2: while(...))
                    let is_iteration = {
                        let arena = state.arena();
                        Self::is_iteration_or_nested_iteration(arena, statement_idx)
                    };

                    // Push label onto stack
                    state.enter_labeled_statement(label_name, is_iteration);

                    // Check the contained statement
                    state.check_statement(statement_idx);

                    // Pop label from stack
                    state.leave_labeled_statement();
                }
            }
            _ => {
                // Catch-all for other statement types
                state.get_type_of_node(stmt_idx);
            }
        }
    }

    /// Check if a statement is an iteration statement, either directly or through nested labels.
    /// This handles cases like `target1: target2: while(true)` where both target1 and target2
    /// should be considered as wrapping an iteration statement.
    fn is_iteration_or_nested_iteration(
        arena: &tsz_parser::parser::node::NodeArena,
        stmt_idx: tsz_parser::parser::NodeIndex,
    ) -> bool {
        let Some(stmt_node) = arena.get(stmt_idx) else {
            return false;
        };

        // Check if it's directly an iteration statement
        if matches!(
            stmt_node.kind,
            syntax_kind_ext::FOR_STATEMENT
                | syntax_kind_ext::FOR_IN_STATEMENT
                | syntax_kind_ext::FOR_OF_STATEMENT
                | syntax_kind_ext::WHILE_STATEMENT
                | syntax_kind_ext::DO_STATEMENT
        ) {
            return true;
        }

        // Check if it's a labeled statement wrapping an iteration (recursively)
        if stmt_node.kind == syntax_kind_ext::LABELED_STATEMENT
            && let Some(labeled) = arena.get_labeled_statement(stmt_node)
        {
            return Self::is_iteration_or_nested_iteration(arena, labeled.statement);
        }

        false
    }
}

impl Default for StatementChecker {
    fn default() -> Self {
        Self::new()
    }
}