Properly handle unions of tuple types

This commit is contained in:
Anders Hejlsberg 2018-10-06 13:49:42 -07:00
parent c9ea6c3e03
commit 7f3f98ea83

View File

@ -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(<TupleTypeReference>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(<TupleTypeReference>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(<TupleTypeReference>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((<UnionType>type).types, f) : f(type);
}
function everyType(type: Type, f: (t: Type) => boolean): boolean {
return type.flags & TypeFlags.Union ? every((<UnionType>type).types, f) : f(type);
}
function filterType(type: Type, f: (t: Type) => boolean): Type {
if (type.flags & TypeFlags.Union) {
const types = (<UnionType>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((<UnionType>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(<TupleTypeReference>t, elementIndex)) :
createArrayType(elementType);
return checkDestructuringAssignment(restExpression, type, checkMode);
}