Skip to content

Constrain infer type parameters made to preserve distributivity for … #49793

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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))

Original file line number Diff line number Diff line change
@@ -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]>; }; }

Original file line number Diff line number Diff line change
@@ -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 };