Skip to content

Commit f0826cf

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 variable `_superProps` that contains accessors for each property accessed on super within the async method. This allows accessing the properties by name (instead of quoted string), which fixes the quoted/unquoted confusion. The change keeps the generic accessor for element access statements to match quoting behaviour. Fixes microsoft#21088.
1 parent 9aeb6e2 commit f0826cf

20 files changed

+471
-131
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16180,9 +16180,9 @@ namespace ts {
1618016180
// // js
1618116181
// ...
1618216182
// asyncMethod() {
16183-
// const _super = name => super[name];
16183+
// const _super_asyncMethod = name => super.asyncMethod;
1618416184
// return __awaiter(this, arguments, Promise, function *() {
16185-
// let x = yield _super("asyncMethod").call(this);
16185+
// let x = yield _super_asyncMethod.call(this);
1618616186
// return x;
1618716187
// });
1618816188
// }
@@ -16201,19 +16201,19 @@ namespace ts {
1620116201
// // js
1620216202
// ...
1620316203
// asyncMethod(ar) {
16204-
// const _super = (function (geti, seti) {
16205-
// const cache = Object.create(null);
16206-
// return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } });
16207-
// })(name => super[name], (name, value) => super[name] = value);
16204+
// const _super_a = {get value() { return super.a; }, set value(v) { super.a = v; }};
16205+
// const _super_b = {get value() { return super.b; }, set value(v) { super.b = v; }};
1620816206
// return __awaiter(this, arguments, Promise, function *() {
16209-
// [_super("a").value, _super("b").value] = yield ar;
16207+
// [_super_a.value, _super_b.value] = yield ar;
1621016208
// });
1621116209
// }
1621216210
// ...
1621316211
//
16214-
// This helper creates an object with a "value" property that wraps the `super` property or indexed access for both get and set.
16215-
// This is required for destructuring assignments, as a call expression cannot be used as the target of a destructuring assignment
16216-
// while a property access can.
16212+
// This helper creates an object with a "value" property that wraps the `super` property for both get and set. This is required for
16213+
// destructuring assignments, as a call expression cannot be used as the target of a destructuring assignment while a property
16214+
// access can.
16215+
//
16216+
// For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations.
1621716217
if (container.kind === SyntaxKind.MethodDeclaration && hasModifier(container, ModifierFlags.Async)) {
1621816218
if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) {
1621916219
getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding;

src/compiler/transformers/es2017.ts

Lines changed: 140 additions & 13 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,26 @@ 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+
const variableStatement = createSuperAccessVariableStatement(resolver, node, capturedSuperProperties);
447+
substitutedSuperAccessors[getNodeId(variableStatement)] = true;
448+
addStatementsAfterPrologue(statements, [variableStatement]);
449+
}
450+
418451
const block = createBlock(statements, /*multiLine*/ true);
419452
setTextRange(block, node.body);
420453

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) {
454+
if (emitSuperHelpers && hasSuperElementAccess) {
455+
// Emit helpers for super element access expressions (`super[x]`).
424456
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) {
425-
enableSubstitutionForAsyncMethodsWithSuper();
426457
addEmitHelper(block, advancedAsyncSuperHelper);
427458
}
428459
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
429-
enableSubstitutionForAsyncMethodsWithSuper();
430460
addEmitHelper(block, asyncSuperHelper);
431461
}
432462
}
@@ -452,6 +482,8 @@ namespace ts {
452482
}
453483

454484
enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames;
485+
capturedSuperProperties = savedCapturedSuperProperties;
486+
hasSuperElementAccess = savedHasSuperElementAccess;
455487
return result;
456488
}
457489

@@ -493,6 +525,8 @@ namespace ts {
493525
context.enableEmitNotification(SyntaxKind.GetAccessor);
494526
context.enableEmitNotification(SyntaxKind.SetAccessor);
495527
context.enableEmitNotification(SyntaxKind.Constructor);
528+
// We need to be notified when entering the generated accessor arrow functions.
529+
context.enableEmitNotification(SyntaxKind.VariableStatement);
496530
}
497531
}
498532

@@ -516,6 +550,14 @@ namespace ts {
516550
return;
517551
}
518552
}
553+
// Disable substitution in the generated super accessor itself.
554+
else if (enabledSubstitutions && substitutedSuperAccessors[getNodeId(node)]) {
555+
const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags;
556+
enclosingSuperContainerFlags = 0 as NodeCheckFlags;
557+
previousOnEmitNode(hint, node, emitCallback);
558+
enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags;
559+
return;
560+
}
519561
previousOnEmitNode(hint, node, emitCallback);
520562
}
521563

