Skip to content

Commit

Permalink
Revise mapped tuple type instantiation logic (#57031)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahejlsberg authored Jan 19, 2024
1 parent 4a34f45 commit 458eae0
Show file tree
Hide file tree
Showing 11 changed files with 612 additions and 257 deletions.
68 changes: 29 additions & 39 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -40441,22 +40432,21 @@ 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)) {
error(e, Diagnostics.A_rest_element_type_must_be_an_array_type);
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;
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
circularInlineMappedGenericTupleTypeNoCrash.ts(11,12): error TS2589: Type instantiation is excessively deep and possibly infinite.


==== circularInlineMappedGenericTupleTypeNoCrash.ts (1 errors) ====
class Foo<Elements extends readonly unknown[]> {
public readonly elements: { [P in keyof Elements]: { bar: Elements[P] } };

public constructor(
...elements: { [P in keyof Elements]: { bar: Elements[P] } }
) {
this.elements = elements;
}

public add(): Foo<[...Elements, "abc"]> {
return new Foo<[...Elements, "abc"]>(...this.elements, { bar: "abc" });
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class Foo<Elements extends readonly unknown[]> {
>this.elements : { [P in keyof Elements]: { bar: Elements[P]; }; }
>this : this
>elements : { [P in keyof Elements]: { bar: Elements[P]; }; }
>{ bar: "abc" } : { bar: "abc"; }
>bar : "abc"
>{ bar: "abc" } : { bar: string; }
>bar : string
>"abc" : "abc"
}
}
Expand Down
151 changes: 151 additions & 0 deletions tests/baselines/reference/mappedTypesGenericTuples.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//// [tests/cases/conformance/types/mapped/mappedTypesGenericTuples.ts] ////

=== mappedTypesGenericTuples.ts ===
// Property keys are `number` following the fixed part of a tuple

type K<T> = { [P in keyof T]: P };
>K : Symbol(K, Decl(mappedTypesGenericTuples.ts, 0, 0))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 2, 7))
>P : Symbol(P, Decl(mappedTypesGenericTuples.ts, 2, 15))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 2, 7))
>P : Symbol(P, Decl(mappedTypesGenericTuples.ts, 2, 15))

type M<T> = { [P in keyof T]: T[P] };
>M : Symbol(M, Decl(mappedTypesGenericTuples.ts, 2, 34))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 3, 7))
>P : Symbol(P, Decl(mappedTypesGenericTuples.ts, 3, 15))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 3, 7))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 3, 7))
>P : Symbol(P, Decl(mappedTypesGenericTuples.ts, 3, 15))

type KA = K<[string, string, boolean]>; // ["0", "1", "2"]
>KA : Symbol(KA, Decl(mappedTypesGenericTuples.ts, 3, 37))
>K : Symbol(K, Decl(mappedTypesGenericTuples.ts, 0, 0))

type KB = K<[string, string, ...string[], string]>; // ["0", "1", ...number[], number]
>KB : Symbol(KB, Decl(mappedTypesGenericTuples.ts, 5, 39))
>K : Symbol(K, Decl(mappedTypesGenericTuples.ts, 0, 0))

type KC = K<[...string[]]>; // number[]
>KC : Symbol(KC, Decl(mappedTypesGenericTuples.ts, 6, 51))
>K : Symbol(K, Decl(mappedTypesGenericTuples.ts, 0, 0))

type KD = K<string[]>; // number[]
>KD : Symbol(KD, Decl(mappedTypesGenericTuples.ts, 7, 27))
>K : Symbol(K, Decl(mappedTypesGenericTuples.ts, 0, 0))

type A = { a: string };
>A : Symbol(A, Decl(mappedTypesGenericTuples.ts, 8, 22))
>a : Symbol(a, Decl(mappedTypesGenericTuples.ts, 10, 10))

type B = { b: string };
>B : Symbol(B, Decl(mappedTypesGenericTuples.ts, 10, 23))
>b : Symbol(b, Decl(mappedTypesGenericTuples.ts, 11, 10))

type C = { c: string };
>C : Symbol(C, Decl(mappedTypesGenericTuples.ts, 11, 23))
>c : Symbol(c, Decl(mappedTypesGenericTuples.ts, 12, 10))

type D = { d: string };
>D : Symbol(D, Decl(mappedTypesGenericTuples.ts, 12, 23))
>d : Symbol(d, Decl(mappedTypesGenericTuples.ts, 13, 10))

