diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 21317bec3fb..0d24f7c89fe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6902,6 +6902,16 @@ namespace ts { return false; } + function isSuperPropertyAccess(node: Node) { + return node.kind === SyntaxKind.PropertyAccessExpression + && (node).expression.kind === SyntaxKind.SuperKeyword; + } + + function isSuperElementAccess(node: Node) { + return node.kind === SyntaxKind.ElementAccessExpression + && (node).expression.kind === SyntaxKind.SuperKeyword; + } + function checkSuperExpression(node: Node): Type { const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent).expression === node; const classDeclaration = getContainingClass(node); @@ -6935,7 +6945,12 @@ namespace ts { // Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference. if (container.kind === SyntaxKind.MethodDeclaration && container.flags & NodeFlags.Async) { - getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper; + if ((isSuperPropertyAccess(node.parent) || isSuperElementAccess(node.parent)) && isAssignmentTarget(node.parent)) { + getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding; + } + else { + getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper; + } } if (needToCaptureLexicalThis) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index d6a709fc5e7..5c1eaf67b0e 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2126,6 +2126,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return; } + if (languageVersion === ScriptTarget.ES6 && + node.expression.kind === SyntaxKind.SuperKeyword && + isInAsyncMethodWithSuperInES6(node)) { + const name = createSynthesizedNode(SyntaxKind.StringLiteral); + name.text = node.name.text; + emitSuperAccessInAsyncMethod(node.expression, name); + return; + } + emit(node.expression); const indentedBeforeDot = indentIfOnDifferentLines(node, node.expression, node.dotToken); @@ -2207,6 +2216,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi if (tryEmitConstantValue(node)) { return; } + + if (languageVersion === ScriptTarget.ES6 && + node.expression.kind === SyntaxKind.SuperKeyword && + isInAsyncMethodWithSuperInES6(node)) { + emitSuperAccessInAsyncMethod(node.expression, node.argumentExpression); + return; + } + emit(node.expression); write("["); emit(node.argumentExpression); @@ -2292,10 +2309,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi && (node).expression.kind === SyntaxKind.SuperKeyword; } - function isInAsyncMethodWithSuperInES6(node: CallExpression) { + function isInAsyncMethodWithSuperInES6(node: Node) { if (languageVersion === ScriptTarget.ES6) { const container = getSuperContainer(node, /*includeFunctions*/ false); - if (container && resolver.getNodeCheckFlags(container) & NodeCheckFlags.AsyncMethodWithSuper) { + if (container && resolver.getNodeCheckFlags(container) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding)) { return true; } } @@ -2303,10 +2320,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return false; } - function emitSuperAccessInAsyncMethod(node: Expression) { + function emitSuperAccessInAsyncMethod(superNode: Node, argumentExpression: Expression) { + const container = getSuperContainer(superNode, /*includeFunctions*/ false); + const isSuperBinding = resolver.getNodeCheckFlags(container) & NodeCheckFlags.AsyncMethodWithSuperBinding; write("_super("); - emit(node); - write(")"); + emit(argumentExpression); + write(isSuperBinding ? ").value" : ")"); } function emitCallExpression(node: CallExpression) { @@ -2323,26 +2342,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi superCall = true; } else { - if (isSuperPropertyAccess(expression)) { - superCall = true; - if (isInAsyncMethodWithSuperInES6(node)) { - isAsyncMethodWithSuper = true; - const name = createSynthesizedNode(SyntaxKind.StringLiteral); - name.text = expression.name.text; - emitSuperAccessInAsyncMethod(name); - } - } - else if (isSuperElementAccess(expression)) { - superCall = true; - if (isInAsyncMethodWithSuperInES6(node)) { - isAsyncMethodWithSuper = true; - emitSuperAccessInAsyncMethod(expression.argumentExpression); - } - } - - if (!isAsyncMethodWithSuper) { - emit(expression); - } + superCall = isSuperPropertyAccess(expression) || isSuperElementAccess(expression); + isAsyncMethodWithSuper = superCall && isInAsyncMethodWithSuperInES6(node); + emit(expression); } if (superCall && (languageVersion < ScriptTarget.ES6 || isAsyncMethodWithSuper)) { @@ -4497,8 +4499,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi increaseIndent(); writeLine(); - if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) { - write("const _super = name => super[name];"); + if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) { + writeLines(` +const _super = (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);`); + writeLine(); + } + else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) { + write(`const _super = name => super[name];`); writeLine(); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index dc17b9de5ec..002543081f8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2023,13 +2023,14 @@ namespace ts { SuperStatic = 0x00000200, // Static 'super' reference ContextChecked = 0x00000400, // Contextual types have been assigned AsyncMethodWithSuper = 0x00000800, - CaptureArguments = 0x00001000, // Lexical 'arguments' used in body (for async functions) + AsyncMethodWithSuperBinding = 0x00001000, + CaptureArguments = 0x00002000, // Lexical 'arguments' used in body (for async functions) // Values for enum members have been computed, and any errors have been reported for them. - EnumValuesComputed = 0x00002000, - BlockScopedBindingInLoop = 0x00004000, - LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration. - LoopWithBlockScopedBindingCapturedInFunction = 0x00010000, // Loop that contains block scoped variable captured in closure + EnumValuesComputed = 0x00004000, + BlockScopedBindingInLoop = 0x00008000, + LexicalModuleMergesWithClass = 0x00010000, // Instantiated lexical module declaration is merged with a previous class declaration. + LoopWithBlockScopedBindingCapturedInFunction = 0x00020000, // Loop that contains block scoped variable captured in closure } /* @internal */ diff --git a/tests/baselines/reference/asyncMethodWithSuper_es6.js b/tests/baselines/reference/asyncMethodWithSuper_es6.js index 315ce5fdb90..74d4de79881 100644 --- a/tests/baselines/reference/asyncMethodWithSuper_es6.js +++ b/tests/baselines/reference/asyncMethodWithSuper_es6.js @@ -5,9 +5,48 @@ class A { } class B extends A { - async y() { + // async method with only call/get on 'super' does not require a binding + async simple() { + // call with property access super.x(); + + // call with element access super["x"](); + + // property access (read) + const a = super.x; + + // element access (read) + const b = super["x"]; + } + + // async method with assignment/destructuring on 'super' requires a binding + async advanced() { + const f = () => {}; + + // call with property access + super.x(); + + // call with element access + super["x"](); + + // property access (read) + const a = super.x; + + // element access (read) + const b = super["x"]; + + // property access (assign) + super.x = f; + + // element access (assign) + super["x"] = f; + + // destructuring assign with property access + ({ f: super.x } = { f }); + + // destructuring assign with element access + ({ f: super["x"] } = { f }); } } @@ -17,11 +56,44 @@ class A { } } class B extends A { - y() { + // async method with only call/get on 'super' does not require a binding + simple() { const _super = name => super[name]; return __awaiter(this, void 0, Promise, function* () { + // call with property access _super("x").call(this); + // call with element access _super("x").call(this); + // property access (read) + const a = _super("x"); + // element access (read) + const b = _super("x"); + }); + } + // async method with assignment/destructuring on 'super' requires a binding + advanced() { + const _super = (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, Promise, function* () { + const f = () => { }; + // call with property access + _super("x").value.call(this); + // call with element access + _super("x").value.call(this); + // property access (read) + const a = _super("x").value; + // element access (read) + const b = _super("x").value; + // property access (assign) + _super("x").value = f; + // element access (assign) + _super("x").value = f; + // destructuring assign with property access + ({ f: _super("x").value } = { f }); + // destructuring assign with element access + ({ f: _super("x").value } = { f }); }); } } diff --git a/tests/baselines/reference/asyncMethodWithSuper_es6.symbols b/tests/baselines/reference/asyncMethodWithSuper_es6.symbols index 2b7388702f1..37937a061a8 100644 --- a/tests/baselines/reference/asyncMethodWithSuper_es6.symbols +++ b/tests/baselines/reference/asyncMethodWithSuper_es6.symbols @@ -11,16 +11,92 @@ class B extends A { >B : Symbol(B, Decl(asyncMethodWithSuper_es6.ts, 3, 1)) >A : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) - async y() { ->y : Symbol(y, Decl(asyncMethodWithSuper_es6.ts, 5, 19)) + // async method with only call/get on 'super' does not require a binding + async simple() { +>simple : Symbol(simple, Decl(asyncMethodWithSuper_es6.ts, 5, 19)) + // call with property access super.x(); >super.x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) >super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) >x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + // call with element access super["x"](); >super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) >"x" : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + + // property access (read) + const a = super.x; +>a : Symbol(a, Decl(asyncMethodWithSuper_es6.ts, 15, 13)) +>super.x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + + // element access (read) + const b = super["x"]; +>b : Symbol(b, Decl(asyncMethodWithSuper_es6.ts, 18, 13)) +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>"x" : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + } + + // async method with assignment/destructuring on 'super' requires a binding + async advanced() { +>advanced : Symbol(advanced, Decl(asyncMethodWithSuper_es6.ts, 19, 5)) + + const f = () => {}; +>f : Symbol(f, Decl(asyncMethodWithSuper_es6.ts, 23, 13)) + + // call with property access + super.x(); +>super.x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + + // call with element access + super["x"](); +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>"x" : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + + // property access (read) + const a = super.x; +>a : Symbol(a, Decl(asyncMethodWithSuper_es6.ts, 32, 13)) +>super.x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + + // element access (read) + const b = super["x"]; +>b : Symbol(b, Decl(asyncMethodWithSuper_es6.ts, 35, 13)) +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>"x" : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) + + // property access (assign) + super.x = f; +>super.x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) +>f : Symbol(f, Decl(asyncMethodWithSuper_es6.ts, 23, 13)) + + // element access (assign) + super["x"] = f; +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>"x" : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) +>f : Symbol(f, Decl(asyncMethodWithSuper_es6.ts, 23, 13)) + + // destructuring assign with property access + ({ f: super.x } = { f }); +>f : Symbol(f, Decl(asyncMethodWithSuper_es6.ts, 44, 10)) +>super.x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>x : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) +>f : Symbol(f, Decl(asyncMethodWithSuper_es6.ts, 44, 27)) + + // destructuring assign with element access + ({ f: super["x"] } = { f }); +>f : Symbol(f, Decl(asyncMethodWithSuper_es6.ts, 47, 10)) +>super : Symbol(A, Decl(asyncMethodWithSuper_es6.ts, 0, 0)) +>"x" : Symbol(A.x, Decl(asyncMethodWithSuper_es6.ts, 0, 9)) +>f : Symbol(f, Decl(asyncMethodWithSuper_es6.ts, 47, 30)) } } diff --git a/tests/baselines/reference/asyncMethodWithSuper_es6.types b/tests/baselines/reference/asyncMethodWithSuper_es6.types index 7786b417e81..04c5b5a9baf 100644 --- a/tests/baselines/reference/asyncMethodWithSuper_es6.types +++ b/tests/baselines/reference/asyncMethodWithSuper_es6.types @@ -11,19 +11,113 @@ class B extends A { >B : B >A : A - async y() { ->y : () => Promise + // async method with only call/get on 'super' does not require a binding + async simple() { +>simple : () => Promise + // call with property access super.x(); >super.x() : void >super.x : () => void >super : A >x : () => void + // call with element access super["x"](); >super["x"]() : void >super["x"] : () => void >super : A >"x" : string + + // property access (read) + const a = super.x; +>a : () => void +>super.x : () => void +>super : A +>x : () => void + + // element access (read) + const b = super["x"]; +>b : () => void +>super["x"] : () => void +>super : A +>"x" : string + } + + // async method with assignment/destructuring on 'super' requires a binding + async advanced() { +>advanced : () => Promise + + const f = () => {}; +>f : () => void +>() => {} : () => void + + // call with property access + super.x(); +>super.x() : void +>super.x : () => void +>super : A +>x : () => void + + // call with element access + super["x"](); +>super["x"]() : void +>super["x"] : () => void +>super : A +>"x" : string + + // property access (read) + const a = super.x; +>a : () => void +>super.x : () => void +>super : A +>x : () => void + + // element access (read) + const b = super["x"]; +>b : () => void +>super["x"] : () => void +>super : A +>"x" : string + + // property access (assign) + super.x = f; +>super.x = f : () => void +>super.x : () => void +>super : A +>x : () => void +>f : () => void + + // element access (assign) + super["x"] = f; +>super["x"] = f : () => void +>super["x"] : () => void +>super : A +>"x" : string +>f : () => void + + // destructuring assign with property access + ({ f: super.x } = { f }); +>({ f: super.x } = { f }) : { f: () => void; } +>{ f: super.x } = { f } : { f: () => void; } +>{ f: super.x } : { f: () => void; } +>f : () => void +>super.x : () => void +>super : A +>x : () => void +>{ f } : { f: () => void; } +>f : () => void + + // destructuring assign with element access + ({ f: super["x"] } = { f }); +>({ f: super["x"] } = { f }) : { f: () => void; } +>{ f: super["x"] } = { f } : { f: () => void; } +>{ f: super["x"] } : { f: () => void; } +>f : () => void +>super["x"] : () => void +>super : A +>"x" : string +>{ f } : { f: () => void; } +>f : () => void } } diff --git a/tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts b/tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts index 2225200ef60..795fe7defb0 100644 --- a/tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts +++ b/tests/cases/conformance/async/es6/asyncMethodWithSuper_es6.ts @@ -6,8 +6,47 @@ class A { } class B extends A { - async y() { + // async method with only call/get on 'super' does not require a binding + async simple() { + // call with property access super.x(); + + // call with element access super["x"](); + + // property access (read) + const a = super.x; + + // element access (read) + const b = super["x"]; + } + + // async method with assignment/destructuring on 'super' requires a binding + async advanced() { + const f = () => {}; + + // call with property access + super.x(); + + // call with element access + super["x"](); + + // property access (read) + const a = super.x; + + // element access (read) + const b = super["x"]; + + // property access (assign) + super.x = f; + + // element access (assign) + super["x"] = f; + + // destructuring assign with property access + ({ f: super.x } = { f }); + + // destructuring assign with element access + ({ f: super["x"] } = { f }); } } \ No newline at end of file