mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-10 18:04:18 -05:00
Constrain infer type parameters made to preserver distributivity for inlined homomorphic mapped types (#49793)
This commit is contained in:
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
@@ -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))
|
||||
|
||||
@@ -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]>; }; }
|
||||
|
||||
@@ -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 };
|
||||
Reference in New Issue
Block a user