Constrain infer type parameters made to preserver distributivity for inlined homomorphic mapped types (#49793)

This commit is contained in:
Wesley Wigham
2022-07-06 09:32:37 -07:00
committed by GitHub
parent e2e3c1285f
commit 0f868035af
5 changed files with 170 additions and 3 deletions

View File

@@ -5217,6 +5217,11 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
return typeToTypeNodeHelper(type, context);
}
function isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type: MappedType) {
return isMappedTypeWithKeyofConstraintDeclaration(type)
&& !(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter);
}
function createMappedTypeNodeFromType(type: MappedType) {
Debug.assert(!!(type.flags & TypeFlags.Object));
const readonlyToken = type.declaration.readonlyToken ? factory.createToken(type.declaration.readonlyToken.kind) as ReadonlyKeyword | PlusToken | MinusToken : undefined;
@@ -5226,7 +5231,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
// 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 (!(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) {
if (isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) {
const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String));
const name = typeParameterToName(newParam, context);
newTypeVariable = factory.createTypeReferenceNode(name);
@@ -5242,13 +5247,14 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined);
context.approximateLength += 10;
const result = setEmitFlags(mappedTypeNode, EmitFlags.SingleLine);
if (isMappedTypeWithKeyofConstraintDeclaration(type) && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) {
if (isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) {
// homomorphic mapped type with a non-homomorphic naive inlining
// wrap it with a conditional like `SomeModifiersType extends infer U ? {..the mapped type...} : never` to ensure the resulting
// type stays homomorphic
const originalConstraint = instantiateType(getConstraintOfTypeParameter(getTypeFromTypeNode((type.declaration.typeParameter.constraint! as TypeOperatorNode).type) as TypeParameter) || unknownType, type.mapper);
return factory.createConditionalTypeNode(
typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context),
factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier)),
factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, originalConstraint.flags & TypeFlags.Unknown ? undefined : typeToTypeNodeHelper(originalConstraint, context))),
result,
factory.createKeywordTypeNode(SyntaxKind.NeverKeyword)
);

View File

@@ -0,0 +1,61 @@
//// [tests/cases/compiler/declarationEmitMappedTypeDistributivityPreservesConstraints.ts] ////
//// [types.ts]
type Fns = Record<string, (...params: unknown[]) => unknown>
type Map<T extends Fns> = { [K in keyof T]: T[K]; };
type AllArg<T extends Fns> = { [K in keyof T]: Parameters<T[K]> };
function fn<T extends { x: Map<T['x']> }>(sliceIndex: T): AllArg<T['x']> {
return null!;
}
export default { fn };
//// [reexport.ts]
import test from "./types";
export default { test };
//// [types.js]
"use strict";
exports.__esModule = true;
function fn(sliceIndex) {
return null;
}
exports["default"] = { fn: fn };
//// [reexport.js]
"use strict";
exports.__esModule = true;
var types_1 = require("./types");
exports["default"] = { test: types_1["default"] };
//// [types.d.ts]
declare type Fns = Record<string, (...params: unknown[]) => unknown>;
declare type Map<T extends Fns> = {
[K in keyof T]: T[K];
};
declare type AllArg<T extends Fns> = {
[K in keyof T]: Parameters<T[K]>;
};
declare function fn<T extends {
x: Map<T['x']>;
}>(sliceIndex: T): AllArg<T['x']>;
declare const _default: {
fn: typeof fn;
};
export default _default;
//// [reexport.d.ts]
declare const _default: {
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;
};
};
export default _default;

View File

