Skip to content

Commit cea1cfb

Browse files
authored
Consistently error when rest element isn't last in tuple type (microsoft#40254)
* Consistently error when rest element isn't last in tuple type * Add regression test * Accept new baselines * Stricter circular recursion check in type inference * Revert "Stricter circular recursion check in type inference" This reverts commit 80e6df6. * Revert "Accept new baselines" This reverts commit 355706d. * Accept new baselines
1 parent de5ef35 commit cea1cfb

File tree

6 files changed

+105
-6
lines changed

6 files changed

+105
-6
lines changed

src/compiler/checker.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -31172,6 +31172,7 @@ namespace ts {
3117231172
function checkTupleType(node: TupleTypeNode) {
3117331173
const elementTypes = node.elements;
3117431174
let seenOptionalElement = false;
31175+
let seenRestElement = false;
3117531176
const hasNamedElement = some(elementTypes, isNamedTupleMember);
3117631177
for (let i = 0; i < elementTypes.length; i++) {
3117731178
const e = elementTypes[i];
@@ -31181,16 +31182,17 @@ namespace ts {
3118131182
}
3118231183
const flags = getTupleElementFlags(e);
3118331184
if (flags & ElementFlags.Variadic) {
31184-
if (!isArrayLikeType(getTypeFromTypeNode((<RestTypeNode | NamedTupleMember>e).type))) {
31185+
const type = getTypeFromTypeNode((<RestTypeNode | NamedTupleMember>e).type);
31186+
if (!isArrayLikeType(type)) {
3118531187
error(e, Diagnostics.A_rest_element_type_must_be_an_array_type);
3118631188
break;
3118731189
}
31190+
if (isArrayType(type) || isTupleType(type) && type.target.combinedFlags & ElementFlags.Rest) {
31191+
seenRestElement = true;
31192+
}
3118831193
}
3118931194
else if (flags & ElementFlags.Rest) {
31190-
if (i !== elementTypes.length - 1) {
31191-
grammarErrorOnNode(e, Diagnostics.A_rest_element_must_be_last_in_a_tuple_type);
31192-
break;
31193-
}
31195+
seenRestElement = true;
3119431196
}
3119531197
else if (flags & ElementFlags.Optional) {
3119631198
seenOptionalElement = true;
@@ -31199,6 +31201,10 @@ namespace ts {
3119931201
grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element);
3120031202
break;
3120131203
}
31204+
if (seenRestElement && i !== elementTypes.length - 1) {
31205+
grammarErrorOnNode(e, Diagnostics.A_rest_element_must_be_last_in_a_tuple_type);
31206+
break;
31207+
}
3120231208
}
3120331209
forEach(node.elements, checkSourceElement);
3120431210
}

tests/baselines/reference/variadicTuples1.errors.txt

+20-1
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,12 @@ tests/cases/conformance/types/tuple/variadicTuples1.ts(203,5): error TS2322: Typ
4141
Type '"2"' is not assignable to type 'number | "0" | "length" | "toString" | "toLocaleString" | "pop" | "push" | "concat" | "join" | "reverse" | "shift" | "slice" | "sort" | "splice" | "unshift" | "indexOf" | "lastIndexOf" | "every" | "some" | "forEach" | "map" | "filter" | "reduce" | "reduceRight" | "1"'.
4242
tests/cases/conformance/types/tuple/variadicTuples1.ts(346,14): error TS7019: Rest parameter 'x' implicitly has an 'any[]' type.
4343
tests/cases/conformance/types/tuple/variadicTuples1.ts(354,26): error TS2322: Type 'string' is not assignable to type 'number | undefined'.
44+
tests/cases/conformance/types/tuple/variadicTuples1.ts(393,19): error TS1256: A rest element must be last in a tuple type.
45+
tests/cases/conformance/types/tuple/variadicTuples1.ts(396,20): error TS1256: A rest element must be last in a tuple type.
46+
tests/cases/conformance/types/tuple/variadicTuples1.ts(397,12): error TS1256: A rest element must be last in a tuple type.
4447

4548

46-
==== tests/cases/conformance/types/tuple/variadicTuples1.ts (20 errors) ====
49+
==== tests/cases/conformance/types/tuple/variadicTuples1.ts (23 errors) ====
4750
// Variadics in tuple types
4851

4952
type TV0<T extends unknown[]> = [string, ...T];
@@ -496,4 +499,20 @@ tests/cases/conformance/types/tuple/variadicTuples1.ts(354,26): error TS2322: Ty
496499

497500
callApi(getUser);
498501
callApi(getOrgUser);
502+
503+
// Repro from #40235
504+
505+
type Numbers = number[];
506+
type Unbounded = [...Numbers, boolean];
507+
~~~~~~~~~~
508+
!!! error TS1256: A rest element must be last in a tuple type.
509+
const data: Unbounded = [false, false];
510+
511+
type U1 = [string, ...Numbers, boolean];
512+
~~~~~~~~~~
513+
!!! error TS1256: A rest element must be last in a tuple type.
514+
type U2 = [...[string, ...Numbers], boolean];
515+
~~~~~~~~~~~~~~~~~~~~~~~
516+
!!! error TS1256: A rest element must be last in a tuple type.
517+
type U3 = [...[string, number], boolean];
499518

tests/baselines/reference/variadicTuples1.js

+17
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,16 @@ function callApi<T extends unknown[] = [], U = void>(method: (...args: [...T, ob
387387

388388
callApi(getUser);
389389
callApi(getOrgUser);
390+
391+
// Repro from #40235
392+
393+
type Numbers = number[];
394+
type Unbounded = [...Numbers, boolean];
395+
const data: Unbounded = [false, false];
396+
397+
type U1 = [string, ...Numbers, boolean];
398+
type U2 = [...[string, ...Numbers], boolean];
399+
type U3 = [...[string, number], boolean];
390400

391401

392402
//// [variadicTuples1.js]
@@ -609,6 +619,7 @@ function callApi(method) {
609619
}
610620
callApi(getUser);
611621
callApi(getOrgUser);
622+
var data = [false, false];
612623

613624

614625
//// [variadicTuples1.d.ts]
@@ -774,3 +785,9 @@ declare function getOrgUser(id: string, orgId: number, options?: {
774785
z?: boolean;
775786
}): void;
776787
declare function callApi<T extends unknown[] = [], U = void>(method: (...args: [...T, object]) => U): (...args_0: T) => U;
788+
declare type Numbers = number[];
789+
declare type Unbounded = [...Numbers, boolean];
790+
declare const data: Unbounded;
791+
declare type U1 = [string, ...Numbers, boolean];
792+
declare type U2 = [...[string, ...Numbers], boolean];
793+
declare type U3 = [...[string, number], boolean];

tests/baselines/reference/variadicTuples1.symbols

+24
Original file line numberDiff line numberDiff line change
@@ -1327,3 +1327,27 @@ callApi(getOrgUser);
13271327
>callApi : Symbol(callApi, Decl(variadicTuples1.ts, 380, 100))
13281328
>getOrgUser : Symbol(getOrgUser, Decl(variadicTuples1.ts, 378, 71))
13291329

1330+
// Repro from #40235
1331+
1332+
type Numbers = number[];
1333+
>Numbers : Symbol(Numbers, Decl(variadicTuples1.ts, 387, 20))
1334+
1335+
type Unbounded = [...Numbers, boolean];
1336+
>Unbounded : Symbol(Unbounded, Decl(variadicTuples1.ts, 391, 24))
1337+
>Numbers : Symbol(Numbers, Decl(variadicTuples1.ts, 387, 20))
1338+
1339+
const data: Unbounded = [false, false];
1340+
>data : Symbol(data, Decl(variadicTuples1.ts, 393, 5))
1341+
>Unbounded : Symbol(Unbounded, Decl(variadicTuples1.ts, 391, 24))
1342+
1343+
type U1 = [string, ...Numbers, boolean];
1344+
>U1 : Symbol(U1, Decl(variadicTuples1.ts, 393, 39))
1345+
>Numbers : Symbol(Numbers, Decl(variadicTuples1.ts, 387, 20))
1346+
1347+
type U2 = [...[string, ...Numbers], boolean];
1348+
>U2 : Symbol(U2, Decl(variadicTuples1.ts, 395, 40))
1349+
>Numbers : Symbol(Numbers, Decl(variadicTuples1.ts, 387, 20))
1350+
1351+
type U3 = [...[string, number], boolean];
1352+
>U3 : Symbol(U3, Decl(variadicTuples1.ts, 396, 45))
1353+

tests/baselines/reference/variadicTuples1.types

+23
Original file line numberDiff line numberDiff line change
@@ -1383,3 +1383,26 @@ callApi(getOrgUser);
13831383
>callApi : <T extends unknown[] = [], U = void>(method: (...args_0: T, args_1: object) => U) => (...args_0: T) => U
13841384
>getOrgUser : (id: string, orgId: number, options?: { y?: number | undefined; z?: boolean | undefined; } | undefined) => void
13851385

1386+
// Repro from #40235
1387+
1388+
type Numbers = number[];
1389+
>Numbers : Numbers
1390+
1391+
type Unbounded = [...Numbers, boolean];
1392+
>Unbounded : (number | boolean)[]
1393+
1394+
const data: Unbounded = [false, false];
1395+
>data : (number | boolean)[]
1396+
>[false, false] : false[]
1397+
>false : false
1398+
>false : false
1399+
1400+
type U1 = [string, ...Numbers, boolean];
1401+
>U1 : [string, ...(number | boolean)[]]
1402+
1403+
type U2 = [...[string, ...Numbers], boolean];
1404+
>U2 : [string, ...(number | boolean)[]]
1405+
1406+
type U3 = [...[string, number], boolean];
1407+
>U3 : [string, number, boolean]
1408+

tests/cases/conformance/types/tuple/variadicTuples1.ts

+10
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,13 @@ function callApi<T extends unknown[] = [], U = void>(method: (...args: [...T, ob
389389

390390
callApi(getUser);
391391
callApi(getOrgUser);
392+
393+
// Repro from #40235
394+
395+
type Numbers = number[];
396+
type Unbounded = [...Numbers, boolean];
397+
const data: Unbounded = [false, false];
398+
399+
type U1 = [string, ...Numbers, boolean];
400+
type U2 = [...[string, ...Numbers], boolean];
401+
type U3 = [...[string, number], boolean];

0 commit comments

Comments
 (0)