type V0<T extends unknown[]> = [A, B?, ...T, ...C[]]
>V0 : Symbol(V0, Decl(mappedTypesGenericTuples.ts, 13, 23))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 15, 8))
>A : Symbol(A, Decl(mappedTypesGenericTuples.ts, 8, 22))
>B : Symbol(B, Decl(mappedTypesGenericTuples.ts, 10, 23))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 15, 8))
>C : Symbol(C, Decl(mappedTypesGenericTuples.ts, 11, 23))

type V1<T extends unknown[]> = [A, ...T, B, ...C[], D]
>V1 : Symbol(V1, Decl(mappedTypesGenericTuples.ts, 15, 52))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 16, 8))
>A : Symbol(A, Decl(mappedTypesGenericTuples.ts, 8, 22))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 16, 8))
>B : Symbol(B, Decl(mappedTypesGenericTuples.ts, 10, 23))
>C : Symbol(C, Decl(mappedTypesGenericTuples.ts, 11, 23))
>D : Symbol(D, Decl(mappedTypesGenericTuples.ts, 12, 23))

type K0<T extends unknown[]> = K<V0<T>>; // ["0", "1"?, ...K<T>, ...number[]]
>K0 : Symbol(K0, Decl(mappedTypesGenericTuples.ts, 16, 54))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 18, 8))
>K : Symbol(K, Decl(mappedTypesGenericTuples.ts, 0, 0))
>V0 : Symbol(V0, Decl(mappedTypesGenericTuples.ts, 13, 23))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 18, 8))

type K1<T extends unknown[]> = K<V1<T>>; // ["0", ...K<T>, number, ...number[], number]
>K1 : Symbol(K1, Decl(mappedTypesGenericTuples.ts, 18, 40))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 19, 8))
>K : Symbol(K, Decl(mappedTypesGenericTuples.ts, 0, 0))
>V1 : Symbol(V1, Decl(mappedTypesGenericTuples.ts, 15, 52))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 19, 8))

type M0<T extends unknown[]> = M<V0<T>>; // [A, B?, ...M<T>, ...C[]]
>M0 : Symbol(M0, Decl(mappedTypesGenericTuples.ts, 19, 40))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 21, 8))
>M : Symbol(M, Decl(mappedTypesGenericTuples.ts, 2, 34))
>V0 : Symbol(V0, Decl(mappedTypesGenericTuples.ts, 13, 23))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 21, 8))

type M1<T extends unknown[]> = M<V1<T>>; // [A, ...M<T>, B, ...C[], D]
>M1 : Symbol(M1, Decl(mappedTypesGenericTuples.ts, 21, 40))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 22, 8))
>M : Symbol(M, Decl(mappedTypesGenericTuples.ts, 2, 34))
>V1 : Symbol(V1, Decl(mappedTypesGenericTuples.ts, 15, 52))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 22, 8))

// Repro from #48856

type Keys<O extends unknown[]> = { [K in keyof O]: K };
>Keys : Symbol(Keys, Decl(mappedTypesGenericTuples.ts, 22, 40))
>O : Symbol(O, Decl(mappedTypesGenericTuples.ts, 26, 10))
>K : Symbol(K, Decl(mappedTypesGenericTuples.ts, 26, 36))
>O : Symbol(O, Decl(mappedTypesGenericTuples.ts, 26, 10))
>K : Symbol(K, Decl(mappedTypesGenericTuples.ts, 26, 36))

type Keys1 = Keys<[string, ...string[]]>;
>Keys1 : Symbol(Keys1, Decl(mappedTypesGenericTuples.ts, 26, 55))
>Keys : Symbol(Keys, Decl(mappedTypesGenericTuples.ts, 22, 40))

type Keys2 = Keys<[string, ...string[], number]>;
>Keys2 : Symbol(Keys2, Decl(mappedTypesGenericTuples.ts, 28, 41))
>Keys : Symbol(Keys, Decl(mappedTypesGenericTuples.ts, 22, 40))

// Repro from #56888

type T1 = ['a', 'b', 'c'] extends readonly [infer H, ...unknown[]] ? H : never; // "a"
>T1 : Symbol(T1, Decl(mappedTypesGenericTuples.ts, 29, 49))
>H : Symbol(H, Decl(mappedTypesGenericTuples.ts, 33, 49))
>H : Symbol(H, Decl(mappedTypesGenericTuples.ts, 33, 49))

type T2 = ['a', 'b', 'c'] extends Readonly<[infer H, ...unknown[]]> ? H : never; // "a"
>T2 : Symbol(T2, Decl(mappedTypesGenericTuples.ts, 33, 79))
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
>H : Symbol(H, Decl(mappedTypesGenericTuples.ts, 34, 49))
>H : Symbol(H, Decl(mappedTypesGenericTuples.ts, 34, 49))

