diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index b48539e872200..c0916d3233987 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -186,6 +186,7 @@ namespace ts { let enclosingFunction: FunctionLikeDeclaration; let enclosingNonArrowFunction: FunctionLikeDeclaration; let enclosingNonAsyncFunctionBody: FunctionLikeDeclaration | ClassElement; + let isInConstructorWithCapturedSuper: boolean; /** * Used to track if we are emitting body of the converted loop @@ -231,14 +232,17 @@ namespace ts { const savedCurrentParent = currentParent; const savedCurrentNode = currentNode; const savedConvertedLoopState = convertedLoopState; + const savedIsInConstructorWithCapturedSuper = isInConstructorWithCapturedSuper; if (nodeStartsNewLexicalEnvironment(node)) { - // don't treat content of nodes that start new lexical environment as part of converted loop copy + // don't treat content of nodes that start new lexical environment as part of converted loop copy or constructor body + isInConstructorWithCapturedSuper = false; convertedLoopState = undefined; } onBeforeVisitNode(node); const visited = f(node); + isInConstructorWithCapturedSuper = savedIsInConstructorWithCapturedSuper; convertedLoopState = savedConvertedLoopState; enclosingFunction = savedEnclosingFunction; enclosingNonArrowFunction = savedEnclosingNonArrowFunction; @@ -251,6 +255,14 @@ namespace ts { return visited; } + function returnCapturedThis(node: Node): Node { + return setOriginalNode(createReturn(createIdentifier("_this")), node); + } + + function isReturnVoidStatementInConstructorWithCapturedSuper(node: Node): boolean { + return isInConstructorWithCapturedSuper && node.kind === SyntaxKind.ReturnStatement && !(node).expression; + } + function shouldCheckNode(node: Node): boolean { return (node.transformFlags & TransformFlags.ES2015) !== 0 || node.kind === SyntaxKind.LabeledStatement || @@ -258,10 +270,16 @@ namespace ts { } function visitorWorker(node: Node): VisitResult { - if (shouldCheckNode(node)) { + if (isReturnVoidStatementInConstructorWithCapturedSuper(node)) { + return returnCapturedThis(node); + } + else if (shouldCheckNode(node)) { return visitJavaScript(node); } - else if (node.transformFlags & TransformFlags.ContainsES2015) { + else if (node.transformFlags & TransformFlags.ContainsES2015 || (isInConstructorWithCapturedSuper && !isExpression(node))) { + // we want to dive in this branch either if node has children with ES2015 specific syntax + // or we are inside constructor that captures result of the super call so all returns without expression should be + // rewritten. Note: we skip expressions since returns should never appear there return visitEachChild(node, visitor, context); } else { @@ -283,6 +301,7 @@ namespace ts { function visitNodesInConvertedLoop(node: Node): VisitResult { switch (node.kind) { case SyntaxKind.ReturnStatement: + node = isReturnVoidStatementInConstructorWithCapturedSuper(node) ? returnCapturedThis(node) : node; return visitReturnStatement(node); case SyntaxKind.VariableStatement: @@ -864,7 +883,10 @@ namespace ts { } if (constructor) { - const body = saveStateAndInvoke(constructor, constructor => visitNodes(constructor.body.statements, visitor, isStatement, /*start*/ statementOffset)); + const body = saveStateAndInvoke(constructor, constructor => { + isInConstructorWithCapturedSuper = superCaptureStatus === SuperCaptureResult.ReplaceSuperCapture; + return visitNodes(constructor.body.statements, visitor, isStatement, /*start*/ statementOffset); + }); addRange(statements, body); } diff --git a/tests/baselines/reference/constructorWithCapturedSuper.js b/tests/baselines/reference/constructorWithCapturedSuper.js new file mode 100644 index 0000000000000..895f78eb04b8b --- /dev/null +++ b/tests/baselines/reference/constructorWithCapturedSuper.js @@ -0,0 +1,123 @@ +//// [constructorWithCapturedSuper.ts] +let oneA: A; + +class A { + constructor() { + return oneA; + } +} + +class B extends A { + constructor(x: number) { + super(); + if (x === 1) { + return; + } + while (x < 2) { + return; + } + try { + return + } + catch (e) { + return; + } + finally { + return; + } + } +} + +class C extends A { + constructor(x: number) { + super(); + for (let i = 0; i < 10; ++i) { + () => i + x; + if (x === 1) { + return; + } + } + } +} + +class D extends A { + constructor(x: number) { + super(); + () => { + return; + } + function foo() { + return; + } + } +} + +//// [constructorWithCapturedSuper.js] +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var oneA; +var A = (function () { + function A() { + return oneA; + } + return A; +}()); +var B = (function (_super) { + __extends(B, _super); + function B(x) { + var _this = _super.call(this) || this; + if (x === 1) { + return _this; + } + while (x < 2) { + return _this; + } + try { + return _this; + } + catch (e) { + return _this; + } + finally { + return _this; + } + return _this; + } + return B; +}(A)); +var C = (function (_super) { + __extends(C, _super); + function C(x) { + var _this = _super.call(this) || this; + var _loop_1 = function (i) { + (function () { return i + x; }); + if (x === 1) { + return { value: _this }; + } + }; + for (var i = 0; i < 10; ++i) { + var state_1 = _loop_1(i); + if (typeof state_1 === "object") + return state_1.value; + } + return _this; + } + return C; +}(A)); +var D = (function (_super) { + __extends(D, _super); + function D(x) { + var _this = _super.call(this) || this; + (function () { + return; + }); + function foo() { + return; + } + return _this; + } + return D; +}(A)); diff --git a/tests/baselines/reference/constructorWithCapturedSuper.symbols b/tests/baselines/reference/constructorWithCapturedSuper.symbols new file mode 100644 index 0000000000000..1743ee61612e0 --- /dev/null +++ b/tests/baselines/reference/constructorWithCapturedSuper.symbols @@ -0,0 +1,96 @@ +=== tests/cases/compiler/constructorWithCapturedSuper.ts === +let oneA: A; +>oneA : Symbol(oneA, Decl(constructorWithCapturedSuper.ts, 0, 3)) +>A : Symbol(A, Decl(constructorWithCapturedSuper.ts, 0, 12)) + +class A { +>A : Symbol(A, Decl(constructorWithCapturedSuper.ts, 0, 12)) + + constructor() { + return oneA; +>oneA : Symbol(oneA, Decl(constructorWithCapturedSuper.ts, 0, 3)) + } +} + +class B extends A { +>B : Symbol(B, Decl(constructorWithCapturedSuper.ts, 6, 1)) +>A : Symbol(A, Decl(constructorWithCapturedSuper.ts, 0, 12)) + + constructor(x: number) { +>x : Symbol(x, Decl(constructorWithCapturedSuper.ts, 9, 16)) + + super(); +>super : Symbol(A, Decl(constructorWithCapturedSuper.ts, 0, 12)) + + if (x === 1) { +>x : Symbol(x, Decl(constructorWithCapturedSuper.ts, 9, 16)) + + return; + } + while (x < 2) { +>x : Symbol(x, Decl(constructorWithCapturedSuper.ts, 9, 16)) + + return; + } + try { + return + } + catch (e) { +>e : Symbol(e, Decl(constructorWithCapturedSuper.ts, 20, 15)) + + return; + } + finally { + return; + } + } +} + +class C extends A { +>C : Symbol(C, Decl(constructorWithCapturedSuper.ts, 27, 1)) +>A : Symbol(A, Decl(constructorWithCapturedSuper.ts, 0, 12)) + + constructor(x: number) { +>x : Symbol(x, Decl(constructorWithCapturedSuper.ts, 30, 16)) + + super(); +>super : Symbol(A, Decl(constructorWithCapturedSuper.ts, 0, 12)) + + for (let i = 0; i < 10; ++i) { +>i : Symbol(i, Decl(constructorWithCapturedSuper.ts, 32, 16)) +>i : Symbol(i, Decl(constructorWithCapturedSuper.ts, 32, 16)) +>i : Symbol(i, Decl(constructorWithCapturedSuper.ts, 32, 16)) + + () => i + x; +>i : Symbol(i, Decl(constructorWithCapturedSuper.ts, 32, 16)) +>x : Symbol(x, Decl(constructorWithCapturedSuper.ts, 30, 16)) + + if (x === 1) { +>x : Symbol(x, Decl(constructorWithCapturedSuper.ts, 30, 16)) + + return; + } + } + } +} + +class D extends A { +>D : Symbol(D, Decl(constructorWithCapturedSuper.ts, 39, 1)) +>A : Symbol(A, Decl(constructorWithCapturedSuper.ts, 0, 12)) + + constructor(x: number) { +>x : Symbol(x, Decl(constructorWithCapturedSuper.ts, 42, 16)) + + super(); +>super : Symbol(A, Decl(constructorWithCapturedSuper.ts, 0, 12)) + + () => { + return; + } + function foo() { +>foo : Symbol(foo, Decl(constructorWithCapturedSuper.ts, 46, 9)) + + return; + } + } +} diff --git a/tests/baselines/reference/constructorWithCapturedSuper.types b/tests/baselines/reference/constructorWithCapturedSuper.types new file mode 100644 index 0000000000000..13696029d536f --- /dev/null +++ b/tests/baselines/reference/constructorWithCapturedSuper.types @@ -0,0 +1,113 @@ +=== tests/cases/compiler/constructorWithCapturedSuper.ts === +let oneA: A; +>oneA : A +>A : A + +class A { +>A : A + + constructor() { + return oneA; +>oneA : A + } +} + +class B extends A { +>B : B +>A : A + + constructor(x: number) { +>x : number + + super(); +>super() : void +>super : typeof A + + if (x === 1) { +>x === 1 : boolean +>x : number +>1 : 1 + + return; + } + while (x < 2) { +>x < 2 : boolean +>x : number +>2 : 2 + + return; + } + try { + return + } + catch (e) { +>e : any + + return; + } + finally { + return; + } + } +} + +class C extends A { +>C : C +>A : A + + constructor(x: number) { +>x : number + + super(); +>super() : void +>super : typeof A + + for (let i = 0; i < 10; ++i) { +>i : number +>0 : 0 +>i < 10 : boolean +>i : number +>10 : 10 +>++i : number +>i : number + + () => i + x; +>() => i + x : () => number +>i + x : number +>i : number +>x : number + + if (x === 1) { +>x === 1 : boolean +>x : number +>1 : 1 + + return; + } + } + } +} + +class D extends A { +>D : D +>A : A + + constructor(x: number) { +>x : number + + super(); +>super() : void +>super : typeof A + + () => { +>() => { return; } : () => void + + return; + } + function foo() { +>foo : () => void + + return; + } + } +} diff --git a/tests/cases/compiler/constructorWithCapturedSuper.ts b/tests/cases/compiler/constructorWithCapturedSuper.ts new file mode 100644 index 0000000000000..942803e771de6 --- /dev/null +++ b/tests/cases/compiler/constructorWithCapturedSuper.ts @@ -0,0 +1,52 @@ +let oneA: A; + +class A { + constructor() { + return oneA; + } +} + +class B extends A { + constructor(x: number) { + super(); + if (x === 1) { + return; + } + while (x < 2) { + return; + } + try { + return + } + catch (e) { + return; + } + finally { + return; + } + } +} + +class C extends A { + constructor(x: number) { + super(); + for (let i = 0; i < 10; ++i) { + () => i + x; + if (x === 1) { + return; + } + } + } +} + +class D extends A { + constructor(x: number) { + super(); + () => { + return; + } + function foo() { + return; + } + } +} \ No newline at end of file