Skip to content

Commit dcf2f7e

Browse files
committed
Per-property super accessors in async functions.
TypeScript must hoist accessors for super properties when converting async method bodies to the `__awaiter` pattern for targets before ES2016. Previously, TypeScript would reify all property accesses into element accesses, i.e. convert the property name into a string parameter and pass it to `super[...]`. That breaks optimizers like Closure Compiler or Uglify in advanced mode, when property renaming is enabled, as it mixes quoted and un-quoted property access (`super['x']` vs just `x` at the declaration site). This change creates a specific accessor for each property access on super, which fixes the quoted/unquoted confusion. It keeps the generic accessor for element access statements. Fixes #21088.
1 parent ac0d5da commit dcf2f7e

File tree

6 files changed

+253
-69
lines changed

6 files changed

+253
-69
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15688,9 +15688,9 @@ namespace ts {
1568815688
// // js
1568915689
// ...
1569015690
// asyncMethod() {
15691-
// const _super = name => super[name];
15691+
// const _super_asyncMethod = name => super.asyncMethod;
1569215692
// return __awaiter(this, arguments, Promise, function *() {
15693-
// let x = yield _super("asyncMethod").call(this);
15693+
// let x = yield _super_asyncMethod.call(this);
1569415694
// return x;
1569515695
// });
1569615696
// }
@@ -15709,19 +15709,19 @@ namespace ts {
1570915709
// // js
1571015710
// ...
1571115711
// asyncMethod(ar) {
15712-
// const _super = (function (geti, seti) {
15713-
// const cache = Object.create(null);
15714-
// return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } });
15715-
// })(name => super[name], (name, value) => super[name] = value);
15712+
// const _super_a = {get value() { return super.a; }, set value(v) { super.a = v; }};
15713+
// const _super_b = {get value() { return super.b; }, set value(v) { super.b = v; }};
1571615714
// return __awaiter(this, arguments, Promise, function *() {
15717-
// [_super("a").value, _super("b").value] = yield ar;
15715+
// [_super_a.value, _super_b.value] = yield ar;
1571815716
// });
1571915717
// }
1572015718
// ...
1572115719
//
15722-
// This helper creates an object with a "value" property that wraps the `super` property or indexed access for both get and set.
15723-
// This is required for destructuring assignments, as a call expression cannot be used as the target of a destructuring assignment
15724-
// while a property access can.
15720+
// This helper creates an object with a "value" property that wraps the `super` property for both get and set. This is required for
15721+
// destructuring assignments, as a call expression cannot be used as the target of a destructuring assignment while a property
15722+
// access can.
15723+
//
15724+
// For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations.
1572515725
if (container.kind === SyntaxKind.MethodDeclaration && hasModifier(container, ModifierFlags.Async)) {
1572615726
if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) {
1572715727
getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding;

src/compiler/transformers/es2017.ts

Lines changed: 137 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ namespace ts {
3232

3333
let enclosingFunctionParameterNames: UnderscoreEscapedMap<true>;
3434

35+
/**
36+
* Keeps track of property names accessed on super (`super.x`) within async functions.
37+
*/
38+
let capturedSuperProperties: UnderscoreEscapedMap<true>;
39+
/** Whether the async function contains an element access on super (`super[x]`). */
40+
let hasSuperElementAccess: boolean;
41+
/** A set of node IDs for generated super accessors (variable statements). */
42+
const substitutedSuperAccessors: boolean[] = [];
43+
3544
// Save the previous transformation hooks.
3645
const previousOnEmitNode = context.onEmitNode;
3746
const previousOnSubstituteNode = context.onSubstituteNode;
@@ -53,10 +62,6 @@ namespace ts {
5362
}
5463

5564
function visitor(node: Node): VisitResult<Node> {
56-
if ((node.transformFlags & TransformFlags.ContainsES2017) === 0) {
57-
return node;
58-
}
59-
6065
switch (node.kind) {
6166
case SyntaxKind.AsyncKeyword:
6267
// ES2017 async modifier should be elided for targets < ES2017
@@ -77,6 +82,18 @@ namespace ts {
7782
case SyntaxKind.ArrowFunction:
7883
return visitArrowFunction(<ArrowFunction>node);
7984

85+
case SyntaxKind.PropertyAccessExpression:
86+
if (capturedSuperProperties && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.SuperKeyword) {
87+
capturedSuperProperties.set(node.name.escapedText, true);
88+
}
89+
return visitEachChild(node, visitor, context);
90+
91+
case SyntaxKind.ElementAccessExpression:
92+
if (capturedSuperProperties && (<ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword) {
93+
hasSuperElementAccess = true;
94+
}
95+
return visitEachChild(node, visitor, context);
96+
8097
default:
8198
return visitEachChild(node, visitor, context);
8299
}
@@ -398,6 +415,11 @@ namespace ts {
398415
recordDeclarationName(parameter, enclosingFunctionParameterNames);
399416
}
400417

418+
const savedCapturedSuperProperties = capturedSuperProperties;
419+
const savedHasSuperElementAccess = hasSuperElementAccess;
420+
capturedSuperProperties = createUnderscoreEscapedMap<true>();
421+
hasSuperElementAccess = false;
422+
401423
let result: ConciseBody;
402424
if (!isArrowFunction) {
403425
const statements: Statement[] = [];
@@ -415,18 +437,29 @@ namespace ts {
415437

416438
addStatementsAfterPrologue(statements, endLexicalEnvironment());
417439

440+
// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
441+
// This step isn't needed if we eventually transform this to ES5.
442+
const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 && resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuperBinding | NodeCheckFlags.AsyncMethodWithSuper);
443+
444+
if (emitSuperHelpers) {
445+
enableSubstitutionForAsyncMethodsWithSuper();
446+
// For each super property access (`super.x`), emit a `_super_x` accessor arrow function.
447+
capturedSuperProperties.forEach((_, key) => {
448+
const variableStatement = createSuperAccessVariableStatement(resolver, node, key);
449+
substitutedSuperAccessors[getNodeId(variableStatement)] = true;
450+
addStatementsAfterPrologue(statements, [variableStatement]);
451+
});
452+
}
453+
418454
const block = createBlock(statements, /*multiLine*/ true);
419455
setTextRange(block, node.body);
420456

421-
// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
422-
// This step isn't needed if we eventually transform this to ES5.
423-
if (languageVersion >= ScriptTarget.ES2015) {
457+
if (emitSuperHelpers && hasSuperElementAccess) {
458+
// Emit helpers for super element access expressions (`super[x]`).
424459
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) {
425-
enableSubstitutionForAsyncMethodsWithSuper();
426460
addEmitHelper(block, advancedAsyncSuperHelper);
427461
}
428462
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
429-
enableSubstitutionForAsyncMethodsWithSuper();
430463
addEmitHelper(block, asyncSuperHelper);
431464
}
432465
}
@@ -452,6 +485,8 @@ namespace ts {
452485
}
453486

454487
enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames;
488+
capturedSuperProperties = savedCapturedSuperProperties;
489+
hasSuperElementAccess = savedHasSuperElementAccess;
455490
return result;
456491
}
457492

@@ -493,6 +528,8 @@ namespace ts {
493528
context.enableEmitNotification(SyntaxKind.GetAccessor);
494529
context.enableEmitNotification(SyntaxKind.SetAccessor);
495530
context.enableEmitNotification(SyntaxKind.Constructor);
531+
// We need to be notified when entering the generated accessor arrow functions.
532+
context.enableEmitNotification(SyntaxKind.VariableStatement);
496533
}
497534
}
498535

@@ -516,6 +553,14 @@ namespace ts {
516553
return;
517554
}
518555
}
556+
// Disable substitution in the generated super accessor itself.
557+
else if (enabledSubstitutions && substitutedSuperAccessors[getNodeId(node)]) {
558+
const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags;
559+
enclosingSuperContainerFlags = 0 as NodeCheckFlags;
560+
previousOnEmitNode(hint, node, emitCallback);
561+
enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags;
562+
return;
563+
}
519564
previousOnEmitNode(hint, node, emitCallback);
520565
}
521566

@@ -548,17 +593,33 @@ namespace ts {
548593

549594
function substitutePropertyAccessExpression(node: PropertyAccessExpression) {
550595
if (node.expression.kind === SyntaxKind.SuperKeyword) {
551-
return createSuperAccessInAsyncMethod(
552-
createLiteral(idText(node.name)),
553-
node
554-
);
596+
if (enclosingSuperContainerFlags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
597+
return setTextRange(
598+
createPropertyAccess(
599+
createCall(
600+
createFileLevelUniqueName("_super_" + idText(node.name)),
601+
/* typeArguments */ undefined,
602+
/* argumentsArray */ undefined),
603+
"value"),
604+
node
605+
);
606+
}
607+
else {
608+
return setTextRange(
609+
createCall(
610+
createFileLevelUniqueName("_super_" + idText(node.name)),
611+
/* typeArguments */ undefined,
612+
/* argumentsArray */ undefined),
613+
node
614+
);
615+
}
555616
}
556617
return node;
557618
}
558619

559620
function substituteElementAccessExpression(node: ElementAccessExpression) {
560621
if (node.expression.kind === SyntaxKind.SuperKeyword) {
561-
return createSuperAccessInAsyncMethod(
622+
return createSuperElementAccessInAsyncMethod(
562623
node.argumentExpression,
563624
node
564625
);
@@ -593,7 +654,7 @@ namespace ts {
593654
|| kind === SyntaxKind.SetAccessor;
594655
}
595656

596-
function createSuperAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
657+
function createSuperElementAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
597658
if (enclosingSuperContainerFlags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
598659
return setTextRange(
599660
createPropertyAccess(
@@ -620,6 +681,67 @@ namespace ts {
620681
}
621682
}
622683

684+
/** Creates a variable statement with an accessor function for the given super property name. */
685+
export function createSuperAccessVariableStatement(resolver: EmitResolver, node: FunctionLikeDeclaration, name: __String) {
686+
let accessor: Expression;
687+
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) {
688+
// For async methods assigning into a super property, emit an object literal containing a "value" getter and setter.
689+
accessor = createObjectLiteral([
690+
createGetAccessor(
691+
/* decorators */ undefined,
692+
/* modifiers */ undefined,
693+
"value",
694+
/* parameters */ [],
695+
/* type */ undefined,
696+
createBlock([
697+
createReturn(createPropertyAccess(
698+
createSuper(),
699+
unescapeLeadingUnderscores(name)))])),
700+
createSetAccessor(
701+
/* decorators */ undefined,
702+
/* modifiers */ undefined,
703+
"value",
704+
/* parameters */ [
705+
createParameter(
706+
/* decorators */ undefined,
707+
/* modifiers */ undefined,
708+
/* dotDotDotToken */ undefined,
709+
"v")],
710+
createBlock([
711+
createExpressionStatement(
712+
createAssignment(
713+
createPropertyAccess(
714+
createSuper(),
715+
unescapeLeadingUnderscores(name)),
716+
createIdentifier("v")))])),
717+
]);
718+
}
719+
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
720+
accessor = createArrowFunction(
721+
/* modifiers */ undefined,
722+
/* typeParameters */ undefined,
723+
[],
724+
/* type */ undefined,
725+
/* equalsGreaterThanToken */ undefined,
726+
createPropertyAccess(
727+
createSuper(),
728+
unescapeLeadingUnderscores(name)));
729+
}
730+
else {
731+
return Debug.fail(`unexpected node check flags ${resolver.getNodeCheckFlags(node)}`);
732+
}
733+
return createVariableStatement(
734+
/* modifiers */ undefined,
735+
createVariableDeclarationList(
736+
[
737+
createVariableDeclaration(
738+
createFileLevelUniqueName("_super_" + name),
739+
/* type */ undefined,
740+
accessor)
741+
],
742+
NodeFlags.Const));
743+
}
744+
623745
const awaiterHelper: EmitHelper = {
624746
name: "typescript:awaiter",
625747
scoped: false,

0 commit comments

Comments
 (0)