From 647421f593fc70d4937b51d1f3fe95720a2da6a8 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 23 Sep 2020 17:09:29 -0700 Subject: [PATCH] Cache property name widening contexts When creating a new `WideningContext` for a given property, check whether there's an existing `WideningContext` with the same `parent` and `propertyName`. Otherwise, we won't adequately leverage the cache of computed `siblings` stored on the `WideningContext`. --- src/compiler/checker.ts | 20 ++++++++++++++++---- src/compiler/types.ts | 9 +++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7750d8af73e51..12d621e5cee69 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19212,8 +19212,20 @@ namespace ts { return regularNew; } - function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: Type[] | undefined): WideningContext { - return { parent, propertyName, siblings, resolvedProperties: undefined }; + function createSiblingWideningContext(siblings: Type[]): WideningContext { + return { siblings }; + } + + function createPropertyWideningContext(parent: WideningContext, propertyName: __String): WideningContext { + const cache = (parent.childContexts ??= new Map<__String, WideningContext>()); + const existing = cache.get(propertyName); + if (existing) { + return existing; + } + + const context: WideningContext = { parent, propertyName }; + cache.set(propertyName, context); + return context; } function getSiblingsOfContext(context: WideningContext): Type[] { @@ -19256,7 +19268,7 @@ namespace ts { return prop; } const original = getTypeOfSymbol(prop); - const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined); + const propContext = context && createPropertyWideningContext(context, prop.escapedName); const widened = getWidenedTypeWithContext(original, propContext); return widened === original ? prop : createSymbolWithType(prop, widened); } @@ -19310,7 +19322,7 @@ namespace ts { result = getWidenedTypeOfObjectLiteral(type, context); } else if (type.flags & TypeFlags.Union) { - const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type).types); + const unionContext = context || createSiblingWideningContext((type).types); const widenedTypes = sameMap((type).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext)); // Widening an empty object literal transitions from a highly restrictive type to // a highly inclusive one. For that reason we perform subtype reduction here if the diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a584cf7a0994e..9d5c68f843a94 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5550,10 +5550,11 @@ namespace ts { /* @internal */ export interface WideningContext { - parent?: WideningContext; // Parent context - propertyName?: __String; // Name of property in parent - siblings?: Type[]; // Types of siblings - resolvedProperties?: Symbol[]; // Properties occurring in sibling object literals + parent?: WideningContext; // Parent context + propertyName?: __String; // Name of property in parent + siblings?: Type[]; // Types of siblings + resolvedProperties?: Symbol[]; // Properties occurring in sibling object literals + childContexts?: ESMap<__String, WideningContext>; // WideningContexts with parent === this and propertyName as the key } /* @internal */