@@ -0,0 +1,49 @@
=== tests/cases/compiler/types.ts ===
type Fns = Record<string, (...params: unknown[]) => unknown>
>Fns : Symbol(Fns, Decl(types.ts, 0, 0))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>params : Symbol(params, Decl(types.ts, 0, 27))
type Map<T extends Fns> = { [K in keyof T]: T[K]; };
>Map : Symbol(Map, Decl(types.ts, 0, 60))
>T : Symbol(T, Decl(types.ts, 2, 9))
>Fns : Symbol(Fns, Decl(types.ts, 0, 0))
>K : Symbol(K, Decl(types.ts, 2, 29))
>T : Symbol(T, Decl(types.ts, 2, 9))
>T : Symbol(T, Decl(types.ts, 2, 9))
>K : Symbol(K, Decl(types.ts, 2, 29))
type AllArg<T extends Fns> = { [K in keyof T]: Parameters<T[K]> };
>AllArg : Symbol(AllArg, Decl(types.ts, 2, 52))
>T : Symbol(T, Decl(types.ts, 4, 12))
>Fns : Symbol(Fns, Decl(types.ts, 0, 0))
>K : Symbol(K, Decl(types.ts, 4, 32))
>T : Symbol(T, Decl(types.ts, 4, 12))
>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(types.ts, 4, 12))
>K : Symbol(K, Decl(types.ts, 4, 32))
function fn<T extends { x: Map<T['x']> }>(sliceIndex: T): AllArg<T['x']> {
>fn : Symbol(fn, Decl(types.ts, 4, 66))
>T : Symbol(T, Decl(types.ts, 6, 12))
>x : Symbol(x, Decl(types.ts, 6, 23))
>Map : Symbol(Map, Decl(types.ts, 0, 60))
>T : Symbol(T, Decl(types.ts, 6, 12))
>sliceIndex : Symbol(sliceIndex, Decl(types.ts, 6, 42))
>T : Symbol(T, Decl(types.ts, 6, 12))
>AllArg : Symbol(AllArg, Decl(types.ts, 2, 52))
>T : Symbol(T, Decl(types.ts, 6, 12))
return null!;
}
export default { fn };
>fn : Symbol(fn, Decl(types.ts, 10, 16))
=== tests/cases/compiler/reexport.ts ===
import test from "./types";
>test : Symbol(test, Decl(reexport.ts, 0, 6))
export default { test };
>test : Symbol(test, Decl(reexport.ts, 1, 16))

View File

@@ -0,0 +1,33 @@
=== tests/cases/compiler/types.ts ===
type Fns = Record<string, (...params: unknown[]) => unknown>
>Fns : { [x: string]: (...params: unknown[]) => unknown; }
>params : unknown[]
type Map<T extends Fns> = { [K in keyof T]: T[K]; };
>Map : Map<T>
type AllArg<T extends Fns> = { [K in keyof T]: Parameters<T[K]> };
>AllArg : AllArg<T>
function fn<T extends { x: Map<T['x']> }>(sliceIndex: T): AllArg<T['x']> {
>fn : <T extends { x: Map<T['x']>; }>(sliceIndex: T) => AllArg<T['x']>
>x : Map<T["x"]>
>sliceIndex : T
return null!;
>null! : null
>null : null
}
export default { fn };
>{ fn } : { fn: <T extends { x: Map<T["x"]>; }>(sliceIndex: T) => AllArg<T["x"]>; }
>fn : <T extends { x: Map<T["x"]>; }>(sliceIndex: T) => AllArg<T["x"]>
=== tests/cases/compiler/reexport.ts ===
import test from "./types";
>test : { fn: <T extends { x: { [K in keyof T["x"]]: T["x"][K]; }; }>(sliceIndex: T) => { [K in keyof T["x"]]: Parameters<T["x"][K]>; }; }
export default { test };
>{ test } : { test: { fn: <T extends { x: { [K in keyof T["x"]]: T["x"][K]; }; }>(sliceIndex: T) => { [K in keyof T["x"]]: Parameters<T["x"][K]>; }; }; }
>test : { fn: <T extends { x: { [K in keyof T["x"]]: T["x"][K]; }; }>(sliceIndex: T) => { [K in keyof T["x"]]: Parameters<T["x"][K]>; }; }

View File

@@ -0,0 +1,18 @@
// @declaration: true
// @filename: types.ts
type Fns = Record<string, (...params: unknown[]) => unknown>
type Map<T extends Fns> = { [K in keyof T]: T[K]; };
type AllArg<T extends Fns> = { [K in keyof T]: Parameters<T[K]> };
function fn<T extends { x: Map<T['x']> }>(sliceIndex: T): AllArg<T['x']> {
return null!;
}
export default { fn };
// @filename: reexport.ts
import test from "./types";
export default { test };