Fix logic for determining whether to simplify keyof on mapped types (#44042)

* Fix logic for determining whether to simplify keyof on mapped types

* Add regression test

* Improve hasDistributiveNameType check

* Add more tests

* Address code review feedback

* Add more tests
This commit is contained in:
Anders Hejlsberg
2021-05-19 13:43:22 -07:00
committed by GitHub
parent 87c5b6a752
commit 73736d9b84
6 changed files with 553 additions and 12 deletions

View File

@@ -14333,16 +14333,22 @@ namespace ts {
}
// Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N<P>]: X }, to simply N<K>. This however presumes
// that N distributes over union types, i.e. that N<A | B | C> is equivalent to N<A> | N<B> | N<C>. That presumption may not
// be true when N is a non-distributive conditional type or an instantiable type with a non-distributive conditional type as
// a constituent. In those cases, we cannot reduce keyof M and need to preserve it as is.
function maybeNonDistributiveNameType(type: Type | undefined): boolean {
return !!(type && (
type.flags & TypeFlags.Conditional && (!(type as ConditionalType).root.isDistributive || maybeNonDistributiveNameType((type as ConditionalType).checkType)) ||
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && some((type as UnionOrIntersectionType | TemplateLiteralType).types, maybeNonDistributiveNameType) ||
type.flags & (TypeFlags.Index | TypeFlags.StringMapping) && maybeNonDistributiveNameType((type as IndexType | StringMappingType).type) ||
type.flags & TypeFlags.IndexedAccess && maybeNonDistributiveNameType((type as IndexedAccessType).indexType) ||
type.flags & TypeFlags.Substitution && maybeNonDistributiveNameType((type as SubstitutionType).substitute)));
// that N distributes over union types, i.e. that N<A | B | C> is equivalent to N<A> | N<B> | N<C>. Specifically, we only
// want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable
// introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because
// they're the same type regardless of what's being distributed over.
function hasDistributiveNameType(mappedType: MappedType) {
const typeVariable = getTypeParameterFromMappedType(mappedType);
return isDistributive(getNameTypeFromMappedType(mappedType) || typeVariable);
function isDistributive(type: Type): boolean {
return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Primitive | TypeFlags.Never | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.NonPrimitive) ? true :
type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable :
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) :
type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) :
type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).substitute) :
type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) :
false;
}
}
function getLiteralTypeFromPropertyName(name: PropertyName) {
@@ -14393,9 +14399,9 @@ namespace ts {
function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type {
const includeOrigin = stringsOnly === keyofStringsOnly && !noIndexSignatures;
type = getReducedType(type);
return type.flags & TypeFlags.Union ? getIntersectionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
return type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && maybeNonDistributiveNameType(getNameTypeFromMappedType(type)) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) :
type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && !hasDistributiveNameType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) :
getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, noIndexSignatures) :
type === wildcardType ? wildcardType :
type.flags & TypeFlags.Unknown ? neverType :

View File

