Don't reduce 'keyof M' for mapped types with non-distributive 'as' clauses (#41186)

* Don't reduce 'keyof M' for mapped types with non-distributive as clauses

* Add regression test

* Accept new baselines
This commit is contained in:
Anders Hejlsberg 2020-10-21 12:16:46 -07:00 committed by GitHub
parent 672861abc6
commit 5d021b401a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 223 additions and 1 deletions

View File

@ -13475,6 +13475,19 @@ namespace ts {
constraint;
}
// 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 is
// generally true, except when N is a non-distributive conditional type or an instantiable type with non-distributive
// conditional type as a constituent. In those cases, we cannot reduce keyof M and need to preserve it as is.
function isNonDistributiveNameType(type: Type | undefined): boolean {
return !!(type && (
type.flags & TypeFlags.Conditional && !(<ConditionalType>type).root.isDistributive ||
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && some((<UnionOrIntersectionType | TemplateLiteralType>type).types, isNonDistributiveNameType) ||
type.flags & (TypeFlags.Index | TypeFlags.StringMapping) && isNonDistributiveNameType((<IndexType | StringMappingType>type).type) ||
type.flags & TypeFlags.IndexedAccess && isNonDistributiveNameType((<IndexedAccessType>type).indexType) ||
type.flags & TypeFlags.Substitution && isNonDistributiveNameType((<SubstitutionType>type).substitute)));
}
function getLiteralTypeFromPropertyName(name: PropertyName) {
if (isPrivateIdentifier(name)) {
return neverType;
@ -13522,7 +13535,7 @@ namespace ts {
type = getReducedType(type);
return type.flags & TypeFlags.Union ? getIntersectionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, stringsOnly) :
type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && isNonDistributiveNameType(getNameTypeFromMappedType(type)) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, stringsOnly) :
getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(<MappedType>type, noIndexSignatures) :
type === wildcardType ? wildcardType :
type.flags & TypeFlags.Unknown ? neverType :

View File

@ -57,6 +57,35 @@ const e1: T1 = {
};
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"
//// [mappedTypeAsClauses.js]
@ -66,6 +95,9 @@ var e1 = {
foo: "hello"
};
var e2 = "foo";
var primitiveCar; // { name: string; seats: number; }
var keys; // "name" | "seats"
var carKeys; // "name" | "seats"
//// [mappedTypeAsClauses.d.ts]
@ -135,3 +167,25 @@ declare type T1 = PickByValueType<Example, string>;
declare const e1: T1;
declare type T2 = keyof T1;
declare const e2: T2;
interface Car {
name: string;
seats: number;
engine: Engine;
wheels: Wheel[];
}
interface Engine {
manufacturer: string;
horsepower: number;
}
interface Wheel {
type: "summer" | "winter";
radius: number;
}
declare type Primitive = string | number | boolean;
declare type OnlyPrimitives<T> = {
[K in keyof T as T[K] extends Primitive ? K : never]: T[K];
};
declare let primitiveCar: OnlyPrimitives<Car>;
declare let keys: keyof OnlyPrimitives<Car>;
declare type KeysOfPrimitives<T> = keyof OnlyPrimitives<T>;
declare let carKeys: KeysOfPrimitives<Car>;

View File

@ -188,3 +188,79 @@ const e2: T2 = "foo";
>e2 : Symbol(e2, Decl(mappedTypeAsClauses.ts, 57, 5))
>T2 : Symbol(T2, Decl(mappedTypeAsClauses.ts, 55, 2))
// Repro from #41133
interface Car {
>Car : Symbol(Car, Decl(mappedTypeAsClauses.ts, 57, 21))
name: string;
>name : Symbol(Car.name, Decl(mappedTypeAsClauses.ts, 61, 15))
seats: number;
>seats : Symbol(Car.seats, Decl(mappedTypeAsClauses.ts, 62, 17))
engine: Engine;
>engine : Symbol(Car.engine, Decl(mappedTypeAsClauses.ts, 63, 18))
>Engine : Symbol(Engine, Decl(mappedTypeAsClauses.ts, 66, 1))
wheels: Wheel[];
>wheels : Symbol(Car.wheels, Decl(mappedTypeAsClauses.ts, 64, 19))
>Wheel : Symbol(Wheel, Decl(mappedTypeAsClauses.ts, 71, 1))
}
interface Engine {
>Engine : Symbol(Engine, Decl(mappedTypeAsClauses.ts, 66, 1))
manufacturer: string;
>manufacturer : Symbol(Engine.manufacturer, Decl(mappedTypeAsClauses.ts, 68, 18))
horsepower: number;
>horsepower : Symbol(Engine.horsepower, Decl(mappedTypeAsClauses.ts, 69, 25))
}
interface Wheel {
>Wheel : Symbol(Wheel, Decl(mappedTypeAsClauses.ts, 71, 1))
type: "summer" | "winter";
>type : Symbol(Wheel.type, Decl(mappedTypeAsClauses.ts, 73, 17))
radius: number;
>radius : Symbol(Wheel.radius, Decl(mappedTypeAsClauses.ts, 74, 30))
}
type Primitive = string | number | boolean;
>Primitive : Symbol(Primitive, Decl(mappedTypeAsClauses.ts, 76, 1))
type OnlyPrimitives<T> = { [K in keyof T as T[K] extends Primitive ? K : never]: T[K] };
>OnlyPrimitives : Symbol(OnlyPrimitives, Decl(mappedTypeAsClauses.ts, 78, 43))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 79, 20))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 79, 28))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 79, 20))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 79, 20))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 79, 28))
>Primitive : Symbol(Primitive, Decl(mappedTypeAsClauses.ts, 76, 1))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 79, 28))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 79, 20))
>K : Symbol(K, Decl(mappedTypeAsClauses.ts, 79, 28))
let primitiveCar: OnlyPrimitives<Car>; // { name: string; seats: number; }
>primitiveCar : Symbol(primitiveCar, Decl(mappedTypeAsClauses.ts, 81, 3))
>OnlyPrimitives : Symbol(OnlyPrimitives, Decl(mappedTypeAsClauses.ts, 78, 43))
>Car : Symbol(Car, Decl(mappedTypeAsClauses.ts, 57, 21))
let keys: keyof OnlyPrimitives<Car>; // "name" | "seats"
>keys : Symbol(keys, Decl(mappedTypeAsClauses.ts, 82, 3))
>OnlyPrimitives : Symbol(OnlyPrimitives, Decl(mappedTypeAsClauses.ts, 78, 43))
>Car : Symbol(Car, Decl(mappedTypeAsClauses.ts, 57, 21))
type KeysOfPrimitives<T> = keyof OnlyPrimitives<T>;
>KeysOfPrimitives : Symbol(KeysOfPrimitives, Decl(mappedTypeAsClauses.ts, 82, 36))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 84, 22))
>OnlyPrimitives : Symbol(OnlyPrimitives, Decl(mappedTypeAsClauses.ts, 78, 43))
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 84, 22))
let carKeys: KeysOfPrimitives<Car>; // "name" | "seats"
>carKeys : Symbol(carKeys, Decl(mappedTypeAsClauses.ts, 86, 3))
>KeysOfPrimitives : Symbol(KeysOfPrimitives, Decl(mappedTypeAsClauses.ts, 82, 36))
>Car : Symbol(Car, Decl(mappedTypeAsClauses.ts, 57, 21))

