1use crate::diagnostics::{
4 Diagnostic, DiagnosticCategory, DiagnosticRelatedInformation, diagnostic_codes,
5 diagnostic_messages, format_message,
6};
7use crate::state::CheckerState;
8use tsz_parser::parser::NodeIndex;
9use tsz_solver::TypeId;
10
11impl<'a> CheckerState<'a> {
12 pub fn try_elaborate_object_literal_arg_error(
22 &mut self,
23 arg_idx: NodeIndex,
24 param_type: TypeId,
25 ) -> bool {
26 use tsz_parser::parser::syntax_kind_ext;
27
28 let arg_node = match self.ctx.arena.get(arg_idx) {
29 Some(node) => node,
30 None => return false,
31 };
32
33 match arg_node.kind {
34 k if k == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION => {
35 self.try_elaborate_object_literal_properties(arg_idx, param_type)
36 }
37 k if k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION => {
38 self.try_elaborate_array_literal_elements(arg_idx, param_type)
39 }
40 k if k == syntax_kind_ext::ARROW_FUNCTION
41 || k == syntax_kind_ext::FUNCTION_EXPRESSION =>
42 {
43 self.try_elaborate_function_arg_return_error(arg_idx, param_type)
44 }
45 _ => false,
46 }
47 }
48
49 fn try_elaborate_function_arg_return_error(
50 &mut self,
51 arg_idx: NodeIndex,
52 param_type: TypeId,
53 ) -> bool {
54 use tsz_parser::parser::syntax_kind_ext;
55
56 let Some(arg_node) = self.ctx.arena.get(arg_idx) else {
57 return false;
58 };
59 let Some(func) = self.ctx.arena.get_function(arg_node) else {
60 return false;
61 };
62
63 let Some(expected_return_type) = self.first_callable_return_type(param_type) else {
64 return false;
65 };
66
67 let Some(body_node) = self.ctx.arena.get(func.body) else {
68 return false;
69 };
70
71 match body_node.kind {
72 k if k == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
74 || k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION =>
75 {
76 self.try_elaborate_object_literal_arg_error(func.body, expected_return_type)
77 }
78 k if k == syntax_kind_ext::PARENTHESIZED_EXPRESSION => {
79 let Some(paren) = self.ctx.arena.get_parenthesized(body_node) else {
80 return false;
81 };
82 self.try_elaborate_object_literal_arg_error(paren.expression, expected_return_type)
83 }
84 _ => false,
85 }
86 }
87
88 fn first_callable_return_type(&self, ty: TypeId) -> Option<TypeId> {
89 use tsz_solver::type_queries::{
90 get_callable_shape, get_function_shape, get_type_application,
91 };
92
93 if let Some(shape) = get_function_shape(self.ctx.types, ty) {
94 return Some(shape.return_type);
95 }
96
97 if let Some(shape) = get_callable_shape(self.ctx.types, ty) {
98 return shape.call_signatures.first().map(|sig| sig.return_type);
99 }
100
101 if let Some(app) = get_type_application(self.ctx.types, ty) {
102 return self.first_callable_return_type(app.base);
103 }
104
105 None
106 }
107
108 fn try_elaborate_object_literal_properties(
110 &mut self,
111 arg_idx: NodeIndex,
112 param_type: TypeId,
113 ) -> bool {
114 use tsz_parser::parser::syntax_kind_ext;
115
116 let arg_node = match self.ctx.arena.get(arg_idx) {
117 Some(node) => node,
118 None => return false,
119 };
120
121 let obj = match self.ctx.arena.get_literal_expr(arg_node) {
122 Some(obj) => obj.clone(),
123 None => return false,
124 };
125
126 let mut elaborated = false;
127
128 for &elem_idx in &obj.elements.nodes {
129 let Some(elem_node) = self.ctx.arena.get(elem_idx) else {
130 continue;
131 };
132
133 let (prop_name_idx, prop_value_idx) = match elem_node.kind {
135 k if k == syntax_kind_ext::PROPERTY_ASSIGNMENT => {
136 match self.ctx.arena.get_property_assignment(elem_node) {
137 Some(prop) => (prop.name, prop.initializer),
138 None => continue,
139 }
140 }
141 k if k == syntax_kind_ext::SHORTHAND_PROPERTY_ASSIGNMENT => {
142 match self.ctx.arena.get_shorthand_property(elem_node) {
143 Some(prop) => (prop.name, prop.name),
144 None => continue,
145 }
146 }
147 _ => continue,
148 };
149
150 let prop_name = match self.ctx.arena.get_identifier_at(prop_name_idx) {
152 Some(ident) => ident.escaped_text.clone(),
153 None => continue,
154 };
155
156 let target_prop_type = match self
158 .resolve_property_access_with_env(param_type, &prop_name)
159 {
160 tsz_solver::operations::property::PropertyAccessResult::Success {
161 type_id, ..
162 } => type_id,
163 _ => continue,
164 };
165
166 let source_prop_type = self.get_type_of_node(prop_value_idx);
168
169 if source_prop_type == TypeId::ERROR
171 || source_prop_type == TypeId::ANY
172 || target_prop_type == TypeId::ERROR
173 || target_prop_type == TypeId::ANY
174 {
175 continue;
176 }
177
178 if !self.is_assignable_to(source_prop_type, target_prop_type) {
180 let source_prop_type_for_diagnostic =
181 if self.is_fresh_literal_expression(prop_value_idx) {
182 self.widen_literal_type(source_prop_type)
183 } else {
184 source_prop_type
185 };
186
187 self.error_type_not_assignable_at_with_anchor(
189 source_prop_type_for_diagnostic,
190 target_prop_type,
191 prop_name_idx,
192 );
193 elaborated = true;
194 }
195 }
196
197 elaborated
198 }
199
200 fn try_elaborate_array_literal_elements(
202 &mut self,
203 arg_idx: NodeIndex,
204 param_type: TypeId,
205 ) -> bool {
206 use tsz_parser::parser::syntax_kind_ext;
207
208 let arg_node = match self.ctx.arena.get(arg_idx) {
209 Some(node) if node.kind == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION => node,
210 _ => return false,
211 };
212
213 let arr = match self.ctx.arena.get_literal_expr(arg_node) {
214 Some(arr) => arr.clone(),
215 None => return false,
216 };
217
218 let ctx_helper = tsz_solver::ContextualTypeContext::with_expected_and_options(
219 self.ctx.types,
220 param_type,
221 self.ctx.compiler_options.no_implicit_any,
222 );
223
224 let mut elaborated = false;
225
226 for (index, &elem_idx) in arr.elements.nodes.iter().enumerate() {
227 let Some(elem_node) = self.ctx.arena.get(elem_idx) else {
228 continue;
229 };
230
231 if elem_node.kind == syntax_kind_ext::SPREAD_ELEMENT {
233 continue;
234 }
235
236 let target_element_type = if let Some(t) = ctx_helper.get_tuple_element_type(index) {
238 t
239 } else if let Some(t) = ctx_helper.get_array_element_type() {
240 t
241 } else {
242 continue;
243 };
244
245 let elem_type = self.get_type_of_node(elem_idx);
246
247 if elem_type == TypeId::ERROR
249 || elem_type == TypeId::ANY
250 || target_element_type == TypeId::ERROR
251 || target_element_type == TypeId::ANY
252 {
253 continue;
254 }
255
256 if !self.is_assignable_to(elem_type, target_element_type) {
257 tracing::debug!(
258 "try_elaborate_array_literal_elements: elem_type = {:?}, target_element_type = {:?}, file = {}",
259 elem_type,
260 target_element_type,
261 self.ctx.file_name
262 );
263 self.error_type_not_assignable_at_with_anchor(
264 elem_type,
265 target_element_type,
266 elem_idx,
267 );
268 elaborated = true;
269 }
270 }
271
272 elaborated
273 }
274
275 pub fn error_argument_not_assignable_at(
277 &mut self,
278 arg_type: TypeId,
279 param_type: TypeId,
280 idx: NodeIndex,
281 ) {
282 tracing::debug!(
283 "error_argument_not_assignable_at: File name: {}",
284 self.ctx.file_name
285 );
286
287 if arg_type == TypeId::ERROR || param_type == TypeId::ERROR {
290 return;
291 }
292 if arg_type == TypeId::ANY || param_type == TypeId::ANY {
293 return;
294 }
295 if arg_type == TypeId::UNKNOWN || param_type == TypeId::UNKNOWN {
296 return;
297 }
298 if let Some(loc) = self.get_source_location(idx) {
299 let arg_str = self.format_type_for_assignability_message(arg_type);
300 let param_str = self.format_type_for_assignability_message(param_type);
301 let message = format_message(
302 diagnostic_messages::ARGUMENT_OF_TYPE_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE,
303 &[&arg_str, ¶m_str],
304 );
305 tracing::debug!("File name: {}", self.ctx.file_name);
306 self.ctx.diagnostics.push(Diagnostic::error(
308 self.ctx.file_name.clone(),
309 loc.start,
310 loc.length(),
311 message,
312 diagnostic_codes::ARGUMENT_OF_TYPE_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE,
313 ));
314 }
315 }
316
317 pub fn error_argument_count_mismatch_at(
320 &mut self,
321 expected: usize,
322 got: usize,
323 idx: NodeIndex,
324 ) {
325 let report_idx = self.call_error_anchor_node(idx);
326 if let Some(loc) = self.get_source_location(report_idx) {
327 let mut builder = tsz_solver::SpannedDiagnosticBuilder::with_symbols(
328 self.ctx.types,
329 &self.ctx.binder.symbols,
330 self.ctx.file_name.as_str(),
331 )
332 .with_def_store(&self.ctx.definition_store);
333 let diag = builder.argument_count_mismatch(expected, got, loc.start, loc.length());
334 self.ctx
335 .diagnostics
336 .push(diag.to_checker_diagnostic(&self.ctx.file_name));
337 }
338 }
339
340 pub fn error_spread_must_be_tuple_or_rest_at(&mut self, idx: NodeIndex) {
343 if let Some(loc) = self.get_source_location(idx) {
344 self.ctx.diagnostics.push(Diagnostic::error(self.ctx.file_name.clone(), loc.start, loc.length(), diagnostic_messages::A_SPREAD_ARGUMENT_MUST_EITHER_HAVE_A_TUPLE_TYPE_OR_BE_PASSED_TO_A_REST_PARAMETER.to_string(), diagnostic_codes::A_SPREAD_ARGUMENT_MUST_EITHER_HAVE_A_TUPLE_TYPE_OR_BE_PASSED_TO_A_REST_PARAMETER));
345 }
346 }
347
348 pub fn error_expected_at_least_arguments_at(
351 &mut self,
352 expected_min: usize,
353 got: usize,
354 idx: NodeIndex,
355 ) {
356 let report_idx = self.call_error_anchor_node(idx);
357 if let Some(loc) = self.get_source_location(report_idx) {
358 let message = format!("Expected at least {expected_min} arguments, but got {got}.");
359 self.ctx.diagnostics.push(Diagnostic::error(
360 self.ctx.file_name.clone(),
361 loc.start,
362 loc.length(),
363 message,
364 diagnostic_codes::EXPECTED_AT_LEAST_ARGUMENTS_BUT_GOT,
365 ));
366 }
367 }
368
369 fn call_error_anchor_node(&self, idx: NodeIndex) -> NodeIndex {
371 use tsz_parser::parser::syntax_kind_ext;
372
373 let Some(node) = self.ctx.arena.get(idx) else {
374 return idx;
375 };
376 if node.kind != syntax_kind_ext::CALL_EXPRESSION {
377 return idx;
378 }
379
380 let Some(call) = self.ctx.arena.get_call_expr(node) else {
381 return idx;
382 };
383 let Some(callee_node) = self.ctx.arena.get(call.expression) else {
384 return idx;
385 };
386
387 if callee_node.kind == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION
388 && let Some(access) = self.ctx.arena.get_access_expr(callee_node)
389 {
390 return access.name_or_argument;
391 }
392
393 call.expression
394 }
395
396 pub fn error_no_overload_matches_at(
398 &mut self,
399 idx: NodeIndex,
400 failures: &[tsz_solver::PendingDiagnostic],
401 ) {
402 tracing::debug!(
403 "error_no_overload_matches_at: File name: {}",
404 self.ctx.file_name
405 );
406
407 if self.should_suppress_concat_overload_error(idx) {
408 return;
409 }
410
411 use tsz_solver::PendingDiagnostic;
412
413 let Some(loc) = self.get_source_location(idx) else {
414 return;
415 };
416
417 let mut formatter = self.ctx.create_type_formatter();
418 let mut related = Vec::new();
419 let span =
420 tsz_solver::SourceSpan::new(self.ctx.file_name.as_str(), loc.start, loc.length());
421
422 tracing::debug!("File name: {}", self.ctx.file_name);
423
424 for failure in failures {
425 let pending: PendingDiagnostic = PendingDiagnostic {
426 span: Some(span.clone()),
427 ..failure.clone()
428 };
429 let diag = formatter.render(&pending);
430 if let Some(diag_span) = diag.span.as_ref() {
431 related.push(DiagnosticRelatedInformation {
432 file: diag_span.file.to_string(),
433 start: diag_span.start,
434 length: diag_span.length,
435 message_text: diag.message.clone(),
436 category: DiagnosticCategory::Message,
437 code: diag.code,
438 });
439 }
440 }
441
442 self.ctx.diagnostics.push(Diagnostic {
443 code: diagnostic_codes::NO_OVERLOAD_MATCHES_THIS_CALL,
444 category: DiagnosticCategory::Error,
445 message_text: diagnostic_messages::NO_OVERLOAD_MATCHES_THIS_CALL.to_string(),
446 file: self.ctx.file_name.clone(),
447 start: loc.start,
448 length: loc.length(),
449 related_information: related,
450 });
451 }
452
453 fn should_suppress_concat_overload_error(&mut self, idx: NodeIndex) -> bool {
454 use crate::query_boundaries::call_checker::array_element_type_for_type;
455 use tsz_solver::type_queries::contains_type_parameters_db;
456
457 let Some(node) = self.ctx.arena.get(idx) else {
458 return false;
459 };
460 let Some(call) = self.ctx.arena.get_call_expr(node) else {
461 return false;
462 };
463 let Some(expr_node) = self.ctx.arena.get(call.expression) else {
464 return false;
465 };
466 let Some(access) = self.ctx.arena.get_access_expr(expr_node) else {
467 return false;
468 };
469 let Some(name_node) = self.ctx.arena.get(access.name_or_argument) else {
470 return false;
471 };
472 let Some(name_ident) = self.ctx.arena.get_identifier(name_node) else {
473 return false;
474 };
475 if name_ident.escaped_text != "concat" {
476 return false;
477 }
478
479 let Some(args) = &call.arguments else {
480 return false;
481 };
482 if args.nodes.is_empty() {
483 return false;
484 }
485
486 args.nodes.iter().all(|&arg_idx| {
487 let arg_type = self.get_type_of_node(arg_idx);
488 array_element_type_for_type(self.ctx.types, arg_type).is_some()
489 && contains_type_parameters_db(self.ctx.types, arg_type)
490 })
491 }
492
493 pub fn error_type_parameter_used_as_value(&mut self, name: &str, idx: NodeIndex) {
495 if let Some(loc) = self.get_source_location(idx) {
496 use tsz_common::diagnostics::diagnostic_codes;
497
498 let message =
499 format!("'{name}' only refers to a type, but is being used as a value here.");
500
501 self.ctx.push_diagnostic(Diagnostic::error(
502 self.ctx.file_name.clone(),
503 loc.start,
504 loc.length(),
505 message,
506 diagnostic_codes::ONLY_REFERS_TO_A_TYPE_BUT_IS_BEING_USED_AS_A_VALUE_HERE,
507 ));
508 }
509 }
510
511 pub fn error_this_type_mismatch_at(
513 &mut self,
514 expected_this: TypeId,
515 actual_this: TypeId,
516 idx: NodeIndex,
517 ) {
518 if let Some(loc) = self.get_source_location(idx) {
519 let mut builder = tsz_solver::SpannedDiagnosticBuilder::with_symbols(
520 self.ctx.types,
521 &self.ctx.binder.symbols,
522 self.ctx.file_name.as_str(),
523 )
524 .with_def_store(&self.ctx.definition_store);
525 let diag =
526 builder.this_type_mismatch(expected_this, actual_this, loc.start, loc.length());
527 self.ctx
528 .diagnostics
529 .push(diag.to_checker_diagnostic(&self.ctx.file_name));
530 }
531 }
532
533 pub fn error_not_callable_at(&mut self, type_id: TypeId, idx: NodeIndex) {
535 if type_id == TypeId::ERROR || type_id == TypeId::UNKNOWN {
537 return;
538 }
539
540 if let Some(loc) = self.get_source_location(idx) {
541 let mut builder = tsz_solver::SpannedDiagnosticBuilder::with_symbols(
542 self.ctx.types,
543 &self.ctx.binder.symbols,
544 self.ctx.file_name.as_str(),
545 )
546 .with_def_store(&self.ctx.definition_store);
547 let diag = builder.not_callable(type_id, loc.start, loc.length());
548 self.ctx
549 .diagnostics
550 .push(diag.to_checker_diagnostic(&self.ctx.file_name));
551 }
552 }
553
554 pub fn error_get_accessor_not_callable_at(&mut self, idx: NodeIndex) {
557 use tsz_parser::parser::syntax_kind_ext;
558
559 let report_idx = self
560 .ctx
561 .arena
562 .get(idx)
563 .and_then(|node| {
564 if node.kind == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION {
565 self.ctx
566 .arena
567 .get_access_expr(node)
568 .map(|access| access.name_or_argument)
569 } else {
570 None
571 }
572 })
573 .unwrap_or(idx);
574
575 if let Some(loc) = self.get_source_location(report_idx) {
576 use crate::diagnostics::diagnostic_codes;
577 self.ctx.diagnostics.push(
578 crate::diagnostics::Diagnostic::error(
579 self.ctx.file_name.clone(),
580 loc.start,
581 loc.length(),
582 "This expression is not callable because it is a 'get' accessor. Did you mean to use it without '()'?".to_string(),
583 diagnostic_codes::THIS_EXPRESSION_IS_NOT_CALLABLE_BECAUSE_IT_IS_A_GET_ACCESSOR_DID_YOU_MEAN_TO_USE,
584 ),
585 );
586 }
587 }
588
589 pub fn error_class_constructor_without_new_at(&mut self, type_id: TypeId, idx: NodeIndex) {
592 if type_id == TypeId::ERROR || type_id == TypeId::UNKNOWN {
594 return;
595 }
596
597 let Some(loc) = self.get_source_location(idx) else {
598 return;
599 };
600
601 let mut formatter = self.ctx.create_type_formatter();
602 let type_str = formatter.format(type_id);
603
604 let message =
605 diagnostic_messages::VALUE_OF_TYPE_IS_NOT_CALLABLE_DID_YOU_MEAN_TO_INCLUDE_NEW
606 .replace("{0}", &type_str);
607
608 self.ctx.diagnostics.push(Diagnostic::error(
609 self.ctx.file_name.clone(),
610 loc.start,
611 loc.length(),
612 message,
613 diagnostic_codes::VALUE_OF_TYPE_IS_NOT_CALLABLE_DID_YOU_MEAN_TO_INCLUDE_NEW,
614 ));
615 }
616
617 pub fn error_non_void_function_called_with_new_at(&mut self, idx: NodeIndex) {
619 let Some(loc) = self.get_source_location(idx) else {
620 return;
621 };
622
623 self.ctx.diagnostics.push(Diagnostic::error(
624 self.ctx.file_name.clone(),
625 loc.start,
626 loc.length(),
627 diagnostic_messages::ONLY_A_VOID_FUNCTION_CAN_BE_CALLED_WITH_THE_NEW_KEYWORD
628 .to_string(),
629 diagnostic_codes::ONLY_A_VOID_FUNCTION_CAN_BE_CALLED_WITH_THE_NEW_KEYWORD,
630 ));
631 }
632}