Skip to content

Commit 84241ef

Browse files
committed
Optimize interning of reverse mapped types
1 parent d7d8f33 commit 84241ef

13 files changed

Lines changed: 329 additions & 54 deletions

src/compiler/checker.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5058,9 +5058,21 @@ namespace ts {
50585058
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
50595059
}
50605060

5061+
function shouldUsePlaceholderForProperty(propertySymbol: Symbol, context: NodeBuilderContext) {
5062+
// Use placeholders for reverse mapped types we've either already descended into, or which
5063+
// are nested reverse mappings within a mapping over a non-anonymous type. The later is a restriction mostly just to
5064+
// reduce the blowup in printback size from doing, eg, a deep reverse mapping over `Window`.
5065+
// Since anonymous types usually come from expressions, this allows us to preserve the output
5066+
// for deep mappings which likely come from expressions, while truncating those parts which
5067+
// come from mappings over library functions.
5068+
return !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped) && (
5069+
contains(context.reverseMappedStack, propertySymbol as ReverseMappedSymbol)
5070+
|| (context.reverseMappedStack?.[0] && !(getObjectFlags(last(context.reverseMappedStack).propertyType) & ObjectFlags.Anonymous)));
5071+
}
5072+
50615073
function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) {
50625074
const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped);
5063-
const propertyType = propertyIsReverseMapped && context.flags & NodeBuilderFlags.InReverseMappedType ?
5075+
const propertyType = shouldUsePlaceholderForProperty(propertySymbol, context) ?
50645076
anyType : getTypeOfSymbol(propertySymbol);
50655077
const saveEnclosingDeclaration = context.enclosingDeclaration;
50665078
context.enclosingDeclaration = undefined;
@@ -5090,16 +5102,20 @@ namespace ts {
50905102
}
50915103
}
50925104
else {
5093-
const savedFlags = context.flags;
5094-
context.flags |= propertyIsReverseMapped ? NodeBuilderFlags.InReverseMappedType : 0;
50955105
let propertyTypeNode: TypeNode;
5096-
if (propertyIsReverseMapped && !!(savedFlags & NodeBuilderFlags.InReverseMappedType)) {
5106+
if (shouldUsePlaceholderForProperty(propertySymbol, context)) {
50975107
propertyTypeNode = createElidedInformationPlaceholder(context);
50985108
}
50995109
else {
5110+
if (propertyIsReverseMapped) {
5111+
context.reverseMappedStack ||= [];
5112+
context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol);
5113+
}
51005114
propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, propertyType, propertySymbol, saveEnclosingDeclaration) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
5115+
if (propertyIsReverseMapped) {
5116+
context.reverseMappedStack!.pop();
5117+
}
51015118
}
5102-
context.flags = savedFlags;
51035119

51045120
const modifiers = isReadonlySymbol(propertySymbol) ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined;
51055121
if (modifiers) {
@@ -7608,6 +7624,7 @@ namespace ts {
76087624
typeParameterNamesByText?: Set<string>;
76097625
usedSymbolNames?: Set<string>;
76107626
remappedSymbolNames?: ESMap<SymbolId, string>;
7627+
reverseMappedStack?: ReverseMappedSymbol[];
76117628
}
76127629

76137630
function isDefaultBindingContext(location: Node) {
@@ -10659,6 +10676,14 @@ namespace ts {
1065910676
}
1066010677
}
1066110678

10679+
type ReplaceableIndexedAccessType = IndexedAccessType & { objectType: TypeParameter, indexType: TypeParameter };
10680+
function replaceIndexedAccess(instantiable: Type, type: ReplaceableIndexedAccessType, replacement: Type) {
10681+
// map type.indexType to 0
10682+
// map type.objectType to `[TReplacement]`
10683+
// thus making the indexed access `[TReplacement][0]` or `TReplacement`
10684+
return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getLiteralType(0), createTupleType([replacement])]));
10685+
}
10686+
1066210687
function resolveReverseMappedTypeMembers(type: ReverseMappedType) {
1066310688
const indexInfo = getIndexInfoOfType(type.source, IndexKind.String);
1066410689
const modifiers = getMappedTypeModifiers(type.mappedType);
@@ -10672,8 +10697,21 @@ namespace ts {
1067210697
inferredProp.declarations = prop.declarations;
1067310698
inferredProp.nameType = getSymbolLinks(prop).nameType;
1067410699
inferredProp.propertyType = getTypeOfSymbol(prop);
10675-
inferredProp.mappedType = type.mappedType;
10676-
inferredProp.constraintType = type.constraintType;
10700+
if (type.constraintType.type.flags & TypeFlags.IndexedAccess
10701+
&& (type.constraintType.type as IndexedAccessType).objectType.flags & TypeFlags.TypeParameter
10702+
&& (type.constraintType.type as IndexedAccessType).indexType.flags & TypeFlags.TypeParameter) {
10703+
// A reverse mapping of `{[K in keyof T[K_1]]: T[K_1]}` is the same as that of `{[K in keyof T]: T}`, since all we care about is
10704+
// inferring to the "type parameter" (or indexed access) shared by the constraint and template. So, to reduce the number of
10705+
// type identities produced, we simplify such indexed access occurences
10706+
const newTypeParam = (type.constraintType.type as IndexedAccessType).objectType;
10707+
const newMappedType = replaceIndexedAccess(type.mappedType, type.constraintType.type as ReplaceableIndexedAccessType, newTypeParam);
10708+
inferredProp.mappedType = newMappedType as MappedType;
10709+
inferredProp.constraintType = getIndexType(newTypeParam) as IndexType;
10710+
}
10711+
else {
10712+
inferredProp.mappedType = type.mappedType;
10713+
inferredProp.constraintType = type.constraintType;
10714+
}
1067710715
members.set(prop.escapedName, inferredProp);
1067810716
}
1067910717
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
@@ -20249,7 +20287,11 @@ namespace ts {
2024920287
}
2025020288

2025120289
function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) {
20252-
return inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType);
20290+
const links = getSymbolLinks(symbol);
20291+
if (!links.type) {
20292+
links.type = inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType);
20293+
}
20294+
return links.type;
2025320295
}
2025420296