View File

@ -120,3 +120,53 @@ const e2: T2 = "foo";
>e2 : "foo"
>"foo" : "foo"
// Repro from #41133
interface Car {
name: string;
>name : string
seats: number;
>seats : number
engine: Engine;
>engine : Engine
wheels: Wheel[];
>wheels : Wheel[]
}
interface Engine {
manufacturer: string;
>manufacturer : string
horsepower: number;
>horsepower : number
}
interface Wheel {
type: "summer" | "winter";
>type : "summer" | "winter"
radius: number;
>radius : number
}
type Primitive = string | number | boolean;
>Primitive : Primitive
type OnlyPrimitives<T> = { [K in keyof T as T[K] extends Primitive ? K : never]: T[K] };
>OnlyPrimitives : OnlyPrimitives<T>
let primitiveCar: OnlyPrimitives<Car>; // { name: string; seats: number; }
>primitiveCar : OnlyPrimitives<Car>
let keys: keyof OnlyPrimitives<Car>; // "name" | "seats"
>keys : "name" | "seats"
type KeysOfPrimitives<T> = keyof OnlyPrimitives<T>;
>KeysOfPrimitives : keyof OnlyPrimitives<T>
let carKeys: KeysOfPrimitives<Car>; // "name" | "seats"
>carKeys : "name" | "seats"

View File

@ -59,3 +59,32 @@ const e1: T1 = {
};
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"