diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 65db187fee627..a9e078c5fb172 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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) ); diff --git a/tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.js b/tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.js new file mode 100644 index 0000000000000..c5b2e53abb3ba --- /dev/null +++ b/tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.js @@ -0,0 +1,61 @@ +//// [tests/cases/compiler/declarationEmitMappedTypeDistributivityPreservesConstraints.ts] //// + +//// [types.ts] +type Fns = Record unknown> + +type Map = { [K in keyof T]: T[K]; }; + +type AllArg = { [K in keyof T]: Parameters }; + +function fn }>(sliceIndex: T): AllArg { + 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 unknown>; +declare type Map = { + [K in keyof T]: T[K]; +}; +declare type AllArg = { + [K in keyof T]: Parameters; +}; +declare function fn; +}>(sliceIndex: T): AllArg; +declare const _default: { + fn: typeof fn; +}; +export default _default; +//// [reexport.d.ts] +declare const _default: { + test: { + fn: 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; } : never; + }; +}; +export default _default; diff --git a/tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.symbols b/tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.symbols new file mode 100644 index 0000000000000..c996d9f4d6c54 --- /dev/null +++ b/tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.symbols @@ -0,0 +1,49 @@ +=== tests/cases/compiler/types.ts === +type Fns = Record 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 = { [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 = { [K in keyof T]: Parameters }; +>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 }>(sliceIndex: T): AllArg { +>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)) + diff --git a/tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.types b/tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.types new file mode 100644 index 0000000000000..41b6e4b4bd372 --- /dev/null +++ b/tests/baselines/reference/declarationEmitMappedTypeDistributivityPreservesConstraints.types @@ -0,0 +1,33 @@ +=== tests/cases/compiler/types.ts === +type Fns = Record unknown> +>Fns : { [x: string]: (...params: unknown[]) => unknown; } +>params : unknown[] + +type Map = { [K in keyof T]: T[K]; }; +>Map : Map + +type AllArg = { [K in keyof T]: Parameters }; +>AllArg : AllArg + +function fn }>(sliceIndex: T): AllArg { +>fn : ; }>(sliceIndex: T) => AllArg +>x : Map +>sliceIndex : T + + return null!; +>null! : null +>null : null +} + +export default { fn }; +>{ fn } : { fn: ; }>(sliceIndex: T) => AllArg; } +>fn : ; }>(sliceIndex: T) => AllArg + +=== tests/cases/compiler/reexport.ts === +import test from "./types"; +>test : { fn: (sliceIndex: T) => { [K in keyof T["x"]]: Parameters; }; } + +export default { test }; +>{ test } : { test: { fn: (sliceIndex: T) => { [K in keyof T["x"]]: Parameters; }; }; } +>test : { fn: (sliceIndex: T) => { [K in keyof T["x"]]: Parameters; }; } + diff --git a/tests/cases/compiler/declarationEmitMappedTypeDistributivityPreservesConstraints.ts b/tests/cases/compiler/declarationEmitMappedTypeDistributivityPreservesConstraints.ts new file mode 100644 index 0000000000000..4250a1130e83d --- /dev/null +++ b/tests/cases/compiler/declarationEmitMappedTypeDistributivityPreservesConstraints.ts @@ -0,0 +1,18 @@ +// @declaration: true +// @filename: types.ts +type Fns = Record unknown> + +type Map = { [K in keyof T]: T[K]; }; + +type AllArg = { [K in keyof T]: Parameters }; + +function fn }>(sliceIndex: T): AllArg { + return null!; +} + +export default { fn }; + +// @filename: reexport.ts + +import test from "./types"; +export default { test }; \ No newline at end of file