Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proper treatment of splicing tuples in array literals #36861

Merged
merged 1 commit into from
Feb 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 45 additions & 34 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17182,7 +17182,7 @@ namespace ts {

/**
* Check if a Type was written as a tuple type literal.
* Prefer using isTupleLikeType() unless the use of `elementTypes` is required.
* Prefer using isTupleLikeType() unless the use of `elementTypes`/`getTypeArguments` is required.
*/
function isTupleType(type: Type): type is TupleTypeReference {
return !!(getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).target.objectFlags & ObjectFlags.Tuple);
Expand Down Expand Up @@ -22251,58 +22251,69 @@ namespace ts {
function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type {
elibarzilay marked this conversation as resolved.
Show resolved Hide resolved
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);
elibarzilay marked this conversation as resolved.
Show resolved Hide resolved
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
elibarzilay marked this conversation as resolved.
Show resolved Hide resolved
// 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, so these are only arrays
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