Skip to content

Commit dc58fb5

Browse files
Immediately return the result of super calls when they are the first & last statement in a constructor.
1 parent bc4cf6d commit dc58fb5

File tree

1 file changed

+83
-36
lines changed
  • src/compiler/transformers

1 file changed

+83
-36
lines changed

src/compiler/transformers/es6.ts

Lines changed: 83 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,30 @@ namespace ts {
139139
loopOutParameters?: LoopOutParameter[];
140140
}
141141

142+
const enum SuperCaptureResult {
143+
/**
144+
* A capture may have been added for calls to 'super', but
145+
* the caller should emit subsequent statements normally.
146+
*/
147+
NoReplacement,
148+
/**
149+
* A call to 'super()' got replaced with a capturing statement like:
150+
*
151+
* var _this = _super.call(...) || this;
152+
*
153+
* Callers should skip the current statement.
154+
*/
155+
ReplaceSuperCapture,
156+
/**
157+
* A call to 'super()' got replaced with a capturing statement like:
158+
*
159+
* return _super.call(...) || this;
160+
*
161+
* Callers should skip the current statement and avoid any returns of '_this'.
162+
*/
163+
ReplaceWithReturn,
164+
}
165+
142166
export function transformES6(context: TransformationContext) {
143167
const {
144168
startLexicalEnvironment,
@@ -814,6 +838,7 @@ namespace ts {
814838
startLexicalEnvironment();
815839

816840
let statementOffset = -1;
841+
let thisCaptureStatus: SuperCaptureResult | undefined;
817842
if (hasSynthesizedSuper) {
818843
// If a super call has already been synthesized,
819844
// we're going to assume that we should just transform everything after that.
@@ -829,7 +854,11 @@ namespace ts {
829854
addDefaultValueAssignmentsIfNeeded(statements, constructor);
830855
addRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper);
831856
Debug.assert(statementOffset >= 0, "statementOffset not initialized correctly!");
832-
statementOffset = declareOrCaptureThisForConstructorIfNeeded(statements, constructor, !!extendsClauseElement, statementOffset);
857+
858+
thisCaptureStatus = declareOrCaptureOrReturnThisForConstructorIfNeeded(statements, constructor, !!extendsClauseElement, statementOffset);
859+
if (thisCaptureStatus === SuperCaptureResult.ReplaceSuperCapture || thisCaptureStatus === SuperCaptureResult.ReplaceWithReturn) {
860+
statementOffset++;
861+
}
833862
}
834863

835864
addDefaultSuperCallIfNeeded(statements, constructor, extendsClauseElement, hasSynthesizedSuper);
@@ -841,7 +870,9 @@ namespace ts {
841870

842871
// Return `_this` unless we're sure enough that it would be pointless to add a return statement.
843872
// If there's a constructor that we can tell returns in enough places, then we *do not* want to add a return.
844-
if (extendsClauseElement && !(constructor && isSufficientlyCoveredByReturnStatements(constructor.body))) {
873+
if (extendsClauseElement
874+
&& thisCaptureStatus !== SuperCaptureResult.ReplaceWithReturn
875+
&& !(constructor && isSufficientlyCoveredByReturnStatements(constructor.body))) {
845876
statements.push(
846877
createReturn(
847878
createIdentifier("_this")
@@ -1190,45 +1221,61 @@ namespace ts {
11901221
*
11911222
* @returns The new statement offset into the `statements` array.
11921223
*/
1193-
function declareOrCaptureThisForConstructorIfNeeded(statements: Statement[], ctor: ConstructorDeclaration, hasExtendsClause: boolean, firstNonPrologue: number) {
1194-
if (hasExtendsClause) {
1195-
// Most of the time, a 'super' call will be the first real statement in a constructor body.
1196-
// In these cases, we'd like to transform these into a *single* statement instead of a declaration
1197-
// followed by an assignment statement for '_this'. For instance, if we emitted without an initializer,
1198-
// we'd get:
1199-
//
1200-
// var _this;
1201-
// _this = super();
1202-
//
1203-
// instead of
1204-
//
1205-
// var _this = super();
1206-
//
1207-
let initializer: Expression = undefined;
1208-
let firstStatement: Statement;
1209-
if (firstNonPrologue < ctor.body.statements.length) {
1210-
firstStatement = ctor.body.statements[firstNonPrologue];
1211-
1212-
if (firstStatement.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((firstStatement as ExpressionStatement).expression)) {
1213-
const superCall = (firstStatement as ExpressionStatement).expression as CallExpression;
1214-
initializer = setOriginalNode(
1215-
saveStateAndInvoke(superCall, visitImmediateSuperCallInBody),
1216-
superCall
1217-
);
1218-
}
1219-
}
1220-
captureThisForNode(statements, ctor, /*initializer*/ initializer, firstStatement);
1224+
function declareOrCaptureOrReturnThisForConstructorIfNeeded(statements: Statement[], ctor: ConstructorDeclaration, hasExtendsClause: boolean, statementOffset: number) {
1225+
// If this isn't a derived class, just capture 'this' for arrow functions if necessary.
1226+
if (!hasExtendsClause) {
1227+
addCaptureThisForNodeIfNeeded(statements, ctor);
1228+
return SuperCaptureResult.NoReplacement;
1229+
}
12211230

1222-
// We're actually skipping an extra statement. Signal this to the caller.
1223-
if (initializer) {
1224-
return firstNonPrologue + 1;
1231+
// Most of the time, a 'super' call will be the first real statement in a constructor body.
1232+
// In these cases, we'd like to transform these into a *single* statement instead of a declaration
1233+
// followed by an assignment statement for '_this'. For instance, if we emitted without an initializer,
1234+
// we'd get:
1235+
//
1236+
// var _this;
1237+
// _this = _super.call(...) || this;
1238+
//
1239+
// instead of
1240+
//
1241+
// var _this = _super.call(...) || this;
1242+
//
1243+
// Additionally, if the 'super()' call is the last statement, we should just avoid capturing
1244+
// entirely and immediately return the result like so:
1245+
//
1246+
// return _super.call(...) || this;
1247+
//
1248+
let firstStatement: Statement;
1249+
let superCallExpression: Expression;
1250+
1251+
const ctorStatements = ctor.body.statements;
1252+
if (statementOffset < ctorStatements.length) {
1253+
firstStatement = ctorStatements[statementOffset];
1254+
1255+
if (firstStatement.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((firstStatement as ExpressionStatement).expression)) {
1256+
const superCall = (firstStatement as ExpressionStatement).expression as CallExpression;
1257+
superCallExpression = setOriginalNode(
1258+
saveStateAndInvoke(superCall, visitImmediateSuperCallInBody),
1259+
superCall
1260+
);
12251261
}
12261262
}
1227-
else {
1228-
addCaptureThisForNodeIfNeeded(statements, ctor);
1263+
1264+
// Return the result if we have an immediate super() call on the last statement.
1265+
if (superCallExpression && statementOffset === ctorStatements.length - 1) {
1266+
statements.push(createReturn(superCallExpression));
1267+
return SuperCaptureResult.ReplaceWithReturn;
1268+
}
1269+
1270+
// Perform the capture.
1271+
captureThisForNode(statements, ctor, superCallExpression, firstStatement);
1272+
1273+
// If we're actually replacing the original statement, we need to signal this to the caller.
1274+
if (superCallExpression) {
1275+
return SuperCaptureResult.ReplaceSuperCapture;
12291276
}
12301277

1231-
return firstNonPrologue;
1278+
return SuperCaptureResult.NoReplacement;
12321279
}
12331280

12341281
/**

0 commit comments

Comments
 (0)