From ba03f5b539f0619d855237caa17ae01d28a600fe Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 16 Sep 2019 12:45:44 -0700 Subject: [PATCH] Reverse mapped types should have inferable indexes if their source had an inferable index --- src/compiler/checker.ts | 6 +-- .../reverseMappedTypeAssignableToIndex.js | 22 ++++++++ ...reverseMappedTypeAssignableToIndex.symbols | 54 +++++++++++++++++++ .../reverseMappedTypeAssignableToIndex.types | 42 +++++++++++++++ .../reverseMappedTypeAssignableToIndex.ts | 19 +++++++ 5 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/reverseMappedTypeAssignableToIndex.js create mode 100644 tests/baselines/reference/reverseMappedTypeAssignableToIndex.symbols create mode 100644 tests/baselines/reference/reverseMappedTypeAssignableToIndex.types create mode 100644 tests/cases/compiler/reverseMappedTypeAssignableToIndex.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f8c8817888591..e4c0575fb0cf5 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 0000000000000..b3ee2dcda2e8a --- /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 0000000000000..38ed9ff039f0e --- /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 0000000000000..589e9ff3477a3 --- /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 0000000000000..a1244fe8eb277 --- /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