Skip to main content

tsz_checker/error_reporter/
call_errors.rs

1//! Function call error reporting (TS2345, TS2554, TS2769).
2
3use 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    /// Try to elaborate an argument type mismatch for object/array literal arguments.
13    ///
14    /// When an object literal argument has a property whose value type doesn't match
15    /// the expected property type, tsc reports TS2322 on the specific property name
16    /// rather than TS2345 on the whole argument. Similarly for array literals, tsc
17    /// reports TS2322 on each element that doesn't match the expected element type.
18    ///
19    /// Returns `true` if elaboration produced at least one property-level error (TS2322),
20    /// meaning the caller should NOT emit TS2345 on the whole argument.
21    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            // Expression-bodied arrow function: () => ({ ... })
73            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    /// Elaborate object literal property type mismatches with TS2322.
109    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            // Only elaborate regular property assignments and shorthand properties
134            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            // Get the property name string
151            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            // Look up the expected property type in the target parameter type
157            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            // Get the type of the property value in the object literal
167            let source_prop_type = self.get_type_of_node(prop_value_idx);
168
169            // Skip if types are unresolved
170            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            // Check if the property value type is assignable to the target property type
179            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                // Emit TS2322 on the property name node
188                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    /// Elaborate array literal element type mismatches with TS2322.
201    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            // Skip spread elements
232            if elem_node.kind == syntax_kind_ext::SPREAD_ELEMENT {
233                continue;
234            }
235
236            // Get the expected element type from the parameter array/tuple type
237            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            // Skip if types are unresolved
248            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    /// Report an argument not assignable error using solver diagnostics with source tracking.
276    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        // Suppress cascading errors when either type is ERROR, ANY, or UNKNOWN
288
289        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, &param_str],
304            );
305            tracing::debug!("File name: {}", self.ctx.file_name);
306            // TODO: tsc emits elaboration (missing properties, callable, type mismatch) as related information
307            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    /// Report an argument count mismatch error using solver diagnostics with source tracking.
318    /// TS2554: Expected {0} arguments, but got {1}.
319    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    /// Report a spread argument type error (TS2556).
341    /// TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.
342    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    /// Report an "expected at least N arguments" error (TS2555).
349    /// TS2555: Expected at least {0} arguments, but got {1}.
350    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    /// Prefer callee name span for call-arity diagnostics.
370    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    /// Report "No overload matches this call" with related overload failures.
397    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    /// Report TS2693: type parameter used as value
494    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    /// Report a "this type mismatch" error using solver diagnostics with source tracking.
512    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    /// Report a "type is not callable" error using solver diagnostics with source tracking.
534    pub fn error_not_callable_at(&mut self, type_id: TypeId, idx: NodeIndex) {
535        // Suppress cascade errors from unresolved types
536        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    /// Report TS6234: "This expression is not callable because it is a 'get' accessor.
555    /// Did you mean to access it without '()'?"
556    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    /// Report TS2348: "Value of type '{0}' is not callable. Did you mean to include 'new'?"
590    /// This is specifically for class constructors called without 'new'.
591    pub fn error_class_constructor_without_new_at(&mut self, type_id: TypeId, idx: NodeIndex) {
592        // Suppress cascade errors from unresolved types
593        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    /// Report TS2350: "Only a void function can be called with the 'new' keyword."
618    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}