diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2fa1d8acf4a..e3907885a4c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15004,9 +15004,9 @@ namespace ts { * Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module * with no call or construct signatures. */ - function isObjectTypeWithInferableIndex(type: Type) { - return type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && - !typeHasCallOrConstructSignatures(type); + function isObjectTypeWithInferableIndex(type: Type): boolean { + return !!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && + !typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); } function createSymbolWithType(source: Symbol, type: Type | undefined) { diff --git a/tests/baselines/reference/reverseMappedTypeAssignableToIndex.js b/tests/baselines/reference/reverseMappedTypeAssignableToIndex.js new file mode 100644 index 00000000000..b3ee2dcda2e --- /dev/null +++ b/tests/baselines/reference/reverseMappedTypeAssignableToIndex.js @@ -0,0 +1,22 @@ +//// [reverseMappedTypeAssignableToIndex.ts] +// Simple mapped type and inferrence +type Mapped = { [K in keyof T]: { name: T[K] } }; +type InferFromMapped = T extends Mapped ? R : never; + +// Object literal type and associated mapped type +// Note that in the real code we don't have a direct reference to LiteralType +type LiteralType = { + first: "first"; + second: "second"; +} +type MappedLiteralType = { + first: { name: "first" }, + second: { name: "second" }, +}; + +type Inferred = InferFromMapped; + +// UNEXPECTED resolves to false +type Test1 = Inferred extends Record ? true : false; + +//// [reverseMappedTypeAssignableToIndex.js] diff --git a/tests/baselines/reference/reverseMappedTypeAssignableToIndex.symbols b/tests/baselines/reference/reverseMappedTypeAssignableToIndex.symbols new file mode 100644 index 00000000000..38ed9ff039f --- /dev/null +++ b/tests/baselines/reference/reverseMappedTypeAssignableToIndex.symbols @@ -0,0 +1,54 @@ +=== tests/cases/compiler/reverseMappedTypeAssignableToIndex.ts === +// Simple mapped type and inferrence +type Mapped = { [K in keyof T]: { name: T[K] } }; +>Mapped : Symbol(Mapped, Decl(reverseMappedTypeAssignableToIndex.ts, 0, 0)) +>T : Symbol(T, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 12)) +>K : Symbol(K, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 20)) +>T : Symbol(T, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 12)) +>name : Symbol(name, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 36)) +>T : Symbol(T, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 12)) +>K : Symbol(K, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 20)) + +type InferFromMapped = T extends Mapped ? R : never; +>InferFromMapped : Symbol(InferFromMapped, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 52)) +>T : Symbol(T, Decl(reverseMappedTypeAssignableToIndex.ts, 2, 21)) +>T : Symbol(T, Decl(reverseMappedTypeAssignableToIndex.ts, 2, 21)) +>Mapped : Symbol(Mapped, Decl(reverseMappedTypeAssignableToIndex.ts, 0, 0)) +>R : Symbol(R, Decl(reverseMappedTypeAssignableToIndex.ts, 2, 48)) +>R : Symbol(R, Decl(reverseMappedTypeAssignableToIndex.ts, 2, 48)) + +// Object literal type and associated mapped type +// Note that in the real code we don't have a direct reference to LiteralType +type LiteralType = { +>LiteralType : Symbol(LiteralType, Decl(reverseMappedTypeAssignableToIndex.ts, 2, 64)) + + first: "first"; +>first : Symbol(first, Decl(reverseMappedTypeAssignableToIndex.ts, 6, 20)) + + second: "second"; +>second : Symbol(second, Decl(reverseMappedTypeAssignableToIndex.ts, 7, 16)) +} +type MappedLiteralType = { +>MappedLiteralType : Symbol(MappedLiteralType, Decl(reverseMappedTypeAssignableToIndex.ts, 9, 1)) + + first: { name: "first" }, +>first : Symbol(first, Decl(reverseMappedTypeAssignableToIndex.ts, 10, 26)) +>name : Symbol(name, Decl(reverseMappedTypeAssignableToIndex.ts, 11, 9)) + + second: { name: "second" }, +>second : Symbol(second, Decl(reverseMappedTypeAssignableToIndex.ts, 11, 26)) +>name : Symbol(name, Decl(reverseMappedTypeAssignableToIndex.ts, 12, 10)) + +}; + +type Inferred = InferFromMapped; +>Inferred : Symbol(Inferred, Decl(reverseMappedTypeAssignableToIndex.ts, 13, 2)) +>InferFromMapped : Symbol(InferFromMapped, Decl(reverseMappedTypeAssignableToIndex.ts, 1, 52)) +>MappedLiteralType : Symbol(MappedLiteralType, Decl(reverseMappedTypeAssignableToIndex.ts, 9, 1)) + +// UNEXPECTED resolves to false +type Test1 = Inferred extends Record ? true : false; +>Test1 : Symbol(Test1, Decl(reverseMappedTypeAssignableToIndex.ts, 15, 51)) +>Inferred : Symbol(Inferred, Decl(reverseMappedTypeAssignableToIndex.ts, 13, 2)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/reverseMappedTypeAssignableToIndex.types b/tests/baselines/reference/reverseMappedTypeAssignableToIndex.types new file mode 100644 index 00000000000..589e9ff3477 --- /dev/null +++ b/tests/baselines/reference/reverseMappedTypeAssignableToIndex.types @@ -0,0 +1,42 @@ +=== tests/cases/compiler/reverseMappedTypeAssignableToIndex.ts === +// Simple mapped type and inferrence +type Mapped = { [K in keyof T]: { name: T[K] } }; +>Mapped : Mapped +>name : T[K] + +type InferFromMapped = T extends Mapped ? R : never; +>InferFromMapped : InferFromMapped + +// Object literal type and associated mapped type +// Note that in the real code we don't have a direct reference to LiteralType +type LiteralType = { +>LiteralType : LiteralType + + first: "first"; +>first : "first" + + second: "second"; +>second : "second" +} +type MappedLiteralType = { +>MappedLiteralType : MappedLiteralType + + first: { name: "first" }, +>first : { name: "first"; } +>name : "first" + + second: { name: "second" }, +>second : { name: "second"; } +>name : "second" + +}; + +type Inferred = InferFromMapped; +>Inferred : { first: "first"; second: "second"; } + +// UNEXPECTED resolves to false +type Test1 = Inferred extends Record ? true : false; +>Test1 : true +>true : true +>false : false + diff --git a/tests/cases/compiler/reverseMappedTypeAssignableToIndex.ts b/tests/cases/compiler/reverseMappedTypeAssignableToIndex.ts new file mode 100644 index 00000000000..a1244fe8eb2 --- /dev/null +++ b/tests/cases/compiler/reverseMappedTypeAssignableToIndex.ts @@ -0,0 +1,19 @@ +// Simple mapped type and inferrence +type Mapped = { [K in keyof T]: { name: T[K] } }; +type InferFromMapped = T extends Mapped ? R : never; + +// Object literal type and associated mapped type +// Note that in the real code we don't have a direct reference to LiteralType +type LiteralType = { + first: "first"; + second: "second"; +} +type MappedLiteralType = { + first: { name: "first" }, + second: { name: "second" }, +}; + +type Inferred = InferFromMapped; + +// UNEXPECTED resolves to false +type Test1 = Inferred extends Record ? true : false; \ No newline at end of file