Skip to content

Commit 0fb5e3a

Browse files
authored
Preserve type parameter constraint in emitted mapped types while preserving their distributivity (#54759)
1 parent 7156300 commit 0fb5e3a

11 files changed

+528
-29
lines changed

src/compiler/checker.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -6963,6 +6963,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
69636963
const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) as QuestionToken | PlusToken | MinusToken : undefined;
69646964
let appropriateConstraintTypeNode: TypeNode;
69656965
let newTypeVariable: TypeReferenceNode | undefined;
6966+
let templateType = getTemplateTypeFromMappedType(type);
6967+
const typeParameter = getTypeParameterFromMappedType(type);
69666968
// If the mapped type isn't `keyof` constraint-declared, _but_ still has modifiers preserved, and its naive instantiation won't preserve modifiers because its constraint isn't `keyof` constrained, we have work to do
69676969
const needsModifierPreservingWrapper = !isMappedTypeWithKeyofConstraintDeclaration(type)
69686970
&& !(getModifiersTypeFromMappedType(type).flags & TypeFlags.Unknown)
@@ -6972,9 +6974,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
69726974
// We have a { [P in keyof T]: X }
69736975
// We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType`
69746976
if (isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) {
6975-
const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String));
6976-
const name = typeParameterToName(newParam, context);
6977+
const newConstraintParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String));
6978+
const name = typeParameterToName(newConstraintParam, context);
6979+
const target = type.target as MappedType;
69776980
newTypeVariable = factory.createTypeReferenceNode(name);
6981+
templateType = instantiateType(
6982+
getTemplateTypeFromMappedType(target),
6983+
makeArrayTypeMapper([getTypeParameterFromMappedType(target), getModifiersTypeFromMappedType(target)], [typeParameter, newConstraintParam]),
6984+
);
69786985
}
69796986
appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, newTypeVariable || typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context));
69806987
}
@@ -6989,9 +6996,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
69896996
else {
69906997
appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context);
69916998
}
6992-
const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode);
6999+
const typeParameterNode = typeParameterToDeclarationWithConstraint(typeParameter, context, appropriateConstraintTypeNode);
7000+
7001+
// nameType and templateType nodes have to be in the new scope
7002+
const cleanup = enterNewScope(context, type.declaration, /*expandedParams*/ undefined, [getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(type.declaration.typeParameter))]);
69937003
const nameTypeNode = type.declaration.nameType ? typeToTypeNodeHelper(getNameTypeFromMappedType(type)!, context) : undefined;
6994-
const templateTypeNode = typeToTypeNodeHelper(removeMissingType(getTemplateTypeFromMappedType(type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context);
7004+
const templateTypeNode = typeToTypeNodeHelper(removeMissingType(templateType, !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context);
7005+
cleanup();
7006+
69957007
const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined);
69967008
context.approximateLength += 10;
69977009
const result = setEmitFlags(mappedTypeNode, EmitFlags.SingleLine);

tests/baselines/reference/computedTypesKeyofNoIndexSignatureType.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
=== computedTypesKeyofNoIndexSignatureType.ts ===
44
type Compute<A> = { [K in keyof A]: Compute<A[K]>; } & {};
5-
>Compute : { [K in keyof A]: A[K] extends infer T ? { [K_1 in keyof T]: A[K][K_1] extends infer T_1 ? { [K_2 in keyof T_1]: A[K][K_1][K_2] extends infer T_2 ? { [K_3 in keyof T_2]: A[K][K_1][K_2][K_3] extends infer T_3 ? { [K_4 in keyof T_3]: A[K][K_1][K_2][K_3][K_4] extends infer T_4 ? { [K_5 in keyof T_4]: A[K][K_1][K_2][K_3][K_4][K_5] extends infer T_5 ? { [K_6 in keyof T_5]: A[K][K_1][K_2][K_3][K_4][K_5][K_6] extends infer T_6 ? { [K_7 in keyof T_6]: A[K][K_1][K_2][K_3][K_4][K_5][K_6][K_7] extends infer T_7 ? { [K_8 in keyof T_7]: A[K][K_1][K_2][K_3][K_4][K_5][K_6][K_7][K_8] extends infer T_8 ? { [K_9 in keyof T_8]: A[K][K_1][K_2][K_3][K_4][K_5][K_6][K_7][K_8][K_9] extends infer T_9 ? { [K_10 in keyof T_9]: any; } : never; } : never; } : never; } : never; } : never; } : never; } : never; } : never; } : never; } : never; }
6-
> : ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5+
>Compute : { [K in keyof A]: A[K] extends infer T ? { [K_1 in keyof T]: T[K_1] extends infer T_1 ? { [K_2 in keyof T_1]: T_1[K_2] extends infer T_2 ? { [K_3 in keyof T_2]: T_2[K_3] extends infer T_3 ? { [K_4 in keyof T_3]: T_3[K_4] extends infer T_4 ? { [K_5 in keyof T_4]: T_4[K_5] extends infer T_5 ? { [K_6 in keyof T_5]: T_5[K_6] extends infer T_6 ? { [K_7 in keyof T_6]: T_6[K_7] extends infer T_7 ? { [K_8 in keyof T_7]: T_7[K_8] extends infer T_8 ? { [K_9 in keyof T_8]: T_8[K_9] extends infer T_9 ? { [K_10 in keyof T_9]: any; } : never; } : never; } : never; } : never; } : never; } : never; } : never; } : never; } : never; } : never; }
6+
> : ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
77

88
type EqualsTest<T> = <A>() => A extends T ? 1 : 0;
99
>EqualsTest : EqualsTest<T>

tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ declare const _default: {
5252
fn: <T extends {
5353
x: T["x"] extends infer T_1 extends {
5454
[x: string]: (...params: unknown[]) => unknown;
55-
} ? { [K in keyof T_1]: T["x"][K]; } : never;
55+
} ? { [K in keyof T_1]: T_1[K]; } : never;
5656
}>(sliceIndex: T) => T["x"] extends infer T_2 extends {
5757
[x: string]: (...params: unknown[]) => unknown;
58-
} ? { [K_1 in keyof T_2]: Parameters<T["x"][K_1]>; } : never;
58+
} ? { [K_1 in keyof T_2]: Parameters<T_2[K_1]>; } : never;
5959
};
6060
};
6161
export default _default;

tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.types

+6-6
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ export default { fn };
3636

3737
=== reexport.ts ===
3838
import test from "./types";
39-
>test : { fn: <T extends { x: T["x"] extends infer T_1 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K in keyof T_1]: T["x"][K]; } : never; }>(sliceIndex: T) => T["x"] extends infer T_2 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K_1 in keyof T_2]: Parameters<T["x"][K_1]>; } : never; }
40-
> : ^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
39+
>test : { fn: <T extends { x: T["x"] extends infer T_1 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K in keyof T_1]: T_1[K]; } : never; }>(sliceIndex: T) => T["x"] extends infer T_2 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K_1 in keyof T_2]: Parameters<T_2[K_1]>; } : never; }
40+
> : ^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4141

4242
export default { test };
43-
>{ test } : { test: { fn: <T extends { x: T["x"] extends infer T_1 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K in keyof T_1]: T["x"][K]; } : never; }>(sliceIndex: T) => T["x"] extends infer T_2 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K_1 in keyof T_2]: Parameters<T["x"][K_1]>; } : never; }; }
44-
> : ^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45-
>test : { fn: <T extends { x: T["x"] extends infer T_1 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K in keyof T_1]: T["x"][K]; } : never; }>(sliceIndex: T) => T["x"] extends infer T_2 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K_1 in keyof T_2]: Parameters<T["x"][K_1]>; } : never; }
46-
> : ^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43+
>{ test } : { test: { fn: <T extends { x: T["x"] extends infer T_1 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K in keyof T_1]: T_1[K]; } : never; }>(sliceIndex: T) => T["x"] extends infer T_2 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K_1 in keyof T_2]: Parameters<T_2[K_1]>; } : never; }; }
44+
> : ^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45+
>test : { fn: <T extends { x: T["x"] extends infer T_1 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K in keyof T_1]: T_1[K]; } : never; }>(sliceIndex: T) => T["x"] extends infer T_2 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K_1 in keyof T_2]: Parameters<T_2[K_1]>; } : never; }
46+
> : ^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4747

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//// [tests/cases/compiler/declarationEmitMappedTypePreservesTypeParameterConstraint.ts] ////
2+
3+
//// [declarationEmitMappedTypePreservesTypeParameterConstraint.ts]
4+
// repro from https://github.com/microsoft/TypeScript/issues/54560
5+
6+
declare type requiredKeys<T extends object> = {
7+
[k in keyof T]: undefined extends T[k] ? never : k;
8+
}[keyof T];
9+
10+
declare type addQuestionMarks<
11+
T extends object,
12+
R extends keyof T = requiredKeys<T>
13+
> = Pick<Required<T>, R> & Partial<T>;
14+
15+
declare type identity<T> = T;
16+
17+
declare type flatten<T> = identity<{
18+
[k in keyof T]: T[k];
19+
}>;
20+
21+
export declare abstract class ZodType<Output = any> {
22+
readonly _output: Output;
23+
}
24+
25+
export declare class ZodLiteral<T> extends ZodType<T> {}
26+
27+
export declare type ZodTypeAny = ZodType<any>;
28+
29+
export declare type baseObjectOutputType<Shape extends ZodRawShape> = {
30+
[k in keyof Shape]: Shape[k]["_output"];
31+
};
32+
33+
export declare type objectOutputType<Shape extends ZodRawShape> = flatten<
34+
addQuestionMarks<baseObjectOutputType<Shape>>
35+
>;
36+
37+
export declare type ZodRawShape = {
38+
[k: string]: ZodTypeAny;
39+
};
40+
41+
export const buildSchema = <V extends string>(
42+
version: V
43+
): objectOutputType<{
44+
version: ZodLiteral<V>;
45+
}> => ({} as any);
46+
47+
// repro from https://github.com/microsoft/TypeScript/issues/55049
48+
49+
type evaluate<t> = { [k in keyof t]: t[k] } & unknown
50+
51+
export type entryOf<o> = evaluate<
52+
{ [k in keyof o]-?: [k, o[k] & ({} | null)] }[o extends readonly unknown[]
53+
? keyof o & number
54+
: keyof o]
55+
>
56+
57+
export type entriesOf<o extends object> = evaluate<entryOf<o>[]>
58+
59+
export const entriesOf = <o extends object>(o: o) =>
60+
Object.entries(o) as entriesOf<o>
61+
62+
63+
//// [declarationEmitMappedTypePreservesTypeParameterConstraint.js]
64+
"use strict";
65+
// repro from https://github.com/microsoft/TypeScript/issues/54560
66+
Object.defineProperty(exports, "__esModule", { value: true });
67+
exports.entriesOf = exports.buildSchema = void 0;
68+
var buildSchema = function (version) { return ({}); };
69+
exports.buildSchema = buildSchema;
70+
var entriesOf = function (o) {
71+
return Object.entries(o);
72+
};
73+
exports.entriesOf = entriesOf;
74+
75+
76+
//// [declarationEmitMappedTypePreservesTypeParameterConstraint.d.ts]
77+
declare type requiredKeys<T extends object> = {
78+
[k in keyof T]: undefined extends T[k] ? never : k;
79+
}[keyof T];
80+
declare type addQuestionMarks<T extends object, R extends keyof T = requiredKeys<T>> = Pick<Required<T>, R> & Partial<T>;
81+
declare type identity<T> = T;
82+
declare type flatten<T> = identity<{
83+
[k in keyof T]: T[k];
84+
}>;
85+
export declare abstract class ZodType<Output = any> {
86+
readonly _output: Output;
87+
}
88+
export declare class ZodLiteral<T> extends ZodType<T> {
89+
}
90+
export declare type ZodTypeAny = ZodType<any>;
91+
export declare type baseObjectOutputType<Shape extends ZodRawShape> = {
92+
[k in keyof Shape]: Shape[k]["_output"];
93+
};
94+
export declare type objectOutputType<Shape extends ZodRawShape> = flatten<addQuestionMarks<baseObjectOutputType<Shape>>>;
95+
export declare type ZodRawShape = {
96+
[k: string]: ZodTypeAny;
97+
};
98+
export declare const buildSchema: <V extends string>(version: V) => objectOutputType<{
99+
version: ZodLiteral<V>;
100+
}>;
101+
type evaluate<t> = {
102+
[k in keyof t]: t[k];
103+
} & unknown;
104+
export type entryOf<o> = evaluate<{
105+
[k in keyof o]-?: [k, o[k] & ({} | null)];
106+
}[o extends readonly unknown[] ? keyof o & number : keyof o]>;
107+
export type entriesOf<o extends object> = evaluate<entryOf<o>[]>;
108+
export declare const entriesOf: <o extends object>(o: o) => entriesOf<o>;
109+
export {};

0 commit comments

Comments
 (0)