-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Handle 'keyof' for generic tuple types #39218
Changes from all commits
d4a4f9a
a290919
d633323
1310370
9bcbf8c
12f49c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the tuple has a rest, shouldn't the keys contain There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The keys already contain There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm. I question if we should be OK with |
||
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; | ||
} | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the
keyof T
in{[K in keyof T]: any}
whereT extends [...Some, ...Variadic, ...Tuple]
? That will fall back togetIndexType
(sinceT
isn't a tuple type), which seems to still be lacking a specialized implementation for variadic tuples? Or is thegetLiteralTypeFromProperties
fallback sufficient? If so, why would we need to call outisTupleType
here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The apparent type of a tuple type is simply the tuple type itself, so we need a special case for (generic) tuple types here as we'd otherwise just create the same type again. I'm not quite sure what you're asking about the mapped type. It's homomorphic, which means we'll just iterate over
getPropertiesOfType
forT
, which works just fine for a type parameter constrained to a generic tuple type.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but what if we have a non-homomorphic
keyof T
? (Eg, because it appears via instantiation). The apparent type of the tuple being itself is fine, so long asgetIndexType
handles it. If it does, then this is redundant. If it does not, then this doesn't handle the case where the apparent type resolves to a tuple type.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, seems better to do the check after
getApparentType
. Although it is surprisingly hard to construct an example where this fails because we typically callgetApparentType
on a mapped type before resolving members and that turns aMappedType<keyof T>
intoMappedType<keyof [...Some, ...Variadic]>
before we even get to this code. But anyways, I'll make the change.