Skip to content

Commit 56e2cb3

Browse files
authored
Reverse mapped types should have inferable indexes if their source had an inferable index (#33450)
1 parent a58b86b commit 56e2cb3

5 files changed

+140
-3
lines changed

src/compiler/checker.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15004,9 +15004,9 @@ namespace ts {
1500415004
* Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module
1500515005
* with no call or construct signatures.
1500615006
*/
15007-
function isObjectTypeWithInferableIndex(type: Type) {
15008-
return type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 &&
15009-
!typeHasCallOrConstructSignatures(type);
15007+
function isObjectTypeWithInferableIndex(type: Type): boolean {
15008+
return !!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 &&
15009+
!typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source));
1501015010
}
1501115011

1501215012
function createSymbolWithType(source: Symbol, type: Type | undefined) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//// [reverseMappedTypeAssignableToIndex.ts]
2+
// Simple mapped type and inferrence
3+
type Mapped<T> = { [K in keyof T]: { name: T[K] } };
4+
type InferFromMapped<T> = T extends Mapped<infer R> ? R : never;
5+
6+
// Object literal type and associated mapped type
7+
// Note that in the real code we don't have a direct reference to LiteralType
8+
type LiteralType = {
9+
first: "first";
10+
second: "second";
11+
}
12+
type MappedLiteralType = {
13+
first: { name: "first" },
14+
second: { name: "second" },
15+
};
16+
17+
type Inferred = InferFromMapped<MappedLiteralType>;
18+
19+
// UNEXPECTED resolves to false
20+
type Test1 = Inferred extends Record<any, string> ? true : false;
21+
22+
//// [reverseMappedTypeAssignableToIndex.js]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
=== tests/cases/compiler/reverseMappedTypeAssignableToIndex.ts ===
2+
// Simple mapped type and inferrence
3+
type Mapped<T> = { [K in keyof T]: { name: T[K] } };
4+
>Mapped : Symbol(Mapped, Decl(reverseMappedTypeAssignableToIndex.ts, 0, 0))
5+
>T : Symbol(T, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 12))
6+
>K : Symbol(K, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 20))
7+
>T : Symbol(T, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 12))
8+
>name : Symbol(name, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 36))
9+
>T : Symbol(T, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 12))
10+
>K : Symbol(K, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 20))
11+
12+
type InferFromMapped<T> = T extends Mapped<infer R> ? R : never;
13+
>InferFromMapped : Symbol(InferFromMapped, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 52))
14+
>T : Symbol(T, Decl(reverseMappedTypeAssignableToIndex.ts, 2, 21))
15+
>T : Symbol(T, Decl(reverseMappedTypeAssignableToIndex.ts, 2, 21))
16+
>Mapped : Symbol(Mapped, Decl(reverseMappedTypeAssignableToIndex.ts, 0, 0))
17+
>R : Symbol(R, Decl(reverseMappedTypeAssignableToIndex.ts, 2, 48))
18+
>R : Symbol(R, Decl(reverseMappedTypeAssignableToIndex.ts, 2, 48))
19+
20+
// Object literal type and associated mapped type
21+
// Note that in the real code we don't have a direct reference to LiteralType
22+
type LiteralType = {
23+
>LiteralType : Symbol(LiteralType, Decl(reverseMappedTypeAssignableToIndex.ts, 2, 64))
24+
25+
first: "first";
26+
>first : Symbol(first, Decl(reverseMappedTypeAssignableToIndex.ts, 6, 20))
27+
28+
second: "second";
29+
>second : Symbol(second, Decl(reverseMappedTypeAssignableToIndex.ts, 7, 16))
30+
}
31+
type MappedLiteralType = {
32+
>MappedLiteralType : Symbol(MappedLiteralType, Decl(reverseMappedTypeAssignableToIndex.ts, 9, 1))
33+
34+
first: { name: "first" },
35+
>first : Symbol(first, Decl(reverseMappedTypeAssignableToIndex.ts, 10, 26))
36+
>name : Symbol(name, Decl(reverseMappedTypeAssignableToIndex.ts, 11, 9))
37+
38+
second: { name: "second" },
39+
>second : Symbol(second, Decl(reverseMappedTypeAssignableToIndex.ts, 11, 26))
40+
>name : Symbol(name, Decl(reverseMappedTypeAssignableToIndex.ts, 12, 10))
41+
42+
};
43+
44+
type Inferred = InferFromMapped<MappedLiteralType>;
45+
>Inferred : Symbol(Inferred, Decl(reverseMappedTypeAssignableToIndex.ts, 13, 2))
46+
>InferFromMapped : Symbol(InferFromMapped, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 52))
47+
>MappedLiteralType : Symbol(MappedLiteralType, Decl(reverseMappedTypeAssignableToIndex.ts, 9, 1))
48+
49+
// UNEXPECTED resolves to false
50+
type Test1 = Inferred extends Record<any, string> ? true : false;
51+
>Test1 : Symbol(Test1, Decl(reverseMappedTypeAssignableToIndex.ts, 15, 51))
52+
>Inferred : Symbol(Inferred, Decl(reverseMappedTypeAssignableToIndex.ts, 13, 2))
53+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
54+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/reverseMappedTypeAssignableToIndex.ts ===
2+
// Simple mapped type and inferrence
3+
type Mapped<T> = { [K in keyof T]: { name: T[K] } };
4+
>Mapped : Mapped<T>
5+
>name : T[K]
6+
7+
type InferFromMapped<T> = T extends Mapped<infer R> ? R : never;
8+
>InferFromMapped : InferFromMapped<T>
9+
10+
// Object literal type and associated mapped type
11+
// Note that in the real code we don't have a direct reference to LiteralType
12+
type LiteralType = {
13+
>LiteralType : LiteralType
14+
15+
first: "first";
16+
>first : "first"
17+
18+
second: "second";
19+
>second : "second"
20+
}
21+
type MappedLiteralType = {
22+
>MappedLiteralType : MappedLiteralType
23+
24+
first: { name: "first" },
25+
>first : { name: "first"; }
26+
>name : "first"
27+
28+
second: { name: "second" },
29+
>second : { name: "second"; }
30+
>name : "second"
31+
32+
};
33+
34+
type Inferred = InferFromMapped<MappedLiteralType>;
35+
>Inferred : { first: "first"; second: "second"; }
36+
37+
// UNEXPECTED resolves to false
38+
type Test1 = Inferred extends Record<any, string> ? true : false;
39+
>Test1 : true
40+
>true : true
41+
>false : false
42+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Simple mapped type and inferrence
2+
type Mapped<T> = { [K in keyof T]: { name: T[K] } };
3+
type InferFromMapped<T> = T extends Mapped<infer R> ? R : never;
4+
5+
// Object literal type and associated mapped type
6+
// Note that in the real code we don't have a direct reference to LiteralType
7+
type LiteralType = {
8+
first: "first";
9+
second: "second";
10+
}
11+
type MappedLiteralType = {
12+
first: { name: "first" },
13+
second: { name: "second" },
14+
};
15+
16+
type Inferred = InferFromMapped<MappedLiteralType>;
17+
18+
// UNEXPECTED resolves to false
19+
type Test1 = Inferred extends Record<any, string> ? true : false;

0 commit comments

Comments
 (0)