From 9c6f65175b281438fe5ac935262bb4e3ba5f3c73 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 15 Sep 2017 10:05:14 -0700 Subject: [PATCH] Refactor truthy-spread-union creation for performance Only create properties once, only if needed, and don't create an intermediate anonymous type. The code is also inlined with the rest of `getSpreadType`. --- src/compiler/checker.ts | 62 +++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a7c98f657dc..2dbf8b9253f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7822,6 +7822,7 @@ namespace ts { * and right = the new element to be spread. */ function getSpreadType(left: Type, right: Type): Type { + let truthyRight: Type; if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) { return anyType; } @@ -7832,25 +7833,18 @@ namespace ts { return left; } if (left.flags & TypeFlags.Union) { - // if the union is `false | T` make all the properties of T optional - const wl = getPartialTypeFromFalsyUnion(left as UnionType); - if (wl) { - left = wl; - } - else { - return mapType(left, t => getSpreadType(t, right)); - } + return mapType(left, t => getSpreadType(t, right)); } if (right.flags & TypeFlags.Union) { - const wr = getPartialTypeFromFalsyUnion(right as UnionType); - if (wr) { - right = wr; - } - else { + truthyRight = getTruthyTypeFromFalsyUnion(right as UnionType); + if (!truthyRight || truthyRight.flags & TypeFlags.Union) { return mapType(right, t => getSpreadType(left, t)); } + else { + right = truthyRight; + } } - if (right.flags & (TypeFlags.NonPrimitive | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.StringLike)) { + if (right.flags & (TypeFlags.NonPrimitive | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.StringLike | TypeFlags.EnumLike)) { return emptyObjectType; } @@ -7875,9 +7869,10 @@ namespace ts { skippedPrivateMembers.set(rightProp.escapedName, true); } else if (!isClassMethod(rightProp) && !isSetterWithoutGetter) { - members.set(rightProp.escapedName, getNonReadonlySymbol(rightProp)); + members.set(rightProp.escapedName, getSymbolOfSpreadProperty(rightProp, !!truthyRight)); } } + for (const leftProp of getPropertiesOfType(left)) { if (leftProp.flags & SymbolFlags.SetAccessor && !(leftProp.flags & SymbolFlags.GetAccessor) || skippedPrivateMembers.has(leftProp.escapedName) @@ -7899,19 +7894,22 @@ namespace ts { } } else { - members.set(leftProp.escapedName, getNonReadonlySymbol(leftProp)); + members.set(leftProp.escapedName, getSymbolOfSpreadProperty(leftProp, /*makeOptional*/ false)); } } return createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); } - function getNonReadonlySymbol(prop: Symbol) { - if (!isReadonlySymbol(prop)) { + function getSymbolOfSpreadProperty(prop: Symbol, makeOptional: boolean) { + if (!isReadonlySymbol(prop) && (!makeOptional || prop.flags & SymbolFlags.Optional)) { return prop; } - const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional); + const flags = SymbolFlags.Property | (makeOptional ? SymbolFlags.Optional : prop.flags & SymbolFlags.Optional); const result = createSymbol(flags, prop.escapedName); result.type = getTypeOfSymbol(prop); + if (makeOptional) { + result.type = getUnionType([result.type, undefinedType]); + } result.declarations = prop.declarations; result.syntheticOrigin = prop; return result; @@ -7921,25 +7919,10 @@ namespace ts { return prop.flags & SymbolFlags.Method && find(prop.declarations, decl => isClassLike(decl.parent)); } - function getPartialTypeFromFalsyUnion(type: UnionType): Type | undefined { - if (type.types.length === 2) { - const truthy = removeDefinitelyFalsyTypes(type); - if (truthy !== type) { - const members = createSymbolTable(); - for (const prop of getPropertiesOfType(truthy)) { - if (prop.flags & SymbolFlags.Optional) { - members.set(prop.escapedName, prop); - } - else { - const result = createSymbol(prop.flags | SymbolFlags.Optional, prop.escapedName); - result.type = getUnionType([getTypeOfSymbol(prop), undefinedType]); - result.declarations = prop.declarations; - result.syntheticOrigin = prop; - members.set(prop.escapedName, result); - } - } - return createAnonymousType(undefined, members, emptyArray, emptyArray, getIndexInfoOfType(truthy, IndexKind.String), getIndexInfoOfType(truthy, IndexKind.Number)); - } + function getTruthyTypeFromFalsyUnion(type: UnionType): Type | undefined { + const truthy = removeDefinitelyFalsyTypes(type); + if (truthy !== type) { + return truthy; } } @@ -13784,7 +13767,8 @@ namespace ts { } function isValidSpreadType(type: Type): boolean { - return !!(type.flags & (TypeFlags.Any | TypeFlags.PossiblyFalsy | TypeFlags.NonPrimitive) || + return !!(type.flags & (TypeFlags.Any | TypeFlags.NonPrimitive) || + getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) || type.flags & TypeFlags.Object && !isGenericMappedType(type) || type.flags & TypeFlags.UnionOrIntersection && !forEach((type).types, t => !isValidSpreadType(t))); }