@@ -548,8 +590,10 @@ namespace ts {
548590

549591
function substitutePropertyAccessExpression(node: PropertyAccessExpression) {
550592
if (node.expression.kind === SyntaxKind.SuperKeyword) {
551-
return createSuperAccessInAsyncMethod(
552-
createLiteral(idText(node.name)),
593+
return setTextRange(
594+
createPropertyAccess(
595+
createFileLevelUniqueName("_superProps"),
596+
node.name),
553597
node
554598
);
555599
}
@@ -558,7 +602,7 @@ namespace ts {
558602

559603
function substituteElementAccessExpression(node: ElementAccessExpression) {
560604
if (node.expression.kind === SyntaxKind.SuperKeyword) {
561-
return createSuperAccessInAsyncMethod(
605+
return createSuperElementAccessInAsyncMethod(
562606
node.argumentExpression,
563607
node
564608
);
@@ -593,7 +637,7 @@ namespace ts {
593637
|| kind === SyntaxKind.SetAccessor;
594638
}
595639

596-
function createSuperAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
640+
function createSuperElementAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
597641
if (enclosingSuperContainerFlags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
598642
return setTextRange(
599643
createPropertyAccess(
@@ -620,6 +664,89 @@ namespace ts {
620664
}
621665
}
622666

667+
/** Creates a variable named `_superProps` with accessor properties for the given property names. */
668+
export function createSuperAccessVariableStatement(resolver: EmitResolver, node: FunctionLikeDeclaration, names: UnderscoreEscapedMap<true>) {
669+
// Create a variable declaration with a getter/setter (if binding) definition for each name:
670+
// const _superProps = Object.create(null, { x: { get: () => super.x, set: (v) => super.x = v }, ... });
671+
const hasBinding = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) !== 0;
672+
const accessors: PropertyAssignment[] = [];
673+
names.forEach((_, key) => {
674+
const name = unescapeLeadingUnderscores(key);
675+
const getterAndSetter: PropertyAssignment[] = [];
676+
getterAndSetter.push(createPropertyAssignment(
677+
"get",
678+
createArrowFunction(
679+
/* modifiers */ undefined,
680+
/* typeParameters */ undefined,
681+
/* parameters */ [],
682+
/* type */ undefined,
683+
/* equalsGreaterThanToken */ undefined,
684+
createPropertyAccess(
685+
createSuper(),
686+
name
687+
)
688+
)
689+
));
690+
if (hasBinding) {
691+
getterAndSetter.push(
692+
createPropertyAssignment(
693+
"set",
694+
createArrowFunction(
695+
/* modifiers */ undefined,
696+
/* typeParameters */ undefined,
697+
/* parameters */ [
698+
createParameter(
699+
/* decorators */ undefined,
700+
/* modifiers */ undefined,
701+
/* dotDotDotToken */ undefined,
702+
"v",
703+
/* questionToken */ undefined,
704+
/* type */ undefined,
705+
/* initializer */ undefined
706+
)
707+
],
708+
/* type */ undefined,
709+
/* equalsGreaterThanToken */ undefined,
710+
createAssignment(
711+
createPropertyAccess(
712+
createSuper(),
713+
name),
714+
createIdentifier("v")
715+
)
716+
)
717+
)
718+
);
719+
}
720+
accessors.push(
721+
createPropertyAssignment(
722+
name,
723+
createObjectLiteral(getterAndSetter),
724+
)
725+
);
726+
});
727+
return createVariableStatement(
728+
/* modifiers */ undefined,
729+
createVariableDeclarationList(
730+
[
731+
createVariableDeclaration(
732+
createFileLevelUniqueName("_superProps"),
733+
/* type */ undefined,
734+
createCall(
735+
createPropertyAccess(
736+
createIdentifier("Object"),
737+
"create"
738+
),
739+
/* typeArguments */ undefined,
740+
[
741+
createNull(),
742+
createObjectLiteral(accessors, /* multiline */ true)
743+
]
744+
)
745+
)
746+
],
747+
NodeFlags.Const));
748+
}
749+
623750
const awaiterHelper: EmitHelper = {
624751
name: "typescript:awaiter",
625752
scoped: false,

0 commit comments

Comments
 (0)