@@ -0,0 +1,158 @@
tests/cases/conformance/types/mapped/mappedTypeAsClauses.ts(130,3): error TS2345: Argument of type '"a"' is not assignable to parameter of type '"b"'.
==== tests/cases/conformance/types/mapped/mappedTypeAsClauses.ts (1 errors) ====
// Mapped type 'as N' clauses
type Getters<T> = { [P in keyof T & string as `get${Capitalize<P>}`]: () => T[P] };
type TG1 = Getters<{ foo: string, bar: number, baz: { z: boolean } }>;
// Mapped type with 'as N' clause has no constraint on 'in T' clause
type PropDef<K extends keyof any, T> = { name: K, type: T };
type TypeFromDefs<T extends PropDef<keyof any, any>> = { [P in T as P['name']]: P['type'] };
type TP1 = TypeFromDefs<{ name: 'a', type: string } | { name: 'b', type: number } | { name: 'a', type: boolean }>;
// No array or tuple type mapping when 'as N' clause present
type TA1 = Getters<string[]>;
type TA2 = Getters<[number, boolean]>;
// Filtering using 'as N' clause
type Methods<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
type TM1 = Methods<{ foo(): number, bar(x: string): boolean, baz: string | number }>;
// Mapping to multiple names using 'as N' clause
type DoubleProp<T> = { [P in keyof T & string as `${P}1` | `${P}2`]: T[P] }
type TD1 = DoubleProp<{ a: string, b: number }>; // { a1: string, a2: string, b1: number, b2: number }
type TD2 = keyof TD1; // 'a1' | 'a2' | 'b1' | 'b2'
type TD3<U> = keyof DoubleProp<U>; // `${keyof U & string}1` | `${keyof U & string}2`
// Repro from #40619
type Lazyify<T> = {
[K in keyof T as `get${Capitalize<K & string>}`]: () => T[K]
};
interface Person {
readonly name: string;
age: number;
location?: string;
}
type LazyPerson = Lazyify<Person>;
// Repro from #40833
type Example = {foo: string, bar: number};
type PickByValueType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
};
type T1 = PickByValueType<Example, string>;
const e1: T1 = {
foo: "hello"
};
type T2 = keyof T1;
const e2: T2 = "foo";
// Repro from #41133
interface Car {
name: string;
seats: number;
engine: Engine;
wheels: Wheel[];
}
interface Engine {
manufacturer: string;
horsepower: number;
}
interface Wheel {
type: "summer" | "winter";
radius: number;
}
type Primitive = string | number | boolean;
type OnlyPrimitives<T> = { [K in keyof T as T[K] extends Primitive ? K : never]: T[K] };
let primitiveCar: OnlyPrimitives<Car>; // { name: string; seats: number; }
let keys: keyof OnlyPrimitives<Car>; // "name" | "seats"
type KeysOfPrimitives<T> = keyof OnlyPrimitives<T>;
let carKeys: KeysOfPrimitives<Car>; // "name" | "seats"
// Repro from #41453
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
type If<Cond extends boolean, Then, Else> = Cond extends true ? Then : Else;
type GetKey<S, V> = keyof { [TP in keyof S as Equal<S[TP], V> extends true ? TP : never]: any };
type GetKeyWithIf<S, V> = keyof { [TP in keyof S as If<Equal<S[TP], V>, TP, never>]: any };
type GetObjWithIf<S, V> = { [TP in keyof S as If<Equal<S[TP], V>, TP, never>]: any };
type Task = {
isDone: boolean;
};
type Schema = {
root: {
title: string;
task: Task;
}
Task: Task;
};
type Res1 = GetKey<Schema, Schema['root']['task']>; // "Task"
type Res2 = GetKeyWithIf<Schema, Schema['root']['task']>; // "Task"
type Res3 = keyof GetObjWithIf<Schema, Schema['root']['task']>; // "Task"
// Repro from #44019
type KeysExtendedBy<T, U> = keyof { [K in keyof T as U extends T[K] ? K : never] : T[K] };
interface M {
a: boolean;
b: number;
}
function f(x: KeysExtendedBy<M, number>) {
return x;
}
f("a"); // Error, should allow only "b"
~~~
!!! error TS2345: Argument of type '"a"' is not assignable to parameter of type '"b"'.
type NameMap = { 'a': 'x', 'b': 'y', 'c': 'z' };
// Distributive, will be simplified
type TS0<T> = keyof { [P in keyof T as keyof Record<P, number>]: string };
type TS1<T> = keyof { [P in keyof T as Extract<P, 'a' | 'b' | 'c'>]: string };
type TS2<T> = keyof { [P in keyof T as P & ('a' | 'b' | 'c')]: string };
type TS3<T> = keyof { [P in keyof T as Exclude<P, 'a' | 'b' | 'c'>]: string };
type TS4<T> = keyof { [P in keyof T as NameMap[P & keyof NameMap]]: string };
type TS5<T> = keyof { [P in keyof T & keyof NameMap as NameMap[P]]: string };
type TS6<T, U, V> = keyof { [ K in keyof T as V & (K extends U ? K : never)]: string };
// Non-distributive, won't be simplified
type TN0<T> = keyof { [P in keyof T as T[P] extends number ? P : never]: string };
type TN1<T> = keyof { [P in keyof T as number extends T[P] ? P : never]: string };
type TN2<T> = keyof { [P in keyof T as 'a' extends P ? 'x' : 'y']: string };
type TN3<T> = keyof { [P in keyof T as Exclude<Exclude<Exclude<P, 'c'>, 'b'>, 'a'>]: string };
type TN4<T, U> = keyof { [K in keyof T as (K extends U ? T[K] : never) extends T[K] ? K : never]: string };
type TN5<T, U> = keyof { [K in keyof T as keyof { [P in K as T[P] extends U ? K : never]: true }]: string };

