Navigation Menu

Skip to content

Commit

Permalink
Fix super property transform in async arrow in method (#51240)
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Oct 21, 2022
1 parent eed0511 commit 3abd351
Show file tree
Hide file tree
Showing 8 changed files with 438 additions and 17 deletions.
9 changes: 6 additions & 3 deletions src/compiler/checker.ts
Expand Up @@ -26706,13 +26706,16 @@ namespace ts {
const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true);
let container = immediateContainer;
let needToCaptureLexicalThis = false;
let inAsyncFunction = false;

// adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting
if (!isCallExpression) {
while (container && container.kind === SyntaxKind.ArrowFunction) {
if (hasSyntacticModifier(container, ModifierFlags.Async)) inAsyncFunction = true;
container = getSuperContainer(container, /*stopOnFunctions*/ true);
needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015;
}
if (container && hasSyntacticModifier(container, ModifierFlags.Async)) inAsyncFunction = true;
}

const canUseSuperExpression = isLegalUsageOfSuperExpression(container);
Expand Down Expand Up @@ -26824,12 +26827,12 @@ namespace ts {
// as a call expression cannot be used as the target of a destructuring assignment while a property access can.
//
// For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations.
if (container.kind === SyntaxKind.MethodDeclaration && hasSyntacticModifier(container, ModifierFlags.Async)) {
if (container.kind === SyntaxKind.MethodDeclaration && inAsyncFunction) {
if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) {
getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding;
getNodeLinks(container).flags |= NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync;
}
else {
getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper;
getNodeLinks(container).flags |= NodeCheckFlags.MethodWithSuperPropertyAccessInAsync;
}
}

Expand Down
91 changes: 84 additions & 7 deletions src/compiler/transformers/es2017.ts
Expand Up @@ -140,8 +140,11 @@ namespace ts {
return visitEachChild(node, visitor, context);

case SyntaxKind.GetAccessor:
return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitGetAccessorDeclaration, node as GetAccessorDeclaration);
case SyntaxKind.SetAccessor:
return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitSetAccessorDeclaration, node as SetAccessorDeclaration);
case SyntaxKind.Constructor:
return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitConstructorDeclaration, node as ConstructorDeclaration);
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitDefault, node);
Expand Down Expand Up @@ -278,6 +281,15 @@ namespace ts {
);
}

function visitConstructorDeclaration(node: ConstructorDeclaration) {
return factory.updateConstructorDeclaration(
node,
visitNodes(node.modifiers, visitor, isModifierLike),
visitParameterList(node.parameters, visitor, context),
transformMethodBody(node)
);
}

/**
* Visits a MethodDeclaration node.
*
Expand All @@ -298,7 +310,28 @@ namespace ts {
/*type*/ undefined,
getFunctionFlags(node) & FunctionFlags.Async
? transformAsyncFunctionBody(node)
: visitFunctionBody(node.body, visitor, context)
: transformMethodBody(node)
);
}

function visitGetAccessorDeclaration(node: GetAccessorDeclaration) {
return factory.updateGetAccessorDeclaration(
node,
visitNodes(node.modifiers, visitor, isModifierLike),
node.name,
visitParameterList(node.parameters, visitor, context),
/*type*/ undefined,
transformMethodBody(node)
);
}

function visitSetAccessorDeclaration(node: SetAccessorDeclaration) {
return factory.updateSetAccessorDeclaration(
node,
visitNodes(node.modifiers, visitor, isModifierLike),
node.name,
visitParameterList(node.parameters, visitor, context),
transformMethodBody(node)
);
}

Expand Down Expand Up @@ -446,6 +479,50 @@ namespace ts {
return false;
}

function transformMethodBody(node: MethodDeclaration | AccessorDeclaration | ConstructorDeclaration): FunctionBody | undefined {
Debug.assertIsDefined(node.body);

const savedCapturedSuperProperties = capturedSuperProperties;
const savedHasSuperElementAccess = hasSuperElementAccess;
capturedSuperProperties = new Set();
hasSuperElementAccess = false;

let updated = visitFunctionBody(node.body, visitor, context);

// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
// This step isn't needed if we eventually transform this to ES5.
const originalMethod = getOriginalNode(node, isFunctionLikeDeclaration);
const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 &&
resolver.getNodeCheckFlags(node) & (NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync | NodeCheckFlags.MethodWithSuperPropertyAccessInAsync) &&
(getFunctionFlags(originalMethod) & FunctionFlags.AsyncGenerator) !== FunctionFlags.AsyncGenerator;

if (emitSuperHelpers) {
enableSubstitutionForAsyncMethodsWithSuper();
if (capturedSuperProperties.size) {
const variableStatement = createSuperAccessVariableStatement(factory, resolver, node, capturedSuperProperties);
substitutedSuperAccessors[getNodeId(variableStatement)] = true;

const statements = updated.statements.slice();
insertStatementsAfterStandardPrologue(statements, [variableStatement]);
updated = factory.updateBlock(updated, statements);
}

if (hasSuperElementAccess) {
// Emit helpers for super element access expressions (`super[x]`).
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync) {
addEmitHelper(updated, advancedAsyncSuperHelper);
}
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAccessInAsync) {
addEmitHelper(updated, asyncSuperHelper);
}
}
}

