Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix super property transform in async arrow in method #51240

Merged
merged 1 commit into from Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/compiler/checker.ts
Expand Up @@ -26700,13 +26700,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 @@ -26818,12 +26821,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 = ''; });
}
}