Skip to content

Commit ea4791d

Browse files
authored
Preserve the homomorphism of inlined mapped types in declaration emit (microsoft#48091)
1 parent c70be8b commit ea4791d

6 files changed

+118
-3
lines changed

src/compiler/checker.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -5079,10 +5079,16 @@ namespace ts {
50795079
const readonlyToken = type.declaration.readonlyToken ? factory.createToken(type.declaration.readonlyToken.kind) as ReadonlyKeyword | PlusToken | MinusToken : undefined;
50805080
const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) as QuestionToken | PlusToken | MinusToken : undefined;
50815081
let appropriateConstraintTypeNode: TypeNode;
5082+
let newTypeVariable: TypeReferenceNode | undefined;
50825083
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
50835084
// We have a { [P in keyof T]: X }
50845085
// We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType`
5085-
appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context));
5086+
if (!(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) {
5087+
const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String));
5088+
const name = typeParameterToName(newParam, context);
5089+
newTypeVariable = factory.createTypeReferenceNode(name);
5090+
}
5091+
appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, newTypeVariable || typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context));
50865092
}
50875093
else {
50885094
appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context);
@@ -5092,7 +5098,19 @@ namespace ts {
50925098
const templateTypeNode = typeToTypeNodeHelper(removeMissingType(getTemplateTypeFromMappedType(type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context);
50935099
const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined);
50945100
context.approximateLength += 10;
5095-
return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine);
5101+
const result = setEmitFlags(mappedTypeNode, EmitFlags.SingleLine);
5102+
if (isMappedTypeWithKeyofConstraintDeclaration(type) && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) {
5103+
// homomorphic mapped type with a non-homomorphic naive inlining
5104+
// wrap it with a conditional like `SomeModifiersType extends infer U ? {..the mapped type...} : never` to ensure the resulting
5105+
// type stays homomorphic
5106+
return factory.createConditionalTypeNode(
5107+
typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context),
5108+
factory.createInferTypeNode(factory.createTypeParameterDeclaration(factory.cloneNode(newTypeVariable!.typeName) as Identifier)),
5109+
result,
5110+
factory.createKeywordTypeNode(SyntaxKind.NeverKeyword)
5111+
);
5112+
}
5113+
return result;
50965114
}
50975115

