From 1beb1037c0b9db37e3bc0ca122bd5f2c13fadcfb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 1 Jun 2022 10:01:56 -0700 Subject: [PATCH] Fix check in isMappedTypeGenericIndexedAccess (#49341) * Fix check in isMappedTypeGenericIndexedAccess * Add regression tests --- src/compiler/checker.ts | 2 +- .../mappedTypeGenericIndexedAccess.js | 105 ++++++++++++ .../mappedTypeGenericIndexedAccess.symbols | 150 ++++++++++++++++++ .../mappedTypeGenericIndexedAccess.types | 147 +++++++++++++++++ .../mappedTypeGenericIndexedAccess.ts | 46 ++++++ 5 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/mappedTypeGenericIndexedAccess.js create mode 100644 tests/baselines/reference/mappedTypeGenericIndexedAccess.symbols create mode 100644 tests/baselines/reference/mappedTypeGenericIndexedAccess.types create mode 100644 tests/cases/compiler/mappedTypeGenericIndexedAccess.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4fdc8b7f3f017..8188e14f5c898 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12308,7 +12308,7 @@ namespace ts { let objectType; return !!(type.flags & TypeFlags.IndexedAccess && getObjectFlags(objectType = (type as IndexedAccessType).objectType) & ObjectFlags.Mapped && !isGenericMappedType(objectType) && isGenericIndexType((type as IndexedAccessType).indexType) && - !(objectType as MappedType).declaration.questionToken && !(objectType as MappedType).declaration.nameType); + !(getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.ExcludeOptional) && !(objectType as MappedType).declaration.nameType); } /** diff --git a/tests/baselines/reference/mappedTypeGenericIndexedAccess.js b/tests/baselines/reference/mappedTypeGenericIndexedAccess.js new file mode 100644 index 0000000000000..a06d9b27218a6 --- /dev/null +++ b/tests/baselines/reference/mappedTypeGenericIndexedAccess.js @@ -0,0 +1,105 @@ +//// [mappedTypeGenericIndexedAccess.ts] +// Repro from #49242 + +type Types = { + first: { a1: true }; + second: { a2: true }; + third: { a3: true }; +} + +class Test { + entries: { [T in keyof Types]?: Types[T][] }; + + constructor() { + this.entries = {}; + } + + addEntry(name: T, entry: Types[T]) { + if (!this.entries[name]) { + this.entries[name] = []; + } + this.entries[name]?.push(entry); + } +} + +// Repro from #49338 + +type TypesMap = { + [0]: { foo: 'bar'; }; + [1]: { a: 'b'; }; +}; + +type P = { t: T; } & TypesMap[T]; + +type TypeHandlers = { + [T in keyof TypesMap]?: (p: P) => void; +}; + +const typeHandlers: TypeHandlers = { + [0]: (p) => console.log(p.foo), + [1]: (p) => console.log(p.a), +}; + +const onSomeEvent = (p: P) => + typeHandlers[p.t]?.(p); + + +//// [mappedTypeGenericIndexedAccess.js] +"use strict"; +// Repro from #49242 +var _a; +var Test = /** @class */ (function () { + function Test() { + this.entries = {}; + } + Test.prototype.addEntry = function (name, entry) { + var _a; + if (!this.entries[name]) { + this.entries[name] = []; + } + (_a = this.entries[name]) === null || _a === void 0 ? void 0 : _a.push(entry); + }; + return Test; +}()); +var typeHandlers = (_a = {}, + _a[0] = function (p) { return console.log(p.foo); }, + _a[1] = function (p) { return console.log(p.a); }, + _a); +var onSomeEvent = function (p) { var _a; return (_a = typeHandlers[p.t]) === null || _a === void 0 ? void 0 : _a.call(typeHandlers, p); }; + + +//// [mappedTypeGenericIndexedAccess.d.ts] +declare type Types = { + first: { + a1: true; + }; + second: { + a2: true; + }; + third: { + a3: true; + }; +}; +declare class Test { + entries: { + [T in keyof Types]?: Types[T][]; + }; + constructor(); + addEntry(name: T, entry: Types[T]): void; +} +declare type TypesMap = { + [0]: { + foo: 'bar'; + }; + [1]: { + a: 'b'; + }; +}; +declare type P = { + t: T; +} & TypesMap[T]; +declare type TypeHandlers = { + [T in keyof TypesMap]?: (p: P) => void; +}; +declare const typeHandlers: TypeHandlers; +declare const onSomeEvent: (p: P) => void | undefined; diff --git a/tests/baselines/reference/mappedTypeGenericIndexedAccess.symbols b/tests/baselines/reference/mappedTypeGenericIndexedAccess.symbols new file mode 100644 index 0000000000000..329932a9afdbf --- /dev/null +++ b/tests/baselines/reference/mappedTypeGenericIndexedAccess.symbols @@ -0,0 +1,150 @@ +=== tests/cases/compiler/mappedTypeGenericIndexedAccess.ts === +// Repro from #49242 + +type Types = { +>Types : Symbol(Types, Decl(mappedTypeGenericIndexedAccess.ts, 0, 0)) + + first: { a1: true }; +>first : Symbol(first, Decl(mappedTypeGenericIndexedAccess.ts, 2, 14)) +>a1 : Symbol(a1, Decl(mappedTypeGenericIndexedAccess.ts, 3, 12)) + + second: { a2: true }; +>second : Symbol(second, Decl(mappedTypeGenericIndexedAccess.ts, 3, 24)) +>a2 : Symbol(a2, Decl(mappedTypeGenericIndexedAccess.ts, 4, 13)) + + third: { a3: true }; +>third : Symbol(third, Decl(mappedTypeGenericIndexedAccess.ts, 4, 25)) +>a3 : Symbol(a3, Decl(mappedTypeGenericIndexedAccess.ts, 5, 12)) +} + +class Test { +>Test : Symbol(Test, Decl(mappedTypeGenericIndexedAccess.ts, 6, 1)) + + entries: { [T in keyof Types]?: Types[T][] }; +>entries : Symbol(Test.entries, Decl(mappedTypeGenericIndexedAccess.ts, 8, 12)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 9, 16)) +>Types : Symbol(Types, Decl(mappedTypeGenericIndexedAccess.ts, 0, 0)) +>Types : Symbol(Types, Decl(mappedTypeGenericIndexedAccess.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 9, 16)) + + constructor() { + this.entries = {}; +>this.entries : Symbol(Test.entries, Decl(mappedTypeGenericIndexedAccess.ts, 8, 12)) +>this : Symbol(Test, Decl(mappedTypeGenericIndexedAccess.ts, 6, 1)) +>entries : Symbol(Test.entries, Decl(mappedTypeGenericIndexedAccess.ts, 8, 12)) + } + + addEntry(name: T, entry: Types[T]) { +>addEntry : Symbol(Test.addEntry, Decl(mappedTypeGenericIndexedAccess.ts, 13, 5)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 15, 13)) +>Types : Symbol(Types, Decl(mappedTypeGenericIndexedAccess.ts, 0, 0)) +>name : Symbol(name, Decl(mappedTypeGenericIndexedAccess.ts, 15, 36)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 15, 13)) +>entry : Symbol(entry, Decl(mappedTypeGenericIndexedAccess.ts, 15, 44)) +>Types : Symbol(Types, Decl(mappedTypeGenericIndexedAccess.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 15, 13)) + + if (!this.entries[name]) { +>this.entries : Symbol(Test.entries, Decl(mappedTypeGenericIndexedAccess.ts, 8, 12)) +>this : Symbol(Test, Decl(mappedTypeGenericIndexedAccess.ts, 6, 1)) +>entries : Symbol(Test.entries, Decl(mappedTypeGenericIndexedAccess.ts, 8, 12)) +>name : Symbol(name, Decl(mappedTypeGenericIndexedAccess.ts, 15, 36)) + + this.entries[name] = []; +>this.entries : Symbol(Test.entries, Decl(mappedTypeGenericIndexedAccess.ts, 8, 12)) +>this : Symbol(Test, Decl(mappedTypeGenericIndexedAccess.ts, 6, 1)) +>entries : Symbol(Test.entries, Decl(mappedTypeGenericIndexedAccess.ts, 8, 12)) +>name : Symbol(name, Decl(mappedTypeGenericIndexedAccess.ts, 15, 36)) + } + this.entries[name]?.push(entry); +>this.entries[name]?.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>this.entries : Symbol(Test.entries, Decl(mappedTypeGenericIndexedAccess.ts, 8, 12)) +>this : Symbol(Test, Decl(mappedTypeGenericIndexedAccess.ts, 6, 1)) +>entries : Symbol(Test.entries, Decl(mappedTypeGenericIndexedAccess.ts, 8, 12)) +>name : Symbol(name, Decl(mappedTypeGenericIndexedAccess.ts, 15, 36)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>entry : Symbol(entry, Decl(mappedTypeGenericIndexedAccess.ts, 15, 44)) + } +} + +// Repro from #49338 + +type TypesMap = { +>TypesMap : Symbol(TypesMap, Decl(mappedTypeGenericIndexedAccess.ts, 21, 1)) + + [0]: { foo: 'bar'; }; +>[0] : Symbol([0], Decl(mappedTypeGenericIndexedAccess.ts, 25, 17)) +>0 : Symbol([0], Decl(mappedTypeGenericIndexedAccess.ts, 25, 17)) +>foo : Symbol(foo, Decl(mappedTypeGenericIndexedAccess.ts, 26, 10)) + + [1]: { a: 'b'; }; +>[1] : Symbol([1], Decl(mappedTypeGenericIndexedAccess.ts, 26, 25)) +>1 : Symbol([1], Decl(mappedTypeGenericIndexedAccess.ts, 26, 25)) +>a : Symbol(a, Decl(mappedTypeGenericIndexedAccess.ts, 27, 10)) + +}; + +type P = { t: T; } & TypesMap[T]; +>P : Symbol(P, Decl(mappedTypeGenericIndexedAccess.ts, 28, 2)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 30, 7)) +>TypesMap : Symbol(TypesMap, Decl(mappedTypeGenericIndexedAccess.ts, 21, 1)) +>t : Symbol(t, Decl(mappedTypeGenericIndexedAccess.ts, 30, 36)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 30, 7)) +>TypesMap : Symbol(TypesMap, Decl(mappedTypeGenericIndexedAccess.ts, 21, 1)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 30, 7)) + +type TypeHandlers = { +>TypeHandlers : Symbol(TypeHandlers, Decl(mappedTypeGenericIndexedAccess.ts, 30, 59)) + + [T in keyof TypesMap]?: (p: P) => void; +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 33, 5)) +>TypesMap : Symbol(TypesMap, Decl(mappedTypeGenericIndexedAccess.ts, 21, 1)) +>p : Symbol(p, Decl(mappedTypeGenericIndexedAccess.ts, 33, 29)) +>P : Symbol(P, Decl(mappedTypeGenericIndexedAccess.ts, 28, 2)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 33, 5)) + +}; + +const typeHandlers: TypeHandlers = { +>typeHandlers : Symbol(typeHandlers, Decl(mappedTypeGenericIndexedAccess.ts, 36, 5)) +>TypeHandlers : Symbol(TypeHandlers, Decl(mappedTypeGenericIndexedAccess.ts, 30, 59)) + + [0]: (p) => console.log(p.foo), +>[0] : Symbol([0], Decl(mappedTypeGenericIndexedAccess.ts, 36, 36)) +>0 : Symbol([0], Decl(mappedTypeGenericIndexedAccess.ts, 36, 36)) +>p : Symbol(p, Decl(mappedTypeGenericIndexedAccess.ts, 37, 10)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>p.foo : Symbol(foo, Decl(mappedTypeGenericIndexedAccess.ts, 26, 10)) +>p : Symbol(p, Decl(mappedTypeGenericIndexedAccess.ts, 37, 10)) +>foo : Symbol(foo, Decl(mappedTypeGenericIndexedAccess.ts, 26, 10)) + + [1]: (p) => console.log(p.a), +>[1] : Symbol([1], Decl(mappedTypeGenericIndexedAccess.ts, 37, 35)) +>1 : Symbol([1], Decl(mappedTypeGenericIndexedAccess.ts, 37, 35)) +>p : Symbol(p, Decl(mappedTypeGenericIndexedAccess.ts, 38, 10)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>p.a : Symbol(a, Decl(mappedTypeGenericIndexedAccess.ts, 27, 10)) +>p : Symbol(p, Decl(mappedTypeGenericIndexedAccess.ts, 38, 10)) +>a : Symbol(a, Decl(mappedTypeGenericIndexedAccess.ts, 27, 10)) + +}; + +const onSomeEvent = (p: P) => +>onSomeEvent : Symbol(onSomeEvent, Decl(mappedTypeGenericIndexedAccess.ts, 41, 5)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 41, 21)) +>TypesMap : Symbol(TypesMap, Decl(mappedTypeGenericIndexedAccess.ts, 21, 1)) +>p : Symbol(p, Decl(mappedTypeGenericIndexedAccess.ts, 41, 47)) +>P : Symbol(P, Decl(mappedTypeGenericIndexedAccess.ts, 28, 2)) +>T : Symbol(T, Decl(mappedTypeGenericIndexedAccess.ts, 41, 21)) + + typeHandlers[p.t]?.(p); +>typeHandlers : Symbol(typeHandlers, Decl(mappedTypeGenericIndexedAccess.ts, 36, 5)) +>p.t : Symbol(t, Decl(mappedTypeGenericIndexedAccess.ts, 30, 36)) +>p : Symbol(p, Decl(mappedTypeGenericIndexedAccess.ts, 41, 47)) +>t : Symbol(t, Decl(mappedTypeGenericIndexedAccess.ts, 30, 36)) +>p : Symbol(p, Decl(mappedTypeGenericIndexedAccess.ts, 41, 47)) + diff --git a/tests/baselines/reference/mappedTypeGenericIndexedAccess.types b/tests/baselines/reference/mappedTypeGenericIndexedAccess.types new file mode 100644 index 0000000000000..31effd4a6ce3b --- /dev/null +++ b/tests/baselines/reference/mappedTypeGenericIndexedAccess.types @@ -0,0 +1,147 @@ +=== tests/cases/compiler/mappedTypeGenericIndexedAccess.ts === +// Repro from #49242 + +type Types = { +>Types : { first: { a1: true;}; second: { a2: true;}; third: { a3: true;}; } + + first: { a1: true }; +>first : { a1: true; } +>a1 : true +>true : true + + second: { a2: true }; +>second : { a2: true; } +>a2 : true +>true : true + + third: { a3: true }; +>third : { a3: true; } +>a3 : true +>true : true +} + +class Test { +>Test : Test + + entries: { [T in keyof Types]?: Types[T][] }; +>entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } + + constructor() { + this.entries = {}; +>this.entries = {} : {} +>this.entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } +>this : this +>entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } +>{} : {} + } + + addEntry(name: T, entry: Types[T]) { +>addEntry : (name: T, entry: Types[T]) => void +>name : T +>entry : Types[T] + + if (!this.entries[name]) { +>!this.entries[name] : boolean +>this.entries[name] : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; }[T] +>this.entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } +>this : this +>entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } +>name : T + + this.entries[name] = []; +>this.entries[name] = [] : never[] +>this.entries[name] : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; }[T] +>this.entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } +>this : this +>entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } +>name : T +>[] : never[] + } + this.entries[name]?.push(entry); +>this.entries[name]?.push(entry) : number | undefined +>this.entries[name]?.push : ((...items: Types[T][]) => number) | undefined +>this.entries[name] : Types[T][] | undefined +>this.entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } +>this : this +>entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } +>name : T +>push : ((...items: Types[T][]) => number) | undefined +>entry : Types[T] + } +} + +// Repro from #49338 + +type TypesMap = { +>TypesMap : { 0: { foo: 'bar';}; 1: { a: 'b';}; } + + [0]: { foo: 'bar'; }; +>[0] : { foo: 'bar'; } +>0 : 0 +>foo : "bar" + + [1]: { a: 'b'; }; +>[1] : { a: 'b'; } +>1 : 1 +>a : "b" + +}; + +type P = { t: T; } & TypesMap[T]; +>P : P +>t : T + +type TypeHandlers = { +>TypeHandlers : { 0?: ((p: P<0>) => void) | undefined; 1?: ((p: P<1>) => void) | undefined; } + + [T in keyof TypesMap]?: (p: P) => void; +>p : P + +}; + +const typeHandlers: TypeHandlers = { +>typeHandlers : TypeHandlers +>{ [0]: (p) => console.log(p.foo), [1]: (p) => console.log(p.a),} : { 0: (p: P<0>) => void; 1: (p: P<1>) => void; } + + [0]: (p) => console.log(p.foo), +>[0] : (p: P<0>) => void +>0 : 0 +>(p) => console.log(p.foo) : (p: P<0>) => void +>p : P<0> +>console.log(p.foo) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>p.foo : "bar" +>p : P<0> +>foo : "bar" + + [1]: (p) => console.log(p.a), +>[1] : (p: P<1>) => void +>1 : 1 +>(p) => console.log(p.a) : (p: P<1>) => void +>p : P<1> +>console.log(p.a) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>p.a : "b" +>p : P<1> +>a : "b" + +}; + +const onSomeEvent = (p: P) => +>onSomeEvent : (p: P) => void | undefined +>(p: P) => typeHandlers[p.t]?.(p) : (p: P) => void | undefined +>p : P + + typeHandlers[p.t]?.(p); +>typeHandlers[p.t]?.(p) : void | undefined +>typeHandlers[p.t] : ((p: P) => void) | undefined +>typeHandlers : TypeHandlers +>p.t : T +>p : { t: T; } & ({ foo: "bar"; } | { a: "b"; }) +>t : T +>p : P + diff --git a/tests/cases/compiler/mappedTypeGenericIndexedAccess.ts b/tests/cases/compiler/mappedTypeGenericIndexedAccess.ts new file mode 100644 index 0000000000000..68240b8bc87fb --- /dev/null +++ b/tests/cases/compiler/mappedTypeGenericIndexedAccess.ts @@ -0,0 +1,46 @@ +// @strict: true +// @declaration: true + +// Repro from #49242 + +type Types = { + first: { a1: true }; + second: { a2: true }; + third: { a3: true }; +} + +class Test { + entries: { [T in keyof Types]?: Types[T][] }; + + constructor() { + this.entries = {}; + } + + addEntry(name: T, entry: Types[T]) { + if (!this.entries[name]) { + this.entries[name] = []; + } + this.entries[name]?.push(entry); + } +} + +// Repro from #49338 + +type TypesMap = { + [0]: { foo: 'bar'; }; + [1]: { a: 'b'; }; +}; + +type P = { t: T; } & TypesMap[T]; + +type TypeHandlers = { + [T in keyof TypesMap]?: (p: P) => void; +}; + +const typeHandlers: TypeHandlers = { + [0]: (p) => console.log(p.foo), + [1]: (p) => console.log(p.a), +}; + +const onSomeEvent = (p: P) => + typeHandlers[p.t]?.(p);