Skip to content

Simplify tuple destructuring logic #32178

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

Merged
merged 9 commits into from
Jul 3, 2019
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
71 changes: 39 additions & 32 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ namespace ts {
NoIndexSignatures = 1 << 0,
Writing = 1 << 1,
CacheSymbol = 1 << 2,
NoTupleBoundsCheck = 1 << 3,
}

const enum CallbackCheck {
Expand Down Expand Up @@ -5115,21 +5116,25 @@ namespace ts {
}
else if (isArrayLikeType(parentType)) {
const indexType = getLiteralType(index);
const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, declaration.name), declaration.name);
const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0;
const declaredType = getConstraintForLocation(getIndexedAccessTypeOrUndefined(parentType, indexType, declaration.name, accessFlags) || errorType, declaration.name);
type = getFlowTypeOfDestructuring(declaration, declaredType);
}
else {
type = elementType;
}
}
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
// undefined from the final type.
if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined)) {
type = getTypeWithFacts(type, TypeFacts.NEUndefined);
if (!declaration.initializer) {
return type;
}
return declaration.initializer && !getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration)) ?
getUnionType([type, checkDeclarationInitializer(declaration)], UnionReduction.Subtype) :
type;
if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) {
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
// undefined from the final type.
return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ?
getTypeWithFacts(type, TypeFacts.NEUndefined) :
type;
}
return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype);
}

function getTypeForDeclarationFromJSDocComment(declaration: Node) {
Expand Down Expand Up @@ -10131,7 +10136,7 @@ namespace ts {
propType;
}
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) {
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement)) {
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) {
const indexNode = getIndexNodeForAccessExpression(accessNode);
if (isTupleType(objectType)) {
error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2,
Expand Down Expand Up @@ -19196,26 +19201,7 @@ namespace ts {
function getArrayLiteralTupleTypeIfApplicable(elementTypes: Type[], contextualType: Type | undefined, hasRestElement: boolean, elementCount = elementTypes.length, readonly = false) {
// Infer a tuple type when the contextual type is or contains a tuple-like type
if (readonly || (contextualType && forEachType(contextualType, isTupleLikeType))) {
const minLength = elementCount - (hasRestElement ? 1 : 0);
const pattern = contextualType && contextualType.pattern;
// If array literal is contextually typed by a binding pattern or an assignment pattern, pad the resulting
// tuple type with the corresponding binding or assignment element types to make the lengths equal.
if (!hasRestElement && pattern && (pattern.kind === SyntaxKind.ArrayBindingPattern || pattern.kind === SyntaxKind.ArrayLiteralExpression)) {
const patternElements = (<BindingPattern | ArrayLiteralExpression>pattern).elements;
for (let i = elementCount; i < patternElements.length; i++) {
const e = patternElements[i];
if (hasDefaultValue(e)) {
elementTypes.push((<TypeReference>contextualType).typeArguments![i]);
}
else if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && (<BindingElement>e).dotDotDotToken || e.kind === SyntaxKind.SpreadElement)) {
if (e.kind !== SyntaxKind.OmittedExpression) {
error(e, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
}
elementTypes.push(strictNullChecks ? implicitNeverType : undefinedWideningType);
}
}
}
return createTupleType(elementTypes, minLength, hasRestElement, readonly);
return createTupleType(elementTypes, elementCount - (hasRestElement ? 1 : 0), hasRestElement, readonly);
}
}

Expand Down Expand Up @@ -23655,8 +23641,10 @@ namespace ts {
if (isArrayLikeType(sourceType)) {
// We create a synthetic expression so that getIndexedAccessType doesn't get confused
// when the element is a SyntaxKind.ElementAccessExpression.
const elementType = getIndexedAccessType(sourceType, indexType, createSyntheticExpression(element, indexType));
const type = getFlowTypeOfDestructuring(element, elementType);
const accessFlags = hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0;
const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, createSyntheticExpression(element, indexType), accessFlags) || errorType;
const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType;
const type = getFlowTypeOfDestructuring(element, assignedType);
return checkDestructuringAssignment(element, type, checkMode);
}
return checkDestructuringAssignment(element, elementType, checkMode);
Expand Down Expand Up @@ -24245,10 +24233,13 @@ namespace ts {
function checkDeclarationInitializer(declaration: HasExpressionInitializer) {
const initializer = getEffectiveInitializer(declaration)!;
const type = getTypeOfExpression(initializer, /*cache*/ true);
const padded = isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ?
padTupleType(type, declaration.name) : type;
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const ||
isDeclarationReadonly(declaration) ||
isTypeAssertion(initializer) ||
isLiteralOfContextualType(type, getContextualType(initializer)) ? type : getWidenedLiteralType(type);
isLiteralOfContextualType(padded, getContextualType(initializer)) ? padded : getWidenedLiteralType(padded);
if (isInJSFile(declaration)) {
if (widened.flags & TypeFlags.Nullable) {
reportImplicitAny(declaration, anyType);
Expand All @@ -24262,6 +24253,22 @@ namespace ts {
return widened;
}

function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) {
const patternElements = pattern.elements;
const arity = getTypeReferenceArity(type);
const elementTypes = arity ? type.typeArguments!.slice() : [];
for (let i = arity; i < patternElements.length; i++) {
const e = patternElements[i];
if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) {
elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType);
if (!isOmittedExpression(e) && !hasDefaultValue(e)) {
reportImplicitAny(e, anyType);
}
}
}
return createTupleType(elementTypes, type.target.minLength, /*hasRestElement*/ false, type.target.readonly);
}

function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean {
if (contextualType) {
if (contextualType.flags & TypeFlags.UnionOrIntersection) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,6): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,11): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,16): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,6): error TS2493: Tuple type '[]' of length '0' has no element at index '0'.
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,11): error TS2493: Tuple type '[]' of length '0' has no element at index '1'.
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,16): error TS2493: Tuple type '[]' of length '0' has no element at index '2'.


==== tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts (3 errors) ====
Expand All @@ -9,11 +9,11 @@ tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,16): error T
var [x11 = 0, y11 = ""] = [1, "hello"];
var [a11, b11, c11] = [];
~~~
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '0'.
~~~
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '1'.
~~~
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '2'.

var [a2, [b2, { x12, y12: c2 }]=["abc", { x12: 10, y12: false }]] = [1, ["hello", { x12: 5, y12: true }]];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var _m = [[x13, y13], { x: x13, y: y13 }], a3 = _m[0], b3 = _m[1];
//// [declarationEmitDestructuringArrayPattern2.d.ts]
declare var x10: number, y10: string, z10: boolean;
declare var x11: number, y11: string;
declare var a11: any, b11: any, c11: any;
declare var a11: undefined, b11: undefined, c11: undefined;
declare var a2: number, b2: string, x12: number, c2: boolean;
declare var x13: number, y13: string;
declare var a3: (string | number)[], b3: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ var [x11 = 0, y11 = ""] = [1, "hello"];
>"hello" : "hello"

var [a11, b11, c11] = [];
>a11 : any
>b11 : any
>c11 : any
>[] : [undefined?, undefined?, undefined?]
>a11 : undefined
>b11 : undefined
>c11 : undefined
>[] : []

var [a2, [b2, { x12, y12: c2 }]=["abc", { x12: 10, y12: false }]] = [1, ["hello", { x12: 5, y12: true }]];
>a2 : number
Expand Down
24 changes: 12 additions & 12 deletions tests/baselines/reference/declarationsAndAssignments.errors.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(5,16): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(5,16): error TS2493: Tuple type '[number, string]' of length '2' has no element at index '2'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(22,17): error TS2353: Object literal may only specify known properties, and 'x' does not exist in type '{}'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(22,23): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{}'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(23,25): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{ x: any; }'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(24,19): error TS2353: Object literal may only specify known properties, and 'x' does not exist in type '{ y: any; }'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(28,28): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{ x: any; }'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(29,22): error TS2353: Object literal may only specify known properties, and 'x' does not exist in type '{ y: any; }'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(58,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'y' must be of type 'string | number', but here has type 'string'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,10): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,13): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,16): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(63,13): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(63,16): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,10): error TS2493: Tuple type '[]' of length '0' has no element at index '0'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,13): error TS2493: Tuple type '[]' of length '0' has no element at index '1'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,16): error TS2493: Tuple type '[]' of length '0' has no element at index '2'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(63,13): error TS2493: Tuple type '[number]' of length '1' has no element at index '1'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(63,16): error TS2493: Tuple type '[number]' of length '1' has no element at index '2'.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(67,9): error TS2461: Type '{}' is not an array type.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(68,9): error TS2461: Type '{ 0: number; 1: number; }' is not an array type.
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(73,11): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
Expand All @@ -29,7 +29,7 @@ tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(138,9):
var [x, y] = [1, "hello"];
var [x, y, z] = [1, "hello"];
~
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
!!! error TS2493: Tuple type '[number, string]' of length '2' has no element at index '2'.
var [,, x] = [0, 1, 2];
var x: number;
var y: string;
Expand Down Expand Up @@ -103,16 +103,16 @@ tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(138,9):
function f8() {
var [a, b, c] = []; // Error, [] is an empty tuple
~
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '0'.
~
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '1'.
~
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '2'.
var [d, e, f] = [1]; // Error, [1] is a tuple
~
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
!!! error TS2493: Tuple type '[number]' of length '1' has no element at index '1'.
~
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
!!! error TS2493: Tuple type '[number]' of length '1' has no element at index '2'.
}

function f9() {
Expand Down
18 changes: 9 additions & 9 deletions tests/baselines/reference/declarationsAndAssignments.types
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ function f0() {
var [x, y, z] = [1, "hello"];
>x : number
>y : string
>z : any
>[1, "hello"] : [number, string, undefined?]
>z : undefined
>[1, "hello"] : [number, string]
>1 : 1
>"hello" : "hello"

Expand Down Expand Up @@ -255,16 +255,16 @@ function f8() {
>f8 : () => void

var [a, b, c] = []; // Error, [] is an empty tuple
>a : any
>b : any
>c : any
>[] : [undefined?, undefined?, undefined?]
>a : undefined
>b : undefined
>c : undefined
>[] : []

var [d, e, f] = [1]; // Error, [1] is a tuple
>d : number
>e : any
>f : any
>[1] : [number, undefined?, undefined?]
>e : undefined
>f : undefined
>[1] : [number]
>1 : 1
}

Expand Down
Loading