type T3 = ['a', 'b', 'c'] extends readonly [...unknown[], infer L] ? L : never; // "c"
>T3 : Symbol(T3, Decl(mappedTypesGenericTuples.ts, 34, 80))
>L : Symbol(L, Decl(mappedTypesGenericTuples.ts, 35, 63))
>L : Symbol(L, Decl(mappedTypesGenericTuples.ts, 35, 63))

type T4 = ['a', 'b', 'c'] extends Readonly<[...unknown[], infer L]> ? L : never; // "c"
>T4 : Symbol(T4, Decl(mappedTypesGenericTuples.ts, 35, 79))
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
>L : Symbol(L, Decl(mappedTypesGenericTuples.ts, 36, 63))
>L : Symbol(L, Decl(mappedTypesGenericTuples.ts, 36, 63))

// Repro from #56888

type R1<T> = readonly [...unknown[], T]; // readonly [...unknown[], T]
>R1 : Symbol(R1, Decl(mappedTypesGenericTuples.ts, 36, 80))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 40, 8))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 40, 8))

type R2<T> = Readonly<[...unknown[], T]>; // readonly [...unknown[], T]
>R2 : Symbol(R2, Decl(mappedTypesGenericTuples.ts, 40, 40))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 41, 8))
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypesGenericTuples.ts, 41, 8))

90 changes: 90 additions & 0 deletions tests/baselines/reference/mappedTypesGenericTuples.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//// [tests/cases/conformance/types/mapped/mappedTypesGenericTuples.ts] ////

=== mappedTypesGenericTuples.ts ===
// Property keys are `number` following the fixed part of a tuple

type K<T> = { [P in keyof T]: P };
>K : K<T>

type M<T> = { [P in keyof T]: T[P] };
>M : M<T>

type KA = K<[string, string, boolean]>; // ["0", "1", "2"]
>KA : ["0", "1", "2"]

type KB = K<[string, string, ...string[], string]>; // ["0", "1", ...number[], number]
>KB : ["0", "1", ...number[], number]

type KC = K<[...string[]]>; // number[]
>KC : number[]

type KD = K<string[]>; // number[]
>KD : number[]

type A = { a: string };
>A : { a: string; }
>a : string

type B = { b: string };
>B : { b: string; }
>b : string

type C = { c: string };
>C : { c: string; }
>c : string

type D = { d: string };
>D : { d: string; }
>d : string

type V0<T extends unknown[]> = [A, B?, ...T, ...C[]]
>V0 : [A, (B | undefined)?, ...T, ...C[]]

type V1<T extends unknown[]> = [A, ...T, B, ...C[], D]
>V1 : [A, ...T, B, ...C[], D]

type K0<T extends unknown[]> = K<V0<T>>; // ["0", "1"?, ...K<T>, ...number[]]
>K0 : ["0", ("1" | undefined)?, ...K<T>, ...number[]]

type K1<T extends unknown[]> = K<V1<T>>; // ["0", ...K<T>, number, ...number[], number]
>K1 : ["0", ...K<T>, number, ...number[], number]

type M0<T extends unknown[]> = M<V0<T>>; // [A, B?, ...M<T>, ...C[]]
>M0 : [A, (B | undefined)?, ...M<T>, ...C[]]

type M1<T extends unknown[]> = M<V1<T>>; // [A, ...M<T>, B, ...C[], D]
>M1 : [A, ...M<T>, B, ...C[], D]

// Repro from #48856

type Keys<O extends unknown[]> = { [K in keyof O]: K };
>Keys : Keys<O>

type Keys1 = Keys<[string, ...string[]]>;
>Keys1 : ["0", ...number[]]

type Keys2 = Keys<[string, ...string[], number]>;
>Keys2 : ["0", ...number[], number]

// Repro from #56888

type T1 = ['a', 'b', 'c'] extends readonly [infer H, ...unknown[]] ? H : never; // "a"
>T1 : "a"

type T2 = ['a', 'b', 'c'] extends Readonly<[infer H, ...unknown[]]> ? H : never; // "a"
>T2 : "a"

type T3 = ['a', 'b', 'c'] extends readonly [...unknown[], infer L] ? L : never; // "c"
>T3 : "c"

type T4 = ['a', 'b', 'c'] extends Readonly<[...unknown[], infer L]> ? L : never; // "c"
>T4 : "c"

// Repro from #56888

type R1<T> = readonly [...unknown[], T]; // readonly [...unknown[], T]
>R1 : R1<T>

type R2<T> = Readonly<[...unknown[], T]>; // readonly [...unknown[], T]
>R2 : readonly [...unknown[], T]

Loading

0 comments on commit 458eae0

Please sign in to comment.