Skip to main content

tsz_checker/error_reporter/
generics.rs

1//! Generic type and comparison error reporting (TS2314, TS2344, TS2367, TS2352).
2
3use crate::diagnostics::{Diagnostic, diagnostic_codes, diagnostic_messages, format_message};
4use crate::state::CheckerState;
5use tsz_parser::parser::NodeIndex;
6use tsz_solver::TypeId;
7
8impl<'a> CheckerState<'a> {
9    // =========================================================================
10    // Generic Type Errors
11    // =========================================================================
12
13    /// Report TS2314: Generic type 'X' requires N type argument(s).
14    pub fn error_generic_type_requires_type_arguments_at(
15        &mut self,
16        name: &str,
17        required_count: usize,
18        idx: NodeIndex,
19    ) {
20        if let Some(loc) = self.get_source_location(idx) {
21            let message = format_message(
22                diagnostic_messages::GENERIC_TYPE_REQUIRES_TYPE_ARGUMENT_S,
23                &[name, &required_count.to_string()],
24            );
25            // Use push_diagnostic for deduplication - same type may be resolved multiple times
26            self.ctx.push_diagnostic(Diagnostic::error(
27                self.ctx.file_name.clone(),
28                loc.start,
29                loc.length(),
30                message,
31                diagnostic_codes::GENERIC_TYPE_REQUIRES_TYPE_ARGUMENT_S,
32            ));
33        }
34    }
35
36    /// Report TS2344: Type does not satisfy constraint.
37    pub fn error_type_constraint_not_satisfied(
38        &mut self,
39        type_arg: TypeId,
40        constraint: TypeId,
41        idx: NodeIndex,
42    ) {
43        // Suppress cascade errors from unresolved types
44        if type_arg == TypeId::ERROR
45            || constraint == TypeId::ERROR
46            || type_arg == TypeId::UNKNOWN
47            || constraint == TypeId::UNKNOWN
48            || type_arg == TypeId::ANY
49            || constraint == TypeId::ANY
50        {
51            return;
52        }
53
54        // Also suppress when either side CONTAINS error types (e.g., { new(): error }).
55        // This happens when a forward-referenced class hasn't been fully resolved yet.
56        if tsz_solver::type_queries::contains_error_type_db(self.ctx.types, type_arg)
57            || tsz_solver::type_queries::contains_error_type_db(self.ctx.types, constraint)
58        {
59            return;
60        }
61
62        if let Some(loc) = self.get_source_location(idx) {
63            // Deduplicate: get_type_from_type_node may re-resolve type references when
64            // type_parameter_scope changes, causing validate_type_reference_type_arguments
65            // to be called multiple times for the same node.
66            let key = (
67                loc.start,
68                diagnostic_codes::TYPE_DOES_NOT_SATISFY_THE_CONSTRAINT,
69            );
70            if self.ctx.emitted_diagnostics.contains(&key) {
71                return;
72            }
73            self.ctx.emitted_diagnostics.insert(key);
74
75            let type_str = self.format_type(type_arg);
76            let constraint_str = self.format_type(constraint);
77            let message = format_message(
78                diagnostic_messages::TYPE_DOES_NOT_SATISFY_THE_CONSTRAINT,
79                &[&type_str, &constraint_str],
80            );
81            self.ctx.diagnostics.push(Diagnostic::error(
82                self.ctx.file_name.clone(),
83                loc.start,
84                loc.length(),
85                message,
86                diagnostic_codes::TYPE_DOES_NOT_SATISFY_THE_CONSTRAINT,
87            ));
88        }
89    }
90
91    /// Report TS2367: This condition will always return 'false'/'true' since the types have no overlap.
92    ///
93    /// The message depends on the operator:
94    /// - For `===` and `==`: "always return 'false'"
95    /// - For `!==` and `!=`: "always return 'true'"
96    pub fn error_comparison_no_overlap(
97        &mut self,
98        left_type: TypeId,
99        right_type: TypeId,
100        is_equality: bool,
101        idx: NodeIndex,
102    ) {
103        // Suppress cascade errors from unresolved types
104        if left_type == TypeId::ERROR
105            || right_type == TypeId::ERROR
106            || left_type == TypeId::ANY
107            || right_type == TypeId::ANY
108            || left_type == TypeId::UNKNOWN
109            || right_type == TypeId::UNKNOWN
110        {
111            return;
112        }
113
114        if let Some(loc) = self.get_source_location(idx) {
115            let left_str = self.format_type(left_type);
116            let right_str = self.format_type(right_type);
117            let result = if is_equality { "false" } else { "true" };
118            let message = format_message(
119                diagnostic_messages::THIS_COMPARISON_APPEARS_TO_BE_UNINTENTIONAL_BECAUSE_THE_TYPES_AND_HAVE_NO_OVERLA,
120                &[result, &left_str, &right_str],
121            );
122            self.ctx.diagnostics.push(Diagnostic::error(self.ctx.file_name.clone(), loc.start, loc.length(), message, diagnostic_codes::THIS_COMPARISON_APPEARS_TO_BE_UNINTENTIONAL_BECAUSE_THE_TYPES_AND_HAVE_NO_OVERLA));
123        }
124    }
125
126    /// Report TS2352: Conversion of type 'X' to type 'Y' may be a mistake because neither type
127    /// sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
128    pub fn error_type_assertion_no_overlap(
129        &mut self,
130        source_type: TypeId,
131        target_type: TypeId,
132        idx: NodeIndex,
133    ) {
134        if let Some(loc) = self.get_source_location(idx) {
135            let source_str = self.format_type(source_type);
136            let target_str = self.format_type(target_type);
137            let message = format_message(
138                diagnostic_messages::CONVERSION_OF_TYPE_TO_TYPE_MAY_BE_A_MISTAKE_BECAUSE_NEITHER_TYPE_SUFFICIENTLY_OV,
139                &[&source_str, &target_str],
140            );
141            self.ctx.diagnostics.push(Diagnostic::error(self.ctx.file_name.clone(), loc.start, loc.length(), message, diagnostic_codes::CONVERSION_OF_TYPE_TO_TYPE_MAY_BE_A_MISTAKE_BECAUSE_NEITHER_TYPE_SUFFICIENTLY_OV));
142        }
143    }
144
145    // =========================================================================
146    // Diagnostic Utilities
147    // =========================================================================
148
149    /// Create a diagnostic collector for batch error reporting.
150    pub fn create_diagnostic_collector(&self) -> tsz_solver::DiagnosticCollector<'_> {
151        tsz_solver::DiagnosticCollector::new(self.ctx.types, self.ctx.file_name.as_str())
152    }
153
154    /// Merge diagnostics from a collector into the checker's diagnostics.
155    pub fn merge_diagnostics(&mut self, collector: &tsz_solver::DiagnosticCollector) {
156        for diag in collector.to_checker_diagnostics() {
157            self.ctx.diagnostics.push(diag);
158        }
159    }
160}