Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 55 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5141,9 +5141,26 @@ namespace ts {
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
}

function shouldUsePlaceholderForProperty(propertySymbol: Symbol, context: NodeBuilderContext) {
// Use placeholders for reverse mapped types we've either already descended into, or which
// are nested reverse mappings within a mapping over a non-anonymous type. The later is a restriction mostly just to
// reduce the blowup in printback size from doing, eg, a deep reverse mapping over `Window`.
// Since anonymous types usually come from expressions, this allows us to preserve the output
// for deep mappings which likely come from expressions, while truncating those parts which
// come from mappings over library functions.
return !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped)
&& (
contains(context.reverseMappedStack, propertySymbol as ReverseMappedSymbol)
|| (
context.reverseMappedStack?.[0]
&& !(getObjectFlags(last(context.reverseMappedStack).propertyType) & ObjectFlags.Anonymous)
)
);
}

function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) {
const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped);
const propertyType = propertyIsReverseMapped && context.flags & NodeBuilderFlags.InReverseMappedType ?
const propertyType = shouldUsePlaceholderForProperty(propertySymbol, context) ?
anyType : getTypeOfSymbol(propertySymbol);
const saveEnclosingDeclaration = context.enclosingDeclaration;
context.enclosingDeclaration = undefined;
Expand Down Expand Up @@ -5174,16 +5191,20 @@ namespace ts {
}
}
else {
const savedFlags = context.flags;
context.flags |= propertyIsReverseMapped ? NodeBuilderFlags.InReverseMappedType : 0;
let propertyTypeNode: TypeNode;
if (propertyIsReverseMapped && !!(savedFlags & NodeBuilderFlags.InReverseMappedType)) {
if (shouldUsePlaceholderForProperty(propertySymbol, context)) {
propertyTypeNode = createElidedInformationPlaceholder(context);
}
else {
if (propertyIsReverseMapped) {
context.reverseMappedStack ||= [];
context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol);
}
propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, propertyType, propertySymbol, saveEnclosingDeclaration) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
if (propertyIsReverseMapped) {
context.reverseMappedStack!.pop();
}
}
context.flags = savedFlags;

const modifiers = isReadonlySymbol(propertySymbol) ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined;
if (modifiers) {
Expand Down Expand Up @@ -7711,6 +7732,7 @@ namespace ts {
typeParameterNamesByText?: Set<string>;
usedSymbolNames?: Set<string>;
remappedSymbolNames?: ESMap<SymbolId, string>;
reverseMappedStack?: ReverseMappedSymbol[];
}

function isDefaultBindingContext(location: Node) {
Expand Down Expand Up @@ -10865,6 +10887,14 @@ namespace ts {
}
}

type ReplaceableIndexedAccessType = IndexedAccessType & { objectType: TypeParameter, indexType: TypeParameter };
function replaceIndexedAccess(instantiable: Type, type: ReplaceableIndexedAccessType, replacement: Type) {
// map type.indexType to 0
// map type.objectType to `[TReplacement]`
// thus making the indexed access `[TReplacement][0]` or `TReplacement`
return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getLiteralType(0), createTupleType([replacement])]));
}

function getIndexInfoOfIndexSymbol(indexSymbol: Symbol, indexKind: IndexKind) {
const declaration = getIndexDeclarationOfIndexSymbol(indexSymbol, indexKind);
if (!declaration) return undefined;
Expand All @@ -10885,8 +10915,21 @@ namespace ts {
inferredProp.declarations = prop.declarations;
inferredProp.nameType = getSymbolLinks(prop).nameType;
inferredProp.propertyType = getTypeOfSymbol(prop);
inferredProp.mappedType = type.mappedType;
inferredProp.constraintType = type.constraintType;
if (type.constraintType.type.flags & TypeFlags.IndexedAccess
&& (type.constraintType.type as IndexedAccessType).objectType.flags & TypeFlags.TypeParameter
&& (type.constraintType.type as IndexedAccessType).indexType.flags & TypeFlags.TypeParameter) {
// 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
// inferring to the "type parameter" (or indexed access) shared by the constraint and template. So, to reduce the number of
// type identities produced, we simplify such indexed access occurences
const newTypeParam = (type.constraintType.type as IndexedAccessType).objectType;
const newMappedType = replaceIndexedAccess(type.mappedType, type.constraintType.type as ReplaceableIndexedAccessType, newTypeParam);
inferredProp.mappedType = newMappedType as MappedType;
inferredProp.constraintType = getIndexType(newTypeParam) as IndexType;
}
else {
inferredProp.mappedType = type.mappedType;
inferredProp.constraintType = type.constraintType;
}
members.set(prop.escapedName, inferredProp);
}
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
Expand Down Expand Up @@ -20597,7 +20640,11 @@ namespace ts {
}

function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) {
return inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType);
const links = getSymbolLinks(symbol);
if (!links.type) {
links.type = inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType);
}
return links.type;
}

