Skip to content

Preserve type parameter constraint in emitted mapped types while preserving their distributivity #54759

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
17 changes: 13 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6808,6 +6808,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) as QuestionToken | PlusToken | MinusToken : undefined;
let appropriateConstraintTypeNode: TypeNode;
let newTypeVariable: TypeReferenceNode | undefined;
let templateType = getTemplateTypeFromMappedType(type);
let typeParameter = getTypeParameterFromMappedType(type);
// 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
const needsModifierPreservingWrapper = !isMappedTypeWithKeyofConstraintDeclaration(type)
&& !(getModifiersTypeFromMappedType(type).flags & TypeFlags.Unknown)
Expand All @@ -6817,9 +6819,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// We have a { [P in keyof T]: X }
// We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType`
if (isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) {
const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String));
const name = typeParameterToName(newParam, context);
const newConstraintParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String));
const newTypeParam = createTypeParameter(typeParameter.symbol);
const name = typeParameterToName(newConstraintParam, context);
const target = type.target as MappedType;
typeParameter = newTypeParam;
newTypeVariable = factory.createTypeReferenceNode(name);
templateType = instantiateType(
getTemplateTypeFromMappedType(target),
makeArrayTypeMapper([getTypeParameterFromMappedType(target), getModifiersTypeFromMappedType(target)], [newTypeParam, newConstraintParam]),
);
}
appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, newTypeVariable || typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context));
}
Expand All @@ -6834,9 +6843,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
else {
appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context);
}
const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode);
const typeParameterNode = typeParameterToDeclarationWithConstraint(typeParameter, context, appropriateConstraintTypeNode);
const nameTypeNode = type.declaration.nameType ? typeToTypeNodeHelper(getNameTypeFromMappedType(type)!, context) : undefined;
const templateTypeNode = typeToTypeNodeHelper(removeMissingType(getTemplateTypeFromMappedType(type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context);
const templateTypeNode = typeToTypeNodeHelper(removeMissingType(templateType, !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context);
const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined);
context.approximateLength += 10;
const result = setEmitFlags(mappedTypeNode, EmitFlags.SingleLine);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

=== computedTypesKeyofNoIndexSignatureType.ts ===
type Compute<A> = { [K in keyof A]: Compute<A[K]>; } & {};
>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; }
>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; }

type EqualsTest<T> = <A>() => A extends T ? 1 : 0;
>EqualsTest : EqualsTest<T>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ declare const _default: {
fn: <T_1 extends {
x: T_1["x"] extends infer T extends {
[x: string]: (...params: unknown[]) => unknown;
} ? { [K in keyof T]: T_1["x"][K]; } : never;
} ? { [K in keyof T]: T[K]; } : never;
}>(sliceIndex: T_1) => T_1["x"] extends infer T_2 extends {
[x: string]: (...params: unknown[]) => unknown;
} ? { [K_1 in keyof T_2]: Parameters<T_1["x"][K_1]>; } : never;
} ? { [K_1 in keyof T_2]: Parameters<T_2[K_1]>; } : never;
};
};
export default _default;
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ export default { fn };

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

export default { test };
>{ test } : { test: { fn: <T_1 extends { x: T_1["x"] extends infer T extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K in keyof T]: T_1["x"][K]; } : never; }>(sliceIndex: T_1) => T_1["x"] extends infer T_2 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K_1 in keyof T_2]: Parameters<T_1["x"][K_1]>; } : never; }; }
>test : { fn: <T_1 extends { x: T_1["x"] extends infer T extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K in keyof T]: T_1["x"][K]; } : never; }>(sliceIndex: T_1) => T_1["x"] extends infer T_2 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K_1 in keyof T_2]: Parameters<T_1["x"][K_1]>; } : never; }
>{ test } : { test: { fn: <T_1 extends { x: T_1["x"] extends infer T extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K in keyof T]: T[K]; } : never; }>(sliceIndex: T_1) => T_1["x"] extends infer T_2 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K_1 in keyof T_2]: Parameters<T_2[K_1]>; } : never; }; }
>test : { fn: <T_1 extends { x: T_1["x"] extends infer T extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K in keyof T]: T[K]; } : never; }>(sliceIndex: T_1) => T_1["x"] extends infer T_2 extends { [x: string]: (...params: unknown[]) => unknown; } ? { [K_1 in keyof T_2]: Parameters<T_2[K_1]>; } : never; }

Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//// [tests/cases/compiler/declarationEmitMappedTypePreservesTypeParameterConstraint.ts] ////

