Skip to content

Commit 85a3475

Browse files
authored
Merge pull request microsoft#26707 from mprobst/async-super-rename-safe
Per-property super accessors in async functions.
2 parents 6175e60 + 539c455 commit 85a3475

21 files changed

+503
-150
lines changed

src/compiler/binder.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3445,7 +3445,9 @@ namespace ts {
34453445
// ES6 syntax, and requires a lexical `this` binding.
34463446
if (transformFlags & TransformFlags.Super) {
34473447
transformFlags ^= TransformFlags.Super;
3448-
transformFlags |= TransformFlags.ContainsSuper;
3448+
// super inside of an async function requires hoisting the super access (ES2017).
3449+
// same for super inside of an async generator, which is ESNext.
3450+
transformFlags |= TransformFlags.ContainsSuper | TransformFlags.ContainsES2017 | TransformFlags.ContainsESNext;
34493451
}
34503452

34513453
node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
@@ -3461,7 +3463,9 @@ namespace ts {
34613463
// ES6 syntax, and requires a lexical `this` binding.
34623464
if (expressionFlags & TransformFlags.Super) {
34633465
transformFlags &= ~TransformFlags.Super;
3464-
transformFlags |= TransformFlags.ContainsSuper;
3466+
// super inside of an async function requires hoisting the super access (ES2017).
3467+
// same for super inside of an async generator, which is ESNext.
3468+
transformFlags |= TransformFlags.ContainsSuper | TransformFlags.ContainsES2017 | TransformFlags.ContainsESNext;
34653469
}
34663470

34673471
node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;

src/compiler/checker.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16183,16 +16183,18 @@ namespace ts {
1618316183
// // js
1618416184
// ...
1618516185
// asyncMethod() {
16186-
// const _super = name => super[name];
16186+
// const _super = Object.create(null, {
16187+
// asyncMethod: { get: () => super.asyncMethod },
16188+
// });
1618716189
// return __awaiter(this, arguments, Promise, function *() {
16188-
// let x = yield _super("asyncMethod").call(this);
16190+
// let x = yield _super.asyncMethod.call(this);
1618916191
// return x;
1619016192
// });
1619116193
// }
1619216194
// ...
1619316195
//
1619416196
// The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases
16195-
// are legal in ES6, but also likely less frequent, we emit the same more complex helper for both scenarios:
16197+
// are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment:
1619616198
//
1619716199
// // ts
1619816200
// ...
@@ -16204,19 +16206,20 @@ namespace ts {
1620416206
// // js
1620516207
// ...
1620616208
// asyncMethod(ar) {
16207-
// const _super = (function (geti, seti) {
16208-
// const cache = Object.create(null);
16209-
// return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } });
16210-
// })(name => super[name], (name, value) => super[name] = value);
16209+
// const _super = Object.create(null, {
16210+
// a: { get: () => super.a, set: (v) => super.a = v },
16211+
// b: { get: () => super.b, set: (v) => super.b = v }
16212+
// };
1621116213
// return __awaiter(this, arguments, Promise, function *() {
16212-
// [_super("a").value, _super("b").value] = yield ar;
16214+
// [_super.a, _super.b] = yield ar;
1621316215
// });
1621416216
// }
1621516217
// ...
1621616218
//
16217-
// This helper creates an object with a "value" property that wraps the `super` property or indexed access for both get and set.
16218-
// This is required for destructuring assignments, as a call expression cannot be used as the target of a destructuring assignment
16219-
// while a property access can.
16219+
// Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments
16220+
// as a call expression cannot be used as the target of a destructuring assignment while a property access can.
16221+
//
16222+
// For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations.
1622016223
if (container.kind === SyntaxKind.MethodDeclaration && hasModifier(container, ModifierFlags.Async)) {
1622116224
if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) {
1622216225
getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding;

src/compiler/transformers/es2017.ts

Lines changed: 144 additions & 14 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;
@@ -56,7 +65,6 @@ namespace ts {
5665
if ((node.transformFlags & TransformFlags.ContainsES2017) === 0) {
5766
return node;
5867
}
59-
6068
switch (node.kind) {
6169
case SyntaxKind.AsyncKeyword:
6270
// ES2017 async modifier should be elided for targets < ES2017
@@ -77,6 +85,18 @@ namespace ts {
7785
case SyntaxKind.ArrowFunction:
7886
return visitArrowFunction(<ArrowFunction>node);
7987

88+
case SyntaxKind.PropertyAccessExpression:
89+
if (capturedSuperProperties && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.SuperKeyword) {
90+
capturedSuperProperties.set(node.name.escapedText, true);
91+
}
92+
return visitEachChild(node, visitor, context);
93+
94+
case SyntaxKind.ElementAccessExpression:
95+
if (capturedSuperProperties && (<ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword) {
96+
hasSuperElementAccess = true;
97+
}
98+
return visitEachChild(node, visitor, context);
99+
80100
default:
81101
return visitEachChild(node, visitor, context);
82102
}
@@ -398,6 +418,11 @@ namespace ts {
398418
recordDeclarationName(parameter, enclosingFunctionParameterNames);
399419
}
400420

421+
const savedCapturedSuperProperties = capturedSuperProperties;
422+
const savedHasSuperElementAccess = hasSuperElementAccess;
423+
capturedSuperProperties = createUnderscoreEscapedMap<true>();
424+
hasSuperElementAccess = false;
425+
401426
let result: ConciseBody;
402427
if (!isArrowFunction) {
403428
const statements: Statement[] = [];
@@ -415,18 +440,26 @@ namespace ts {
415440

416441
addStatementsAfterPrologue(statements, endLexicalEnvironment());
417442

443+
// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
444+
// This step isn't needed if we eventually transform this to ES5.
445+
const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 && resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuperBinding | NodeCheckFlags.AsyncMethodWithSuper);
446+
447+
if (emitSuperHelpers) {
448+
enableSubstitutionForAsyncMethodsWithSuper();
449+
const variableStatement = createSuperAccessVariableStatement(resolver, node, capturedSuperProperties);
450+
substitutedSuperAccessors[getNodeId(variableStatement)] = true;
451+
addStatementsAfterPrologue(statements, [variableStatement]);
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;
560+
previousOnEmitNode(hint, node, emitCallback);
561+
enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags;
562+
return;
563+
}
519564
previousOnEmitNode(hint, node, emitCallback);
520565
}
521566

@@ -548,8 +593,10 @@ namespace ts {
548593

549594
function substitutePropertyAccessExpression(node: PropertyAccessExpression) {
550595
if (node.expression.kind === SyntaxKind.SuperKeyword) {
551-
return createSuperAccessInAsyncMethod(
552-
createLiteral(idText(node.name)),
596+
return setTextRange(
597+
createPropertyAccess(
598+
createFileLevelUniqueName("_super"),
599+
node.name),
553600
node
554601
);
555602
}
@@ -558,7 +605,7 @@ namespace ts {
558605

559606
function substituteElementAccessExpression(node: ElementAccessExpression) {
560607
if (node.expression.kind === SyntaxKind.SuperKeyword) {
561-
return createSuperAccessInAsyncMethod(
608+
return createSuperElementAccessInAsyncMethod(
562609
node.argumentExpression,
563610
node
564611
);
@@ -593,12 +640,12 @@ namespace ts {
593640
|| kind === SyntaxKind.SetAccessor;
594641
}
595642

596-
function createSuperAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
643+
function createSuperElementAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
597644
if (enclosingSuperContainerFlags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
598645
return setTextRange(
599646
createPropertyAccess(
600647
createCall(
601-
createFileLevelUniqueName("_super"),
648+
createFileLevelUniqueName("_superIndex"),
602649
/*typeArguments*/ undefined,
603650
[argumentExpression]
604651
),
@@ -610,7 +657,7 @@ namespace ts {
610657
else {
611658
return setTextRange(
612659
createCall(
613-
createFileLevelUniqueName("_super"),
660+
createFileLevelUniqueName("_superIndex"),
614661
/*typeArguments*/ undefined,
615662
[argumentExpression]
616663
),
@@ -620,6 +667,89 @@ namespace ts {
620667
}
621668
}
622669

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

673803
export const advancedAsyncSuperHelper: EmitHelper = {
674804
name: "typescript:advanced-async-super",
675805
scoped: true,
676806
text: helperString`
677-
const ${"_super"} = (function (geti, seti) {
807+
const ${"_superIndex"} = (function (geti, seti) {
678808
const cache = Object.create(null);
679809
return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } });
680810
})(name => super[name], (name, value) => super[name] = value);`

0 commit comments

Comments
 (0)