Skip to content

Commit 25da0a9

Browse files
committed
When calculating spreads, merge empty object into nonempty object to produce partial object to reduce complexity
1 parent 239937d commit 25da0a9

File tree

7 files changed

+1040
-18
lines changed

7 files changed

+1040
-18
lines changed

src/compiler/checker.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12522,6 +12522,55 @@ namespace ts {
1252212522
return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type);
1252312523
}
1252412524

12525+
function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) {
12526+
return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index));
12527+
}
12528+
12529+
function tryMergeUnionOfObjectTypeAndEmptyObject(type: UnionType, readonly: boolean): Type | undefined {
12530+
if (type.types.length === 2) {
12531+
const firstType = type.types[0];
12532+
const secondType = type.types[1];
12533+
if (every(type.types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) {
12534+
return isEmptyObjectType(firstType) ? firstType : isEmptyObjectType(secondType) ? secondType : emptyObjectType;
12535+
}
12536+
if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(firstType)) {
12537+
return getAnonymousPartialType(secondType);
12538+
}
12539+
if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(secondType)) {
12540+
return getAnonymousPartialType(firstType);
12541+
}
12542+
}
12543+
12544+
function getAnonymousPartialType(type: Type) {
12545+
// gets the type as if it had been spread, but where everything in the spread is made optional
12546+
const members = createSymbolTable();
12547+
for (const prop of getPropertiesOfType(type)) {
12548+
if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) {
12549+
// do nothing, skip privates
12550+
}
12551+
else if (isSpreadableProperty(prop)) {
12552+
const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
12553+
const flags = SymbolFlags.Property | SymbolFlags.Optional;
12554+
const result = createSymbol(flags, prop.escapedName, readonly ? CheckFlags.Readonly : 0);
12555+
result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop);
12556+
result.declarations = prop.declarations;
12557+
result.nameType = prop.nameType;
12558+
result.syntheticOrigin = prop;
12559+
members.set(prop.escapedName, result);
12560+
}
12561+
}
12562+
const spread = createAnonymousType(
12563+
type.symbol,
12564+
members,
12565+
emptyArray,
12566+
emptyArray,
12567+
getIndexInfoOfType(type, IndexKind.String),
12568+
getIndexInfoOfType(type, IndexKind.Number));
12569+
spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
12570+
return spread;
12571+
}
12572+
}
12573+
1252512574
/**
1252612575
* Since the source of spread types are object literals, which are not binary,
1252712576
* this function should be called in a left folding style, with left = previous result of getSpreadType
@@ -12541,9 +12590,17 @@ namespace ts {
1254112590
return left;
1254212591
}
1254312592
if (left.flags & TypeFlags.Union) {
12593+
const merged = tryMergeUnionOfObjectTypeAndEmptyObject(left as UnionType, readonly);
12594+
if (merged) {
12595+
return getSpreadType(merged, right, symbol, objectFlags, readonly);
12596+
}
1254412597
return mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly));
1254512598
}
1254612599
if (right.flags & TypeFlags.Union) {
12600+
const merged = tryMergeUnionOfObjectTypeAndEmptyObject(right as UnionType, readonly);
12601+
if (merged) {
12602+
return getSpreadType(left, merged, symbol, objectFlags, readonly);
12603+
}
1254712604
return mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly));
1254812605
}
1254912606
if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//// [objectSpreadRepeatedNullCheckPerf.ts]
2+
interface Props {
3+
readonly a?: string
4+
readonly b?: string
5+
readonly c?: string
6+
readonly d?: string
7+
readonly e?: string
8+
readonly f?: string
9+
readonly g?: string
10+
readonly h?: string
11+
readonly i?: string
12+
readonly j?: string
13+
readonly k?: string
14+
readonly l?: string
15+
readonly m?: string
16+
readonly n?: string
17+
readonly o?: string
18+
readonly p?: string
19+
readonly q?: string
20+
readonly r?: string
21+
readonly s?: string
22+
readonly t?: string
23+
readonly u?: string
24+
readonly v?: string
25+
readonly w?: string
26+
readonly x?: string
27+
readonly y?: string
28+
readonly z?: string
29+
}
30+
31+
function parseWithSpread(config: Record<string, number>): Props {
32+
return {
33+
...config.a !== undefined && { a: config.a.toString() },
34+
...config.b !== undefined && { b: config.b.toString() },
35+
...config.c !== undefined && { c: config.c.toString() },
36+
...config.d !== undefined && { d: config.d.toString() },
37+
...config.e !== undefined && { e: config.e.toString() },
38+
...config.f !== undefined && { f: config.f.toString() },
39+
...config.g !== undefined && { g: config.g.toString() },
40+
...config.h !== undefined && { h: config.h.toString() },
41+
...config.i !== undefined && { i: config.i.toString() },
42+
...config.j !== undefined && { j: config.j.toString() },
43+
...config.k !== undefined && { k: config.k.toString() },
44+
...config.l !== undefined && { l: config.l.toString() },
45+
...config.m !== undefined && { m: config.m.toString() },
46+
...config.n !== undefined && { n: config.n.toString() },
47+
...config.o !== undefined && { o: config.o.toString() },
48+
...config.p !== undefined && { p: config.p.toString() },
49+
...config.q !== undefined && { q: config.q.toString() },
50+
...config.r !== undefined && { r: config.r.toString() },
51+
...config.s !== undefined && { s: config.s.toString() },
52+
...config.t !== undefined && { t: config.t.toString() },
53+
...config.u !== undefined && { u: config.u.toString() },
54+
...config.v !== undefined && { v: config.v.toString() },
55+
...config.w !== undefined && { w: config.w.toString() },
56+
...config.x !== undefined && { x: config.x.toString() },
57+
...config.y !== undefined && { y: config.y.toString() },
58+
...config.z !== undefined && { z: config.z.toString() }
59+
}
60+
}
61+
62+
parseWithSpread({ a: 1, b: 2, z: 26 })
63+
64+
//// [objectSpreadRepeatedNullCheckPerf.js]
65+
"use strict";
66+
var __assign = (this && this.__assign) || function () {
67+
__assign = Object.assign || function(t) {
68+
for (var s, i = 1, n = arguments.length; i < n; i++) {
69+
s = arguments[i];
70+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
71+
t[p] = s[p];
72+
}
73+
return t;
74+
};
75+
return __assign.apply(this, arguments);
76+
};
77+
function parseWithSpread(config) {
78+
return __assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign({}, config.a !== undefined && { a: config.a.toString() }), config.b !== undefined && { b: config.b.toString() }), config.c !== undefined && { c: config.c.toString() }), config.d !== undefined && { d: config.d.toString() }), config.e !== undefined && { e: config.e.toString() }), config.f !== undefined && { f: config.f.toString() }), config.g !== undefined && { g: config.g.toString() }), config.h !== undefined && { h: config.h.toString() }), config.i !== undefined && { i: config.i.toString() }), config.j !== undefined && { j: config.j.toString() }), config.k !== undefined && { k: config.k.toString() }), config.l !== undefined && { l: config.l.toString() }), config.m !== undefined && { m: config.m.toString() }), config.n !== undefined && { n: config.n.toString() }), config.o !== undefined && { o: config.o.toString() }), config.p !== undefined && { p: config.p.toString() }), config.q !== undefined && { q: config.q.toString() }), config.r !== undefined && { r: config.r.toString() }), config.s !== undefined && { s: config.s.toString() }), config.t !== undefined && { t: config.t.toString() }), config.u !== undefined && { u: config.u.toString() }), config.v !== undefined && { v: config.v.toString() }), config.w !== undefined && { w: config.w.toString() }), config.x !== undefined && { x: config.x.toString() }), config.y !== undefined && { y: config.y.toString() }), config.z !== undefined && { z: config.z.toString() });
79+
}
80+
parseWithSpread({ a: 1, b: 2, z: 26 });

0 commit comments

Comments
 (0)