//// [declarationEmitMappedTypePreservesTypeParameterConstraint.ts]
// repro from https://github.com/microsoft/TypeScript/issues/54560

declare type requiredKeys<T extends object> = {
[k in keyof T]: undefined extends T[k] ? never : k;
}[keyof T];

declare type addQuestionMarks<
T extends object,
R extends keyof T = requiredKeys<T>
> = Pick<Required<T>, R> & Partial<T>;

declare type identity<T> = T;

declare type flatten<T> = identity<{
[k in keyof T]: T[k];
}>;

export declare abstract class ZodType<Output = any> {
readonly _output: Output;
}

export declare class ZodLiteral<T> extends ZodType<T> {}

export declare type ZodTypeAny = ZodType<any>;

export declare type baseObjectOutputType<Shape extends ZodRawShape> = {
[k in keyof Shape]: Shape[k]["_output"];
};

export declare type objectOutputType<Shape extends ZodRawShape> = flatten<
addQuestionMarks<baseObjectOutputType<Shape>>
>;

export declare type ZodRawShape = {
[k: string]: ZodTypeAny;
};

export const buildSchema = <V extends string>(
version: V
): objectOutputType<{
version: ZodLiteral<V>;
}> => ({} as any);

// repro from https://github.com/microsoft/TypeScript/issues/55049

type evaluate<t> = { [k in keyof t]: t[k] } & unknown

export type entryOf<o> = evaluate<
{ [k in keyof o]-?: [k, o[k] & ({} | null)] }[o extends readonly unknown[]
? keyof o & number
: keyof o]
>

export type entriesOf<o extends object> = evaluate<entryOf<o>[]>

export const entriesOf = <o extends object>(o: o) =>
Object.entries(o) as entriesOf<o>


//// [declarationEmitMappedTypePreservesTypeParameterConstraint.js]
"use strict";
// repro from https://github.com/microsoft/TypeScript/issues/54560
Object.defineProperty(exports, "__esModule", { value: true });
exports.entriesOf = exports.buildSchema = void 0;
var buildSchema = function (version) { return ({}); };
exports.buildSchema = buildSchema;
var entriesOf = function (o) {
return Object.entries(o);
};
exports.entriesOf = entriesOf;


//// [declarationEmitMappedTypePreservesTypeParameterConstraint.d.ts]
declare type requiredKeys<T extends object> = {
[k in keyof T]: undefined extends T[k] ? never : k;
}[keyof T];
declare type addQuestionMarks<T extends object, R extends keyof T = requiredKeys<T>> = Pick<Required<T>, R> & Partial<T>;
declare type identity<T> = T;
declare type flatten<T> = identity<{
[k in keyof T]: T[k];
}>;
export declare abstract class ZodType<Output = any> {
readonly _output: Output;
}
export declare class ZodLiteral<T> extends ZodType<T> {
}
export declare type ZodTypeAny = ZodType<any>;
export declare type baseObjectOutputType<Shape extends ZodRawShape> = {
[k in keyof Shape]: Shape[k]["_output"];
};
export declare type objectOutputType<Shape extends ZodRawShape> = flatten<addQuestionMarks<baseObjectOutputType<Shape>>>;
export declare type ZodRawShape = {
[k: string]: ZodTypeAny;
};
export declare const buildSchema: <V extends string>(version: V) => addQuestionMarks<baseObjectOutputType<{
version: ZodLiteral<V>;
}>, undefined extends V ? never : "version"> extends infer T ? { [k in keyof T]: T[k]; } : never;
type evaluate<t> = {
[k in keyof t]: t[k];
} & unknown;
export type entryOf<o> = evaluate<{
[k in keyof o]-?: [k, o[k] & ({} | null)];
}[o extends readonly unknown[] ? keyof o & number : keyof o]>;
export type entriesOf<o extends object> = evaluate<entryOf<o>[]>;
export declare const entriesOf: <o extends object>(o: o) => ({ [k_1 in keyof o]-?: [k_1, o[k_1] & ({} | null)]; }[o extends readonly unknown[] ? keyof o & number : keyof o] extends infer T ? { [k in keyof T]: T[k]; } : never)[];
export {};
Loading