diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a32104c0535..1a5cc357c78 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6468,7 +6468,7 @@ namespace ts { } function getConstraintOfIndexedAccess(type: IndexedAccessType) { - const transformed = getSubstitutedIndexedMappedType(type); + const transformed = getSimplifiedIndexedAccessType(type); if (transformed) { return transformed; } @@ -8359,6 +8359,10 @@ namespace ts { return false; } + function isMappedTypeToNever(type: Type) { + return getObjectFlags(type) & ObjectFlags.Mapped && getTemplateTypeFromMappedType(type as MappedType) === neverType; + } + // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return // undefined if no transformation is possible. function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type { @@ -8383,11 +8387,22 @@ namespace ts { getIntersectionType(stringIndexTypes) ]); } - return getSubstitutedIndexedMappedType(type); - } + // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or + // more mapped types with a template type `never`, '(U & V & { [P in T]: never })[K]', return a + // transformed type that removes the never-mapped type: '(U & V)[K]'. This mirrors what would happen + // eventually anyway, but it easier to reason about. + if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((objectType).types, isMappedTypeToNever)) { + let nonNeverTypes: Type[]; + for (const t of (objectType).types) { + if (!isMappedTypeToNever(t)) { + (nonNeverTypes || (nonNeverTypes = [])).push(t); + } + } + if (nonNeverTypes) { + return getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType); + } + } - function getSubstitutedIndexedMappedType(type: IndexedAccessType): Type { - const objectType = type.objectType; // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we // construct the type Box. diff --git a/tests/baselines/reference/indexedAccessRetainsIndexSignature.js b/tests/baselines/reference/indexedAccessRetainsIndexSignature.js index 6c33317f156..1b77b95527b 100644 --- a/tests/baselines/reference/indexedAccessRetainsIndexSignature.js +++ b/tests/baselines/reference/indexedAccessRetainsIndexSignature.js @@ -2,9 +2,14 @@ type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T] type Omit = Pick> +type Omit1 = Pick>; +// is in fact an equivalent of +type Omit2 = {[P in Diff]: T[P]}; type O = Omit<{ a: number, b: string }, 'a'> +const o: O = { b: '' } //// [indexedAccessRetainsIndexSignature.js] +var o = { b: '' }; diff --git a/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols b/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols index 908d74c9452..9858f6fc162 100644 --- a/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols +++ b/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols @@ -24,10 +24,39 @@ type Omit = Pick> >U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10)) >K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 2, 12)) +type Omit1 = Pick>; +>Omit1 : Symbol(Omit1, Decl(indexedAccessRetainsIndexSignature.ts, 2, 59)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11)) +>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 3, 13)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11)) +>Pick : Symbol(Pick, Decl(lib.d.ts, --, --)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11)) +>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11)) +>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 3, 13)) + +// is in fact an equivalent of + +type Omit2 = {[P in Diff]: T[P]}; +>Omit2 : Symbol(Omit2, Decl(indexedAccessRetainsIndexSignature.ts, 3, 61)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11)) +>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 6, 13)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11)) +>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 6, 37)) +>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11)) +>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 6, 13)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11)) +>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 6, 37)) type O = Omit<{ a: number, b: string }, 'a'> ->O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 2, 59)) +>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 6, 67)) >Omit : Symbol(Omit, Decl(indexedAccessRetainsIndexSignature.ts, 1, 71)) ->a : Symbol(a, Decl(indexedAccessRetainsIndexSignature.ts, 5, 15)) ->b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 5, 26)) +>a : Symbol(a, Decl(indexedAccessRetainsIndexSignature.ts, 8, 15)) +>b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 8, 26)) + +const o: O = { b: '' } +>o : Symbol(o, Decl(indexedAccessRetainsIndexSignature.ts, 9, 5)) +>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 6, 67)) +>b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 9, 14)) diff --git a/tests/baselines/reference/indexedAccessRetainsIndexSignature.types b/tests/baselines/reference/indexedAccessRetainsIndexSignature.types index 8ef6cb62559..3e3f52f7ca6 100644 --- a/tests/baselines/reference/indexedAccessRetainsIndexSignature.types +++ b/tests/baselines/reference/indexedAccessRetainsIndexSignature.types @@ -24,6 +24,30 @@ type Omit = Pick> >U : U >K : K +type Omit1 = Pick>; +>Omit1 : Pick +>U : U +>K : K +>U : U +>Pick : Pick +>U : U +>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T] +>U : U +>K : K + +// is in fact an equivalent of + +type Omit2 = {[P in Diff]: T[P]}; +>Omit2 : Omit2 +>T : T +>K : K +>T : T +>P : P +>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T] +>T : T +>K : K +>T : T +>P : P type O = Omit<{ a: number, b: string }, 'a'> >O : Pick<{ a: number; b: string; }, "b"> @@ -31,3 +55,10 @@ type O = Omit<{ a: number, b: string }, 'a'> >a : number >b : string +const o: O = { b: '' } +>o : Pick<{ a: number; b: string; }, "b"> +>O : Pick<{ a: number; b: string; }, "b"> +>{ b: '' } : { b: string; } +>b : string +>'' : "" + diff --git a/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts b/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts index ff38b2d9090..d23cbe87ddf 100644 --- a/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts +++ b/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts @@ -1,6 +1,10 @@ type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T] type Omit = Pick> +type Omit1 = Pick>; +// is in fact an equivalent of +type Omit2 = {[P in Diff]: T[P]}; type O = Omit<{ a: number, b: string }, 'a'> +const o: O = { b: '' }