View File

@@ -114,6 +114,42 @@ type Schema = {
type Res1 = GetKey<Schema, Schema['root']['task']>; // "Task"
type Res2 = GetKeyWithIf<Schema, Schema['root']['task']>; // "Task"
type Res3 = keyof GetObjWithIf<Schema, Schema['root']['task']>; // "Task"
// Repro from #44019
type KeysExtendedBy<T, U> = keyof { [K in keyof T as U extends T[K] ? K : never] : T[K] };
interface M {
a: boolean;
b: number;
}
function f(x: KeysExtendedBy<M, number>) {
return x;
}
f("a"); // Error, should allow only "b"
type NameMap = { 'a': 'x', 'b': 'y', 'c': 'z' };
// Distributive, will be simplified
type TS0<T> = keyof { [P in keyof T as keyof Record<P, number>]: string };
type TS1<T> = keyof { [P in keyof T as Extract<P, 'a' | 'b' | 'c'>]: string };
type TS2<T> = keyof { [P in keyof T as P & ('a' | 'b' | 'c')]: string };
type TS3<T> = keyof { [P in keyof T as Exclude<P, 'a' | 'b' | 'c'>]: string };
type TS4<T> = keyof { [P in keyof T as NameMap[P & keyof NameMap]]: string };
type TS5<T> = keyof { [P in keyof T & keyof NameMap as NameMap[P]]: string };
type TS6<T, U, V> = keyof { [ K in keyof T as V & (K extends U ? K : never)]: string };
// Non-distributive, won't be simplified
type TN0<T> = keyof { [P in keyof T as T[P] extends number ? P : never]: string };
type TN1<T> = keyof { [P in keyof T as number extends T[P] ? P : never]: string };
type TN2<T> = keyof { [P in keyof T as 'a' extends P ? 'x' : 'y']: string };
type TN3<T> = keyof { [P in keyof T as Exclude<Exclude<Exclude<P, 'c'>, 'b'>, 'a'>]: string };
type TN4<T, U> = keyof { [K in keyof T as (K extends U ? T[K] : never) extends T[K] ? K : never]: string };
type TN5<T, U> = keyof { [K in keyof T as keyof { [P in K as T[P] extends U ? K : never]: true }]: string };
//// [mappedTypeAsClauses.js]
@@ -126,6 +162,10 @@ var e2 = "foo";
var primitiveCar; // { name: string; seats: number; }
var keys; // "name" | "seats"
var carKeys; // "name" | "seats"
function f(x) {
return x;
}
f("a"); // Error, should allow only "b"
//// [mappedTypeAsClauses.d.ts]
@@ -241,3 +281,57 @@ declare type Schema = {
declare type Res1 = GetKey<Schema, Schema['root']['task']>;
declare type Res2 = GetKeyWithIf<Schema, Schema['root']['task']>;
declare type Res3 = keyof GetObjWithIf<Schema, Schema['root']['task']>;
declare type KeysExtendedBy<T, U> = keyof {
[K in keyof T as U extends T[K] ? K : never]: T[K];
};
interface M {
a: boolean;
b: number;
}
declare function f(x: KeysExtendedBy<M, number>): "b";
declare type NameMap = {
'a': 'x';
'b': 'y';
'c': 'z';
};
declare type TS0<T> = keyof {
[P in keyof T as keyof Record<P, number>]: string;
};
declare type TS1<T> = keyof {
[P in keyof T as Extract<P, 'a' | 'b' | 'c'>]: string;
};
declare type TS2<T> = keyof {
[P in keyof T as P & ('a' | 'b' | 'c')]: string;
};
declare type TS3<T> = keyof {
[P in keyof T as Exclude<P, 'a' | 'b' | 'c'>]: string;
};
declare type TS4<T> = keyof {
[P in keyof T as NameMap[P & keyof NameMap]]: string;
};
declare type TS5<T> = keyof {
[P in keyof T & keyof NameMap as NameMap[P]]: string;
};
declare type TS6<T, U, V> = keyof {
[K in keyof T as V & (K extends U ? K : never)]: string;
};
declare type TN0<T> = keyof {
[P in keyof T as T[P] extends number ? P : never]: string;
};
declare type TN1<T> = keyof {
[P in keyof T as number extends T[P] ? P : never]: string;
};
declare type TN2<T> = keyof {
[P in keyof T as 'a' extends P ? 'x' : 'y']: string;
};
declare type TN3<T> = keyof {
[P in keyof T as Exclude<Exclude<Exclude<P, 'c'>, 'b'>, 'a'>]: string;
};
declare type TN4<T, U> = keyof {
[K in keyof T as (K extends U ? T[K] : never) extends T[K] ? K : never]: string;
};
declare type TN5<T, U> = keyof {
[K in keyof T as keyof {
[P in K as T[P] extends U ? K : never]: true;
}]: string;
};

View File

@@ -369,3 +369,174 @@ type Res3 = keyof GetObjWithIf<Schema, Schema['root']['task']>; // "Task"
>Schema : Symbol(Schema, Decl(mappedTypeAsClauses.ts, 102, 2))
>Schema : Symbol(Schema, Decl(mappedTypeAsClauses.ts, 102, 2))
// Repro from #44019
type KeysExtendedBy<T, U> = keyof { [K in keyof T as U extends T[K] ? K : never] : T[K] };
>KeysExtendedBy : Symbol(KeysExtendedBy, Decl(mappedTypeAsClauses.ts, 114, 63))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 118, 20))
>U : Symbol(U, Decl(mappedTypeAsClauses.ts, 118, 22))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 118, 37))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 118, 20))
>U : Symbol(U, Decl(mappedTypeAsClauses.ts, 118, 22))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 118, 20))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 118, 37))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 118, 37))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 118, 20))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 118, 37))
interface M {
>M : Symbol(M, Decl(mappedTypeAsClauses.ts, 118, 90))
a: boolean;
>a : Symbol(M.a, Decl(mappedTypeAsClauses.ts, 120, 13))
b: number;
>b : Symbol(M.b, Decl(mappedTypeAsClauses.ts, 121, 15))
}
function f(x: KeysExtendedBy<M, number>) {
>f : Symbol(f, Decl(mappedTypeAsClauses.ts, 123, 1))
>x : Symbol(x, Decl(mappedTypeAsClauses.ts, 125, 11))
>KeysExtendedBy : Symbol(KeysExtendedBy, Decl(mappedTypeAsClauses.ts, 114, 63))
>M : Symbol(M, Decl(mappedTypeAsClauses.ts, 118, 90))
return x;
>x : Symbol(x, Decl(mappedTypeAsClauses.ts, 125, 11))
}
f("a"); // Error, should allow only "b"
>f : Symbol(f, Decl(mappedTypeAsClauses.ts, 123, 1))
type NameMap = { 'a': 'x', 'b': 'y', 'c': 'z' };
>NameMap : Symbol(NameMap, Decl(mappedTypeAsClauses.ts, 129, 7))
>'a' : Symbol('a', Decl(mappedTypeAsClauses.ts, 131, 16))
>'b' : Symbol('b', Decl(mappedTypeAsClauses.ts, 131, 26))
>'c' : Symbol('c', Decl(mappedTypeAsClauses.ts, 131, 36))
// Distributive, will be simplified
type TS0<T> = keyof { [P in keyof T as keyof Record<P, number>]: string };
>TS0 : Symbol(TS0, Decl(mappedTypeAsClauses.ts, 131, 48))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 135, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 135, 23))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 135, 9))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 135, 23))
type TS1<T> = keyof { [P in keyof T as Extract<P, 'a' | 'b' | 'c'>]: string };
>TS1 : Symbol(TS1, Decl(mappedTypeAsClauses.ts, 135, 74))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 136, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 136, 23))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 136, 9))
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 136, 23))
type TS2<T> = keyof { [P in keyof T as P & ('a' | 'b' | 'c')]: string };
>TS2 : Symbol(TS2, Decl(mappedTypeAsClauses.ts, 136, 78))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 137, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 137, 23))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 137, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 137, 23))
type TS3<T> = keyof { [P in keyof T as Exclude<P, 'a' | 'b' | 'c'>]: string };
>TS3 : Symbol(TS3, Decl(mappedTypeAsClauses.ts, 137, 72))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 138, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 138, 23))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 138, 9))
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 138, 23))
type TS4<T> = keyof { [P in keyof T as NameMap[P & keyof NameMap]]: string };
>TS4 : Symbol(TS4, Decl(mappedTypeAsClauses.ts, 138, 78))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 139, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 139, 23))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 139, 9))
>NameMap : Symbol(NameMap, Decl(mappedTypeAsClauses.ts, 129, 7))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 139, 23))
>NameMap : Symbol(NameMap, Decl(mappedTypeAsClauses.ts, 129, 7))
type TS5<T> = keyof { [P in keyof T & keyof NameMap as NameMap[P]]: string };
>TS5 : Symbol(TS5, Decl(mappedTypeAsClauses.ts, 139, 77))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 140, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 140, 23))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 140, 9))
>NameMap : Symbol(NameMap, Decl(mappedTypeAsClauses.ts, 129, 7))
>NameMap : Symbol(NameMap, Decl(mappedTypeAsClauses.ts, 129, 7))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 140, 23))
type TS6<T, U, V> = keyof { [ K in keyof T as V & (K extends U ? K : never)]: string };
>TS6 : Symbol(TS6, Decl(mappedTypeAsClauses.ts, 140, 77))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 141, 9))
>U : Symbol(U, Decl(mappedTypeAsClauses.ts, 141, 11))
>V : Symbol(V, Decl(mappedTypeAsClauses.ts, 141, 14))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 141, 29))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 141, 9))
>V : Symbol(V, Decl(mappedTypeAsClauses.ts, 141, 14))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 141, 29))
>U : Symbol(U, Decl(mappedTypeAsClauses.ts, 141, 11))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 141, 29))
// Non-distributive, won't be simplified
type TN0<T> = keyof { [P in keyof T as T[P] extends number ? P : never]: string };
>TN0 : Symbol(TN0, Decl(mappedTypeAsClauses.ts, 141, 87))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 145, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 145, 23))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 145, 9))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 145, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 145, 23))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 145, 23))
type TN1<T> = keyof { [P in keyof T as number extends T[P] ? P : never]: string };
>TN1 : Symbol(TN1, Decl(mappedTypeAsClauses.ts, 145, 82))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 146, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 146, 23))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 146, 9))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 146, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 146, 23))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 146, 23))
type TN2<T> = keyof { [P in keyof T as 'a' extends P ? 'x' : 'y']: string };
>TN2 : Symbol(TN2, Decl(mappedTypeAsClauses.ts, 146, 82))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 147, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 147, 23))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 147, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 147, 23))
type TN3<T> = keyof { [P in keyof T as Exclude<Exclude<Exclude<P, 'c'>, 'b'>, 'a'>]: string };
>TN3 : Symbol(TN3, Decl(mappedTypeAsClauses.ts, 147, 76))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 148, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 148, 23))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 148, 9))
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 148, 23))
type TN4<T, U> = keyof { [K in keyof T as (K extends U ? T[K] : never) extends T[K] ? K : never]: string };
>TN4 : Symbol(TN4, Decl(mappedTypeAsClauses.ts, 148, 94))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 149, 9))
>U : Symbol(U, Decl(mappedTypeAsClauses.ts, 149, 11))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 149, 26))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 149, 9))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 149, 26))
>U : Symbol(U, Decl(mappedTypeAsClauses.ts, 149, 11))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 149, 9))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 149, 26))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 149, 9))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 149, 26))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 149, 26))
type TN5<T, U> = keyof { [K in keyof T as keyof { [P in K as T[P] extends U ? K : never]: true }]: string };
>TN5 : Symbol(TN5, Decl(mappedTypeAsClauses.ts, 149, 107))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 150, 9))
>U : Symbol(U, Decl(mappedTypeAsClauses.ts, 150, 11))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 150, 26))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 150, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 150, 51))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 150, 26))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 150, 9))
>P : Symbol(P, Decl(mappedTypeAsClauses.ts, 150, 51))
>U : Symbol(U, Decl(mappedTypeAsClauses.ts, 150, 11))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 150, 26))

