From 17676ac0e2b946637a7fe030148402e35d03cbd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 20 Aug 2026 12:52:28 +0200 Subject: [PATCH 1/2] Fixed mapped types not being considered as homomorphic with substitution constraints --- tsc/internal/checker/checker.go | 2 +- ...dTypeHomomorphismWithPriorKeyofTChecks1.js | 65 +++++++ ...HomomorphismWithPriorKeyofTChecks1.symbols | 158 ++++++++++++++++++ ...peHomomorphismWithPriorKeyofTChecks1.types | 97 +++++++++++ ...appedTypeNotMistakenlyHomomorphic2.symbols | 39 +++++ .../mappedTypeNotMistakenlyHomomorphic2.types | 24 +++ ...dTypeHomomorphismWithPriorKeyofTChecks1.ts | 58 +++++++ .../mappedTypeNotMistakenlyHomomorphic2.ts | 11 ++ 8 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.js create mode 100644 tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.types create mode 100644 tsc/testdata/baselines/reference/compiler/mappedTypeNotMistakenlyHomomorphic2.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/mappedTypeNotMistakenlyHomomorphic2.types create mode 100644 tsc/testdata/tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts create mode 100644 tsc/testdata/tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic2.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 2a4ead5003a89..3fad1e67792ee 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -22636,7 +22636,7 @@ func (c *Checker) cloneTypeParameter(tp *Type) *Type { } func (c *Checker) getHomomorphicTypeVariable(t *Type) *Type { - constraintType := c.getConstraintTypeFromMappedType(t) + constraintType := c.getActualTypeVariable(c.getConstraintTypeFromMappedType(t)) if constraintType.flags&TypeFlagsIndex != 0 { typeVariable := c.getActualTypeVariable(constraintType.AsIndexType().target) if typeVariable.flags&TypeFlagsTypeParameter != 0 { diff --git a/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.js b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.js new file mode 100644 index 0000000000000..f25dfb1596797 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.js @@ -0,0 +1,65 @@ +//// [tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts] //// + +//// [mappedTypeHomomorphismWithPriorKeyofTChecks1.ts] +type SharedUnionFieldsDeep1 = [T] extends [unknown[]] + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] + ? [T[0]] + : T + : keyof T extends infer CommonKeys + ? ([CommonKeys] extends [never] ? true : false) extends false + ? { + [Key in keyof T]: SharedUnionFieldsDeep1; + } + : {} + : T; + +// distributes +declare const actual1: SharedUnionFieldsDeep1< + { tuple: [number] } | { tuple: [number, string] } +>; + +type SharedUnionFieldsDeep2 = [T] extends [unknown[]] + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] + ? [T[0]] + : T + : keyof T extends infer CommonKeys + ? ([CommonKeys] extends [never] ? true : false) extends false + ? keyof T extends infer Keys extends PropertyKey + ? { + [Key in Keys]: SharedUnionFieldsDeep2; + } + : never + : {} + : T; + +// doesn't distribute +declare const actual2: SharedUnionFieldsDeep2< + { tuple: [number] } | { tuple: [number, string] } +>; + +// `& unknown` used here just to avoid printing type alias printing at actual3 +type SharedUnionFieldsDeepMappedType3 = { + [Key in K]: SharedUnionFieldsDeep3; +} & unknown; + +type SharedUnionFieldsDeep3 = [T] extends [unknown[]] + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] + ? [T[0]] + : T + : keyof T extends infer CommonKeys + ? ([CommonKeys] extends [never] ? true : false) extends false + ? SharedUnionFieldsDeepMappedType3 + : {} + : T; + +// doesn't distribute either +declare const actual3: SharedUnionFieldsDeep3< + { tuple: [number] } | { tuple: [number, string] } +>; + + +//// [mappedTypeHomomorphismWithPriorKeyofTChecks1.js] +"use strict"; diff --git a/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.symbols b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.symbols new file mode 100644 index 0000000000000..58f98f8ecd1ff --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.symbols @@ -0,0 +1,158 @@ +//// [tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts] //// + +=== mappedTypeHomomorphismWithPriorKeyofTChecks1.ts === +type SharedUnionFieldsDeep1 = [T] extends [unknown[]] +>SharedUnionFieldsDeep1 : Symbol(SharedUnionFieldsDeep1, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 28)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 28)) + + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 28)) + + ? [T[0]] +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 28)) + + : T +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 28)) + + : keyof T extends infer CommonKeys +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 28)) +>CommonKeys : Symbol(CommonKeys, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 5, 25)) + + ? ([CommonKeys] extends [never] ? true : false) extends false +>CommonKeys : Symbol(CommonKeys, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 5, 25)) + + ? { + [Key in keyof T]: SharedUnionFieldsDeep1; +>Key : Symbol(Key, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 8, 9)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 28)) +>SharedUnionFieldsDeep1 : Symbol(SharedUnionFieldsDeep1, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 28)) +>Key : Symbol(Key, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 8, 9)) + } + : {} + : T; +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 28)) + +// distributes +declare const actual1: SharedUnionFieldsDeep1< +>actual1 : Symbol(actual1, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 14, 13)) +>SharedUnionFieldsDeep1 : Symbol(SharedUnionFieldsDeep1, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 0, 0)) + + { tuple: [number] } | { tuple: [number, string] } +>tuple : Symbol(tuple, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 15, 3)) +>tuple : Symbol(tuple, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 15, 25)) + +>; + +type SharedUnionFieldsDeep2 = [T] extends [unknown[]] +>SharedUnionFieldsDeep2 : Symbol(SharedUnionFieldsDeep2, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 16, 2)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 18, 28)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 18, 28)) + + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 18, 28)) + + ? [T[0]] +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 18, 28)) + + : T +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 18, 28)) + + : keyof T extends infer CommonKeys +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 18, 28)) +>CommonKeys : Symbol(CommonKeys, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 23, 25)) + + ? ([CommonKeys] extends [never] ? true : false) extends false +>CommonKeys : Symbol(CommonKeys, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 23, 25)) + + ? keyof T extends infer Keys extends PropertyKey +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 18, 28)) +>Keys : Symbol(Keys, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 25, 27)) +>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --)) + + ? { + [Key in Keys]: SharedUnionFieldsDeep2; +>Key : Symbol(Key, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 27, 11)) +>Keys : Symbol(Keys, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 25, 27)) +>SharedUnionFieldsDeep2 : Symbol(SharedUnionFieldsDeep2, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 16, 2)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 18, 28)) +>Key : Symbol(Key, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 27, 11)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 18, 28)) + } + : never + : {} + : T; +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 18, 28)) + +// doesn't distribute +declare const actual2: SharedUnionFieldsDeep2< +>actual2 : Symbol(actual2, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 34, 13)) +>SharedUnionFieldsDeep2 : Symbol(SharedUnionFieldsDeep2, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 16, 2)) + + { tuple: [number] } | { tuple: [number, string] } +>tuple : Symbol(tuple, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 35, 3)) +>tuple : Symbol(tuple, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 35, 25)) + +>; + +// `& unknown` used here just to avoid printing type alias printing at actual3 +type SharedUnionFieldsDeepMappedType3 = { +>SharedUnionFieldsDeepMappedType3 : Symbol(SharedUnionFieldsDeepMappedType3, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 36, 2)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 39, 38)) +>K : Symbol(K, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 39, 40)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 39, 38)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 39, 38)) + + [Key in K]: SharedUnionFieldsDeep3; +>Key : Symbol(Key, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 40, 3)) +>K : Symbol(K, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 39, 40)) +>SharedUnionFieldsDeep3 : Symbol(SharedUnionFieldsDeep3, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 41, 12)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 39, 38)) +>Key : Symbol(Key, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 40, 3)) + +} & unknown; + +type SharedUnionFieldsDeep3 = [T] extends [unknown[]] +>SharedUnionFieldsDeep3 : Symbol(SharedUnionFieldsDeep3, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 41, 12)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 43, 28)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 43, 28)) + + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 43, 28)) + + ? [T[0]] +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 43, 28)) + + : T +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 43, 28)) + + : keyof T extends infer CommonKeys +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 43, 28)) +>CommonKeys : Symbol(CommonKeys, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 48, 25)) + + ? ([CommonKeys] extends [never] ? true : false) extends false +>CommonKeys : Symbol(CommonKeys, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 48, 25)) + + ? SharedUnionFieldsDeepMappedType3 +>SharedUnionFieldsDeepMappedType3 : Symbol(SharedUnionFieldsDeepMappedType3, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 36, 2)) +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 43, 28)) + + : {} + : T; +>T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 43, 28)) + +// doesn't distribute either +declare const actual3: SharedUnionFieldsDeep3< +>actual3 : Symbol(actual3, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 55, 13)) +>SharedUnionFieldsDeep3 : Symbol(SharedUnionFieldsDeep3, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 41, 12)) + + { tuple: [number] } | { tuple: [number, string] } +>tuple : Symbol(tuple, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 56, 3)) +>tuple : Symbol(tuple, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 56, 25)) + +>; + diff --git a/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.types b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.types new file mode 100644 index 0000000000000..4665a5bd22bb9 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.types @@ -0,0 +1,97 @@ +//// [tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts] //// + +=== mappedTypeHomomorphismWithPriorKeyofTChecks1.ts === +type SharedUnionFieldsDeep1 = [T] extends [unknown[]] +>SharedUnionFieldsDeep1 : SharedUnionFieldsDeep1 + + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] + ? [T[0]] + : T + : keyof T extends infer CommonKeys + ? ([CommonKeys] extends [never] ? true : false) extends false +>true : true +>false : false +>false : false + + ? { + [Key in keyof T]: SharedUnionFieldsDeep1; + } + : {} + : T; + +// distributes +declare const actual1: SharedUnionFieldsDeep1< +>actual1 : { tuple: [number]; } | { tuple: [number, string]; } + + { tuple: [number] } | { tuple: [number, string] } +>tuple : [number] +>tuple : [number, string] + +>; + +type SharedUnionFieldsDeep2 = [T] extends [unknown[]] +>SharedUnionFieldsDeep2 : SharedUnionFieldsDeep2 + + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] + ? [T[0]] + : T + : keyof T extends infer CommonKeys + ? ([CommonKeys] extends [never] ? true : false) extends false +>true : true +>false : false +>false : false + + ? keyof T extends infer Keys extends PropertyKey + ? { + [Key in Keys]: SharedUnionFieldsDeep2; + } + : never + : {} + : T; + +// doesn't distribute +declare const actual2: SharedUnionFieldsDeep2< +>actual2 : { tuple: [number]; } + + { tuple: [number] } | { tuple: [number, string] } +>tuple : [number] +>tuple : [number, string] + +>; + +// `& unknown` used here just to avoid printing type alias printing at actual3 +type SharedUnionFieldsDeepMappedType3 = { +>SharedUnionFieldsDeepMappedType3 : { [Key in K]: SharedUnionFieldsDeep3; } + + [Key in K]: SharedUnionFieldsDeep3; +} & unknown; + +type SharedUnionFieldsDeep3 = [T] extends [unknown[]] +>SharedUnionFieldsDeep3 : SharedUnionFieldsDeep3 + + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] + ? [T[0]] + : T + : keyof T extends infer CommonKeys + ? ([CommonKeys] extends [never] ? true : false) extends false +>true : true +>false : false +>false : false + + ? SharedUnionFieldsDeepMappedType3 + : {} + : T; + +// doesn't distribute either +declare const actual3: SharedUnionFieldsDeep3< +>actual3 : { tuple: [number]; } + + { tuple: [number] } | { tuple: [number, string] } +>tuple : [number] +>tuple : [number, string] + +>; + diff --git a/tsc/testdata/baselines/reference/compiler/mappedTypeNotMistakenlyHomomorphic2.symbols b/tsc/testdata/baselines/reference/compiler/mappedTypeNotMistakenlyHomomorphic2.symbols new file mode 100644 index 0000000000000..f863319b058b7 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/mappedTypeNotMistakenlyHomomorphic2.symbols @@ -0,0 +1,39 @@ +//// [tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic2.ts] //// + +=== mappedTypeNotMistakenlyHomomorphic2.ts === +// https://github.com/microsoft/TypeScript/issues/63132 + +type KeyMap = keyof T extends PropertyKey ? { [K in keyof T]: K } : never; +>KeyMap : Symbol(KeyMap, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 2, 12)) +>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 2, 12)) +>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --)) +>K : Symbol(K, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 2, 50)) +>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 2, 12)) +>K : Symbol(K, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 2, 50)) + +type X = KeyMap; +>X : Symbol(X, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 2, 77)) +>KeyMap : Symbol(KeyMap, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 0, 0)) + +type U = { a?: number; b: string } | { b: string; readonly c: boolean }; +>U : Symbol(U, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 3, 24)) +>a : Symbol(a, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 5, 10)) +>b : Symbol(b, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 5, 22)) +>b : Symbol(b, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 5, 38)) +>c : Symbol(c, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 5, 49)) + +type MapStringOnly = keyof T extends string ? { [K in keyof T]: [T[K]] } : never; +>MapStringOnly : Symbol(MapStringOnly, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 5, 72)) +>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 6, 19)) +>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 6, 19)) +>K : Symbol(K, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 6, 52)) +>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 6, 19)) +>T : Symbol(T, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 6, 19)) +>K : Symbol(K, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 6, 52)) + +type Y = MapStringOnly; +>Y : Symbol(Y, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 6, 84)) +>MapStringOnly : Symbol(MapStringOnly, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 5, 72)) +>U : Symbol(U, Decl(mappedTypeNotMistakenlyHomomorphic2.ts, 3, 24)) + diff --git a/tsc/testdata/baselines/reference/compiler/mappedTypeNotMistakenlyHomomorphic2.types b/tsc/testdata/baselines/reference/compiler/mappedTypeNotMistakenlyHomomorphic2.types new file mode 100644 index 0000000000000..6cccb9bf1ca67 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/mappedTypeNotMistakenlyHomomorphic2.types @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic2.ts] //// + +=== mappedTypeNotMistakenlyHomomorphic2.ts === +// https://github.com/microsoft/TypeScript/issues/63132 + +type KeyMap = keyof T extends PropertyKey ? { [K in keyof T]: K } : never; +>KeyMap : KeyMap + +type X = KeyMap; +>X : number + +type U = { a?: number; b: string } | { b: string; readonly c: boolean }; +>U : U +>a : number | undefined +>b : string +>b : string +>c : boolean + +type MapStringOnly = keyof T extends string ? { [K in keyof T]: [T[K]] } : never; +>MapStringOnly : MapStringOnly + +type Y = MapStringOnly; +>Y : { a?: [number | undefined] | undefined; b: [string]; } | { b: [string]; readonly c: [boolean]; } + diff --git a/tsc/testdata/tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts b/tsc/testdata/tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts new file mode 100644 index 0000000000000..dbffb0991f2d5 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts @@ -0,0 +1,58 @@ +type SharedUnionFieldsDeep1 = [T] extends [unknown[]] + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] + ? [T[0]] + : T + : keyof T extends infer CommonKeys + ? ([CommonKeys] extends [never] ? true : false) extends false + ? { + [Key in keyof T]: SharedUnionFieldsDeep1; + } + : {} + : T; + +// distributes +declare const actual1: SharedUnionFieldsDeep1< + { tuple: [number] } | { tuple: [number, string] } +>; + +type SharedUnionFieldsDeep2 = [T] extends [unknown[]] + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] + ? [T[0]] + : T + : keyof T extends infer CommonKeys + ? ([CommonKeys] extends [never] ? true : false) extends false + ? keyof T extends infer Keys extends PropertyKey + ? { + [Key in Keys]: SharedUnionFieldsDeep2; + } + : never + : {} + : T; + +// doesn't distribute +declare const actual2: SharedUnionFieldsDeep2< + { tuple: [number] } | { tuple: [number, string] } +>; + +// `& unknown` used here just to avoid printing type alias printing at actual3 +type SharedUnionFieldsDeepMappedType3 = { + [Key in K]: SharedUnionFieldsDeep3; +} & unknown; + +type SharedUnionFieldsDeep3 = [T] extends [unknown[]] + ? // Keep only tuple positions guaranteed across the union. + 1 extends T["length"] + ? [T[0]] + : T + : keyof T extends infer CommonKeys + ? ([CommonKeys] extends [never] ? true : false) extends false + ? SharedUnionFieldsDeepMappedType3 + : {} + : T; + +// doesn't distribute either +declare const actual3: SharedUnionFieldsDeep3< + { tuple: [number] } | { tuple: [number, string] } +>; diff --git a/tsc/testdata/tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic2.ts b/tsc/testdata/tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic2.ts new file mode 100644 index 0000000000000..2e4a917153061 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/mappedTypeNotMistakenlyHomomorphic2.ts @@ -0,0 +1,11 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/63132 + +type KeyMap = keyof T extends PropertyKey ? { [K in keyof T]: K } : never; +type X = KeyMap; + +type U = { a?: number; b: string } | { b: string; readonly c: boolean }; +type MapStringOnly = keyof T extends string ? { [K in keyof T]: [T[K]] } : never; +type Y = MapStringOnly; From 222a5ce39e181c28be3bc11bf55908a052d10254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 20 Aug 2026 13:05:17 +0200 Subject: [PATCH 2/2] Fix mapped type test comment --- .../compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.js | 2 +- .../mappedTypeHomomorphismWithPriorKeyofTChecks1.symbols | 2 +- .../compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.types | 2 +- .../compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.js b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.js index f25dfb1596797..d7ab0979e3733 100644 --- a/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.js +++ b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.js @@ -39,7 +39,7 @@ declare const actual2: SharedUnionFieldsDeep2< { tuple: [number] } | { tuple: [number, string] } >; -// `& unknown` used here just to avoid printing type alias printing at actual3 +// `& unknown` used here just to avoid printing type alias at actual3 type SharedUnionFieldsDeepMappedType3 = { [Key in K]: SharedUnionFieldsDeep3; } & unknown; diff --git a/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.symbols b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.symbols index 58f98f8ecd1ff..71f97cca66b93 100644 --- a/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.symbols +++ b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.symbols @@ -98,7 +98,7 @@ declare const actual2: SharedUnionFieldsDeep2< >; -// `& unknown` used here just to avoid printing type alias printing at actual3 +// `& unknown` used here just to avoid printing type alias at actual3 type SharedUnionFieldsDeepMappedType3 = { >SharedUnionFieldsDeepMappedType3 : Symbol(SharedUnionFieldsDeepMappedType3, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 36, 2)) >T : Symbol(T, Decl(mappedTypeHomomorphismWithPriorKeyofTChecks1.ts, 39, 38)) diff --git a/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.types b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.types index 4665a5bd22bb9..dae46e92623d3 100644 --- a/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.types +++ b/tsc/testdata/baselines/reference/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.types @@ -61,7 +61,7 @@ declare const actual2: SharedUnionFieldsDeep2< >; -// `& unknown` used here just to avoid printing type alias printing at actual3 +// `& unknown` used here just to avoid printing type alias at actual3 type SharedUnionFieldsDeepMappedType3 = { >SharedUnionFieldsDeepMappedType3 : { [Key in K]: SharedUnionFieldsDeep3; } diff --git a/tsc/testdata/tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts b/tsc/testdata/tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts index dbffb0991f2d5..c279131823125 100644 --- a/tsc/testdata/tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts +++ b/tsc/testdata/tests/cases/compiler/mappedTypeHomomorphismWithPriorKeyofTChecks1.ts @@ -36,7 +36,7 @@ declare const actual2: SharedUnionFieldsDeep2< { tuple: [number] } | { tuple: [number, string] } >; -// `& unknown` used here just to avoid printing type alias printing at actual3 +// `& unknown` used here just to avoid printing type alias at actual3 type SharedUnionFieldsDeepMappedType3 = { [Key in K]: SharedUnionFieldsDeep3; } & unknown;