Skip to content

Commit

Permalink
Proper treatment of splicing tuples in array literals
Browse files Browse the repository at this point in the history
Fixes #32465.
  • Loading branch information
elibarzilay committed Feb 18, 2020
1 parent bab0c99 commit ebea675
Show file tree
Hide file tree
Showing 21 changed files with 349 additions and 87 deletions.
77 changes: 44 additions & 33 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22238,58 +22238,69 @@ namespace ts {
function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type {
const elements = node.elements;
const elementCount = elements.length;
let hasNonEndingSpreadElement = false;
const elementTypes: Type[] = [];
const inDestructuringPattern = isAssignmentTarget(node);
let hasEndingSpreadElement = false;
let hasNonEndingSpreadElement = false;
const contextualType = getApparentTypeOfContextualType(node);
const inDestructuringPattern = isAssignmentTarget(node);
const inConstContext = isConstContext(node);
for (let index = 0; index < elementCount; index++) {
const e = elements[index];
if (inDestructuringPattern && e.kind === SyntaxKind.SpreadElement) {
// Given the following situation:
// var c: {};
// [...c] = ["", 0];
//
// c is represented in the tree as a spread element in an array literal.
// But c really functions as a rest element, and its purpose is to provide
// a contextual type for the right hand side of the assignment. Therefore,
// instead of calling checkExpression on "...c", which will give an error
// if c is not iterable/array-like, we need to act as if we are trying to
// get the contextual element type from it. So we do something similar to
// getContextualTypeForElementExpression, which will crucially not error
// if there is no index type / iterated type.
const restArrayType = checkExpression((<SpreadElement>e).expression, checkMode, forceTuple);
const restElementType = getIndexTypeOfType(restArrayType, IndexKind.Number) ||
getIteratedTypeOrElementType(IterationUse.Destructuring, restArrayType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false);
if (restElementType) {
elementTypes.push(restElementType);
for (let i = 0; i < elementCount; i++) {
const e = elements[i];
const spread = e.kind === SyntaxKind.SpreadElement && (<SpreadElement>e).expression;
const spreadType = spread && checkExpression(spread, checkMode, forceTuple);
if (spreadType && isTupleType(spreadType)) {
elementTypes.push(...getTypeArguments(spreadType));
if (spreadType.target.hasRestElement) {
if (i === elementCount - 1) hasEndingSpreadElement = true;
else hasNonEndingSpreadElement = true;
}
}
else {
const elementContextualType = getContextualTypeForElementExpression(contextualType, index);
const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple);
elementTypes.push(type);
}
if (index < elementCount - 1 && e.kind === SyntaxKind.SpreadElement) {
hasNonEndingSpreadElement = true;
if (inDestructuringPattern && spreadType) {
// Given the following situation:
// var c: {};
// [...c] = ["", 0];
//
// c is represented in the tree as a spread element in an array literal.
// But c really functions as a rest element, and its purpose is to provide
// a contextual type for the right hand side of the assignment. Therefore,
// instead of calling checkExpression on "...c", which will give an error
// if c is not iterable/array-like, we need to act as if we are trying to
// get the contextual element type from it. So we do something similar to
// getContextualTypeForElementExpression, which will crucially not error
// if there is no index type / iterated type.
const restElementType = getIndexTypeOfType(spreadType, IndexKind.Number) ||
getIteratedTypeOrElementType(IterationUse.Destructuring, spreadType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false);
if (restElementType) {
elementTypes.push(restElementType);
}
}
else {
const elementContextualType = getContextualTypeForElementExpression(contextualType, elementTypes.length);
const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple);
elementTypes.push(type);
}
if (spread) { // tuples are done above
if (i === elementCount - 1) hasEndingSpreadElement = true;
else hasNonEndingSpreadElement = true;
}
}
}
if (!hasNonEndingSpreadElement) {
const hasRestElement = elementCount > 0 && elements[elementCount - 1].kind === SyntaxKind.SpreadElement;
const minLength = elementCount - (hasRestElement ? 1 : 0);
const minLength = elementTypes.length - (hasEndingSpreadElement ? 1 : 0);
// If array literal is actually a destructuring pattern, mark it as an implied type. We do this such
// that we get the same behavior for "var [x, y] = []" and "[x, y] = []".
let tupleResult;
if (inDestructuringPattern && minLength > 0) {
const type = cloneTypeReference(<TypeReference>createTupleType(elementTypes, minLength, hasRestElement));
const type = cloneTypeReference(<TypeReference>createTupleType(elementTypes, minLength, hasEndingSpreadElement));
type.pattern = node;
return type;
}
else if (tupleResult = getArrayLiteralTupleTypeIfApplicable(elementTypes, contextualType, hasRestElement, elementCount, inConstContext)) {
else if (tupleResult = getArrayLiteralTupleTypeIfApplicable(elementTypes, contextualType, hasEndingSpreadElement, elementTypes.length, inConstContext)) {
return createArrayLiteralType(tupleResult);
}
else if (forceTuple) {
return createArrayLiteralType(createTupleType(elementTypes, minLength, hasRestElement));
return createArrayLiteralType(createTupleType(elementTypes, minLength, hasEndingSpreadElement));
}
}
return createArrayLiteralType(createArrayType(elementTypes.length ?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ tests/cases/conformance/expressions/contextualTyping/arrayLiteralExpressionConte
tests/cases/conformance/expressions/contextualTyping/arrayLiteralExpressionContextualTyping.ts(8,5): error TS2322: Type '[number, number, number, string]' is not assignable to type '[number, number, number]'.
Types of property 'length' are incompatible.
Type '4' is not assignable to type '3'.
tests/cases/conformance/expressions/contextualTyping/arrayLiteralExpressionContextualTyping.ts(14,5): error TS2322: Type '[number, number, number, ...number[]]' is not assignable to type '[number, number, number]'.
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]'.
Types of property 'length' are incompatible.
Type 'number' is not assignable to type '3'.
Type '6' is not assignable to type '3'.


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

Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ var spr1 = [1, 2, 3, ...tup];

var spr2:[number, number, number] = [1, 2, 3, ...tup]; // Error
>spr2 : [number, number, number]
>[1, 2, 3, ...tup] : [number, number, number, ...number[]]
>[1, 2, 3, ...tup] : [number, number, number, number, number, number]
>1 : 1
>2 : 2
>3 : 3
Expand Down
5 changes: 1 addition & 4 deletions tests/baselines/reference/arrayLiterals3.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(11,51): erro
tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(17,5): error TS2322: Type '[number, number, string, boolean]' is not assignable to type '[number, number]'.
Types of property 'length' are incompatible.
Type '4' is not assignable to type '2'.
tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(32,5): error TS2739: Type '(number[] | string[])[]' is missing the following properties from type 'tup': 0, 1
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
tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(34,5): error TS2322: Type '(string | number)[]' is not assignable to type 'myArray'.
The types returned by 'pop()' are incompatible between these types.
Type 'string | number' is not assignable to type 'Number'.
Type 'string' is not assignable to type 'Number'.


==== tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts (8 errors) ====
==== tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts (7 errors) ====
// Each element expression in a non-empty array literal is processed as follows:
// - If the array literal contains no spread elements, and if the array literal is contextually typed (section 4.19)
// 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,
Expand Down Expand Up @@ -58,8 +57,6 @@ tests/cases/conformance/expressions/arrayLiterals/arrayLiterals3.ts(34,5): error
interface myArray extends Array<Number> { }
interface myArray2 extends Array<Number|String> { }
var c0: tup = [...temp2]; // Error
~~
!!! error TS2739: Type '(number[] | string[])[]' is missing the following properties from type 'tup': 0, 1
var c1: [number, number, number] = [...temp1]; // Error cannot assign number[] to [number, number, number]
~~
!!! error TS2739: Type 'number[]' is missing the following properties from type '[number, number, number]': 0, 1, 2
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/arrayLiterals3.types
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ interface myArray extends Array<Number> { }
interface myArray2 extends Array<Number|String> { }
var c0: tup = [...temp2]; // Error
>c0 : tup
>[...temp2] : (number[] | string[])[]
>[...temp2] : [number[], string[]]
>...temp2 : number[] | string[]
>temp2 : [number[], string[]]

Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/constAssertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ declare let vc1: "abc";
declare let a1: readonly [];
declare let a2: readonly [1, 2, 3];
declare let a3: readonly [10, "hello", true];
declare let a4: readonly (1 | 2 | 3)[];
declare let a4: readonly [1, 2, 3];
declare let a5: number[];
declare let a6: readonly number[];
declare let a7: number[];
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/constAssertions.types
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ let a3 = [10, 'hello', true] as const;
>true : true

let a4 = [...[1, 2, 3]] as const;
>a4 : readonly (1 | 2 | 3)[]
>[...[1, 2, 3]] as const : readonly (1 | 2 | 3)[]
>[...[1, 2, 3]] : readonly (1 | 2 | 3)[]
>a4 : readonly [1, 2, 3]
>[...[1, 2, 3]] as const : readonly [1, 2, 3]
>[...[1, 2, 3]] : readonly [1, 2, 3]
>...[1, 2, 3] : 1 | 2 | 3
>[1, 2, 3] : readonly [1, 2, 3]
>1 : 1
Expand Down
16 changes: 8 additions & 8 deletions tests/baselines/reference/declarationsAndAssignments.types
Original file line number Diff line number Diff line change
Expand Up @@ -727,22 +727,22 @@ function f20(v: [number, number, number]) {

[...a3] = v;
>[...a3] = v : [number, number, number]
>[...a3] : number[]
>[...a3] : [number, number, number]
>...a3 : number
>a3 : [number, number, number]
>v : [number, number, number]

[x, ...a2] = v;
>[x, ...a2] = v : [number, number, number]
>[x, ...a2] : [number, ...number[]]
>[x, ...a2] : [number, number, number]
>x : number
>...a2 : number
>a2 : [number, number]
>v : [number, number, number]

[x, y, ...a1] = v;
>[x, y, ...a1] = v : [number, number, number]
>[x, y, ...a1] : [number, number, ...number[]]
>[x, y, ...a1] : [number, number, number]
>x : number
>y : number
>...a1 : number
Expand All @@ -751,7 +751,7 @@ function f20(v: [number, number, number]) {

[x, y, z, ...a0] = v;
>[x, y, z, ...a0] = v : [number, number, number]
>[x, y, z, ...a0] : [number, number, number, ...never[]]
>[x, y, z, ...a0] : [number, number, number]
>x : number
>y : number
>z : number
Expand Down Expand Up @@ -809,22 +809,22 @@ function f21(v: [number, string, boolean]) {

[...a0] = v;
>[...a0] = v : [number, string, boolean]
>[...a0] : (string | number | boolean)[]
>[...a0] : [number, string, boolean]
>...a0 : string | number | boolean
>a0 : [number, string, boolean]
>v : [number, string, boolean]

[x, ...a1] = v;
>[x, ...a1] = v : [number, string, boolean]
>[x, ...a1] : [number, ...(string | boolean)[]]
>[x, ...a1] : [number, string, boolean]
>x : number
>...a1 : string | boolean
>a1 : [string, boolean]
>v : [number, string, boolean]

[x, y, ...a2] = v;
>[x, y, ...a2] = v : [number, string, boolean]
>[x, y, ...a2] : [number, string, ...boolean[]]
>[x, y, ...a2] : [number, string, boolean]
>x : number
>y : string
>...a2 : boolean
Expand All @@ -833,7 +833,7 @@ function f21(v: [number, string, boolean]) {

[x, y, z, ...a3] = v;
>[x, y, z, ...a3] = v : [number, string, boolean]
>[x, y, z, ...a3] : [number, string, boolean, ...never[]]
>[x, y, z, ...a3] : [number, string, boolean]
>x : number
>y : string
>z : boolean
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/destructuringTuple.types
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ declare var receiver: typeof tuple;

[...receiver] = tuple;
>[...receiver] = tuple : [boolean, number, ...string[]]
>[...receiver] : (string | number | boolean)[]
>[...receiver] : [boolean, number, ...string[]]
>...receiver : string | number | boolean
>receiver : [boolean, number, ...string[]]
>tuple : [boolean, number, ...string[]]
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/for-of49.types
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var map = new Map([["", true]]);
>true : true

for ([k, ...[v]] of map) {
>[k, ...[v]] : [string, ...boolean[]]
>[k, ...[v]] : [string, boolean]
>k : string
>...[v] : boolean
>[v] : [boolean]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(37,5): error TS2322: Type '"y"' is not assignable to type '"x"'.
tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(60,5): error TS2322: Type 'string[]' is not assignable to type 'XY[]'.
Type 'string' is not assignable to type 'XY'.
tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(60,12): error TS2322: Type 'string' is not assignable to type 'XY'.


==== tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts (2 errors) ====
Expand Down Expand Up @@ -66,7 +65,6 @@ tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(60,5): error TS23
// Desired: OK
// Error in all extant branches
arr = [...['y']];
~~~
!!! error TS2322: Type 'string[]' is not assignable to type 'XY[]'.
!!! error TS2322: Type 'string' is not assignable to type 'XY'.
~~~~~~~~
!!! error TS2322: Type 'string' is not assignable to type 'XY'.
}
6 changes: 3 additions & 3 deletions tests/baselines/reference/restElementMustBeLast.types
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ var [...a, x] = [1, 2, 3]; // Error, rest must be last element
>3 : 3

[...a, x] = [1, 2, 3]; // Error, rest must be last element
>[...a, x] = [1, 2, 3] : number[]
>[...a, x] : number[]
>[...a, x] = [1, 2, 3] : [number, number, number]
>[...a, x] : [number, number, number, number]
>...a : number
>a : [number, number, number]
>x : number
>[1, 2, 3] : number[]
>[1, 2, 3] : [number, number, number]
>1 : 1
>2 : 2
>3 : 3
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ var a: string, b: number;
>b : number

[...[a, b = 0]] = ["", 1];
>[...[a, b = 0]] = ["", 1] : (string | number)[]
>[...[a, b = 0]] : (string | number)[]
>[...[a, b = 0]] = ["", 1] : [string, number]
>[...[a, b = 0]] : [string, number]
>...[a, b = 0] : string | number
>[a, b = 0] : [string, number]
>a : string
>b = 0 : 0
>b : number
>0 : 0
>["", 1] : (string | number)[]
>["", 1] : [string, number]
>"" : ""
>1 : 1

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

[...[a, b = 0]] = tuple;
>[...[a, b = 0]] = tuple : [string, number]
>[...[a, b = 0]] : (string | number)[]
>[...[a, b = 0]] : [string, number]
>...[a, b = 0] : string | number
>[a, b = 0] : [string, number]
>a : string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ var s: string, s2: string;
>s2 : string

[...[s, s2]] = ["", ""];
>[...[s, s2]] = ["", ""] : string[]
>[...[s, s2]] : string[]
>[...[s, s2]] = ["", ""] : [string, string]
>[...[s, s2]] : [string, string]
>...[s, s2] : string
>[s, s2] : [string, string]
>s : string
>s2 : string
>["", ""] : string[]
>["", ""] : [string, string]
>"" : ""
>"" : ""

Loading

0 comments on commit ebea675

Please sign in to comment.