Handle 'keyof' for generic tuple types (#39218)

* Handle keyof T where T is generic tuple type

* Add tests

* Accept new baselines

* Address CR feedback

* Accept new baselines
This commit is contained in:
Anders Hejlsberg
2020-06-25 13:49:20 -07:00
committed by GitHub
parent 8df85b5cae
commit ee4aee0531
6 changed files with 693 additions and 276 deletions

View File

@@ -10195,7 +10195,8 @@ namespace ts {
return type;
}
if (type.flags & TypeFlags.Index) {
return getIndexType(getApparentType((<IndexType>type).type));
const t = getApparentType((<IndexType>type).type);
return isGenericTupleType(t) ? getKnownKeysOfTupleType(t) : getIndexType(t);
}
if (type.flags & TypeFlags.Conditional) {
if ((<ConditionalType>type).root.isDistributive) {
@@ -10520,9 +10521,6 @@ namespace ts {
return indexedAccess;
}
}
if (isGenericTupleType(type.objectType)) {
return getIndexTypeOfType(type.objectType, IndexKind.Number);
}
const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType);
if (objectConstraint && objectConstraint !== type.objectType) {
return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType);
@@ -10711,9 +10709,6 @@ namespace ts {
return keyofConstraintType;
}
if (t.flags & TypeFlags.IndexedAccess) {
if (isGenericTupleType((<IndexedAccessType>t).objectType)) {
return getIndexTypeOfType((<IndexedAccessType>t).objectType, IndexKind.Number);
}
const baseObjectType = getBaseConstraint((<IndexedAccessType>t).objectType);
const baseIndexType = getBaseConstraint((<IndexedAccessType>t).indexType);
const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType);
@@ -12647,6 +12642,11 @@ namespace ts {
/*readonly*/ false, target.labeledElementDeclarations && target.labeledElementDeclarations.slice(index, endIndex));
}
function getKnownKeysOfTupleType(type: TupleTypeReference) {
return getUnionType(append(arrayOf(type.target.fixedLength, i => getLiteralType("" + i)),
getIndexType(type.target.readonly ? globalReadonlyArrayType : globalArrayType)));
}
function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type {
const type = getTypeFromTypeNode(node.type);
return strictNullChecks ? getOptionalType(type) : type;
@@ -16831,11 +16831,11 @@ namespace ts {
}
}
// For a generic type T, [...T] is assignable to T, T is assignable to readonly [...T], and T is assignable
// to [...T] when T is constrained to a mutable array or tuple type.
if (isSingleElementGenericTupleType(source) && getTypeArguments(source)[0] === target && !source.target.readonly ||
isSingleElementGenericTupleType(target) && getTypeArguments(target)[0] === source && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source))) {
return Ternary.True;
// For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T],
// and U is assignable to [...T] when U is constrained to a mutable array or tuple type.
if (isSingleElementGenericTupleType(source) && !source.target.readonly && (result = isRelatedTo(getTypeArguments(source)[0], target)) ||
isSingleElementGenericTupleType(target) && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source)) && (result = isRelatedTo(source, getTypeArguments(target)[0]))) {
return result;
}
if (target.flags & TypeFlags.TypeParameter) {
@@ -16851,22 +16851,32 @@ namespace ts {
}
}
else if (target.flags & TypeFlags.Index) {
const targetType = (target as IndexType).type;
// A keyof S is related to a keyof T if T is related to S.
if (source.flags & TypeFlags.Index) {
if (result = isRelatedTo((<IndexType>target).type, (<IndexType>source).type, /*reportErrors*/ false)) {
if (result = isRelatedTo(targetType, (<IndexType>source).type, /*reportErrors*/ false)) {
return result;
}
}
// A type S is assignable to keyof T if S is assignable to keyof C, where C is the
// simplified form of T or, if T doesn't simplify, the constraint of T.
const constraint = getSimplifiedTypeOrConstraint((<IndexType>target).type);
if (constraint) {
// We require Ternary.True here such that circular constraints don't cause
// false positives. For example, given 'T extends { [K in keyof T]: string }',
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
// related to other types.
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) {
return Ternary.True;
if (isTupleType(targetType)) {
// An index type can have a tuple type target when the tuple type contains variadic elements.
// Check if the source is related to the known keys of the tuple type.
if (result = isRelatedTo(source, getKnownKeysOfTupleType(targetType), reportErrors)) {
return result;
}
}
else {
// A type S is assignable to keyof T if S is assignable to keyof C, where C is the
// simplified form of T or, if T doesn't simplify, the constraint of T.
const constraint = getSimplifiedTypeOrConstraint(targetType);
if (constraint) {
// We require Ternary.True here such that circular constraints don't cause
// false positives. For example, given 'T extends { [K in keyof T]: string }',
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
// related to other types.
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) {
return Ternary.True;
}
}
}
}