mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-27 23:58:38 -06:00
Check nested conditional types for non-distributiveness in mapped types with 'as' clauses (#41713)
* Check nested conditional types for non-distributiveness * Rename to maybeNonDistributiveNameType * Add regression test
This commit is contained in:
parent
4782c74b75
commit
4d6947ae14
@ -13547,17 +13547,17 @@ 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 {
|
||||
// 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 && !(<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)));
|
||||
type.flags & TypeFlags.Conditional && (!(<ConditionalType>type).root.isDistributive || maybeNonDistributiveNameType((<ConditionalType>type).checkType)) ||
|
||||
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && some((<UnionOrIntersectionType | TemplateLiteralType>type).types, maybeNonDistributiveNameType) ||
|
||||
type.flags & (TypeFlags.Index | TypeFlags.StringMapping) && maybeNonDistributiveNameType((<IndexType | StringMappingType>type).type) ||
|
||||
type.flags & TypeFlags.IndexedAccess && maybeNonDistributiveNameType((<IndexedAccessType>type).indexType) ||
|
||||
type.flags & TypeFlags.Substitution && maybeNonDistributiveNameType((<SubstitutionType>type).substitute)));
|
||||
}
|
||||
|
||||
function getLiteralTypeFromPropertyName(name: PropertyName) {
|
||||
@ -13607,7 +13607,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) || isGenericMappedType(type) && isNonDistributiveNameType(getNameTypeFromMappedType(type)) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, stringsOnly) :
|
||||
type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && maybeNonDistributiveNameType(getNameTypeFromMappedType(type)) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, stringsOnly) :
|
||||
getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(<MappedType>type, noIndexSignatures) :
|
||||
type === wildcardType ? wildcardType :
|
||||
type.flags & TypeFlags.Unknown ? neverType :
|
||||
|
||||
@ -86,6 +86,34 @@ 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"
|
||||
|
||||
|
||||
//// [mappedTypeAsClauses.js]
|
||||
@ -189,3 +217,27 @@ declare let primitiveCar: OnlyPrimitives<Car>;
|
||||
declare let keys: keyof OnlyPrimitives<Car>;
|
||||
declare type KeysOfPrimitives<T> = keyof OnlyPrimitives<T>;
|
||||
declare let carKeys: KeysOfPrimitives<Car>;
|
||||
declare type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
|
||||
declare type If<Cond extends boolean, Then, Else> = Cond extends true ? Then : Else;
|
||||
declare type GetKey<S, V> = keyof {
|
||||
[TP in keyof S as Equal<S[TP], V> extends true ? TP : never]: any;
|
||||
};
|
||||
declare type GetKeyWithIf<S, V> = keyof {
|
||||
[TP in keyof S as If<Equal<S[TP], V>, TP, never>]: any;
|
||||
};
|
||||
declare type GetObjWithIf<S, V> = {
|
||||
[TP in keyof S as If<Equal<S[TP], V>, TP, never>]: any;
|
||||
};
|
||||
declare type Task = {
|
||||
isDone: boolean;
|
||||
};
|
||||
declare type Schema = {
|
||||
root: {
|
||||
title: string;
|
||||
task: Task;
|
||||
};
|
||||
Task: Task;
|
||||
};
|
||||
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']>;
|
||||
|
||||
@ -264,3 +264,108 @@ let carKeys: KeysOfPrimitives<Car>; // "name" | "seats"
|
||||
>KeysOfPrimitives : Symbol(KeysOfPrimitives, Decl(mappedTypeAsClauses.ts, 82, 36))
|
||||
>Car : Symbol(Car, Decl(mappedTypeAsClauses.ts, 57, 21))
|
||||
|
||||
// Repro from #41453
|
||||
|
||||
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
|
||||
>Equal : Symbol(Equal, Decl(mappedTypeAsClauses.ts, 86, 35))
|
||||
>A : Symbol(A, Decl(mappedTypeAsClauses.ts, 90, 11))
|
||||
>B : Symbol(B, Decl(mappedTypeAsClauses.ts, 90, 13))
|
||||
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 90, 21))
|
||||
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 90, 21))
|
||||
>A : Symbol(A, Decl(mappedTypeAsClauses.ts, 90, 11))
|
||||
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 90, 60))
|
||||
>T : Symbol(T, Decl(mappedTypeAsClauses.ts, 90, 60))
|
||||
>B : Symbol(B, Decl(mappedTypeAsClauses.ts, 90, 13))
|
||||
|
||||
type If<Cond extends boolean, Then, Else> = Cond extends true ? Then : Else;
|
||||
>If : Symbol(If, Decl(mappedTypeAsClauses.ts, 90, 104))
|
||||
>Cond : Symbol(Cond, Decl(mappedTypeAsClauses.ts, 92, 8))
|
||||
>Then : Symbol(Then, Decl(mappedTypeAsClauses.ts, 92, 29))
|
||||
>Else : Symbol(Else, Decl(mappedTypeAsClauses.ts, 92, 35))
|
||||
>Cond : Symbol(Cond, Decl(mappedTypeAsClauses.ts, 92, 8))
|
||||
>Then : Symbol(Then, Decl(mappedTypeAsClauses.ts, 92, 29))
|
||||
>Else : Symbol(Else, Decl(mappedTypeAsClauses.ts, 92, 35))
|
||||
|
||||
type GetKey<S, V> = keyof { [TP in keyof S as Equal<S[TP], V> extends true ? TP : never]: any };
|
||||
>GetKey : Symbol(GetKey, Decl(mappedTypeAsClauses.ts, 92, 76))
|
||||
>S : Symbol(S, Decl(mappedTypeAsClauses.ts, 94, 12))
|
||||
>V : Symbol(V, Decl(mappedTypeAsClauses.ts, 94, 14))
|
||||
>TP : Symbol(TP, Decl(mappedTypeAsClauses.ts, 94, 29))
|
||||
>S : Symbol(S, Decl(mappedTypeAsClauses.ts, 94, 12))
|
||||
>Equal : Symbol(Equal, Decl(mappedTypeAsClauses.ts, 86, 35))
|
||||
>S : Symbol(S, Decl(mappedTypeAsClauses.ts, 94, 12))
|
||||
>TP : Symbol(TP, Decl(mappedTypeAsClauses.ts, 94, 29))
|
||||
>V : Symbol(V, Decl(mappedTypeAsClauses.ts, 94, 14))
|
||||
>TP : Symbol(TP, Decl(mappedTypeAsClauses.ts, 94, 29))
|
||||
|
||||
type GetKeyWithIf<S, V> = keyof { [TP in keyof S as If<Equal<S[TP], V>, TP, never>]: any };
|
||||
>GetKeyWithIf : Symbol(GetKeyWithIf, Decl(mappedTypeAsClauses.ts, 94, 96))
|
||||
>S : Symbol(S, Decl(mappedTypeAsClauses.ts, 96, 18))
|
||||
>V : Symbol(V, Decl(mappedTypeAsClauses.ts, 96, 20))
|
||||
>TP : Symbol(TP, Decl(mappedTypeAsClauses.ts, 96, 35))
|
||||
>S : Symbol(S, Decl(mappedTypeAsClauses.ts, 96, 18))
|
||||
>If : Symbol(If, Decl(mappedTypeAsClauses.ts, 90, 104))
|
||||
>Equal : Symbol(Equal, Decl(mappedTypeAsClauses.ts, 86, 35))
|
||||
>S : Symbol(S, Decl(mappedTypeAsClauses.ts, 96, 18))
|
||||
>TP : Symbol(TP, Decl(mappedTypeAsClauses.ts, 96, 35))
|
||||
>V : Symbol(V, Decl(mappedTypeAsClauses.ts, 96, 20))
|
||||
>TP : Symbol(TP, Decl(mappedTypeAsClauses.ts, 96, 35))
|
||||
|
||||
type GetObjWithIf<S, V> = { [TP in keyof S as If<Equal<S[TP], V>, TP, never>]: any };
|
||||
>GetObjWithIf : Symbol(GetObjWithIf, Decl(mappedTypeAsClauses.ts, 96, 91))
|
||||
>S : Symbol(S, Decl(mappedTypeAsClauses.ts, 98, 18))
|
||||
>V : Symbol(V, Decl(mappedTypeAsClauses.ts, 98, 20))
|
||||
>TP : Symbol(TP, Decl(mappedTypeAsClauses.ts, 98, 29))
|
||||
>S : Symbol(S, Decl(mappedTypeAsClauses.ts, 98, 18))
|
||||
>If : Symbol(If, Decl(mappedTypeAsClauses.ts, 90, 104))
|
||||
>Equal : Symbol(Equal, Decl(mappedTypeAsClauses.ts, 86, 35))
|
||||
>S : Symbol(S, Decl(mappedTypeAsClauses.ts, 98, 18))
|
||||
>TP : Symbol(TP, Decl(mappedTypeAsClauses.ts, 98, 29))
|
||||
>V : Symbol(V, Decl(mappedTypeAsClauses.ts, 98, 20))
|
||||
>TP : Symbol(TP, Decl(mappedTypeAsClauses.ts, 98, 29))
|
||||
|
||||
type Task = {
|
||||
>Task : Symbol(Task, Decl(mappedTypeAsClauses.ts, 98, 85))
|
||||
|
||||
isDone: boolean;
|
||||
>isDone : Symbol(isDone, Decl(mappedTypeAsClauses.ts, 100, 13))
|
||||
|
||||
};
|
||||
|
||||
type Schema = {
|
||||
>Schema : Symbol(Schema, Decl(mappedTypeAsClauses.ts, 102, 2))
|
||||
|
||||
root: {
|
||||
>root : Symbol(root, Decl(mappedTypeAsClauses.ts, 104, 15))
|
||||
|
||||
title: string;
|
||||
>title : Symbol(title, Decl(mappedTypeAsClauses.ts, 105, 9))
|
||||
|
||||
task: Task;
|
||||
>task : Symbol(task, Decl(mappedTypeAsClauses.ts, 106, 18))
|
||||
>Task : Symbol(Task, Decl(mappedTypeAsClauses.ts, 98, 85))
|
||||
}
|
||||
Task: Task;
|
||||
>Task : Symbol(Task, Decl(mappedTypeAsClauses.ts, 108, 3))
|
||||
>Task : Symbol(Task, Decl(mappedTypeAsClauses.ts, 98, 85))
|
||||
|
||||
};
|
||||
|
||||
type Res1 = GetKey<Schema, Schema['root']['task']>; // "Task"
|
||||
>Res1 : Symbol(Res1, Decl(mappedTypeAsClauses.ts, 110, 2))
|
||||
>GetKey : Symbol(GetKey, Decl(mappedTypeAsClauses.ts, 92, 76))
|
||||
>Schema : Symbol(Schema, Decl(mappedTypeAsClauses.ts, 102, 2))
|
||||
>Schema : Symbol(Schema, Decl(mappedTypeAsClauses.ts, 102, 2))
|
||||
|
||||
type Res2 = GetKeyWithIf<Schema, Schema['root']['task']>; // "Task"
|
||||
>Res2 : Symbol(Res2, Decl(mappedTypeAsClauses.ts, 112, 51))
|
||||
>GetKeyWithIf : Symbol(GetKeyWithIf, Decl(mappedTypeAsClauses.ts, 94, 96))
|
||||
>Schema : Symbol(Schema, Decl(mappedTypeAsClauses.ts, 102, 2))
|
||||
>Schema : Symbol(Schema, Decl(mappedTypeAsClauses.ts, 102, 2))
|
||||
|
||||
type Res3 = keyof GetObjWithIf<Schema, Schema['root']['task']>; // "Task"
|
||||
>Res3 : Symbol(Res3, Decl(mappedTypeAsClauses.ts, 113, 57))
|
||||
>GetObjWithIf : Symbol(GetObjWithIf, Decl(mappedTypeAsClauses.ts, 96, 91))
|
||||
>Schema : Symbol(Schema, Decl(mappedTypeAsClauses.ts, 102, 2))
|
||||
>Schema : Symbol(Schema, Decl(mappedTypeAsClauses.ts, 102, 2))
|
||||
|
||||
|
||||
@ -170,3 +170,58 @@ type KeysOfPrimitives<T> = keyof OnlyPrimitives<T>;
|
||||
let carKeys: KeysOfPrimitives<Car>; // "name" | "seats"
|
||||
>carKeys : "name" | "seats"
|
||||
|
||||
// Repro from #41453
|
||||
|
||||
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
|
||||
>Equal : Equal<A, B>
|
||||
>true : true
|
||||
>false : false
|
||||
|
||||
type If<Cond extends boolean, Then, Else> = Cond extends true ? Then : Else;
|
||||
>If : If<Cond, Then, Else>
|
||||
>true : true
|
||||
|
||||
type GetKey<S, V> = keyof { [TP in keyof S as Equal<S[TP], V> extends true ? TP : never]: any };
|
||||
>GetKey : keyof { [TP in keyof S as Equal<S[TP], V> extends true ? TP : never]: any; }
|
||||
>true : true
|
||||
|
||||
type GetKeyWithIf<S, V> = keyof { [TP in keyof S as If<Equal<S[TP], V>, TP, never>]: any };
|
||||
>GetKeyWithIf : 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 };
|
||||
>GetObjWithIf : GetObjWithIf<S, V>
|
||||
|
||||
type Task = {
|
||||
>Task : Task
|
||||
|
||||
isDone: boolean;
|
||||
>isDone : boolean
|
||||
|
||||
};
|
||||
|
||||
type Schema = {
|
||||
>Schema : Schema
|
||||
|
||||
root: {
|
||||
>root : { title: string; task: Task; }
|
||||
|
||||
title: string;
|
||||
>title : string
|
||||
|
||||
task: Task;
|
||||
>task : Task
|
||||
}
|
||||
Task: Task;
|
||||
>Task : Task
|
||||
|
||||
};
|
||||
|
||||
type Res1 = GetKey<Schema, Schema['root']['task']>; // "Task"
|
||||
>Res1 : "Task"
|
||||
|
||||
type Res2 = GetKeyWithIf<Schema, Schema['root']['task']>; // "Task"
|
||||
>Res2 : "Task"
|
||||
|
||||
type Res3 = keyof GetObjWithIf<Schema, Schema['root']['task']>; // "Task"
|
||||
>Res3 : "Task"
|
||||
|
||||
|
||||
@ -88,3 +88,31 @@ 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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user