From 7f3f98ea833445a1618092d8365bf4ade7f7e97f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 6 Oct 2018 13:49:42 -0700 Subject: [PATCH] Properly handle unions of tuple types --- src/compiler/checker.ts | 61 ++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e200730bc87..6ee5ad85ced 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4681,14 +4681,14 @@ namespace ts { // If the parent is a tuple type, the rest element has a tuple type of the // remaining tuple element types. Otherwise, the rest element has an array type with same // element type as the parent type. - type = isTupleType(parentType) ? - sliceTupleType(parentType, index) : + type = everyType(parentType, isTupleType) ? + mapType(parentType, t => sliceTupleType(t, index)) : createArrayType(elementType); } else { // Use specific property type when parent is a tuple or numeric index type when parent is an array const index = pattern.elements.indexOf(declaration); - type = isTupleLikeType(parentType) ? + type = everyType(parentType, isTupleLikeType) ? getTupleElementType(parentType, index) || declaration.initializer && checkDeclarationInitializer(declaration) : elementType; if (!type) { @@ -7307,10 +7307,10 @@ namespace ts { } } else if (isUnion) { - const index = !isLateBoundName(name) && ((isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number)) || getIndexInfoOfType(type, IndexKind.String)); - if (index) { - checkFlags |= index.isReadonly ? CheckFlags.Readonly : 0; - indexTypes = append(indexTypes, index.type); + const indexInfo = !isLateBoundName(name) && (isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number) || getIndexInfoOfType(type, IndexKind.String)); + if (indexInfo) { + checkFlags |= indexInfo.isReadonly ? CheckFlags.Readonly : 0; + indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); } else { checkFlags |= CheckFlags.Partial; @@ -9307,11 +9307,8 @@ namespace ts { const propType = getTypeOfSymbol(prop); return accessExpression ? getFlowTypeOfReference(accessExpression, propType) : propType; } - if (isTupleType(objectType)) { - const restType = getRestTypeOfTupleType(objectType); - if (restType && isNumericLiteralName(propName) && +propName >= 0) { - return restType; - } + if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) { + return mapType(objectType, t => getRestTypeOfTupleType(t) || undefinedType); } } if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { @@ -12867,9 +12864,17 @@ namespace ts { } function getTupleElementType(type: Type, index: number) { - return isTupleType(type) ? - index < getLengthOfTupleType(type) ? type.typeArguments![index] : getRestTypeOfTupleType(type) : - getTypeOfPropertyOfType(type, "" + index as __String); + const propType = getTypeOfPropertyOfType(type, "" + index as __String); + if (propType) { + return propType; + } + if (everyType(type, isTupleType)) { + let restType = mapType(type, t => getRestTypeOfTupleType(t) || undefinedType); + if (restType !== undefinedType) { + return restType; + } + } + return undefined; } function isNeitherUnitTypeNorNever(type: Type): boolean { @@ -14361,7 +14366,7 @@ namespace ts { } function getTypeOfDestructuredArrayElement(type: Type, index: number) { - return isTupleLikeType(type) && getTupleElementType(type, index) || + return everyType(type, isTupleLikeType) && getTupleElementType(type, index) || checkIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || errorType; } @@ -14559,6 +14564,10 @@ namespace ts { return type.flags & TypeFlags.Union ? forEach((type).types, f) : f(type); } + function everyType(type: Type, f: (t: Type) => boolean): boolean { + return type.flags & TypeFlags.Union ? every((type).types, f) : f(type); + } + function filterType(type: Type, f: (t: Type) => boolean): Type { if (type.flags & TypeFlags.Union) { const types = (type).types; @@ -16657,11 +16666,6 @@ namespace ts { return mapType(type, t => getIndexTypeOfStructuredType(t, kind), /*noReductions*/ true); } - // Return true if the given contextual type is a tuple-like type - function contextualTypeIsTupleLikeType(type: Type): boolean { - return !!(type.flags & TypeFlags.Union ? forEach((type).types, isTupleLikeType) : isTupleLikeType(type)); - } - // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one // exists. Otherwise, it is the type of the string index signature in T, if one exists. @@ -17191,7 +17195,8 @@ namespace ts { type.pattern = node; return type; } - if (contextualType && contextualTypeIsTupleLikeType(contextualType)) { + // Infer a tuple type when the contextual type is or contains a tuple-like type + if (contextualType && forEachType(contextualType, isTupleLikeType)) { const pattern = 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. @@ -18925,9 +18930,9 @@ namespace ts { } if (isTupleType(objectType) && !objectType.target.hasRestElement && isNumericLiteral(indexExpression)) { const index = +indexExpression.text; - const maximumIndex = length(objectType.target.typeParameters); - if (index >= maximumIndex) { - error(indexExpression, Diagnostics.Index_0_is_out_of_bounds_in_tuple_of_length_1, index, maximumIndex); + const length = getTypeReferenceArity(objectType); + if (index >= length) { + error(indexExpression, Diagnostics.Index_0_is_out_of_bounds_in_tuple_of_length_1, index, length); } } @@ -21668,7 +21673,7 @@ namespace ts { if (element.kind !== SyntaxKind.SpreadElement) { const propName = "" + elementIndex as __String; const type = isTypeAny(sourceType) ? sourceType : - isTupleLikeType(sourceType) ? getTupleElementType(sourceType, elementIndex) : + everyType(sourceType, isTupleLikeType) ? getTupleElementType(sourceType, elementIndex) : elementType; if (type) { return checkDestructuringAssignment(element, type, checkMode); @@ -21694,8 +21699,8 @@ namespace ts { } else { checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - const type = isTupleType(sourceType) ? - sliceTupleType(sourceType, elementIndex) : + const type = everyType(sourceType, isTupleType) ? + mapType(sourceType, t => sliceTupleType(t, elementIndex)) : createArrayType(elementType); return checkDestructuringAssignment(restExpression, type, checkMode); }