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
//! Generic type argument validation (TS2344 constraint checking).
use crate::query_boundaries::generic_checker as query;
use crate::state::CheckerState;
use tsz_parser::parser::NodeIndex;
use tsz_solver::TypeId;
// =============================================================================
// Generic Type Argument Checking Methods
// =============================================================================
impl<'a> CheckerState<'a> {
// =========================================================================
// Type Argument Validation
// =========================================================================
/// Validate explicit type arguments against their constraints for call expressions.
/// Reports TS2344 when a type argument doesn't satisfy its constraint.
/// Reports TS2558 when a non-generic function is called with type arguments.
pub(crate) fn validate_call_type_arguments(
&mut self,
callee_type: TypeId,
type_args_list: &tsz_parser::parser::NodeList,
call_idx: NodeIndex,
) {
use tsz_scanner::SyntaxKind;
if let Some(call_expr) = self.ctx.arena.get_call_expr_at(call_idx)
&& let Some(callee_node) = self.ctx.arena.get(call_expr.expression)
&& callee_node.kind == SyntaxKind::SuperKeyword as u16
&& !type_args_list.nodes.is_empty()
{
self.error_at_node(
call_idx,
crate::diagnostics::diagnostic_messages::SUPER_MAY_NOT_USE_TYPE_ARGUMENTS,
crate::diagnostics::diagnostic_codes::SUPER_MAY_NOT_USE_TYPE_ARGUMENTS,
);
return;
}
let callee_type = self.evaluate_application_type(callee_type);
// Resolve Lazy types so the classifier can see callable/function signatures.
let callee_type = {
let resolved = self.resolve_lazy_type(callee_type);
if resolved != callee_type {
resolved
} else {
callee_type
}
};
let got = type_args_list.nodes.len();
let type_arg_error_anchor = type_args_list.nodes.first().copied().unwrap_or(call_idx);
// Get the type parameters from the callee type. For callables with overloads,
// prefer a signature whose type parameter arity matches the provided type args.
let type_params =
match query::classify_for_type_argument_extraction(self.ctx.types, callee_type) {
query::TypeArgumentExtractionKind::Function(shape_id) => {
let shape = self.ctx.types.function_shape(shape_id);
shape.type_params.clone()
}
query::TypeArgumentExtractionKind::Callable(shape_id) => {
let shape = self.ctx.types.callable_shape(shape_id);
let matching = shape
.call_signatures
.iter()
.find(|sig| {
let max = sig.type_params.len();
let min = sig
.type_params
.iter()
.filter(|tp| tp.default.is_none())
.count();
got >= min && got <= max
})
.map(|sig| sig.type_params.clone());
if let Some(params) = matching {
params
} else {
// Fall back to first signature for diagnostics when no arity match exists.
shape
.call_signatures
.first()
.map(|sig| sig.type_params.clone())
.unwrap_or_default()
}
}
query::TypeArgumentExtractionKind::Other => return,
};
let max_expected = type_params.len();
let min_required = type_params.iter().filter(|tp| tp.default.is_none()).count();
if type_params.is_empty() {
// TS2558: Expected 0 type arguments, but got N.
if got > 0 {
self.error_at_node_msg(
type_arg_error_anchor,
crate::diagnostics::diagnostic_codes::EXPECTED_TYPE_ARGUMENTS_BUT_GOT,
&["0", &got.to_string()],
);
}
return;
}
if got < min_required || got > max_expected {
// TS2558: Expected N type arguments, but got M.
// When there are type params with defaults, show the range
let expected_str = if min_required == max_expected {
max_expected.to_string()
} else {
format!("{min_required}-{max_expected}")
};
self.error_at_node_msg(
type_arg_error_anchor,
crate::diagnostics::diagnostic_codes::EXPECTED_TYPE_ARGUMENTS_BUT_GOT,
&[&expected_str, &got.to_string()],
);
return;
}
self.validate_type_args_against_params(&type_params, type_args_list);
}
/// Validate type arguments against their constraints for type references (e.g., `A<X, Y>`).
/// Reports TS2344 when a type argument doesn't satisfy its constraint.
///
/// This handles cases like `class A<T, U extends T>` where `A<{a: string}, {b: string}>`
/// should error because `{b: string}` doesn't extend `{a: string}`.
pub(crate) fn validate_type_reference_type_arguments(
&mut self,
sym_id: tsz_binder::SymbolId,
type_args_list: &tsz_parser::parser::NodeList,
) {
use tsz_binder::symbol_flags;
let mut sym_id = sym_id;
if let Some(symbol) = self.ctx.binder.get_symbol(sym_id)
&& symbol.flags & symbol_flags::ALIAS != 0
{
let mut visited_aliases = Vec::new();
if let Some(target) = self.resolve_alias_symbol(sym_id, &mut visited_aliases) {
sym_id = target;
}
}
let type_params = self.get_type_params_for_symbol(sym_id);
if type_params.is_empty() {
// Before emitting TS2315, check if this symbol's declaration actually has
// type parameters. Cross-arena symbols (e.g., lib types like Awaited<T>)
// may fail to resolve type parameters because their declaration is in a
// different arena. In that case, check the declaration directly to avoid
// false positives.
let has_type_params_in_decl = self.symbol_declaration_has_type_parameters(sym_id);
// Suppress TS2315 for symbols from unresolved modules (type is ERROR)
let symbol_type = self.get_type_of_symbol(sym_id);
if !has_type_params_in_decl
&& symbol_type != TypeId::ERROR
&& symbol_type != TypeId::ANY
&& let Some(&arg_idx) = type_args_list.nodes.first()
{
let lib_binders = self.get_lib_binders();
let name = self
.ctx
.binder
.get_symbol_with_libs(sym_id, &lib_binders)
.map_or_else(|| "<unknown>".to_string(), |s| s.escaped_name.clone());
self.error_at_node_msg(
arg_idx,
crate::diagnostics::diagnostic_codes::TYPE_IS_NOT_GENERIC,
&[name.as_str()],
);
}
return;
}
// Collect the provided type arguments for circular reference check
let type_args: Vec<TypeId> = type_args_list
.nodes
.iter()
.map(|&arg_idx| self.get_type_from_type_node(arg_idx))
.collect();
let symbol_type = self.get_type_of_symbol(sym_id);
if type_args.contains(&symbol_type) {
// Report TS4109 at the specific type argument node
if let Some(&arg_idx) = type_args_list.nodes.first() {
let lib_binders = self.get_lib_binders();
let name = self
.ctx
.binder
.get_symbol_with_libs(sym_id, &lib_binders)
.map_or_else(|| "<unknown>".to_string(), |s| s.escaped_name.clone());
self.error_at_node_msg(
arg_idx,
crate::diagnostics::diagnostic_codes::TYPE_ARGUMENTS_FOR_CIRCULARLY_REFERENCE_THEMSELVES,
&[name.as_str()],
);
}
}
// Validate type arguments against their constraints
self.validate_type_args_against_params(&type_params, type_args_list);
}
/// Validate explicit type arguments against their constraints for new expressions.
/// Reports TS2344 when a type argument doesn't satisfy its constraint.
pub(crate) fn validate_new_expression_type_arguments(
&mut self,
constructor_type: TypeId,
type_args_list: &tsz_parser::parser::NodeList,
call_idx: NodeIndex,
) {
// Get the type parameters from the constructor type
let Some(shape) = query::callable_shape_for_type(self.ctx.types, constructor_type) else {
return;
};
let got = type_args_list.nodes.len();
let type_arg_error_anchor = type_args_list.nodes.first().copied().unwrap_or(call_idx);
// For callable types with overloaded construct signatures, prefer
// a signature whose type parameter arity matches the provided args.
let type_params = {
let matching = shape
.construct_signatures
.iter()
.find(|sig| {
let max = sig.type_params.len();
let min = sig
.type_params
.iter()
.filter(|tp| tp.default.is_none())
.count();
got >= min && got <= max
})
.map(|sig| sig.type_params.clone());
if let Some(params) = matching {
params
} else {
// Fall back to first signature for diagnostics
shape
.construct_signatures
.first()
.map(|sig| sig.type_params.clone())
.unwrap_or_default()
}
};
if type_params.is_empty() {
// TS2558: Expected 0 type arguments, but got N.
if got > 0 {
self.error_at_node_msg(
type_arg_error_anchor,
crate::diagnostics::diagnostic_codes::EXPECTED_TYPE_ARGUMENTS_BUT_GOT,
&["0", &got.to_string()],
);
}
return;
}
let max_expected = type_params.len();
let min_required = type_params.iter().filter(|tp| tp.default.is_none()).count();
if got < min_required || got > max_expected {
// TS2558: Expected N type arguments, but got M.
let expected_str = if min_required == max_expected {
max_expected.to_string()
} else {
format!("{min_required}-{max_expected}")
};
self.error_at_node_msg(
type_arg_error_anchor,
crate::diagnostics::diagnostic_codes::EXPECTED_TYPE_ARGUMENTS_BUT_GOT,
&[&expected_str, &got.to_string()],
);
return;
}
self.validate_type_args_against_params(&type_params, type_args_list);
}
/// Validate each type argument against its corresponding type parameter constraint.
/// Reports TS2344 when a type argument doesn't satisfy its constraint.
///
/// Shared implementation used by call expressions, new expressions, and type references.
fn validate_type_args_against_params(
&mut self,
type_params: &[tsz_solver::TypeParamInfo],
type_args_list: &tsz_parser::parser::NodeList,
) {
let type_args: Vec<TypeId> = type_args_list
.nodes
.iter()
.map(|&arg_idx| self.get_type_from_type_node(arg_idx))
.collect();
for (i, (param, &type_arg)) in type_params.iter().zip(type_args.iter()).enumerate() {
if let Some(constraint) = param.constraint {
// Skip constraint checking when the type argument is an error type
// (avoids cascading errors from unresolved references)
if type_arg == TypeId::ERROR {
continue;
}
// Skip constraint checking when the type argument contains unresolved type parameters
// (they'll be checked later when fully instantiated)
if query::contains_type_parameters(self.ctx.types, type_arg) {
continue;
}
// Resolve the constraint in case it's a Lazy type
let constraint = self.resolve_lazy_type(constraint);
// Instantiate the constraint with type arguments up to and including the
// current parameter so self-referential constraints are validated.
let mut subst = tsz_solver::TypeSubstitution::new();
for (j, p) in type_params.iter().take(i + 1).enumerate() {
if let Some(&arg) = type_args.get(j) {
subst.insert(p.name, arg);
}
}
let instantiated_constraint = if subst.is_empty() {
constraint
} else {
tsz_solver::instantiate_type(self.ctx.types, constraint, &subst)
};
// Skip if the instantiated constraint contains type parameters
if query::contains_type_parameters(self.ctx.types, instantiated_constraint) {
continue;
}
let is_satisfied = self.is_assignable_to(type_arg, instantiated_constraint);
if !is_satisfied && let Some(&arg_idx) = type_args_list.nodes.get(i) {
self.error_type_constraint_not_satisfied(
type_arg,
instantiated_constraint,
arg_idx,
);
}
}
}
}
/// Check if a symbol's declaration has type parameters, even if they couldn't be
/// resolved via `get_type_params_for_symbol` (e.g., cross-arena lib types).
pub(crate) fn symbol_declaration_has_type_parameters(
&self,
sym_id: tsz_binder::SymbolId,
) -> bool {
let lib_binders = self.get_lib_binders();
let symbol = self.ctx.binder.get_symbol_with_libs(sym_id, &lib_binders);
let Some(symbol) = symbol else {
return false;
};
// Check the value declaration and all declarations for type parameters
let decl_indices: Vec<_> = if symbol.value_declaration.is_some() {
std::iter::once(symbol.value_declaration)
.chain(symbol.declarations.iter().copied())
.collect()
} else {
symbol.declarations.clone()
};
for decl_idx in decl_indices {
// Try current arena first
if let Some(node) = self.ctx.arena.get(decl_idx) {
if let Some(ta) = self.ctx.arena.get_type_alias(node) {
if ta.type_parameters.is_some() {
return true;
}
continue;
}
if let Some(iface) = self.ctx.arena.get_interface(node) {
if iface.type_parameters.is_some() {
return true;
}
continue;
}
if let Some(class) = self.ctx.arena.get_class(node) {
if class.type_parameters.is_some() {
return true;
}
continue;
}
}
// Try cross-arena (lib files)
if let Some(decl_arena) = self.ctx.binder.symbol_arenas.get(&sym_id)
&& let Some(node) = decl_arena.get(decl_idx)
{
if let Some(ta) = decl_arena.get_type_alias(node) {
if ta.type_parameters.is_some() {
return true;
}
continue;
}
if let Some(iface) = decl_arena.get_interface(node) {
if iface.type_parameters.is_some() {
return true;
}
continue;
}
if let Some(class) = decl_arena.get_class(node) {
if class.type_parameters.is_some() {
return true;
}
continue;
}
}
// Try declaration_arenas
if let Some(decl_arena) = self
.ctx
.binder
.declaration_arenas
.get(&(sym_id, decl_idx))
.and_then(|v| v.first())
&& let Some(node) = decl_arena.get(decl_idx)
{
if let Some(ta) = decl_arena.get_type_alias(node) {
if ta.type_parameters.is_some() {
return true;
}
continue;
}
if let Some(iface) = decl_arena.get_interface(node) {
if iface.type_parameters.is_some() {
return true;
}
continue;
}
if let Some(class) = decl_arena.get_class(node) {
if class.type_parameters.is_some() {
return true;
}
continue;
}
}
}
false
}
}