Revise mapped tuple type instantiation logic (#57031)

This commit is contained in:
Anders Hejlsberg
2024-01-19 10:44:19 -08:00
committed by GitHub
parent 4a34f45099
commit 458eae07d3
11 changed files with 612 additions and 257 deletions
+29 -39
View File
@@ -19759,11 +19759,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
) {
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
}
if (isGenericTupleType(t)) {
return instantiateMappedGenericTupleType(t, type, typeVariable, mapper);
}
if (isTupleType(t)) {
return instantiateMappedTupleType(t, type, prependTypeMapping(typeVariable, t, mapper));
return instantiateMappedTupleType(t, type, typeVariable, mapper);
}
}
return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper));
@@ -19783,26 +19780,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state;
}
function instantiateMappedGenericTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) {
// When a tuple type is generic (i.e. when it contains variadic elements), we want to eagerly map the
// non-generic elements and defer mapping the generic elements. In order to facilitate this, we transform
// M<[A, B?, ...T, ...C[]] into [...M<[A]>, ...M<[B?]>, ...M<T>, ...M<C[]>] and then rely on tuple type
// normalization to resolve the non-generic parts of the resulting tuple.
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) {
// We apply the mapped type's template type to each of the fixed part elements. For variadic elements, we
// apply the mapped type itself to the variadic element type. For other elements in the variable part of the
// tuple, we surround the element type with an array type and apply the mapped type to that. This ensures
// that we get sequential property key types for the fixed part of the tuple, and property key type number
// for the remaining elements. For example
//
// type Keys<T> = { [K in keyof T]: K };
// type Foo<T extends any[]> = Keys<[string, string, ...T, string]>; // ["0", "1", ...Keys<T>, number]
//
const elementFlags = tupleType.target.elementFlags;
const elementTypes = map(getElementTypes(tupleType), (t, i) => {
const singleton = elementFlags[i] & ElementFlags.Variadic ? t :
elementFlags[i] & ElementFlags.Rest ? createArrayType(t) :
createTupleType([t], [elementFlags[i]]);
// avoid infinite recursion, if the singleton is the type variable itself
// then we'd just get back here with the same arguments from within instantiateMappedType
if (singleton === typeVariable) {
return mappedType;
}
// The singleton is never a generic tuple type, so it is safe to recurse here.
return instantiateMappedType(mappedType, prependTypeMapping(typeVariable, singleton, mapper));
const fixedLength = tupleType.target.fixedLength;
const fixedMapper = fixedLength ? prependTypeMapping(typeVariable, tupleType, mapper) : mapper;
const newElementTypes = map(getElementTypes(tupleType), (type, i) => {
const flags = elementFlags[i];
return i < fixedLength ? instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(flags & ElementFlags.Optional), fixedMapper) :
flags & ElementFlags.Variadic ? instantiateType(mappedType, prependTypeMapping(typeVariable, type, mapper)) :
getElementTypeOfArrayType(instantiateType(mappedType, prependTypeMapping(typeVariable, createArrayType(type), mapper))) ?? unknownType;
});
const modifiers = getMappedTypeModifiers(mappedType);
const newElementFlags = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) :
modifiers & MappedTypeModifiers.ExcludeOptional ? map(elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) :
elementFlags;
const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, getMappedTypeModifiers(mappedType));
return createTupleType(elementTypes, map(elementTypes, _ => ElementFlags.Variadic), newReadonly);
return contains(newElementTypes, errorType) ? errorType :
createTupleType(newElementTypes, newElementFlags, newReadonly, tupleType.target.labeledElementDeclarations);
}
function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) {
@@ -19811,18 +19814,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType)));
}
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
const elementFlags = tupleType.target.elementFlags;
const elementTypes = map(getElementTypes(tupleType), (_, i) => instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper));
const modifiers = getMappedTypeModifiers(mappedType);
const newTupleModifiers = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) :
modifiers & MappedTypeModifiers.ExcludeOptional ? map(elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) :
elementFlags;
const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers);
return contains(elementTypes, errorType) ? errorType :
createTupleType(elementTypes, newTupleModifiers, newReadonly, tupleType.target.labeledElementDeclarations);
}
function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) {
const templateMapper = appendTypeMapping(mapper, getTypeParameterFromMappedType(type), key);
const propType = instantiateType(getTemplateTypeFromMappedType(type.target as MappedType || type), templateMapper);
@@ -40441,11 +40432,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function checkTupleType(node: TupleTypeNode) {
const elementTypes = node.elements;
let seenOptionalElement = false;
let seenRestElement = false;
for (const e of elementTypes) {
const flags = getTupleElementFlags(e);
for (const e of node.elements) {
let flags = getTupleElementFlags(e);
if (flags & ElementFlags.Variadic) {
const type = getTypeFromTypeNode((e as RestTypeNode | NamedTupleMember).type);
if (!isArrayLikeType(type)) {
@@ -40453,10 +40443,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
break;
}
if (isArrayType(type) || isTupleType(type) && type.target.combinedFlags & ElementFlags.Rest) {
seenRestElement = true;
flags |= ElementFlags.Rest;
}
}
else if (flags & ElementFlags.Rest) {
if (flags & ElementFlags.Rest) {
if (seenRestElement) {
grammarErrorOnNode(e, Diagnostics.A_rest_element_cannot_follow_another_rest_element);
break;
@@ -40470,7 +40460,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
seenOptionalElement = true;
}
else if (seenOptionalElement) {
else if (flags & ElementFlags.Required && seenOptionalElement) {
grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element);
break;
}