Skip to content

Commit ebea675

Browse files
committed
Proper treatment of splicing tuples in array literals
Fixes #32465.
1 parent bab0c99 commit ebea675

21 files changed

+349
-87
lines changed

src/compiler/checker.ts

+44-33
Original file line numberDiff line numberDiff line change
@@ -22238,58 +22238,69 @@ namespace ts {
2223822238
function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type {
2223922239
const elements = node.elements;
2224022240
const elementCount = elements.length;
22241-
let hasNonEndingSpreadElement = false;
2224222241
const elementTypes: Type[] = [];
22243-
const inDestructuringPattern = isAssignmentTarget(node);
22242+
let hasEndingSpreadElement = false;
22243+
let hasNonEndingSpreadElement = false;
2224422244
const contextualType = getApparentTypeOfContextualType(node);
22245+
const inDestructuringPattern = isAssignmentTarget(node);
2224522246
const inConstContext = isConstContext(node);
22246-
for (let index = 0; index < elementCount; index++) {
22247-
const e = elements[index];
22248-
if (inDestructuringPattern && e.kind === SyntaxKind.SpreadElement) {
22249-
// Given the following situation:
22250-
// var c: {};
22251-
// [...c] = ["", 0];
22252-
//
22253-
// c is represented in the tree as a spread element in an array literal.
22254-
// But c really functions as a rest element, and its purpose is to provide
22255-
// a contextual type for the right hand side of the assignment. Therefore,
22256-
// instead of calling checkExpression on "...c", which will give an error
22257-
// if c is not iterable/array-like, we need to act as if we are trying to
22258-
// get the contextual element type from it. So we do something similar to
22259-
// getContextualTypeForElementExpression, which will crucially not error
22260-
// if there is no index type / iterated type.
22261-
const restArrayType = checkExpression((<SpreadElement>e).expression, checkMode, forceTuple);
22262-
const restElementType = getIndexTypeOfType(restArrayType, IndexKind.Number) ||
22263-
getIteratedTypeOrElementType(IterationUse.Destructuring, restArrayType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false);
22264-
if (restElementType) {
22265-
elementTypes.push(restElementType);
22247+
for (let i = 0; i < elementCount; i++) {
22248+
const e = elements[i];
22249+
const spread = e.kind === SyntaxKind.SpreadElement && (<SpreadElement>e).expression;
22250+
const spreadType = spread && checkExpression(spread, checkMode, forceTuple);
22251+
if (spreadType && isTupleType(spreadType)) {
22252+
elementTypes.push(...getTypeArguments(spreadType));
22253+
if (spreadType.target.hasRestElement) {
22254+
if (i === elementCount - 1) hasEndingSpreadElement = true;
22255+
else hasNonEndingSpreadElement = true;
2226622256
}
2226722257
}
2226822258
else {
22269-
const elementContextualType = getContextualTypeForElementExpression(contextualType, index);
22270-
const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple);
22271-
elementTypes.push(type);
22272-
}
22273-
if (index < elementCount - 1 && e.kind === SyntaxKind.SpreadElement) {
22274-
hasNonEndingSpreadElement = true;
22259+
if (inDestructuringPattern && spreadType) {
22260+
// Given the following situation:
22261+
// var c: {};
22262+
// [...c] = ["", 0];
22263+
//
22264+
// c is represented in the tree as a spread element in an array literal.
22265+
// But c really functions as a rest element, and its purpose is to provide
22266+
// a contextual type for the right hand side of the assignment. Therefore,
22267+
// instead of calling checkExpression on "...c", which will give an error
22268+
// if c is not iterable/array-like, we need to act as if we are trying to
22269+
// get the contextual element type from it. So we do something similar to
22270+
// getContextualTypeForElementExpression, which will crucially not error
22271+
// if there is no index type / iterated type.
22272+
const restElementType = getIndexTypeOfType(spreadType, IndexKind.Number) ||
22273+
getIteratedTypeOrElementType(IterationUse.Destructuring, spreadType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false);
22274+
if (restElementType) {
22275+
elementTypes.push(restElementType);
22276+
}
22277+
}
22278+
else {
22279+
const elementContextualType = getContextualTypeForElementExpression(contextualType, elementTypes.length);
22280+
const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple);
22281+
elementTypes.push(type);
22282+
}
22283+
if (spread) { // tuples are done above
22284+
if (i === elementCount - 1) hasEndingSpreadElement = true;
22285+
else hasNonEndingSpreadElement = true;
22286+
}
2227522287
}
2227622288
}
2227722289
if (!hasNonEndingSpreadElement) {
22278-
const hasRestElement = elementCount > 0 && elements[elementCount - 1].kind === SyntaxKind.SpreadElement;
22279-
const minLength = elementCount - (hasRestElement ? 1 : 0);
22290+
const minLength = elementTypes.length - (hasEndingSpreadElement ? 1 : 0);
2228022291
// If array literal is actually a destructuring pattern, mark it as an implied type. We do this such
2228122292
// that we get the same behavior for "var [x, y] = []" and "[x, y] = []".
2228222293
let tupleResult;
2228322294
if (inDestructuringPattern && minLength > 0) {
22284-
const type = cloneTypeReference(<TypeReference>createTupleType(elementTypes, minLength, hasRestElement));
22295+
const type = cloneTypeReference(<TypeReference>createTupleType(elementTypes, minLength, hasEndingSpreadElement));
2228522296
type.pattern = node;
2228622297
return type;
2228722298
}
22288-
else if (tupleResult = getArrayLiteralTupleTypeIfApplicable(elementTypes, contextualType, hasRestElement, elementCount, inConstContext)) {
22299+
else if (tupleResult = getArrayLiteralTupleTypeIfApplicable(elementTypes, contextualType, hasEndingSpreadElement, elementTypes.length, inConstContext)) {
2228922300
return createArrayLiteralType(tupleResult);
2229022301
}
2229122302
else if (forceTuple) {
22292-
return createArrayLiteralType(createTupleType(elementTypes, minLength, hasRestElement));
22303+
return createArrayLiteralType(createTupleType(elementTypes, minLength, hasEndingSpreadElement));
2229322304
}
2229422305
}
2229522306
return createArrayLiteralType(createArrayType(elementTypes.length ?

tests/baselines/reference/arrayLiteralExpressionContextualTyping.errors.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ tests/cases/conformance/expressions/contextualTyping/arrayLiteralExpressionConte
77
tests/cases/conformance/expressions/contextualTyping/arrayLiteralExpressionContextualTyping.ts(8,5): error TS2322: Type '[number, number, number, string]' is not assignable to type '[number, number, number]'.
88
Types of property 'length' are incompatible.
99
Type '4' is not assignable to type '3'.
10-
tests/cases/conformance/expressions/contextualTyping/arrayLiteralExpressionContextualTyping.ts(14,5): error TS2322: Type '[number, number, number, ...number[]]' is not assignable to type '[number, number, number]'.
10+
tests/cases/conformance/expressions/contextualTyping/arrayLiteralExpressionContextualTyping.ts(14,5): error TS2322: Type '[number, number, number, number, number, number]' is not assignable to type '[number, number, number]'.
1111
Types of property 'length' are incompatible.
12-
Type 'number' is not assignable to type '3'.
12+
Type '6' is not assignable to type '3'.
1313

1414

1515
==== tests/cases/conformance/expressions/contextualTyping/arrayLiteralExpressionContextualTyping.ts (4 errors) ====
@@ -40,7 +40,7 @@ tests/cases/conformance/expressions/contextualTyping/arrayLiteralExpressionConte
4040
var spr1 = [1, 2, 3, ...tup];
4141
var spr2:[number, number, number] = [1, 2, 3, ...tup]; // Error
4242
~~~~
43-
!!! error TS2322: Type '[number, number, number, ...number[]]' is not assignable to type '[number, number, number]'.
43+
!!! error TS2322: Type '[number, number, number, number, number, number]' is not assignable to type '[number, number, number]'.
4444
!!! error TS2322: Types of property 'length' are incompatible.
45-
!!! error TS2322: Type 'number' is not assignable to type '3'.
45+
!!! error TS2322: Type '6' is not assignable to type '3'.
4646

tests/baselines/reference/arrayLiteralExpressionContextualTyping.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ var spr1 = [1, 2, 3, ...tup];
6262

6363
var spr2:[number, number, number] = [1, 2, 3, ...tup]; // Error
6464
>spr2 : [number, number, number]
65-
>[1, 2, 3, ...tup] : [number, number, number, ...number[]]
65+
>[1, 2, 3, ...tup] : [number, number, number, number, number, number]
6666
>1 : 1
6767
>2 : 2
6868
>3 : 3

tests/baselines/reference/arrayLiterals3.errors.txt

+1-4
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(11,51): erro
55
tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(17,5): error TS2322: Type '[number, number, string, boolean]' is not assignable to type '[number, number]'.
66
Types of property 'length' are incompatible.
77
Type '4' is not assignable to type '2'.
8-
tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(32,5): error TS2739: Type '(number[] | string[])[]' is missing the following properties from type 'tup': 0, 1
98
tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(33,5): error TS2739: Type 'number[]' is missing the following properties from type '[number, number, number]': 0, 1, 2
109
tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(34,5): error TS2322: Type '(string | number)[]' is not assignable to type 'myArray'.
1110
The types returned by 'pop()' are incompatible between these types.
1211
Type 'string | number' is not assignable to type 'Number'.
1312
Type 'string' is not assignable to type 'Number'.
1413

1514

16-
==== tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts (8 errors) ====
15+
==== tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts (7 errors) ====
1716
// Each element expression in a non-empty array literal is processed as follows:
1817
// - If the array literal contains no spread elements, and if the array literal is contextually typed (section 4.19)
1918
// by a type T and T has a property with the numeric name N, where N is the index of the element expression in the array literal,
@@ -58,8 +57,6 @@ tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(34,5): error
5857
interface myArray extends Array<Number> { }
5958
interface myArray2 extends Array<Number|String> { }
6059
var c0: tup = [...temp2]; // Error
61-
~~
62-
!!! error TS2739: Type '(number[] | string[])[]' is missing the following properties from type 'tup': 0, 1
6360
var c1: [number, number, number] = [...temp1]; // Error cannot assign number[] to [number, number, number]
6461
~~
6562
!!! error TS2739: Type 'number[]' is missing the following properties from type '[number, number, number]': 0, 1, 2

tests/baselines/reference/arrayLiterals3.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ interface myArray extends Array<Number> { }
7171
interface myArray2 extends Array<Number|String> { }
7272
var c0: tup = [...temp2]; // Error
7373
>c0 : tup
74-
>[...temp2] : (number[] | string[])[]
74+
>[...temp2] : [number[], string[]]
7575
>...temp2 : number[] | string[]
7676
>temp2 : [number[], string[]]
7777

tests/baselines/reference/constAssertions.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ declare let vc1: "abc";
143143
declare let a1: readonly [];
144144
declare let a2: readonly [1, 2, 3];
145145
declare let a3: readonly [10, "hello", true];
146-
declare let a4: readonly (1 | 2 | 3)[];
146+
declare let a4: readonly [1, 2, 3];
147147
declare let a5: number[];
148148
declare let a6: readonly number[];
149149
declare let a7: number[];

tests/baselines/reference/constAssertions.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ let a3 = [10, 'hello', true] as const;
125125
>true : true
126126

127127
let a4 = [...[1, 2, 3]] as const;
128-
>a4 : readonly (1 | 2 | 3)[]
129-
>[...[1, 2, 3]] as const : readonly (1 | 2 | 3)[]
130-
>[...[1, 2, 3]] : readonly (1 | 2 | 3)[]
128+
>a4 : readonly [1, 2, 3]
129+
>[...[1, 2, 3]] as const : readonly [1, 2, 3]
130+
>[...[1, 2, 3]] : readonly [1, 2, 3]
131131
>...[1, 2, 3] : 1 | 2 | 3
132132
>[1, 2, 3] : readonly [1, 2, 3]
133133
>1 : 1

tests/baselines/reference/declarationsAndAssignments.types

+8-8
Original file line numberDiff line numberDiff line change
@@ -727,22 +727,22 @@ function f20(v: [number, number, number]) {
727727

728728
[...a3] = v;
729729
>[...a3] = v : [number, number, number]
730-
>[...a3] : number[]
730+
>[...a3] : [number, number, number]
731731
>...a3 : number
732732
>a3 : [number, number, number]
733733
>v : [number, number, number]
734734

735735
[x, ...a2] = v;
736736
>[x, ...a2] = v : [number, number, number]
737-
>[x, ...a2] : [number, ...number[]]
737+
>[x, ...a2] : [number, number, number]
738738
>x : number
739739
>...a2 : number
740740
>a2 : [number, number]
741741
>v : [number, number, number]
742742

743743
[x, y, ...a1] = v;
744744
>[x, y, ...a1] = v : [number, number, number]
745-
>[x, y, ...a1] : [number, number, ...number[]]
745+
>[x, y, ...a1] : [number, number, number]
746746
>x : number
747747
>y : number
748748
>...a1 : number
@@ -751,7 +751,7 @@ function f20(v: [number, number, number]) {
751751

752752
[x, y, z, ...a0] = v;
753753
>[x, y, z, ...a0] = v : [number, number, number]
754-
>[x, y, z, ...a0] : [number, number, number, ...never[]]
754+
>[x, y, z, ...a0] : [number, number, number]
755755
>x : number
756756
>y : number
757757
>z : number
@@ -809,22 +809,22 @@ function f21(v: [number, string, boolean]) {
809809

810810
[...a0] = v;
811811
>[...a0] = v : [number, string, boolean]
812-
>[...a0] : (string | number | boolean)[]
812+
>[...a0] : [number, string, boolean]
813813
>...a0 : string | number | boolean
814814
>a0 : [number, string, boolean]
815815
>v : [number, string, boolean]
816816

817817
[x, ...a1] = v;
818818
>[x, ...a1] = v : [number, string, boolean]
819-
>[x, ...a1] : [number, ...(string | boolean)[]]
819+
>[x, ...a1] : [number, string, boolean]
820820
>x : number
821821
>...a1 : string | boolean
822822
>a1 : [string, boolean]
823823
>v : [number, string, boolean]
824824

825825
[x, y, ...a2] = v;
826826
>[x, y, ...a2] = v : [number, string, boolean]
827-
>[x, y, ...a2] : [number, string, ...boolean[]]
827+
>[x, y, ...a2] : [number, string, boolean]
828828
>x : number
829829
>y : string
830830
>...a2 : boolean
@@ -833,7 +833,7 @@ function f21(v: [number, string, boolean]) {
833833

834834
[x, y, z, ...a3] = v;
835835
>[x, y, z, ...a3] = v : [number, string, boolean]
836-
>[x, y, z, ...a3] : [number, string, boolean, ...never[]]
836+
>[x, y, z, ...a3] : [number, string, boolean]
837837
>x : number
838838
>y : string
839839
>z : boolean

tests/baselines/reference/destructuringTuple.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ declare var receiver: typeof tuple;
1515

1616
[...receiver] = tuple;
1717
>[...receiver] = tuple : [boolean, number, ...string[]]
18-
>[...receiver] : (string | number | boolean)[]
18+
>[...receiver] : [boolean, number, ...string[]]
1919
>...receiver : string | number | boolean
2020
>receiver : [boolean, number, ...string[]]
2121
>tuple : [boolean, number, ...string[]]

tests/baselines/reference/for-of49.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ var map = new Map([["", true]]);
1313
>true : true
1414

1515
for ([k, ...[v]] of map) {
16-
>[k, ...[v]] : [string, ...boolean[]]
16+
>[k, ...[v]] : [string, boolean]
1717
>k : string
1818
>...[v] : boolean
1919
>[v] : [boolean]

tests/baselines/reference/literalFreshnessPropagationOnNarrowing.errors.txt

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(37,5): error TS2322: Type '"y"' is not assignable to type '"x"'.
2-
tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(60,5): error TS2322: Type 'string[]' is not assignable to type 'XY[]'.
3-
Type 'string' is not assignable to type 'XY'.
2+
tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(60,12): error TS2322: Type 'string' is not assignable to type 'XY'.
43

54

65
==== tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts (2 errors) ====
@@ -66,7 +65,6 @@ tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(60,5): error TS23
6665
// Desired: OK
6766
// Error in all extant branches
6867
arr = [...['y']];
69-
~~~
70-
!!! error TS2322: Type 'string[]' is not assignable to type 'XY[]'.
71-
!!! error TS2322: Type 'string' is not assignable to type 'XY'.
68+
~~~~~~~~
69+
!!! error TS2322: Type 'string' is not assignable to type 'XY'.
7270
}

tests/baselines/reference/restElementMustBeLast.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ var [...a, x] = [1, 2, 3]; // Error, rest must be last element
88
>3 : 3
99

1010
[...a, x] = [1, 2, 3]; // Error, rest must be last element
11-
>[...a, x] = [1, 2, 3] : number[]
12-
>[...a, x] : number[]
11+
>[...a, x] = [1, 2, 3] : [number, number, number]
12+
>[...a, x] : [number, number, number, number]
1313
>...a : number
1414
>a : [number, number, number]
1515
>x : number
16-
>[1, 2, 3] : number[]
16+
>[1, 2, 3] : [number, number, number]
1717
>1 : 1
1818
>2 : 2
1919
>3 : 3

tests/baselines/reference/restElementWithAssignmentPattern1.errors.txt

-15
This file was deleted.

tests/baselines/reference/restElementWithAssignmentPattern1.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ var a: string, b: number;
44
>b : number
55

66
[...[a, b = 0]] = ["", 1];
7-
>[...[a, b = 0]] = ["", 1] : (string | number)[]
8-
>[...[a, b = 0]] : (string | number)[]
7+
>[...[a, b = 0]] = ["", 1] : [string, number]
8+
>[...[a, b = 0]] : [string, number]
99
>...[a, b = 0] : string | number
1010
>[a, b = 0] : [string, number]
1111
>a : string
1212
>b = 0 : 0
1313
>b : number
1414
>0 : 0
15-
>["", 1] : (string | number)[]
15+
>["", 1] : [string, number]
1616
>"" : ""
1717
>1 : 1
1818

tests/baselines/reference/restElementWithAssignmentPattern3.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var tuple: [string, number] = ["", 1];
1111

1212
[...[a, b = 0]] = tuple;
1313
>[...[a, b = 0]] = tuple : [string, number]
14-
>[...[a, b = 0]] : (string | number)[]
14+
>[...[a, b = 0]] : [string, number]
1515
>...[a, b = 0] : string | number
1616
>[a, b = 0] : [string, number]
1717
>a : string

tests/baselines/reference/restElementWithAssignmentPattern5.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ var s: string, s2: string;
44
>s2 : string
55

66
[...[s, s2]] = ["", ""];
7-
>[...[s, s2]] = ["", ""] : string[]
8-
>[...[s, s2]] : string[]
7+
>[...[s, s2]] = ["", ""] : [string, string]
8+
>[...[s, s2]] : [string, string]
99
>...[s, s2] : string
1010
>[s, s2] : [string, string]
1111
>s : string
1212
>s2 : string
13-
>["", ""] : string[]
13+
>["", ""] : [string, string]
1414
>"" : ""
1515
>"" : ""
1616

0 commit comments

Comments
 (0)