tsz_checker/error_reporter/
operator_errors.rs1use 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 pub(crate) fn error_circular_class_inheritance(
11 &mut self,
12 extends_expr_idx: NodeIndex,
13 class_idx: NodeIndex,
14 ) {
15 let class_name = if let Some(class_node) = self.ctx.arena.get(class_idx)
17 && let Some(class) = self.ctx.arena.get_class(class_node)
18 && class.name.is_some()
19 && let Some(name_node) = self.ctx.arena.get(class.name)
20 {
21 self.ctx
22 .arena
23 .get_identifier(name_node)
24 .map(|id| id.escaped_text.clone())
25 } else {
26 None
27 };
28
29 let name = class_name.unwrap_or_else(|| String::from("<class>"));
30
31 let Some(loc) = self.get_source_location(extends_expr_idx) else {
32 return;
33 };
34
35 let message = format_message(
36 diagnostic_messages::IS_REFERENCED_DIRECTLY_OR_INDIRECTLY_IN_ITS_OWN_BASE_EXPRESSION,
37 &[&name],
38 );
39
40 self.ctx.error(
41 loc.start,
42 loc.length(),
43 message,
44 diagnostic_codes::IS_REFERENCED_DIRECTLY_OR_INDIRECTLY_IN_ITS_OWN_BASE_EXPRESSION,
45 );
46 }
47
48 pub fn error_not_a_constructor_at(&mut self, type_id: TypeId, idx: NodeIndex) {
51 if type_id == TypeId::ERROR || type_id == TypeId::ANY || type_id == TypeId::UNKNOWN {
53 return;
54 }
55
56 let Some(loc) = self.get_source_location(idx) else {
57 return;
58 };
59
60 let mut formatter = self.ctx.create_type_formatter();
61 let type_str = formatter.format(type_id);
62
63 let message =
64 diagnostic_messages::TYPE_IS_NOT_A_CONSTRUCTOR_FUNCTION_TYPE.replace("{0}", &type_str);
65
66 self.ctx.diagnostics.push(Diagnostic::error(
67 self.ctx.file_name.clone(),
68 loc.start,
69 loc.length(),
70 message,
71 diagnostic_codes::TYPE_IS_NOT_A_CONSTRUCTOR_FUNCTION_TYPE,
72 ));
73 }
74
75 pub fn error_not_constructable_at(&mut self, type_id: TypeId, idx: NodeIndex) {
78 if type_id == TypeId::ERROR || type_id == TypeId::ANY || type_id == TypeId::UNKNOWN {
79 return;
80 }
81
82 let Some(loc) = self.get_source_location(idx) else {
83 return;
84 };
85
86 let mut formatter = self.ctx.create_type_formatter();
87 let type_str = formatter.format(type_id);
88
89 let message =
90 diagnostic_messages::THIS_EXPRESSION_IS_NOT_CONSTRUCTABLE.replace("{0}", &type_str);
91
92 self.ctx.diagnostics.push(Diagnostic::error(
93 self.ctx.file_name.clone(),
94 loc.start,
95 loc.length(),
96 message,
97 diagnostic_codes::THIS_EXPRESSION_IS_NOT_CONSTRUCTABLE,
98 ));
99 }
100
101 pub(crate) fn check_and_emit_nullish_binary_operands(
107 &mut self,
108 left_idx: NodeIndex,
109 right_idx: NodeIndex,
110 left_type: TypeId,
111 right_type: TypeId,
112 op: &str,
113 ) -> bool {
114 if left_type == TypeId::ERROR
115 || right_type == TypeId::ERROR
116 || left_type == TypeId::UNKNOWN
117 || right_type == TypeId::UNKNOWN
118 {
119 return false;
120 }
121
122 let left_is_nullish = left_type == TypeId::NULL || left_type == TypeId::UNDEFINED;
123 let right_is_nullish = right_type == TypeId::NULL || right_type == TypeId::UNDEFINED;
124 let mut emitted_nullish_error = false;
125
126 let should_emit_nullish_error = self.ctx.compiler_options.strict_null_checks
127 && matches!(
128 op,
129 "+" | "-"
130 | "*"
131 | "/"
132 | "%"
133 | "**"
134 | "&"
135 | "|"
136 | "^"
137 | "<<"
138 | ">>"
139 | ">>>"
140 | "<"
141 | ">"
142 | "<="
143 | ">="
144 );
145
146 if left_is_nullish && should_emit_nullish_error {
147 let value_name = if left_type == TypeId::NULL {
148 "null"
149 } else {
150 "undefined"
151 };
152 if let Some(loc) = self.get_source_location(left_idx) {
153 let message = format_message(
154 diagnostic_messages::THE_VALUE_CANNOT_BE_USED_HERE,
155 &[value_name],
156 );
157 self.ctx.diagnostics.push(Diagnostic::error(
158 self.ctx.file_name.clone(),
159 loc.start,
160 loc.length(),
161 message,
162 diagnostic_codes::THE_VALUE_CANNOT_BE_USED_HERE,
163 ));
164 emitted_nullish_error = true;
165 }
166 }
167
168 if right_is_nullish && should_emit_nullish_error {
169 let value_name = if right_type == TypeId::NULL {
170 "null"
171 } else {
172 "undefined"
173 };
174 if let Some(loc) = self.get_source_location(right_idx) {
175 let message = format_message(
176 diagnostic_messages::THE_VALUE_CANNOT_BE_USED_HERE,
177 &[value_name],
178 );
179 self.ctx.diagnostics.push(Diagnostic::error(
180 self.ctx.file_name.clone(),
181 loc.start,
182 loc.length(),
183 message,
184 diagnostic_codes::THE_VALUE_CANNOT_BE_USED_HERE,
185 ));
186 emitted_nullish_error = true;
187 }
188 }
189
190 emitted_nullish_error
191 }
192
193 pub(crate) fn emit_binary_operator_error(
196 &mut self,
197 node_idx: NodeIndex,
198 left_idx: NodeIndex,
199 right_idx: NodeIndex,
200 left_type: TypeId,
201 right_type: TypeId,
202 op: &str,
203 emitted_nullish_error: bool,
204 ) {
205 if left_type == TypeId::ERROR
207 || right_type == TypeId::ERROR
208 || left_type == TypeId::UNKNOWN
209 || right_type == TypeId::UNKNOWN
210 {
211 return;
212 }
213
214 let left_is_nullish = left_type == TypeId::NULL || left_type == TypeId::UNDEFINED;
216 let right_is_nullish = right_type == TypeId::NULL || right_type == TypeId::UNDEFINED;
217
218 let should_emit_nullish_error = self.ctx.compiler_options.strict_null_checks
226 && matches!(
227 op,
228 "+" | "-"
229 | "*"
230 | "/"
231 | "%"
232 | "**"
233 | "&"
234 | "|"
235 | "^"
236 | "<<"
237 | ">>"
238 | ">>>"
239 | "<"
240 | ">"
241 | "<="
242 | ">="
243 );
244
245 use tsz_solver::BinaryOpEvaluator;
246
247 let evaluator = BinaryOpEvaluator::new(self.ctx.types);
248
249 let left_is_symbol = evaluator.is_symbol_like(left_type);
253 let right_is_symbol = evaluator.is_symbol_like(right_type);
254
255 if left_is_symbol || right_is_symbol {
256 let left_type_str =
258 left_is_symbol.then(|| self.ctx.create_type_formatter().format(left_type));
259 let right_type_str =
260 right_is_symbol.then(|| self.ctx.create_type_formatter().format(right_type));
261
262 if let (Some(loc), Some(type_str)) =
264 (self.get_source_location(left_idx), left_type_str.as_deref())
265 {
266 let message = format_message(
267 diagnostic_messages::OPERATOR_CANNOT_BE_APPLIED_TO_TYPE,
268 &[op, type_str],
269 );
270 self.ctx.diagnostics.push(Diagnostic::error(
271 self.ctx.file_name.clone(),
272 loc.start,
273 loc.length(),
274 message,
275 diagnostic_codes::OPERATOR_CANNOT_BE_APPLIED_TO_TYPE,
276 ));
277 }
278
279 if let (Some(loc), Some(type_str)) = (
280 self.get_source_location(right_idx),
281 right_type_str.as_deref(),
282 ) {
283 let message = format_message(
284 diagnostic_messages::OPERATOR_CANNOT_BE_APPLIED_TO_TYPE,
285 &[op, type_str],
286 );
287 self.ctx.diagnostics.push(Diagnostic::error(
288 self.ctx.file_name.clone(),
289 loc.start,
290 loc.length(),
291 message,
292 diagnostic_codes::OPERATOR_CANNOT_BE_APPLIED_TO_TYPE,
293 ));
294 }
295
296 if left_is_symbol && right_is_symbol {
298 return;
299 }
300
301 }
304
305 let mut formatter = self.ctx.create_type_formatter();
306 let left_str = formatter.format(left_type);
307 let right_str = formatter.format(right_type);
308
309 let is_arithmetic = matches!(op, "-" | "*" | "/" | "%" | "**");
313 let is_bitwise = matches!(op, "&" | "|" | "^" | "<<" | ">>" | ">>>");
314 let requires_numeric_operands = is_arithmetic || is_bitwise;
315
316 if is_bitwise {
319 let left_is_boolean = evaluator.is_boolean_like(left_type);
320 let right_is_boolean = evaluator.is_boolean_like(right_type);
321 let is_boolean_bitwise =
322 matches!(op, "&" | "|" | "^") && left_is_boolean && right_is_boolean;
323
324 if is_boolean_bitwise {
325 let suggestion = if op == "&" {
326 "&&"
327 } else if op == "|" {
328 "||"
329 } else {
330 "!=="
331 };
332 if let Some(loc) = self.get_source_location(node_idx) {
333 let message = format!(
334 "The '{op}' operator is not allowed for boolean types. Consider using '{suggestion}' instead."
335 );
336 self.ctx.diagnostics.push(Diagnostic::error(self.ctx.file_name.clone(), loc.start, loc.length(), message, diagnostic_codes::THE_OPERATOR_IS_NOT_ALLOWED_FOR_BOOLEAN_TYPES_CONSIDER_USING_INSTEAD));
337 }
338 return;
339 }
340 }
341
342 let eval_left = self.evaluate_type_for_binary_ops(left_type);
345 let eval_right = self.evaluate_type_for_binary_ops(right_type);
346
347 let snc_off = !self.ctx.compiler_options.strict_null_checks;
354 let left_is_valid_arithmetic = !left_is_symbol
355 && (evaluator.is_arithmetic_operand(eval_left)
356 || (snc_off && (eval_left == TypeId::NULL || eval_left == TypeId::UNDEFINED)));
357 let right_is_valid_arithmetic = !right_is_symbol
358 && (evaluator.is_arithmetic_operand(eval_right)
359 || (snc_off && (eval_right == TypeId::NULL || eval_right == TypeId::UNDEFINED)));
360
361 if op == "+" {
365 if !emitted_nullish_error && let Some(loc) = self.get_source_location(node_idx) {
366 let message = format!(
367 "Operator '{op}' cannot be applied to types '{left_str}' and '{right_str}'."
368 );
369 self.ctx.diagnostics.push(Diagnostic::error(
370 self.ctx.file_name.clone(),
371 loc.start,
372 loc.length(),
373 message,
374 diagnostic_codes::OPERATOR_CANNOT_BE_APPLIED_TO_TYPES_AND,
375 ));
376 }
377 return;
378 }
379
380 if requires_numeric_operands {
381 let mut emitted_specific_error = emitted_nullish_error;
385 if !(left_is_valid_arithmetic || left_is_nullish && should_emit_nullish_error)
386 && let Some(loc) = self.get_source_location(left_idx)
387 {
388 let message = "The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.".to_string();
389 self.ctx.diagnostics.push(Diagnostic::error(self.ctx.file_name.clone(), loc.start, loc.length(), message, diagnostic_codes::THE_LEFT_HAND_SIDE_OF_AN_ARITHMETIC_OPERATION_MUST_BE_OF_TYPE_ANY_NUMBER_BIGINT));
390 emitted_specific_error = true;
391 }
392 if !(right_is_valid_arithmetic || right_is_nullish && should_emit_nullish_error)
393 && let Some(loc) = self.get_source_location(right_idx)
394 {
395 let message = "The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.".to_string();
396 self.ctx.diagnostics.push(Diagnostic::error(self.ctx.file_name.clone(), loc.start, loc.length(), message, diagnostic_codes::THE_RIGHT_HAND_SIDE_OF_AN_ARITHMETIC_OPERATION_MUST_BE_OF_TYPE_ANY_NUMBER_BIGINT));
397 emitted_specific_error = true;
398 }
399 if !emitted_specific_error && let Some(loc) = self.get_source_location(node_idx) {
402 let message = format!(
403 "Operator '{op}' cannot be applied to types '{left_str}' and '{right_str}'."
404 );
405 self.ctx.diagnostics.push(Diagnostic::error(
406 self.ctx.file_name.clone(),
407 loc.start,
408 loc.length(),
409 message,
410 diagnostic_codes::OPERATOR_CANNOT_BE_APPLIED_TO_TYPES_AND,
411 ));
412 }
413 return;
414 }
415
416 let is_relational = matches!(op, "<" | ">" | "<=" | ">=");
420 if is_relational
421 && !emitted_nullish_error
422 && let Some(loc) = self.get_source_location(node_idx)
423 {
424 let message = format!(
425 "Operator '{op}' cannot be applied to types '{left_str}' and '{right_str}'."
426 );
427 self.ctx.diagnostics.push(Diagnostic::error(
428 self.ctx.file_name.clone(),
429 loc.start,
430 loc.length(),
431 message,
432 diagnostic_codes::OPERATOR_CANNOT_BE_APPLIED_TO_TYPES_AND,
433 ));
434 }
435 }
436}