View File

@@ -225,3 +225,79 @@ type Res2 = GetKeyWithIf<Schema, Schema['root']['task']>; // "Task"
type Res3 = keyof GetObjWithIf<Schema, Schema['root']['task']>; // "Task"
>Res3 : "Task"
// Repro from #44019
type KeysExtendedBy<T, U> = keyof { [K in keyof T as U extends T[K] ? K : never] : T[K] };
>KeysExtendedBy : keyof { [K in keyof T as U extends T[K] ? K : never]: T[K]; }
interface M {
a: boolean;
>a : boolean
b: number;
>b : number
}
function f(x: KeysExtendedBy<M, number>) {
>f : (x: KeysExtendedBy<M, number>) => "b"
>x : "b"
return x;
>x : "b"
}
f("a"); // Error, should allow only "b"
>f("a") : "b"
>f : (x: "b") => "b"
>"a" : "a"
type NameMap = { 'a': 'x', 'b': 'y', 'c': 'z' };
>NameMap : NameMap
>'a' : "x"
>'b' : "y"
>'c' : "z"
// Distributive, will be simplified
type TS0<T> = keyof { [P in keyof T as keyof Record<P, number>]: string };
>TS0 : keyof T
type TS1<T> = keyof { [P in keyof T as Extract<P, 'a' | 'b' | 'c'>]: string };
>TS1 : Extract<keyof T, "a" | "b" | "c">
type TS2<T> = keyof { [P in keyof T as P & ('a' | 'b' | 'c')]: string };
>TS2 : keyof T & ("a" | "b" | "c")
type TS3<T> = keyof { [P in keyof T as Exclude<P, 'a' | 'b' | 'c'>]: string };
>TS3 : Exclude<keyof T, "a" | "b" | "c">
type TS4<T> = keyof { [P in keyof T as NameMap[P & keyof NameMap]]: string };
>TS4 : NameMap[keyof T & keyof NameMap]
type TS5<T> = keyof { [P in keyof T & keyof NameMap as NameMap[P]]: string };
>TS5 : NameMap[keyof T & "a"] | NameMap[keyof T & "b"] | NameMap[keyof T & "c"]
type TS6<T, U, V> = keyof { [ K in keyof T as V & (K extends U ? K : never)]: string };
>TS6 : V & (keyof T extends U ? U & keyof T : never)
// Non-distributive, won't be simplified
type TN0<T> = keyof { [P in keyof T as T[P] extends number ? P : never]: string };
>TN0 : keyof { [P in keyof T as T[P] extends number ? P : never]: string; }
type TN1<T> = keyof { [P in keyof T as number extends T[P] ? P : never]: string };
>TN1 : keyof { [P in keyof T as number extends T[P] ? P : never]: string; }
type TN2<T> = keyof { [P in keyof T as 'a' extends P ? 'x' : 'y']: string };
>TN2 : keyof { [P in keyof T as "a" extends P ? "x" : "y"]: string; }
type TN3<T> = keyof { [P in keyof T as Exclude<Exclude<Exclude<P, 'c'>, 'b'>, 'a'>]: string };
>TN3 : keyof { [P in keyof T as Exclude<Exclude<Exclude<P, "c">, "b">, "a">]: string; }
type TN4<T, U> = keyof { [K in keyof T as (K extends U ? T[K] : never) extends T[K] ? K : never]: string };
>TN4 : keyof { [K in keyof T as (K extends U ? T[K] : never) extends T[K] ? K : never]: string; }
type TN5<T, U> = keyof { [K in keyof T as keyof { [P in K as T[P] extends U ? K : never]: true }]: string };
>TN5 : keyof { [K in keyof T as keyof { [P in K as T[P] extends U ? K : never]: true; }]: string; }
>true : true

