diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9dfb4a7e112c2..ff9265f7e26e1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5079,10 +5079,16 @@ namespace ts { const readonlyToken = type.declaration.readonlyToken ? factory.createToken(type.declaration.readonlyToken.kind) as ReadonlyKeyword | PlusToken | MinusToken : undefined; const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) as QuestionToken | PlusToken | MinusToken : undefined; let appropriateConstraintTypeNode: TypeNode; + let newTypeVariable: TypeReferenceNode | undefined; 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` - appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)); + if (!(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { + const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); + const name = typeParameterToName(newParam, context); + newTypeVariable = factory.createTypeReferenceNode(name); + } + appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, newTypeVariable || typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)); } else { appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context); @@ -5092,7 +5098,19 @@ namespace ts { const templateTypeNode = typeToTypeNodeHelper(removeMissingType(getTemplateTypeFromMappedType(type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context); const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined); context.approximateLength += 10; - return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); + const result = setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); + if (isMappedTypeWithKeyofConstraintDeclaration(type) && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter) && 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 + return factory.createConditionalTypeNode( + typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context), + factory.createInferTypeNode(factory.createTypeParameterDeclaration(factory.cloneNode(newTypeVariable!.typeName) as Identifier)), + result, + factory.createKeywordTypeNode(SyntaxKind.NeverKeyword) + ); + } + return result; } function createAnonymousTypeNode(type: ObjectType): TypeNode { diff --git a/tests/baselines/reference/mappedTypeGenericInstantiationPreservesHomomorphism.js b/tests/baselines/reference/mappedTypeGenericInstantiationPreservesHomomorphism.js new file mode 100644 index 0000000000000..2523efd3efe39 --- /dev/null +++ b/tests/baselines/reference/mappedTypeGenericInstantiationPreservesHomomorphism.js @@ -0,0 +1,38 @@ +//// [tests/cases/compiler/mappedTypeGenericInstantiationPreservesHomomorphism.ts] //// + +//// [internal.ts] +export declare function usePrivateType(...args: T): PrivateMapped; + +type PrivateMapped = {[K in keyof Obj]: Obj[K]}; + +//// [api.ts] +import {usePrivateType} from './internal'; +export const mappedUnionWithPrivateType = (...args: T) => usePrivateType(...args); + + +//// [internal.js] +"use strict"; +exports.__esModule = true; +//// [api.js] +"use strict"; +exports.__esModule = true; +exports.mappedUnionWithPrivateType = void 0; +var internal_1 = require("./internal"); +var mappedUnionWithPrivateType = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + return internal_1.usePrivateType.apply(void 0, args); +}; +exports.mappedUnionWithPrivateType = mappedUnionWithPrivateType; + + +//// [internal.d.ts] +export declare function usePrivateType(...args: T): PrivateMapped; +declare type PrivateMapped = { + [K in keyof Obj]: Obj[K]; +}; +export {}; +//// [api.d.ts] +export declare const mappedUnionWithPrivateType: (...args: T) => T[any] extends infer T_1 ? { [K in keyof T_1]: T[any][K]; } : never; diff --git a/tests/baselines/reference/mappedTypeGenericInstantiationPreservesHomomorphism.symbols b/tests/baselines/reference/mappedTypeGenericInstantiationPreservesHomomorphism.symbols new file mode 100644 index 0000000000000..9e8f0afef20c5 --- /dev/null +++ b/tests/baselines/reference/mappedTypeGenericInstantiationPreservesHomomorphism.symbols @@ -0,0 +1,29 @@ +=== tests/cases/compiler/internal.ts === +export declare function usePrivateType(...args: T): PrivateMapped; +>usePrivateType : Symbol(usePrivateType, Decl(internal.ts, 0, 0)) +>T : Symbol(T, Decl(internal.ts, 0, 39)) +>args : Symbol(args, Decl(internal.ts, 0, 60)) +>T : Symbol(T, Decl(internal.ts, 0, 39)) +>PrivateMapped : Symbol(PrivateMapped, Decl(internal.ts, 0, 95)) +>T : Symbol(T, Decl(internal.ts, 0, 39)) + +type PrivateMapped = {[K in keyof Obj]: Obj[K]}; +>PrivateMapped : Symbol(PrivateMapped, Decl(internal.ts, 0, 95)) +>Obj : Symbol(Obj, Decl(internal.ts, 2, 19)) +>K : Symbol(K, Decl(internal.ts, 2, 28)) +>Obj : Symbol(Obj, Decl(internal.ts, 2, 19)) +>Obj : Symbol(Obj, Decl(internal.ts, 2, 19)) +>K : Symbol(K, Decl(internal.ts, 2, 28)) + +=== tests/cases/compiler/api.ts === +import {usePrivateType} from './internal'; +>usePrivateType : Symbol(usePrivateType, Decl(api.ts, 0, 8)) + +export const mappedUnionWithPrivateType = (...args: T) => usePrivateType(...args); +>mappedUnionWithPrivateType : Symbol(mappedUnionWithPrivateType, Decl(api.ts, 1, 12)) +>T : Symbol(T, Decl(api.ts, 1, 43)) +>args : Symbol(args, Decl(api.ts, 1, 64)) +>T : Symbol(T, Decl(api.ts, 1, 43)) +>usePrivateType : Symbol(usePrivateType, Decl(api.ts, 0, 8)) +>args : Symbol(args, Decl(api.ts, 1, 64)) + diff --git a/tests/baselines/reference/mappedTypeGenericInstantiationPreservesHomomorphism.types b/tests/baselines/reference/mappedTypeGenericInstantiationPreservesHomomorphism.types new file mode 100644 index 0000000000000..dd3a310afb4ee --- /dev/null +++ b/tests/baselines/reference/mappedTypeGenericInstantiationPreservesHomomorphism.types @@ -0,0 +1,21 @@ +=== tests/cases/compiler/internal.ts === +export declare function usePrivateType(...args: T): PrivateMapped; +>usePrivateType : (...args: T) => PrivateMapped +>args : T + +type PrivateMapped = {[K in keyof Obj]: Obj[K]}; +>PrivateMapped : PrivateMapped + +=== tests/cases/compiler/api.ts === +import {usePrivateType} from './internal'; +>usePrivateType : (...args: T) => { [K in keyof T[any]]: T[any][K]; } + +export const mappedUnionWithPrivateType = (...args: T) => usePrivateType(...args); +>mappedUnionWithPrivateType : (...args: T) => { [K in keyof T[any]]: T[any][K]; } +>(...args: T) => usePrivateType(...args) : (...args: T) => { [K in keyof T[any]]: T[any][K]; } +>args : T +>usePrivateType(...args) : { [K in keyof T[any]]: T[any][K]; } +>usePrivateType : (...args: T) => { [K in keyof T[any]]: T[any][K]; } +>...args : unknown +>args : T + diff --git a/tests/baselines/reference/mappedTypeUnionConstraintInferences.js b/tests/baselines/reference/mappedTypeUnionConstraintInferences.js index 2e8cfe11ab057..3abfdf0cc0e54 100644 --- a/tests/baselines/reference/mappedTypeUnionConstraintInferences.js +++ b/tests/baselines/reference/mappedTypeUnionConstraintInferences.js @@ -38,7 +38,7 @@ export declare type Omit = Pick>; export declare type PartialProperties = Partial> & Omit; export declare function doSomething_Actual(a: T): { [P in keyof PartialProperties]: PartialProperties[P]; }; +}>(a: T): PartialProperties extends infer T_1 ? { [P in keyof T_1]: PartialProperties[P]; } : never; export declare function doSomething_Expected(a: T): { diff --git a/tests/cases/compiler/mappedTypeGenericInstantiationPreservesHomomorphism.ts b/tests/cases/compiler/mappedTypeGenericInstantiationPreservesHomomorphism.ts new file mode 100644 index 0000000000000..c8cf359509fad --- /dev/null +++ b/tests/cases/compiler/mappedTypeGenericInstantiationPreservesHomomorphism.ts @@ -0,0 +1,9 @@ +// @declaration: true +// @filename: internal.ts +export declare function usePrivateType(...args: T): PrivateMapped; + +type PrivateMapped = {[K in keyof Obj]: Obj[K]}; + +// @filename: api.ts +import {usePrivateType} from './internal'; +export const mappedUnionWithPrivateType = (...args: T) => usePrivateType(...args);