capturedSuperProperties = savedCapturedSuperProperties;
hasSuperElementAccess = savedHasSuperElementAccess;
return updated;
}

function transformAsyncFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody;
function transformAsyncFunctionBody(node: ArrowFunction): ConciseBody;
function transformAsyncFunctionBody(node: FunctionLikeDeclaration): ConciseBody {
Expand Down Expand Up @@ -495,7 +572,7 @@ namespace ts {

// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
// This step isn't needed if we eventually transform this to ES5.
const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 && resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuperBinding | NodeCheckFlags.AsyncMethodWithSuper);
const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 && resolver.getNodeCheckFlags(node) & (NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync | NodeCheckFlags.MethodWithSuperPropertyAccessInAsync);

if (emitSuperHelpers) {
enableSubstitutionForAsyncMethodsWithSuper();
Expand All @@ -511,10 +588,10 @@ namespace ts {

if (emitSuperHelpers && hasSuperElementAccess) {
// Emit helpers for super element access expressions (`super[x]`).
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) {
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync) {
addEmitHelper(block, advancedAsyncSuperHelper);
}
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAccessInAsync) {
addEmitHelper(block, asyncSuperHelper);
}
}
Expand Down Expand Up @@ -601,7 +678,7 @@ namespace ts {
// If we need to support substitutions for `super` in an async method,
// we should track it here.
if (enabledSubstitutions & ES2017SubstitutionFlags.AsyncMethodsWithSuper && isSuperContainer(node)) {
const superContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding);
const superContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.MethodWithSuperPropertyAccessInAsync | NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync);
if (superContainerFlags !== enclosingSuperContainerFlags) {
const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags;
enclosingSuperContainerFlags = superContainerFlags;
Expand Down Expand Up @@ -698,7 +775,7 @@ namespace ts {
}

function createSuperElementAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
if (enclosingSuperContainerFlags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
if (enclosingSuperContainerFlags & NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync) {
return setTextRange(
factory.createPropertyAccessExpression(
factory.createCallExpression(
Expand Down Expand Up @@ -728,7 +805,7 @@ namespace ts {
export function createSuperAccessVariableStatement(factory: NodeFactory, resolver: EmitResolver, node: FunctionLikeDeclaration, names: Set<__String>) {
// Create a variable declaration with a getter/setter (if binding) definition for each name:
// const _super = Object.create(null, { x: { get: () => super.x, set: (v) => super.x = v }, ... });
const hasBinding = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) !== 0;
const hasBinding = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync) !== 0;
const accessors: PropertyAssignment[] = [];
names.forEach((_, key) => {
const name = unescapeLeadingUnderscores(key);
Expand Down
10 changes: 5 additions & 5 deletions src/compiler/transformers/es2018.ts
Expand Up @@ -1013,7 +1013,7 @@ namespace ts {

// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
// This step isn't needed if we eventually transform this to ES5.
const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 && resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuperBinding | NodeCheckFlags.AsyncMethodWithSuper);
const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 && resolver.getNodeCheckFlags(node) & (NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync | NodeCheckFlags.MethodWithSuperPropertyAccessInAsync);

if (emitSuperHelpers) {
enableSubstitutionForAsyncMethodsWithSuper();
Expand All @@ -1028,10 +1028,10 @@ namespace ts {
const block = factory.updateBlock(node.body!, statements);

if (emitSuperHelpers && hasSuperElementAccess) {
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) {
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync) {
addEmitHelper(block, advancedAsyncSuperHelper);
}
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAccessInAsync) {
addEmitHelper(block, asyncSuperHelper);
}
}
Expand Down Expand Up @@ -1185,7 +1185,7 @@ namespace ts {
// If we need to support substitutions for `super` in an async method,
// we should track it here.
if (enabledSubstitutions & ESNextSubstitutionFlags.AsyncMethodsWithSuper && isSuperContainer(node)) {
const superContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding);
const superContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.MethodWithSuperPropertyAccessInAsync | NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync);
if (superContainerFlags !== enclosingSuperContainerFlags) {
const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags;
enclosingSuperContainerFlags = superContainerFlags;
Expand Down Expand Up @@ -1282,7 +1282,7 @@ namespace ts {
}

function createSuperElementAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
if (enclosingSuperContainerFlags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
if (enclosingSuperContainerFlags & NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync) {
return setTextRange(
factory.createPropertyAccessExpression(
factory.createCallExpression(
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/types.ts
Expand Up @@ -5527,8 +5527,8 @@ namespace ts {
SuperInstance = 0x00000100, // Instance 'super' reference
SuperStatic = 0x00000200, // Static 'super' reference
ContextChecked = 0x00000400, // Contextual types have been assigned
AsyncMethodWithSuper = 0x00000800, // An async method that reads a value from a member of 'super'.
AsyncMethodWithSuperBinding = 0x00001000, // An async method that assigns a value to a member of 'super'.
MethodWithSuperPropertyAccessInAsync = 0x00000800, // A method that contains a SuperProperty access in an async context.
MethodWithSuperPropertyAssignmentInAsync = 0x00001000, // A method that contains a SuperProperty assignment in an async context.
CaptureArguments = 0x00002000, // Lexical 'arguments' used in body
EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them.
LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
Expand Down
85 changes: 85 additions & 0 deletions tests/baselines/reference/asyncMethodWithSuper_es6.js
Expand Up @@ -185,6 +185,32 @@ class B extends A {
(async () => super["x"] = f);
}
}

// https://github.com/microsoft/TypeScript/issues/46828
class Base {
set setter(x: any) {}
get getter(): any { return; }
method(x: string): any {}

static set setter(x: any) {}
static get getter(): any { return; }
static method(x: string): any {}
}

class Derived extends Base {
a() { return async () => super.method('') }
b() { return async () => super.getter }
c() { return async () => super.setter = '' }
d() { return async () => super["method"]('') }
e() { return async () => super["getter"] }
f() { return async () => super["setter"] = '' }
static a() { return async () => super.method('') }
static b() { return async () => super.getter }
static c() { return async () => super.setter = '' }
static d() { return async () => super["method"]('') }
static e() { return async () => super["getter"] }
static f() { return async () => super["setter"] = '' }
}


//// [asyncMethodWithSuper_es6.js]
Expand Down Expand Up @@ -377,3 +403,62 @@ class B extends A {
});
}
}
// https://github.com/microsoft/TypeScript/issues/46828
class Base {
set setter(x) { }
get getter() { return; }
method(x) { }
static set setter(x) { }
static get getter() { return; }
static method(x) { }
}
class Derived extends Base {
a() { const _super = Object.create(null, {
method: { get: () => super.method }
}); return () => __awaiter(this, void 0, void 0, function* () { return _super.method.call(this, ''); }); }
b() { const _super = Object.create(null, {
getter: { get: () => super.getter }
}); return () => __awaiter(this, void 0, void 0, function* () { return _super.getter; }); }
c() { const _super = Object.create(null, {
setter: { get: () => super.setter, set: v => super.setter = v }
}); return () => __awaiter(this, void 0, void 0, function* () { return _super.setter = ''; }); }
d() {
const _superIndex = name => super[name];
return () => __awaiter(this, void 0, void 0, function* () { return _superIndex("method").call(this, ''); });
}
e() {
const _superIndex = name => super[name];
return () => __awaiter(this, void 0, void 0, function* () { return _superIndex("getter"); });
}
f() {
const _superIndex = (function (geti, seti) {
const cache = Object.create(null);
return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } });
})(name => super[name], (name, value) => super[name] = value);
return () => __awaiter(this, void 0, void 0, function* () { return _superIndex("setter").value = ''; });
}
static a() { const _super = Object.create(null, {
method: { get: () => super.method }
}); return () => __awaiter(this, void 0, void 0, function* () { return _super.method.call(this, ''); }); }
static b() { const _super = Object.create(null, {
getter: { get: () => super.getter }
}); return () => __awaiter(this, void 0, void 0, function* () { return _super.getter; }); }
static c() { const _super = Object.create(null, {
setter: { get: () => super.setter, set: v => super.setter = v }
}); return () => __awaiter(this, void 0, void 0, function* () { return _super.setter = ''; }); }
static d() {
const _superIndex = name => super[name];
return () => __awaiter(this, void 0, void 0, function* () { return _superIndex("method").call(this, ''); });
}
static e() {
const _superIndex = name => super[name];
return () => __awaiter(this, void 0, void 0, function* () { return _superIndex("getter"); });
}
static f() {
const _superIndex = (function (geti, seti) {
const cache = Object.create(null);
return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } });
})(name => super[name], (name, value) => super[name] = value);
return () => __awaiter(this, void 0, void 0, function* () { return _superIndex("setter").value = ''; });
}
}

0 comments on commit 3abd351

Please sign in to comment.