50985116
function createAnonymousTypeNode(type: ObjectType): TypeNode {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//// [tests/cases/compiler/mappedTypeGenericInstantiationPreservesHomomorphism.ts] ////
2+
3+
//// [internal.ts]
4+
export declare function usePrivateType<T extends unknown[]>(...args: T): PrivateMapped<T[any]>;
5+
6+
type PrivateMapped<Obj> = {[K in keyof Obj]: Obj[K]};
7+
8+
//// [api.ts]
9+
import {usePrivateType} from './internal';
10+
export const mappedUnionWithPrivateType = <T extends unknown[]>(...args: T) => usePrivateType(...args);
11+
12+
13+
//// [internal.js]
14+
"use strict";
15+
exports.__esModule = true;
16+
//// [api.js]
17+
"use strict";
18+
exports.__esModule = true;
19+
exports.mappedUnionWithPrivateType = void 0;
20+
var internal_1 = require("./internal");
21+
var mappedUnionWithPrivateType = function () {
22+
var args = [];
23+
for (var _i = 0; _i < arguments.length; _i++) {
24+
args[_i] = arguments[_i];
25+
}
26+
return internal_1.usePrivateType.apply(void 0, args);
27+
};
28+
exports.mappedUnionWithPrivateType = mappedUnionWithPrivateType;
29+
30+
31+
//// [internal.d.ts]
32+
export declare function usePrivateType<T extends unknown[]>(...args: T): PrivateMapped<T[any]>;
33+
declare type PrivateMapped<Obj> = {
34+
[K in keyof Obj]: Obj[K];
35+
};
36+
export {};
37+
//// [api.d.ts]
38+
export declare const mappedUnionWithPrivateType: <T extends unknown[]>(...args: T) => T[any] extends infer T_1 ? { [K in keyof T_1]: T[any][K]; } : never;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/compiler/internal.ts ===
2+
export declare function usePrivateType<T extends unknown[]>(...args: T): PrivateMapped<T[any]>;
3+
>usePrivateType : Symbol(usePrivateType, Decl(internal.ts, 0, 0))
4+
>T : Symbol(T, Decl(internal.ts, 0, 39))
5+
>args : Symbol(args, Decl(internal.ts, 0, 60))
6+
>T : Symbol(T, Decl(internal.ts, 0, 39))
7+
>PrivateMapped : Symbol(PrivateMapped, Decl(internal.ts, 0, 95))
8+
>T : Symbol(T, Decl(internal.ts, 0, 39))
9+
10+
type PrivateMapped<Obj> = {[K in keyof Obj]: Obj[K]};
11+
>PrivateMapped : Symbol(PrivateMapped, Decl(internal.ts, 0, 95))
12+
>Obj : Symbol(Obj, Decl(internal.ts, 2, 19))
13+
>K : Symbol(K, Decl(internal.ts, 2, 28))
14+
>Obj : Symbol(Obj, Decl(internal.ts, 2, 19))
15+
>Obj : Symbol(Obj, Decl(internal.ts, 2, 19))
16+
>K : Symbol(K, Decl(internal.ts, 2, 28))
17+
18+
=== tests/cases/compiler/api.ts ===
19+
import {usePrivateType} from './internal';
20+
>usePrivateType : Symbol(usePrivateType, Decl(api.ts, 0, 8))
21+
22+
export const mappedUnionWithPrivateType = <T extends unknown[]>(...args: T) => usePrivateType(...args);
23+
>mappedUnionWithPrivateType : Symbol(mappedUnionWithPrivateType, Decl(api.ts, 1, 12))
24+
>T : Symbol(T, Decl(api.ts, 1, 43))
25+
>args : Symbol(args, Decl(api.ts, 1, 64))
26+
>T : Symbol(T, Decl(api.ts, 1, 43))
27+
>usePrivateType : Symbol(usePrivateType, Decl(api.ts, 0, 8))
28+
>args : Symbol(args, Decl(api.ts, 1, 64))
29+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/compiler/internal.ts ===
2+
export declare function usePrivateType<T extends unknown[]>(...args: T): PrivateMapped<T[any]>;
3+
>usePrivateType : <T extends unknown[]>(...args: T) => PrivateMapped<T[any]>
4+
>args : T
5+
6+
type PrivateMapped<Obj> = {[K in keyof Obj]: Obj[K]};
7+
>PrivateMapped : PrivateMapped<Obj>
8+
9+
=== tests/cases/compiler/api.ts ===
10+
import {usePrivateType} from './internal';
11+
>usePrivateType : <T extends unknown[]>(...args: T) => { [K in keyof T[any]]: T[any][K]; }
12+
13+
export const mappedUnionWithPrivateType = <T extends unknown[]>(...args: T) => usePrivateType(...args);
14+
>mappedUnionWithPrivateType : <T extends unknown[]>(...args: T) => { [K in keyof T[any]]: T[any][K]; }
15+
><T extends unknown[]>(...args: T) => usePrivateType(...args) : <T extends unknown[]>(...args: T) => { [K in keyof T[any]]: T[any][K]; }
16+
>args : T
17+
>usePrivateType(...args) : { [K in keyof T[any]]: T[any][K]; }
18+
>usePrivateType : <T extends unknown[]>(...args: T) => { [K in keyof T[any]]: T[any][K]; }
19+
>...args : unknown
20+
>args : T
21+

tests/baselines/reference/mappedTypeUnionConstraintInferences.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
3838
export declare type PartialProperties<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
3939
export declare function doSomething_Actual<T extends {
4040
prop: string;
41-
}>(a: T): { [P in keyof PartialProperties<T, "prop">]: PartialProperties<T, "prop">[P]; };
41+
}>(a: T): PartialProperties<T, "prop"> extends infer T_1 ? { [P in keyof T_1]: PartialProperties<T, "prop">[P]; } : never;
4242
export declare function doSomething_Expected<T extends {
4343
prop: string;
4444
}>(a: T): {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// @declaration: true
2+
// @filename: internal.ts
3+
export declare function usePrivateType<T extends unknown[]>(...args: T): PrivateMapped<T[any]>;
4+
5+
type PrivateMapped<Obj> = {[K in keyof Obj]: Obj[K]};
6+
7+
// @filename: api.ts
8+
import {usePrivateType} from './internal';
9+
export const mappedUnionWithPrivateType = <T extends unknown[]>(...args: T) => usePrivateType(...args);

0 commit comments

Comments
 (0)