diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 06fc5a6d396f0..d3bbfbd9ace31 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13868,11 +13868,18 @@ namespace ts { type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false)); } + function instantiateTypeAsMappedNameType(nameType: Type, type: MappedType, t: Type) { + return instantiateType(nameType, appendTypeMapping(type.mapper, getTypeParameterFromMappedType(type), t)); + } + function getIndexTypeForMappedType(type: MappedType, noIndexSignatures: boolean | undefined) { const constraint = filterType(getConstraintTypeFromMappedType(type), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String))); const nameType = type.declaration.nameType && getTypeFromTypeNode(type.declaration.nameType); + // If the constraint is exclusively string/number/never type(s), we need to pull the property names from the modified type and run them through the `nameType` mapper as well + // since they won't appear in the constraint, due to subtype reducing with the string/number index types + const properties = nameType && everyType(constraint, t => !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.Never))) && getPropertiesOfType(getApparentType(getModifiersTypeFromMappedType(type))); return nameType ? - mapType(constraint, t => instantiateType(nameType, appendTypeMapping(type.mapper, getTypeParameterFromMappedType(type), t))) : + getUnionType([mapType(constraint, t => instantiateTypeAsMappedNameType(nameType, type, t)), mapType(getUnionType(map(properties || emptyArray, p => getLiteralTypeFromProperty(p, TypeFlags.StringOrNumberLiteralOrUnique))), t => instantiateTypeAsMappedNameType(nameType, type, t))]): constraint; } diff --git a/tests/baselines/reference/computedTypesKeyofNoIndexSignatureType.js b/tests/baselines/reference/computedTypesKeyofNoIndexSignatureType.js new file mode 100644 index 0000000000000..600ffaa78f67c --- /dev/null +++ b/tests/baselines/reference/computedTypesKeyofNoIndexSignatureType.js @@ -0,0 +1,23 @@ +//// [computedTypesKeyofNoIndexSignatureType.ts] +type Compute = { [K in keyof A]: Compute; } & {}; + +type EqualsTest = () => A extends T ? 1 : 0; +type Equals = EqualsTest extends EqualsTest ? 1 : 0; + +type Filter = Equals extends 1 ? never : K; + +type OmitIndex = { + [K in keyof T as Filter]: T[K]; +}; + +type IndexObject = { [key: string]: unknown; }; +type FooBar = { foo: "hello"; bar: "world"; }; + +type WithIndex = Compute; // { [x: string]: {}; foo: "hello"; bar: "world"; } <-- OK +type WithoutIndex = OmitIndex; // { foo: "hello"; bar: "world"; } <-- OK + +type FooBarKey = keyof FooBar; // "foo" | "bar" <-- OK +type WithIndexKey = keyof WithIndex; // string | number <-- Expected: string +type WithoutIndexKey = keyof WithoutIndex; // number <-- Expected: "foo" | "bar" + +//// [computedTypesKeyofNoIndexSignatureType.js] diff --git a/tests/baselines/reference/computedTypesKeyofNoIndexSignatureType.symbols b/tests/baselines/reference/computedTypesKeyofNoIndexSignatureType.symbols new file mode 100644 index 0000000000000..a329413972d07 --- /dev/null +++ b/tests/baselines/reference/computedTypesKeyofNoIndexSignatureType.symbols @@ -0,0 +1,83 @@ +=== tests/cases/compiler/computedTypesKeyofNoIndexSignatureType.ts === +type Compute = { [K in keyof A]: Compute; } & {}; +>Compute : Symbol(Compute, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 0)) +>A : Symbol(A, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 13)) +>K : Symbol(K, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 21)) +>A : Symbol(A, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 13)) +>Compute : Symbol(Compute, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 0)) +>A : Symbol(A, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 13)) +>K : Symbol(K, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 21)) + +type EqualsTest = () => A extends T ? 1 : 0; +>EqualsTest : Symbol(EqualsTest, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 58)) +>T : Symbol(T, Decl(computedTypesKeyofNoIndexSignatureType.ts, 2, 16)) +>A : Symbol(A, Decl(computedTypesKeyofNoIndexSignatureType.ts, 2, 22)) +>A : Symbol(A, Decl(computedTypesKeyofNoIndexSignatureType.ts, 2, 22)) +>T : Symbol(T, Decl(computedTypesKeyofNoIndexSignatureType.ts, 2, 16)) + +type Equals = EqualsTest extends EqualsTest ? 1 : 0; +>Equals : Symbol(Equals, Decl(computedTypesKeyofNoIndexSignatureType.ts, 2, 50)) +>A1 : Symbol(A1, Decl(computedTypesKeyofNoIndexSignatureType.ts, 3, 12)) +>A2 : Symbol(A2, Decl(computedTypesKeyofNoIndexSignatureType.ts, 3, 15)) +>EqualsTest : Symbol(EqualsTest, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 58)) +>A2 : Symbol(A2, Decl(computedTypesKeyofNoIndexSignatureType.ts, 3, 15)) +>EqualsTest : Symbol(EqualsTest, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 58)) +>A1 : Symbol(A1, Decl(computedTypesKeyofNoIndexSignatureType.ts, 3, 12)) + +type Filter = Equals extends 1 ? never : K; +>Filter : Symbol(Filter, Decl(computedTypesKeyofNoIndexSignatureType.ts, 3, 68)) +>K : Symbol(K, Decl(computedTypesKeyofNoIndexSignatureType.ts, 5, 12)) +>I : Symbol(I, Decl(computedTypesKeyofNoIndexSignatureType.ts, 5, 14)) +>Equals : Symbol(Equals, Decl(computedTypesKeyofNoIndexSignatureType.ts, 2, 50)) +>K : Symbol(K, Decl(computedTypesKeyofNoIndexSignatureType.ts, 5, 12)) +>I : Symbol(I, Decl(computedTypesKeyofNoIndexSignatureType.ts, 5, 14)) +>K : Symbol(K, Decl(computedTypesKeyofNoIndexSignatureType.ts, 5, 12)) + +type OmitIndex = { +>OmitIndex : Symbol(OmitIndex, Decl(computedTypesKeyofNoIndexSignatureType.ts, 5, 55)) +>T : Symbol(T, Decl(computedTypesKeyofNoIndexSignatureType.ts, 7, 15)) +>I : Symbol(I, Decl(computedTypesKeyofNoIndexSignatureType.ts, 7, 17)) + + [K in keyof T as Filter]: T[K]; +>K : Symbol(K, Decl(computedTypesKeyofNoIndexSignatureType.ts, 8, 3)) +>T : Symbol(T, Decl(computedTypesKeyofNoIndexSignatureType.ts, 7, 15)) +>Filter : Symbol(Filter, Decl(computedTypesKeyofNoIndexSignatureType.ts, 3, 68)) +>K : Symbol(K, Decl(computedTypesKeyofNoIndexSignatureType.ts, 8, 3)) +>I : Symbol(I, Decl(computedTypesKeyofNoIndexSignatureType.ts, 7, 17)) +>T : Symbol(T, Decl(computedTypesKeyofNoIndexSignatureType.ts, 7, 15)) +>K : Symbol(K, Decl(computedTypesKeyofNoIndexSignatureType.ts, 8, 3)) + +}; + +type IndexObject = { [key: string]: unknown; }; +>IndexObject : Symbol(IndexObject, Decl(computedTypesKeyofNoIndexSignatureType.ts, 9, 2)) +>key : Symbol(key, Decl(computedTypesKeyofNoIndexSignatureType.ts, 11, 22)) + +type FooBar = { foo: "hello"; bar: "world"; }; +>FooBar : Symbol(FooBar, Decl(computedTypesKeyofNoIndexSignatureType.ts, 11, 47)) +>foo : Symbol(foo, Decl(computedTypesKeyofNoIndexSignatureType.ts, 12, 15)) +>bar : Symbol(bar, Decl(computedTypesKeyofNoIndexSignatureType.ts, 12, 29)) + +type WithIndex = Compute; // { [x: string]: {}; foo: "hello"; bar: "world"; } <-- OK +>WithIndex : Symbol(WithIndex, Decl(computedTypesKeyofNoIndexSignatureType.ts, 12, 46)) +>Compute : Symbol(Compute, Decl(computedTypesKeyofNoIndexSignatureType.ts, 0, 0)) +>FooBar : Symbol(FooBar, Decl(computedTypesKeyofNoIndexSignatureType.ts, 11, 47)) +>IndexObject : Symbol(IndexObject, Decl(computedTypesKeyofNoIndexSignatureType.ts, 9, 2)) + +type WithoutIndex = OmitIndex; // { foo: "hello"; bar: "world"; } <-- OK +>WithoutIndex : Symbol(WithoutIndex, Decl(computedTypesKeyofNoIndexSignatureType.ts, 14, 47)) +>OmitIndex : Symbol(OmitIndex, Decl(computedTypesKeyofNoIndexSignatureType.ts, 5, 55)) +>WithIndex : Symbol(WithIndex, Decl(computedTypesKeyofNoIndexSignatureType.ts, 12, 46)) + +type FooBarKey = keyof FooBar; // "foo" | "bar" <-- OK +>FooBarKey : Symbol(FooBarKey, Decl(computedTypesKeyofNoIndexSignatureType.ts, 15, 49)) +>FooBar : Symbol(FooBar, Decl(computedTypesKeyofNoIndexSignatureType.ts, 11, 47)) + +type WithIndexKey = keyof WithIndex; // string | number <-- Expected: string +>WithIndexKey : Symbol(WithIndexKey, Decl(computedTypesKeyofNoIndexSignatureType.ts, 17, 30)) +>WithIndex : Symbol(WithIndex, Decl(computedTypesKeyofNoIndexSignatureType.ts, 12, 46)) + +type WithoutIndexKey = keyof WithoutIndex; // number <-- Expected: "foo" | "bar" +>WithoutIndexKey : Symbol(WithoutIndexKey, Decl(computedTypesKeyofNoIndexSignatureType.ts, 18, 36)) +>WithoutIndex : Symbol(WithoutIndex, Decl(computedTypesKeyofNoIndexSignatureType.ts, 14, 47)) + diff --git a/tests/baselines/reference/computedTypesKeyofNoIndexSignatureType.types b/tests/baselines/reference/computedTypesKeyofNoIndexSignatureType.types new file mode 100644 index 0000000000000..4723797638850 --- /dev/null +++ b/tests/baselines/reference/computedTypesKeyofNoIndexSignatureType.types @@ -0,0 +1,43 @@ +=== tests/cases/compiler/computedTypesKeyofNoIndexSignatureType.ts === +type Compute = { [K in keyof A]: Compute; } & {}; +>Compute : { [K in keyof A]: { [K in keyof A[K]]: { [K in keyof A[K][K]]: { [K in keyof A[K][K][K]]: { [K in keyof A[K][K][K][K]]: { [K in keyof A[K][K][K][K][K]]: { [K in keyof A[K][K][K][K][K][K]]: { [K in keyof A[K][K][K][K][K][K][K]]: { [K in keyof A[K][K][K][K][K][K][K][K]]: { [K in keyof A[K][K][K][K][K][K][K][K][K]]: { [K in keyof A[K][K][K][K][K][K][K][K][K][K]]: any; }; }; }; }; }; }; }; }; }; }; } + +type EqualsTest = () => A extends T ? 1 : 0; +>EqualsTest : EqualsTest + +type Equals = EqualsTest extends EqualsTest ? 1 : 0; +>Equals : Equals + +type Filter = Equals extends 1 ? never : K; +>Filter : Filter + +type OmitIndex = { +>OmitIndex : OmitIndex + + [K in keyof T as Filter]: T[K]; +}; + +type IndexObject = { [key: string]: unknown; }; +>IndexObject : IndexObject +>key : string + +type FooBar = { foo: "hello"; bar: "world"; }; +>FooBar : FooBar +>foo : "hello" +>bar : "world" + +type WithIndex = Compute; // { [x: string]: {}; foo: "hello"; bar: "world"; } <-- OK +>WithIndex : { [x: string]: {}; foo: "hello"; bar: "world"; } + +type WithoutIndex = OmitIndex; // { foo: "hello"; bar: "world"; } <-- OK +>WithoutIndex : OmitIndex<{ [x: string]: {}; foo: "hello"; bar: "world"; }, string> + +type FooBarKey = keyof FooBar; // "foo" | "bar" <-- OK +>FooBarKey : keyof FooBar + +type WithIndexKey = keyof WithIndex; // string | number <-- Expected: string +>WithIndexKey : string | number + +type WithoutIndexKey = keyof WithoutIndex; // number <-- Expected: "foo" | "bar" +>WithoutIndexKey : number | "foo" | "bar" + diff --git a/tests/cases/compiler/computedTypesKeyofNoIndexSignatureType.ts b/tests/cases/compiler/computedTypesKeyofNoIndexSignatureType.ts new file mode 100644 index 0000000000000..fcb10067a5260 --- /dev/null +++ b/tests/cases/compiler/computedTypesKeyofNoIndexSignatureType.ts @@ -0,0 +1,20 @@ +type Compute = { [K in keyof A]: Compute; } & {}; + +type EqualsTest = () => A extends T ? 1 : 0; +type Equals = EqualsTest extends EqualsTest ? 1 : 0; + +type Filter = Equals extends 1 ? never : K; + +type OmitIndex = { + [K in keyof T as Filter]: T[K]; +}; + +type IndexObject = { [key: string]: unknown; }; +type FooBar = { foo: "hello"; bar: "world"; }; + +type WithIndex = Compute; // { [x: string]: {}; foo: "hello"; bar: "world"; } <-- OK +type WithoutIndex = OmitIndex; // { foo: "hello"; bar: "world"; } <-- OK + +type FooBarKey = keyof FooBar; // "foo" | "bar" <-- OK +type WithIndexKey = keyof WithIndex; // string | number <-- Expected: string +type WithoutIndexKey = keyof WithoutIndex; // number <-- Expected: "foo" | "bar" \ No newline at end of file