2025520297
function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type {

src/compiler/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4306,7 +4306,6 @@ namespace ts {
43064306
InObjectTypeLiteral = 1 << 22,
43074307
InTypeAlias = 1 << 23, // Writing type in type alias declaration
43084308
InInitialEntityName = 1 << 24, // Set when writing the LHS of an entity name or entity name expression
4309-
InReverseMappedType = 1 << 25,
43104309
}
43114310

43124311
// Ensure the shared flags between this and `NodeBuilderFlags` stay in alignment

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2271,8 +2271,7 @@ declare namespace ts {
22712271
IgnoreErrors = 70221824,
22722272
InObjectTypeLiteral = 4194304,
22732273
InTypeAlias = 8388608,
2274-
InInitialEntityName = 16777216,
2275-
InReverseMappedType = 33554432
2274+
InInitialEntityName = 16777216
22762275
}
22772276
export enum TypeFormatFlags {
22782277
None = 0,

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2271,8 +2271,7 @@ declare namespace ts {
22712271
IgnoreErrors = 70221824,
22722272
InObjectTypeLiteral = 4194304,
22732273
InTypeAlias = 8388608,
2274-
InInitialEntityName = 16777216,
2275-
InReverseMappedType = 33554432
2274+
InInitialEntityName = 16777216
22762275
}
22772276
export enum TypeFormatFlags {
22782277
None = 0,

tests/baselines/reference/genericFunctionInference2.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ declare const foo: Reducer<MyState['combined']['foo']>;
1919

2020
const myReducer1: Reducer<MyState> = combineReducers({
2121
>myReducer1 : Reducer<MyState>
22-
>combineReducers({ combined: combineReducers({ foo }),}) : Reducer<{ combined: { foo: any; }; }>
22+
>combineReducers({ combined: combineReducers({ foo }),}) : Reducer<{ combined: { foo: number; }; }>
2323
>combineReducers : <S>(reducers: { [K in keyof S]: Reducer<S[K]>; }) => Reducer<S>
2424
>{ combined: combineReducers({ foo }),} : { combined: Reducer<{ foo: number; }>; }
2525

@@ -33,8 +33,8 @@ const myReducer1: Reducer<MyState> = combineReducers({
3333
});
3434

3535
const myReducer2 = combineReducers({
36-
>myReducer2 : Reducer<{ combined: { foo: any; }; }>
37-
>combineReducers({ combined: combineReducers({ foo }),}) : Reducer<{ combined: { foo: any; }; }>
36+
>myReducer2 : Reducer<{ combined: { foo: number; }; }>
37+
>combineReducers({ combined: combineReducers({ foo }),}) : Reducer<{ combined: { foo: number; }; }>
3838
>combineReducers : <S>(reducers: { [K in keyof S]: Reducer<S[K]>; }) => Reducer<S>
3939
>{ combined: combineReducers({ foo }),} : { combined: Reducer<{ foo: number; }>; }
4040

tests/baselines/reference/isomorphicMappedTypeInference.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,14 @@ declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
350350
declare var g1: (...args: any[]) => {
351351
sum: number;
352352
nested: {
353-
mul: any;
353+
mul: string;
354354
};
355355
};
356356
declare var g2: (...args: any[]) => {
357357
foo: {
358-
bar: any;
358+
bar: {
359+
baz: boolean;
360+
};
359361
};
360362
};
361363
declare const foo: <T>(object: T, partial: Partial<T>) => T;

tests/baselines/reference/isomorphicMappedTypeInference.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,8 @@ declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
434434

435435
// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } }
436436
var g1 = applySpec({
437-
>g1 : (...args: any[]) => { sum: number; nested: { mul: any; }; }
438-
>applySpec({ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }}) : (...args: any[]) => { sum: number; nested: { mul: any; }; }
437+
>g1 : (...args: any[]) => { sum: number; nested: { mul: string; }; }
438+
>applySpec({ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }}) : (...args: any[]) => { sum: number; nested: { mul: string; }; }
439439
>applySpec : <T>(obj: Spec<T>) => (...args: any[]) => T
440440
>{ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }} : { sum: (a: any) => number; nested: { mul: (b: any) => string; }; }
441441

@@ -459,8 +459,8 @@ var g1 = applySpec({
459459

460460
// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
461461
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
462-
>g2 : (...args: any[]) => { foo: { bar: any; }; }
463-
>applySpec({ foo: { bar: { baz: (x: any) => true } } }) : (...args: any[]) => { foo: { bar: any; }; }
462+
>g2 : (...args: any[]) => { foo: { bar: { baz: boolean; }; }; }
463+
>applySpec({ foo: { bar: { baz: (x: any) => true } } }) : (...args: any[]) => { foo: { bar: { baz: boolean; }; }; }
464464
>applySpec : <T>(obj: Spec<T>) => (...args: any[]) => T
465465
>{ foo: { bar: { baz: (x: any) => true } } } : { foo: { bar: { baz: (x: any) => boolean; }; }; }
466466
>foo : { bar: { baz: (x: any) => boolean; }; }

0 commit comments

Comments
 (0)