View File

@@ -116,3 +116,39 @@ type Schema = {
type Res1 = GetKey<Schema, Schema['root']['task']>; // "Task"
type Res2 = GetKeyWithIf<Schema, Schema['root']['task']>; // "Task"
type Res3 = keyof GetObjWithIf<Schema, Schema['root']['task']>; // "Task"
// Repro from #44019
type KeysExtendedBy<T, U> = keyof { [K in keyof T as U extends T[K] ? K : never] : T[K] };
interface M {
a: boolean;
b: number;
}
function f(x: KeysExtendedBy<M, number>) {
return x;
}
f("a"); // Error, should allow only "b"
type NameMap = { 'a': 'x', 'b': 'y', 'c': 'z' };
// Distributive, will be simplified
type TS0<T> = keyof { [P in keyof T as keyof Record<P, number>]: string };
type TS1<T> = keyof { [P in keyof T as Extract<P, 'a' | 'b' | 'c'>]: string };
type TS2<T> = keyof { [P in keyof T as P & ('a' | 'b' | 'c')]: string };
type TS3<T> = keyof { [P in keyof T as Exclude<P, 'a' | 'b' | 'c'>]: string };
type TS4<T> = keyof { [P in keyof T as NameMap[P & keyof NameMap]]: string };
type TS5<T> = keyof { [P in keyof T & keyof NameMap as NameMap[P]]: string };
type TS6<T, U, V> = keyof { [ K in keyof T as V & (K extends U ? K : never)]: string };
// Non-distributive, won't be simplified
type TN0<T> = keyof { [P in keyof T as T[P] extends number ? P : never]: string };
type TN1<T> = keyof { [P in keyof T as number extends T[P] ? P : never]: string };
type TN2<T> = keyof { [P in keyof T as 'a' extends P ? 'x' : 'y']: string };
type TN3<T> = keyof { [P in keyof T as Exclude<Exclude<Exclude<P, 'c'>, 'b'>, 'a'>]: string };
type TN4<T, U> = keyof { [K in keyof T as (K extends U ? T[K] : never) extends T[K] ? K : never]: string };
type TN5<T, U> = keyof { [K in keyof T as keyof { [P in K as T[P] extends U ? K : never]: true }]: string };