function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type {
Expand Down
1 change: 0 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4343,7 +4343,6 @@ namespace ts {
InObjectTypeLiteral = 1 << 22,
InTypeAlias = 1 << 23, // Writing type in type alias declaration
InInitialEntityName = 1 << 24, // Set when writing the LHS of an entity name or entity name expression
InReverseMappedType = 1 << 25,
}

// Ensure the shared flags between this and `NodeBuilderFlags` stay in alignment
Expand Down
3 changes: 1 addition & 2 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2291,8 +2291,7 @@ declare namespace ts {
IgnoreErrors = 70221824,
InObjectTypeLiteral = 4194304,
InTypeAlias = 8388608,
InInitialEntityName = 16777216,
InReverseMappedType = 33554432
InInitialEntityName = 16777216
}
export enum TypeFormatFlags {
None = 0,
Expand Down
3 changes: 1 addition & 2 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2291,8 +2291,7 @@ declare namespace ts {
IgnoreErrors = 70221824,
InObjectTypeLiteral = 4194304,
InTypeAlias = 8388608,
InInitialEntityName = 16777216,
InReverseMappedType = 33554432
InInitialEntityName = 16777216
}
export enum TypeFormatFlags {
None = 0,
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/genericFunctionInference2.types
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ declare const foo: Reducer<MyState['combined']['foo']>;

const myReducer1: Reducer<MyState> = combineReducers({
>myReducer1 : Reducer<MyState>
>combineReducers({ combined: combineReducers({ foo }),}) : Reducer<{ combined: { foo: any; }; }>
>combineReducers({ combined: combineReducers({ foo }),}) : Reducer<{ combined: { foo: number; }; }>
>combineReducers : <S>(reducers: { [K in keyof S]: Reducer<S[K]>; }) => Reducer<S>
>{ combined: combineReducers({ foo }),} : { combined: Reducer<{ foo: number; }>; }

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

const myReducer2 = combineReducers({
>myReducer2 : Reducer<{ combined: { foo: any; }; }>
>combineReducers({ combined: combineReducers({ foo }),}) : Reducer<{ combined: { foo: any; }; }>
>myReducer2 : Reducer<{ combined: { foo: number; }; }>
>combineReducers({ combined: combineReducers({ foo }),}) : Reducer<{ combined: { foo: number; }; }>
>combineReducers : <S>(reducers: { [K in keyof S]: Reducer<S[K]>; }) => Reducer<S>
>{ combined: combineReducers({ foo }),} : { combined: Reducer<{ foo: number; }>; }

Expand Down
6 changes: 4 additions & 2 deletions tests/baselines/reference/isomorphicMappedTypeInference.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,14 @@ declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
declare var g1: (...args: any[]) => {
sum: number;
nested: {
mul: any;
mul: string;
};
};
declare var g2: (...args: any[]) => {
foo: {
bar: any;
bar: {
baz: boolean;
};
};
};
declare const foo: <T>(object: T, partial: Partial<T>) => T;
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/isomorphicMappedTypeInference.types
Original file line number Diff line number Diff line change
Expand Up @@ -434,8 +434,8 @@ declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;

// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } }
var g1 = applySpec({
>g1 : (...args: any[]) => { sum: number; nested: { mul: any; }; }
>applySpec({ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }}) : (...args: any[]) => { sum: number; nested: { mul: any; }; }
>g1 : (...args: any[]) => { sum: number; nested: { mul: string; }; }
>applySpec({ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }}) : (...args: any[]) => { sum: number; nested: { mul: string; }; }
>applySpec : <T>(obj: Spec<T>) => (...args: any[]) => T
>{ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }} : { sum: (a: any) => number; nested: { mul: (b: any) => string; }; }

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

// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
>g2 : (...args: any[]) => { foo: { bar: any; }; }
>applySpec({ foo: { bar: { baz: (x: any) => true } } }) : (...args: any[]) => { foo: { bar: any; }; }
>g2 : (...args: any[]) => { foo: { bar: { baz: boolean; }; }; }
>applySpec({ foo: { bar: { baz: (x: any) => true } } }) : (...args: any[]) => { foo: { bar: { baz: boolean; }; }; }
>applySpec : <T>(obj: Spec<T>) => (...args: any[]) => T
>{ foo: { bar: { baz: (x: any) => true } } } : { foo: { bar: { baz: (x: any) => boolean; }; }; }
>foo : { bar: { baz: (x: any) => boolean; }; }
Expand Down
Loading