Skip to content

Commit d1b4342

Browse files
authored
Allow nested reverse mapped type printback (#42485)
* Optimize interning of reverse mapped types * Style feedback * Whitespace * Update baseline
1 parent 1f1dcd6 commit d1b4342

13 files changed

+334
-54
lines changed

src/compiler/checker.ts

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5142,9 +5142,26 @@ namespace ts {
51425142
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
51435143
}
51445144

5145+
function shouldUsePlaceholderForProperty(propertySymbol: Symbol, context: NodeBuilderContext) {
5146+
// Use placeholders for reverse mapped types we've either already descended into, or which
5147+
// are nested reverse mappings within a mapping over a non-anonymous type. The later is a restriction mostly just to
5148+
// reduce the blowup in printback size from doing, eg, a deep reverse mapping over `Window`.
5149+
// Since anonymous types usually come from expressions, this allows us to preserve the output
5150+
// for deep mappings which likely come from expressions, while truncating those parts which
5151+
// come from mappings over library functions.
5152+
return !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped)
5153+
&& (
5154+
contains(context.reverseMappedStack, propertySymbol as ReverseMappedSymbol)
5155+
|| (
5156+
context.reverseMappedStack?.[0]
5157+
&& !(getObjectFlags(last(context.reverseMappedStack).propertyType) & ObjectFlags.Anonymous)
5158+
)
5159+
);
5160+
}
5161+
51455162
function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) {
51465163
const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped);
5147-
const propertyType = propertyIsReverseMapped && context.flags & NodeBuilderFlags.InReverseMappedType ?
5164+
const propertyType = shouldUsePlaceholderForProperty(propertySymbol, context) ?
51485165
anyType : getTypeOfSymbol(propertySymbol);
51495166
const saveEnclosingDeclaration = context.enclosingDeclaration;
51505167
context.enclosingDeclaration = undefined;
@@ -5175,16 +5192,20 @@ namespace ts {
51755192
}
51765193
}
51775194
else {
5178-
const savedFlags = context.flags;
5179-
context.flags |= propertyIsReverseMapped ? NodeBuilderFlags.InReverseMappedType : 0;
51805195
let propertyTypeNode: TypeNode;
5181-
if (propertyIsReverseMapped && !!(savedFlags & NodeBuilderFlags.InReverseMappedType)) {
5196+
if (shouldUsePlaceholderForProperty(propertySymbol, context)) {
51825197
propertyTypeNode = createElidedInformationPlaceholder(context);
51835198
}
51845199
else {
5200+
if (propertyIsReverseMapped) {
5201+
context.reverseMappedStack ||= [];
5202+
context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol);
5203+
}
51855204
propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, propertyType, propertySymbol, saveEnclosingDeclaration) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
5205+
if (propertyIsReverseMapped) {
5206+
context.reverseMappedStack!.pop();
5207+
}
51865208
}
5187-
context.flags = savedFlags;
51885209

51895210
const modifiers = isReadonlySymbol(propertySymbol) ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined;
51905211
if (modifiers) {
@@ -7716,6 +7737,7 @@ namespace ts {
77167737
typeParameterNamesByText?: Set<string>;
77177738
usedSymbolNames?: Set<string>;
77187739
remappedSymbolNames?: ESMap<SymbolId, string>;
7740+
reverseMappedStack?: ReverseMappedSymbol[];
77197741
}
77207742

77217743
function isDefaultBindingContext(location: Node) {
@@ -10870,6 +10892,14 @@ namespace ts {
1087010892
}
1087110893
}
1087210894

10895+
type ReplaceableIndexedAccessType = IndexedAccessType & { objectType: TypeParameter, indexType: TypeParameter };
10896+
function replaceIndexedAccess(instantiable: Type, type: ReplaceableIndexedAccessType, replacement: Type) {
10897+
// map type.indexType to 0
10898+
// map type.objectType to `[TReplacement]`
10899+
// thus making the indexed access `[TReplacement][0]` or `TReplacement`
10900+
return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getLiteralType(0), createTupleType([replacement])]));
10901+
}
10902+
1087310903
function getIndexInfoOfIndexSymbol(indexSymbol: Symbol, indexKind: IndexKind) {
1087410904
const declaration = getIndexDeclarationOfIndexSymbol(indexSymbol, indexKind);
1087510905
if (!declaration) return undefined;
@@ -10890,8 +10920,21 @@ namespace ts {
1089010920
inferredProp.declarations = prop.declarations;
1089110921
inferredProp.nameType = getSymbolLinks(prop).nameType;
1089210922
inferredProp.propertyType = getTypeOfSymbol(prop);
10893-
inferredProp.mappedType = type.mappedType;
10894-
inferredProp.constraintType = type.constraintType;
10923+
if (type.constraintType.type.flags & TypeFlags.IndexedAccess
10924+
&& (type.constraintType.type as IndexedAccessType).objectType.flags & TypeFlags.TypeParameter
10925+
&& (type.constraintType.type as IndexedAccessType).indexType.flags & TypeFlags.TypeParameter) {
10926+
// 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
10927+
// inferring to the "type parameter" (or indexed access) shared by the constraint and template. So, to reduce the number of
10928+
// type identities produced, we simplify such indexed access occurences
10929+
const newTypeParam = (type.constraintType.type as IndexedAccessType).objectType;
10930+
const newMappedType = replaceIndexedAccess(type.mappedType, type.constraintType.type as ReplaceableIndexedAccessType, newTypeParam);
10931+
inferredProp.mappedType = newMappedType as MappedType;
10932+
inferredProp.constraintType = getIndexType(newTypeParam) as IndexType;
10933+
}
10934+
else {
10935+
inferredProp.mappedType = type.mappedType;
10936+
inferredProp.constraintType = type.constraintType;
10937+
}
1089510938
members.set(prop.escapedName, inferredProp);
1089610939
}
1089710940
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
@@ -20611,7 +20654,11 @@ namespace ts {
2061120654
}
2061220655

2061320656
function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) {
20614-
return inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType);
20657+
const links = getSymbolLinks(symbol);
20658+
if (!links.type) {
20659+
links.type = inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType);
20660+
}
20661+
return links.type;
2061520662
}
2061620663

2061720664
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
@@ -4343,7 +4343,6 @@ namespace ts {
43434343
InObjectTypeLiteral = 1 << 22,
43444344
InTypeAlias = 1 << 23, // Writing type in type alias declaration
43454345
InInitialEntityName = 1 << 24, // Set when writing the LHS of an entity name or entity name expression
4346-
InReverseMappedType = 1 << 25,
43474346
}
43484347

43494348
// 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
@@ -2291,8 +2291,7 @@ declare namespace ts {
22912291
IgnoreErrors = 70221824,
22922292
InObjectTypeLiteral = 4194304,
22932293
InTypeAlias = 8388608,
2294-
InInitialEntityName = 16777216,
2295-
InReverseMappedType = 33554432
2294+
InInitialEntityName = 16777216
22962295
}
22972296
export enum TypeFormatFlags {
22982297
None = 0,

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,8 +2291,7 @@ declare namespace ts {
22912291
IgnoreErrors = 70221824,
22922292
InObjectTypeLiteral = 4194304,
22932293
InTypeAlias = 8388608,
2294-
InInitialEntityName = 16777216,
2295-
InReverseMappedType = 33554432
2294+
InInitialEntityName = 16777216
22962295
}
22972296
export enum TypeFormatFlags {
22982297
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)