Skip to content

Class emit: cached repeat prototype sets in a variable #33363

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
  •  
  •  
  •  
76 changes: 72 additions & 4 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1575,21 +1575,54 @@ namespace ts {
* @param node The ClassExpression or ClassDeclaration node.
*/
function addClassMembers(statements: Statement[], node: ClassExpression | ClassDeclaration): void {
if (!node.members.length) {
return;
}

const originalPrototypeAccess = factory.createPropertyAccessExpression(factory.getInternalName(node), "prototype");
let prototypeStorageName: Identifier | PropertyAccessExpression;

// If the class has multiple non-static member names, it'll store that prototype as a variable that can be minified:
// var ClassName_prototype = ClassName.prototype;
// ClassName_prototype.memberOne = ...
// ClassName_prototype.memberTwo = ...
if (membersContainMultipleUniqueNames(node.members)) {
prototypeStorageName = factory.createUniqueName(node.name ? `${node.name.escapedText}_prototype` : "proto", GeneratedIdentifierFlags.Optimistic);
statements.push(
factory.createVariableStatement(
/*modifiers*/ undefined,
factory.createVariableDeclarationList([
factory.createVariableDeclaration(
prototypeStorageName,
/*exclamationToken*/ undefined,
/*type*/ undefined,
originalPrototypeAccess
)
])
)
);
}
// Since the class has exactly one non-static member, it'll access that prototype member directly on itself:
// ClassName.prototype.member = ...
else {
prototypeStorageName = originalPrototypeAccess;
}

for (const member of node.members) {
switch (member.kind) {
case SyntaxKind.SemicolonClassElement:
statements.push(transformSemicolonClassElementToStatement(<SemicolonClassElement>member));
break;

case SyntaxKind.MethodDeclaration:
statements.push(transformClassMethodDeclarationToStatement(getClassMemberPrefix(node, member), <MethodDeclaration>member, node));
statements.push(transformClassMethodDeclarationToStatement(getClassMemberPrefix(node, member, prototypeStorageName), <MethodDeclaration>member, node));
break;

case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
const accessors = getAllAccessorDeclarations(node.members, <AccessorDeclaration>member);
if (member === accessors.firstAccessor) {
statements.push(transformAccessorsToStatement(getClassMemberPrefix(node, member), accessors, node));
statements.push(transformAccessorsToStatement(getClassMemberPrefix(node, member, prototypeStorageName), accessors, node));
}

break;
Expand All @@ -1605,6 +1638,41 @@ namespace ts {
}
}

function classMemberAssignsToPrototype(node: ClassElement) {
return !(getEffectiveModifierFlags(node) & ModifierFlags.Static) && !isConstructorDeclaration(node);
}

function membersContainMultipleUniqueNames(members: NodeArray<ClassElement>) {
if (members.length <= 1) {
return false;
}

let foundName: string | undefined;

for (const member of members) {
if (!classMemberAssignsToPrototype(member)) {
continue;
}

// If a name isn't immediately identifiable, we assume it's unique
if (!member.name || !isPropertyNameLiteral(member.name)) {
return true;
}

const text = getTextOfIdentifierOrLiteral(member.name);
if (foundName === undefined) {
foundName = text;
continue;
}

if (text !== foundName) {
return true;
}
}

return false;
}

/**
* Transforms a SemicolonClassElement into a statement for a class body function.
*
Expand Down Expand Up @@ -4313,10 +4381,10 @@ namespace ts {
return node;
}

function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) {
function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement, prototypeStorageName: LeftHandSideExpression) {
return hasSyntacticModifier(member, ModifierFlags.Static)
? factory.getInternalName(node)
: factory.createPropertyAccessExpression(factory.getInternalName(node), "prototype");
: prototypeStorageName;
}

function hasSynthesizedDefaultSuperCall(constructor: ConstructorDeclaration | undefined, hasExtendsClause: boolean) {
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/ES5For-ofTypeCheck10.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ for (var v of new StringIterator) { }
var StringIterator = /** @class */ (function () {
function StringIterator() {
}
StringIterator.prototype.next = function () {
var StringIterator_prototype = StringIterator.prototype;
StringIterator_prototype.next = function () {
return {
done: true,
value: ""
};
};
StringIterator.prototype[Symbol.iterator] = function () {
StringIterator_prototype[Symbol.iterator] = function () {
return this;
};
return StringIterator;
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/abstractProperty.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ var C = /** @class */ (function (_super) {
_this.ro = "readonly please";
return _this;
}
Object.defineProperty(C.prototype, "prop", {
var C_prototype = C.prototype;
Object.defineProperty(C_prototype, "prop", {
get: function () { return "foo"; },
set: function (v) { },
enumerable: false,
configurable: true
});
C.prototype.m = function () { };
C_prototype.m = function () { };
return C;
}(B));
5 changes: 3 additions & 2 deletions tests/baselines/reference/abstractPropertyNegative.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,14 @@ var WrongTypeAccessorImpl2 = /** @class */ (function (_super) {
var AbstractAccessorMismatch = /** @class */ (function () {
function AbstractAccessorMismatch() {
}
Object.defineProperty(AbstractAccessorMismatch.prototype, "p1", {
var AbstractAccessorMismatch_prototype = AbstractAccessorMismatch.prototype;
Object.defineProperty(AbstractAccessorMismatch_prototype, "p1", {
set: function (val) { },
enumerable: false,
configurable: true
});
;
Object.defineProperty(AbstractAccessorMismatch.prototype, "p2", {
Object.defineProperty(AbstractAccessorMismatch_prototype, "p2", {
get: function () { return "should work"; },
enumerable: false,
configurable: true
Expand Down
7 changes: 4 additions & 3 deletions tests/baselines/reference/accessibilityModifiers.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,14 @@ var D = /** @class */ (function () {
var E = /** @class */ (function () {
function E() {
}
E.prototype.method = function () { };
Object.defineProperty(E.prototype, "getter", {
var E_prototype = E.prototype;
E_prototype.method = function () { };
Object.defineProperty(E_prototype, "getter", {
get: function () { return 0; },
enumerable: false,
configurable: true
});
Object.defineProperty(E.prototype, "setter", {
Object.defineProperty(E_prototype, "setter", {
set: function (a) { },
enumerable: false,
configurable: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,26 @@ class LanguageSpec_section_4_5_error_cases {
var LanguageSpec_section_4_5_error_cases = /** @class */ (function () {
function LanguageSpec_section_4_5_error_cases() {
}
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedSetter_SetterFirst", {
var LanguageSpec_section_4_5_error_cases_prototype = LanguageSpec_section_4_5_error_cases.prototype;
Object.defineProperty(LanguageSpec_section_4_5_error_cases_prototype, "AnnotatedSetter_SetterFirst", {
get: function () { return ""; },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedSetter_SetterLast", {
Object.defineProperty(LanguageSpec_section_4_5_error_cases_prototype, "AnnotatedSetter_SetterLast", {
get: function () { return ""; },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedGetter_GetterFirst", {
Object.defineProperty(LanguageSpec_section_4_5_error_cases_prototype, "AnnotatedGetter_GetterFirst", {
get: function () { return ""; },
set: function (aStr) { aStr = 0; },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedGetter_GetterLast", {
Object.defineProperty(LanguageSpec_section_4_5_error_cases_prototype, "AnnotatedGetter_GetterLast", {
get: function () { return ""; },
set: function (aStr) { aStr = 0; },
enumerable: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,37 +55,38 @@ var B = /** @class */ (function (_super) {
var LanguageSpec_section_4_5_inference = /** @class */ (function () {
function LanguageSpec_section_4_5_inference() {
}
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredGetterFromSetterAnnotation", {
var LanguageSpec_section_4_5_inference_prototype = LanguageSpec_section_4_5_inference.prototype;
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredGetterFromSetterAnnotation", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredGetterFromSetterAnnotation_GetterFirst", {
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredGetterFromSetterAnnotation_GetterFirst", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredFromGetter", {
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredFromGetter", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredFromGetter_SetterFirst", {
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredFromGetter_SetterFirst", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredSetterFromGetterAnnotation", {
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredSetterFromGetterAnnotation", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredSetterFromGetterAnnotation_GetterFirst", {
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredSetterFromGetterAnnotation_GetterFirst", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
Expand Down
10 changes: 6 additions & 4 deletions tests/baselines/reference/ambiguousCallsWhereReturnTypesAgree.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,22 @@ class TestClass2 {
var TestClass = /** @class */ (function () {
function TestClass() {
}
TestClass.prototype.bar = function (x) {
var TestClass_prototype = TestClass.prototype;
TestClass_prototype.bar = function (x) {
};
TestClass.prototype.foo = function (x) {
TestClass_prototype.foo = function (x) {
this.bar(x); // should not error
};
return TestClass;
}());
var TestClass2 = /** @class */ (function () {
function TestClass2() {
}
TestClass2.prototype.bar = function (x) {
var TestClass2_prototype = TestClass2.prototype;
TestClass2_prototype.bar = function (x) {
return 0;
};
TestClass2.prototype.foo = function (x) {
TestClass2_prototype.foo = function (x) {
return this.bar(x); // should not error
};
return TestClass2;
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/arrayAssignmentTest1.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ var __extends = (this && this.__extends) || (function () {
var C1 = /** @class */ (function () {
function C1() {
}
C1.prototype.IM1 = function () { return null; };
C1.prototype.C1M1 = function () { return null; };
var C1_prototype = C1.prototype;
C1_prototype.IM1 = function () { return null; };
C1_prototype.C1M1 = function () { return null; };
return C1;
}());
var C2 = /** @class */ (function (_super) {
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/arrayAssignmentTest2.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ var __extends = (this && this.__extends) || (function () {
var C1 = /** @class */ (function () {
function C1() {
}
C1.prototype.IM1 = function () { return null; };
C1.prototype.C1M1 = function () { return null; };
var C1_prototype = C1.prototype;
C1_prototype.IM1 = function () { return null; };
C1_prototype.C1M1 = function () { return null; };
return C1;
}());
var C2 = /** @class */ (function (_super) {
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/arrayAssignmentTest5.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ var Test;
var Bug = /** @class */ (function () {
function Bug() {
}
Bug.prototype.onEnter = function (line, state, offset) {
var Bug_prototype = Bug.prototype;
Bug_prototype.onEnter = function (line, state, offset) {
var lineTokens = this.tokenize(line, state, true);
var tokens = lineTokens.tokens;
if (tokens.length === 0) {
return this.onEnter(line, tokens, offset); // <== this should produce an error since onEnter can not be called with (string, IStateToken[], offset)
}
};
Bug.prototype.tokenize = function (line, state, includeStates) {
Bug_prototype.tokenize = function (line, state, includeStates) {
return null;
};
return Bug;
Expand Down
10 changes: 6 additions & 4 deletions tests/baselines/reference/arrayBestCommonTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,12 @@ var EmptyTypes;
var f = /** @class */ (function () {
function f() {
}
f.prototype.voidIfAny = function (x, y) {
var f_prototype = f.prototype;
f_prototype.voidIfAny = function (x, y) {
if (y === void 0) { y = false; }
return null;
};
f.prototype.x = function () {
f_prototype.x = function () {
(this.voidIfAny([4, 2][0]));
(this.voidIfAny([4, 2, undefined][0]));
(this.voidIfAny([undefined, 2, 4][0]));
Expand Down Expand Up @@ -204,11 +205,12 @@ var NonEmptyTypes;
var f = /** @class */ (function () {
function f() {
}
f.prototype.voidIfAny = function (x, y) {
var f_prototype_1 = f.prototype;
f_prototype_1.voidIfAny = function (x, y) {
if (y === void 0) { y = false; }
return null;
};
f.prototype.x = function () {
f_prototype_1.x = function () {
(this.voidIfAny([4, 2][0]));
(this.voidIfAny([4, 2, undefined][0]));
(this.voidIfAny([undefined, 2, 4][0]));
Expand Down
Loading