-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Revise mapped tuple type instantiation logic #57031
Changes from all commits
bb14f53
d369700
be36e76
2980eae
af142b3
70715ea
abc9c43
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 | ||||
---|---|---|---|---|---|---|
|
@@ -19745,11 +19745,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)); | ||||||
|
@@ -19769,26 +19766,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 | ||||||
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. Can you keep something similar to the original example in place? Or is it too different now for some reason? 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. and as you note the example with the keys, maybe include the example from your PR description as well? type Keys<T> = { [K in keyof T]: K };
type Foo<T extends unknown[]> = Keys<[string, string, ...T, string]>; // ["0", "1", ...Keys<T>, number] 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. Problem is, the original transformation (as witnessed by that example) was inconsistent with regards to property keys, so not sure there's any value in keeping it. 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. Yeah, I was thinking of trying to update the example, not keep it. I guess the issue you might've run into is that we don't have great notation to describe this. The closest thing being something like:
which is not too terrible. But I think having an example still has value, so if we have to go concrete, the 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. I'll add the |
||||||
// | ||||||
// 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) => { | ||||||
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.
Suggested change
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. Sure, will fix. |
||||||
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) { | ||||||
|
@@ -19797,18 +19800,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); | ||||||
|
@@ -40421,22 +40412,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; | ||||||
|
@@ -40450,7 +40440,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; | ||||||
} | ||||||
|
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 |
---|---|---|
@@ -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)) | ||
|
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] | ||
|
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.
An offtopic question if I may. I noticed (on several occasions already) that instantiating array/tuple mapped types eagerly~ leads to inconsistent behaviors with the object variants. The timing of instantiation is quite different and thus the availability of certain information tends to be different between them.
What are your thoughts on making this more consistent? How feasible that would even be? When it comes to objects each property stays deferred for as much as possible and I imagine that this would require deferring this instantiation per tuple element.
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.
In general, the advantage of eager instantiation is it is simpler to implement and it allows sharing of identical instantiations. The specific reason we eagerly instantiate mapped array and tuple types is that elsewhere in the compiler we detect arrays and tuples by looking for instantiations of
Array<T>
and the corresponding synthetic tuple classes. We'd have to extend those cases to also handle mapped types applied to arrays and tuples.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.
Thank you very much for the answer. I might want to attempt unifying this at some point - it's good to know that you don't have strong objections 😉 Or at least I read that from your comment ;p I understand that this might introduce some new complexity but I think it might be inevitable (as long as the goal is to have consistent behavior). When trying to do this, I will gather the test cases backing up that it's a desired change.