| N. 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]: X }, to simply N. This however presumes
+ // that N distributes over union types, i.e. that N is equivalent to N | N | N. 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).root.isDistributive ||
- type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && some((type).types, isNonDistributiveNameType) ||
- type.flags & (TypeFlags.Index | TypeFlags.StringMapping) && isNonDistributiveNameType((type).type) ||
- type.flags & TypeFlags.IndexedAccess && isNonDistributiveNameType((type).indexType) ||
- type.flags & TypeFlags.Substitution && isNonDistributiveNameType((type).substitute)));
+ type.flags & TypeFlags.Conditional && (!(type).root.isDistributive || maybeNonDistributiveNameType((type).checkType)) ||
+ type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && some((type).types, maybeNonDistributiveNameType) ||
+ type.flags & (TypeFlags.Index | TypeFlags.StringMapping) && maybeNonDistributiveNameType((type).type) ||
+ type.flags & TypeFlags.IndexedAccess && maybeNonDistributiveNameType((type).indexType) ||
+ type.flags & TypeFlags.Substitution && maybeNonDistributiveNameType((type).substitute)));
}
function getLiteralTypeFromPropertyName(name: PropertyName) {
@@ -13607,7 +13607,7 @@ namespace ts {
type = getReducedType(type);
return type.flags & TypeFlags.Union ? getIntersectionType(map((type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.Intersection ? getUnionType(map((type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
- type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && isNonDistributiveNameType(getNameTypeFromMappedType(type)) ? getIndexTypeForGenericType(type, stringsOnly) :
+ type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && maybeNonDistributiveNameType(getNameTypeFromMappedType(type)) ? getIndexTypeForGenericType(type, stringsOnly) :
getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type, noIndexSignatures) :
type === wildcardType ? wildcardType :
type.flags & TypeFlags.Unknown ? neverType :
diff --git a/tests/baselines/reference/mappedTypeAsClauses.js b/tests/baselines/reference/mappedTypeAsClauses.js
index 1940283e299..e5e15d8e6b2 100644
--- a/tests/baselines/reference/mappedTypeAsClauses.js
+++ b/tests/baselines/reference/mappedTypeAsClauses.js
@@ -86,6 +86,34 @@ let keys: keyof OnlyPrimitives; // "name" | "seats"
type KeysOfPrimitives = keyof OnlyPrimitives;
let carKeys: KeysOfPrimitives; // "name" | "seats"
+
+// Repro from #41453
+
+type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false;
+
+type If = Cond extends true ? Then : Else;
+
+type GetKey = keyof { [TP in keyof S as Equal extends true ? TP : never]: any };
+
+type GetKeyWithIf = keyof { [TP in keyof S as If, TP, never>]: any };
+
+type GetObjWithIf = { [TP in keyof S as If, TP, never>]: any };
+
+type Task = {
+ isDone: boolean;
+};
+
+type Schema = {
+ root: {
+ title: string;
+ task: Task;
+ }
+ Task: Task;
+};
+
+type Res1 = GetKey; // "Task"
+type Res2 = GetKeyWithIf; // "Task"
+type Res3 = keyof GetObjWithIf; // "Task"
//// [mappedTypeAsClauses.js]
@@ -189,3 +217,27 @@ declare let primitiveCar: OnlyPrimitives;
declare let keys: keyof OnlyPrimitives;
declare type KeysOfPrimitives = keyof OnlyPrimitives;
declare let carKeys: KeysOfPrimitives;
+declare type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false;
+declare type If = Cond extends true ? Then : Else;
+declare type GetKey = keyof {
+ [TP in keyof S as Equal extends true ? TP : never]: any;
+};
+declare type GetKeyWithIf = keyof {
+ [TP in keyof S as If, TP, never>]: any;
+};
+declare type GetObjWithIf = {
+ [TP in keyof S as If, TP, never>]: any;
+};
+declare type Task = {
+ isDone: boolean;
+};
+declare type Schema = {
+ root: {
+ title: string;
+ task: Task;
+ };
+ Task: Task;
+};
+declare type Res1 = GetKey;
+declare type Res2 = GetKeyWithIf;
+declare type Res3 = keyof GetObjWithIf;
diff --git a/tests/baselines/reference/mappedTypeAsClauses.symbols b/tests/baselines/reference/mappedTypeAsClauses.symbols
index 77ca6856b10..1de577a5fd5 100644
--- a/tests/baselines/reference/mappedTypeAsClauses.symbols
+++ b/tests/baselines/reference/mappedTypeAsClauses.symbols
@@ -264,3 +264,108 @@ let carKeys: KeysOfPrimitives; // "name" | "seats"
>KeysOfPrimitives : Symbol(KeysOfPrimitives, Decl(mappedTypeAsClauses.ts, 82, 36))
>Car : Symbol(Car, Decl(mappedTypeAsClauses.ts, 57, 21))
+// Repro from #41453
+
+type Equal = (() => T extends A ? 1 : 2) extends (() => 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 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 = keyof { [TP in keyof S as Equal 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 = keyof { [TP in keyof S as If, 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 = { [TP in keyof S as If, 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; // "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; // "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; // "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))
+
diff --git a/tests/baselines/reference/mappedTypeAsClauses.types b/tests/baselines/reference/mappedTypeAsClauses.types
index 5ada8893a6f..55bb1c780d0 100644
--- a/tests/baselines/reference/mappedTypeAsClauses.types
+++ b/tests/baselines/reference/mappedTypeAsClauses.types
@@ -170,3 +170,58 @@ type KeysOfPrimitives = keyof OnlyPrimitives;
let carKeys: KeysOfPrimitives; // "name" | "seats"
>carKeys : "name" | "seats"
+// Repro from #41453
+
+type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false;
+>Equal : Equal
+>true : true
+>false : false
+
+type If = Cond extends true ? Then : Else;
+>If : If
+>true : true
+
+type GetKey = keyof { [TP in keyof S as Equal extends true ? TP : never]: any };
+>GetKey : keyof { [TP in keyof S as Equal extends true ? TP : never]: any; }
+>true : true
+
+type GetKeyWithIf = keyof { [TP in keyof S as If, TP, never>]: any };
+>GetKeyWithIf : keyof { [TP in keyof S as If, TP, never>]: any; }
+
+type GetObjWithIf = { [TP in keyof S as If, TP, never>]: any };
+>GetObjWithIf : GetObjWithIf
+
+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; // "Task"
+>Res1 : "Task"
+
+type Res2 = GetKeyWithIf; // "Task"
+>Res2 : "Task"
+
+type Res3 = keyof GetObjWithIf; // "Task"
+>Res3 : "Task"
+
diff --git a/tests/cases/conformance/types/mapped/mappedTypeAsClauses.ts b/tests/cases/conformance/types/mapped/mappedTypeAsClauses.ts
index 747307f3652..6c13786d81d 100644
--- a/tests/cases/conformance/types/mapped/mappedTypeAsClauses.ts
+++ b/tests/cases/conformance/types/mapped/mappedTypeAsClauses.ts
@@ -88,3 +88,31 @@ let keys: keyof OnlyPrimitives; // "name" | "seats"
type KeysOfPrimitives = keyof OnlyPrimitives;
let carKeys: KeysOfPrimitives; // "name" | "seats"
+
+// Repro from #41453
+
+type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false;
+
+type If = Cond extends true ? Then : Else;
+
+type GetKey = keyof { [TP in keyof S as Equal extends true ? TP : never]: any };
+
+type GetKeyWithIf = keyof { [TP in keyof S as If, TP, never>]: any };
+
+type GetObjWithIf = { [TP in keyof S as If, TP, never>]: any };
+
+type Task = {
+ isDone: boolean;
+};
+
+type Schema = {
+ root: {
+ title: string;
+ task: Task;
+ }
+ Task: Task;
+};
+
+type Res1 = GetKey; // "Task"
+type Res2 = GetKeyWithIf; // "Task"
+type Res3 = keyof GetObjWithIf; // "Task"