Skip to content

Commit 13b635d

Browse files
author
Josh Goldberg
committed
Class emit: cached repeat prototype repetitions in a variable
If two or more class members would assign to the class `.prototype`, that'll be stored in a temporary variable named like `proto`.
1 parent 73a2146 commit 13b635d

File tree

391 files changed

+21828
-20647
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

391 files changed

+21828
-20647
lines changed

src/compiler/core.ts

+19
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,25 @@ namespace ts {
12401240
return array[array.length - 1];
12411241
}
12421242

1243+
/**
1244+
* Returns whether an array has exactly one member that matches a filter.
1245+
*/
1246+
export function containsExactlyOne<T>(array: ReadonlyArray<T>, filter: (item: T) => boolean) {
1247+
let found = false;
1248+
1249+
for (const item of array) {
1250+
if (filter(item)) {
1251+
if (found) {
1252+
return false;
1253+
}
1254+
1255+
found = true;
1256+
}
1257+
}
1258+
1259+
return found;
1260+
}
1261+
12431262
/**
12441263
* Returns the only element of an array if it contains only one element, `undefined` otherwise.
12451264
*/

src/compiler/transformers/es2015.ts

+57-5
Original file line numberDiff line numberDiff line change
@@ -1543,21 +1543,28 @@ namespace ts {
15431543
* @param node The ClassExpression or ClassDeclaration node.
15441544
*/
15451545
function addClassMembers(statements: Statement[], node: ClassExpression | ClassDeclaration): void {
1546+
if (!node.members.length) {
1547+
return;
1548+
}
1549+
1550+
const prototypeStoragePlacement = statements.length;
1551+
let prototypeStorageName: Identifier | PropertyAccessExpression;
1552+
15461553
for (const member of node.members) {
15471554
switch (member.kind) {
15481555
case SyntaxKind.SemicolonClassElement:
15491556
statements.push(transformSemicolonClassElementToStatement(<SemicolonClassElement>member));
15501557
break;
15511558

15521559
case SyntaxKind.MethodDeclaration:
1553-
statements.push(transformClassMethodDeclarationToStatement(getClassMemberPrefix(node, member), <MethodDeclaration>member, node));
1560+
statements.push(transformClassMethodDeclarationToStatement(getClassMemberPrefix(node, member, getPrototypeStorageName), <MethodDeclaration>member, node));
15541561
break;
15551562

15561563
case SyntaxKind.GetAccessor:
15571564
case SyntaxKind.SetAccessor:
15581565
const accessors = getAllAccessorDeclarations(node.members, <AccessorDeclaration>member);
15591566
if (member === accessors.firstAccessor) {
1560-
statements.push(transformAccessorsToStatement(getClassMemberPrefix(node, member), accessors, node));
1567+
statements.push(transformAccessorsToStatement(getClassMemberPrefix(node, member, getPrototypeStorageName), accessors, node));
15611568
}
15621569

15631570
break;
@@ -1571,6 +1578,51 @@ namespace ts {
15711578
break;
15721579
}
15731580
}
1581+
1582+
/**
1583+
* Lazily creates the way class members will access the class prototype.
1584+
*/
1585+
function getPrototypeStorageName() {
1586+
if (prototypeStorageName) {
1587+
return prototypeStorageName;
1588+
}
1589+
1590+
const originalPrototypeAccess = createPropertyAccess(getInternalName(node), "prototype");
1591+
1592+
// If the class has exactly one non-static member, it'll access prototype members on itself:
1593+
// ClassName.prototype.member = ...
1594+
if (containsExactlyOne(node.members, classMemberAssignsToPrototype)) {
1595+
prototypeStorageName = originalPrototypeAccess;
1596+
return prototypeStorageName;
1597+
}
1598+
1599+
// Since it has multiple non-static members, it'll store that prototype as a variable that can be minified:
1600+
// var proto = ClassName.prototype;
1601+
// proto.memberOne = ...
1602+
// proto.memberTwo = ...
1603+
prototypeStorageName = createUniqueName("proto");
1604+
1605+
statements.splice(
1606+
prototypeStoragePlacement,
1607+
0,
1608+
createVariableStatement(
1609+
/*modifiers*/ undefined,
1610+
createVariableDeclarationList([
1611+
createVariableDeclaration(
1612+
prototypeStorageName,
1613+
/*type*/ undefined,
1614+
originalPrototypeAccess,
1615+
)
1616+
])
1617+
)
1618+
);
1619+
1620+
return prototypeStorageName;
1621+
}
1622+
}
1623+
1624+
function classMemberAssignsToPrototype(node: ClassElement) {
1625+
return !hasModifier(node, ModifierFlags.Static) && !isConstructorDeclaration(node);
15741626
}
15751627

15761628
/**
@@ -4299,10 +4351,10 @@ namespace ts {
42994351
return node;
43004352
}
43014353

4302-
function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) {
4303-
return hasModifier(member, ModifierFlags.Static)
4354+
function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement, getPrototypeStorageName: () => LeftHandSideExpression) {
4355+
return member && hasModifier(member, ModifierFlags.Static)
43044356
? getInternalName(node)
4305-
: createPropertyAccess(getInternalName(node), "prototype");
4357+
: getPrototypeStorageName();
43064358
}
43074359

43084360
function hasSynthesizedDefaultSuperCall(constructor: ConstructorDeclaration | undefined, hasExtendsClause: boolean) {

tests/baselines/reference/ES5For-ofTypeCheck10.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ for (var v of new StringIterator) { }
1919
var StringIterator = /** @class */ (function () {
2020
function StringIterator() {
2121
}
22-
StringIterator.prototype.next = function () {
22+
var proto_1 = StringIterator.prototype;
23+
proto_1.next = function () {
2324
return {
2425
done: true,
2526
value: ""
2627
};
2728
};
28-
StringIterator.prototype[Symbol.iterator] = function () {
29+
proto_1[Symbol.iterator] = function () {
2930
return this;
3031
};
3132
return StringIterator;

tests/baselines/reference/abstractProperty.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,13 @@ var C = /** @class */ (function (_super) {
4848
_this.ro = "readonly please";
4949
return _this;
5050
}
51-
Object.defineProperty(C.prototype, "prop", {
51+
var proto_1 = C.prototype;
52+
Object.defineProperty(proto_1, "prop", {
5253
get: function () { return "foo"; },
5354
set: function (v) { },
5455
enumerable: true,
5556
configurable: true
5657
});
57-
C.prototype.m = function () { };
58+
proto_1.m = function () { };
5859
return C;
5960
}(B));

tests/baselines/reference/abstractPropertyNegative.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,14 @@ var WrongTypeAccessorImpl2 = /** @class */ (function (_super) {
121121
var AbstractAccessorMismatch = /** @class */ (function () {
122122
function AbstractAccessorMismatch() {
123123
}
124-
Object.defineProperty(AbstractAccessorMismatch.prototype, "p1", {
124+
var proto_1 = AbstractAccessorMismatch.prototype;
125+
Object.defineProperty(proto_1, "p1", {
125126
set: function (val) { },
126127
enumerable: true,
127128
configurable: true
128129
});
129130
;
130-
Object.defineProperty(AbstractAccessorMismatch.prototype, "p2", {
131+
Object.defineProperty(proto_1, "p2", {
131132
get: function () { return "should work"; },
132133
enumerable: true,
133134
configurable: true

tests/baselines/reference/accessibilityModifiers.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,14 @@ var D = /** @class */ (function () {
127127
var E = /** @class */ (function () {
128128
function E() {
129129
}
130-
E.prototype.method = function () { };
131-
Object.defineProperty(E.prototype, "getter", {
130+
var proto_1 = E.prototype;
131+
proto_1.method = function () { };
132+
Object.defineProperty(proto_1, "getter", {
132133
get: function () { return 0; },
133134
enumerable: true,
134135
configurable: true
135136
});
136-
Object.defineProperty(E.prototype, "setter", {
137+
Object.defineProperty(proto_1, "setter", {
137138
set: function (a) { },
138139
enumerable: true,
139140
configurable: true

tests/baselines/reference/accessorWithLineTerminator.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ class C {
1111
var C = /** @class */ (function () {
1212
function C() {
1313
}
14-
Object.defineProperty(C.prototype, "x", {
14+
var proto_1 = C.prototype;
15+
Object.defineProperty(proto_1, "x", {
1516
get: function () { return 1; },
1617
set: function (v) { },
1718
enumerable: true,

tests/baselines/reference/accessorWithMismatchedAccessibilityModifiers.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class F {
3535
var C = /** @class */ (function () {
3636
function C() {
3737
}
38-
Object.defineProperty(C.prototype, "x", {
38+
var proto_1 = C.prototype;
39+
Object.defineProperty(proto_1, "x", {
3940
get: function () {
4041
return 1;
4142
},
@@ -49,7 +50,8 @@ var C = /** @class */ (function () {
4950
var D = /** @class */ (function () {
5051
function D() {
5152
}
52-
Object.defineProperty(D.prototype, "x", {
53+
var proto_2 = D.prototype;
54+
Object.defineProperty(proto_2, "x", {
5355
get: function () {
5456
return 1;
5557
},
@@ -63,7 +65,8 @@ var D = /** @class */ (function () {
6365
var E = /** @class */ (function () {
6466
function E() {
6567
}
66-
Object.defineProperty(E.prototype, "x", {
68+
var proto_3 = E.prototype;
69+
Object.defineProperty(proto_3, "x", {
6770
get: function () {
6871
return 1;
6972
},

tests/baselines/reference/accessorsAreNotContextuallyTyped.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ var r = c.x(''); // string
1818
var C = /** @class */ (function () {
1919
function C() {
2020
}
21-
Object.defineProperty(C.prototype, "x", {
21+
var proto_1 = C.prototype;
22+
Object.defineProperty(proto_1, "x", {
2223
get: function () {
2324
return function (x) { return ""; };
2425
},

tests/baselines/reference/accessors_spec_section-4.5_error-cases.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,26 @@ class LanguageSpec_section_4_5_error_cases {
1717
var LanguageSpec_section_4_5_error_cases = /** @class */ (function () {
1818
function LanguageSpec_section_4_5_error_cases() {
1919
}
20-
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedSetter_SetterFirst", {
20+
var proto_1 = LanguageSpec_section_4_5_error_cases.prototype;
21+
Object.defineProperty(proto_1, "AnnotatedSetter_SetterFirst", {
2122
get: function () { return ""; },
2223
set: function (a) { },
2324
enumerable: true,
2425
configurable: true
2526
});
26-
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedSetter_SetterLast", {
27+
Object.defineProperty(proto_1, "AnnotatedSetter_SetterLast", {
2728
get: function () { return ""; },
2829
set: function (a) { },
2930
enumerable: true,
3031
configurable: true
3132
});
32-
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedGetter_GetterFirst", {
33+
Object.defineProperty(proto_1, "AnnotatedGetter_GetterFirst", {
3334
get: function () { return ""; },
3435
set: function (aStr) { aStr = 0; },
3536
enumerable: true,
3637
configurable: true
3738
});
38-
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedGetter_GetterLast", {
39+
Object.defineProperty(proto_1, "AnnotatedGetter_GetterLast", {
3940
get: function () { return ""; },
4041
set: function (aStr) { aStr = 0; },
4142
enumerable: true,

tests/baselines/reference/accessors_spec_section-4.5_inference.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -53,37 +53,38 @@ var B = /** @class */ (function (_super) {
5353
var LanguageSpec_section_4_5_inference = /** @class */ (function () {
5454
function LanguageSpec_section_4_5_inference() {
5555
}
56-
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredGetterFromSetterAnnotation", {
56+
var proto_1 = LanguageSpec_section_4_5_inference.prototype;
57+
Object.defineProperty(proto_1, "InferredGetterFromSetterAnnotation", {
5758
get: function () { return new B(); },
5859
set: function (a) { },
5960
enumerable: true,
6061
configurable: true
6162
});
62-
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredGetterFromSetterAnnotation_GetterFirst", {
63+
Object.defineProperty(proto_1, "InferredGetterFromSetterAnnotation_GetterFirst", {
6364
get: function () { return new B(); },
6465
set: function (a) { },
6566
enumerable: true,
6667
configurable: true
6768
});
68-
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredFromGetter", {
69+
Object.defineProperty(proto_1, "InferredFromGetter", {
6970
get: function () { return new B(); },
7071
set: function (a) { },
7172
enumerable: true,
7273
configurable: true
7374
});
74-
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredFromGetter_SetterFirst", {
75+
Object.defineProperty(proto_1, "InferredFromGetter_SetterFirst", {
7576
get: function () { return new B(); },
7677
set: function (a) { },
7778
enumerable: true,
7879
configurable: true
7980
});
80-
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredSetterFromGetterAnnotation", {
81+
Object.defineProperty(proto_1, "InferredSetterFromGetterAnnotation", {
8182
get: function () { return new B(); },
8283
set: function (a) { },
8384
enumerable: true,
8485
configurable: true
8586
});
86-
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredSetterFromGetterAnnotation_GetterFirst", {
87+
Object.defineProperty(proto_1, "InferredSetterFromGetterAnnotation_GetterFirst", {
8788
get: function () { return new B(); },
8889
set: function (a) { },
8990
enumerable: true,

tests/baselines/reference/aliasUsageInAccessorsOfClass.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ var moduleA = require("./aliasUsage1_moduleA");
6868
var C2 = /** @class */ (function () {
6969
function C2() {
7070
}
71-
Object.defineProperty(C2.prototype, "A", {
71+
var proto_1 = C2.prototype;
72+
Object.defineProperty(proto_1, "A", {
7273
get: function () {
7374
return this.x;
7475
},

tests/baselines/reference/ambiguousCallsWhereReturnTypesAgree.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,22 @@ class TestClass2 {
3232
var TestClass = /** @class */ (function () {
3333
function TestClass() {
3434
}
35-
TestClass.prototype.bar = function (x) {
35+
var proto_1 = TestClass.prototype;
36+
proto_1.bar = function (x) {
3637
};
37-
TestClass.prototype.foo = function (x) {
38+
proto_1.foo = function (x) {
3839
this.bar(x); // should not error
3940
};
4041
return TestClass;
4142
}());
4243
var TestClass2 = /** @class */ (function () {
4344
function TestClass2() {
4445
}
45-
TestClass2.prototype.bar = function (x) {
46+
var proto_2 = TestClass2.prototype;
47+
proto_2.bar = function (x) {
4648
return 0;
4749
};
48-
TestClass2.prototype.foo = function (x) {
50+
proto_2.foo = function (x) {
4951
return this.bar(x); // should not error
5052
};
5153
return TestClass2;

tests/baselines/reference/anyIdenticalToItself.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ function foo(x, y) { }
1717
var C = /** @class */ (function () {
1818
function C() {
1919
}
20-
Object.defineProperty(C.prototype, "X", {
20+
var proto_1 = C.prototype;
21+
Object.defineProperty(proto_1, "X", {
2122
get: function () {
2223
var y;
2324
return y;

tests/baselines/reference/arrayAssignmentTest1.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ var __extends = (this && this.__extends) || (function () {
102102
var C1 = /** @class */ (function () {
103103
function C1() {
104104
}
105-
C1.prototype.IM1 = function () { return null; };
106-
C1.prototype.C1M1 = function () { return null; };
105+
var proto_1 = C1.prototype;
106+
proto_1.IM1 = function () { return null; };
107+
proto_1.C1M1 = function () { return null; };
107108
return C1;
108109
}());
109110
var C2 = /** @class */ (function (_super) {

tests/baselines/reference/arrayAssignmentTest2.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,9 @@ var __extends = (this && this.__extends) || (function () {
7676
var C1 = /** @class */ (function () {
7777
function C1() {
7878
}
79-
C1.prototype.IM1 = function () { return null; };
80-
C1.prototype.C1M1 = function () { return null; };
79+
var proto_1 = C1.prototype;
80+
proto_1.IM1 = function () { return null; };
81+
proto_1.C1M1 = function () { return null; };
8182
return C1;
8283
}());
8384
var C2 = /** @class */ (function (_super) {

0 commit comments

Comments
 (0)