Skip to content

Switch to arrow for ts class wrapper IIFE #18027

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

Merged
merged 2 commits into from
Aug 25, 2017
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2372,6 +2372,24 @@ namespace ts {
);
}

export function createImmediatelyInvokedArrowFunction(statements: Statement[]): CallExpression;
export function createImmediatelyInvokedArrowFunction(statements: Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression;
export function createImmediatelyInvokedArrowFunction(statements: Statement[], param?: ParameterDeclaration, paramValue?: Expression) {
return createCall(
createArrowFunction(
/*modifiers*/ undefined,
/*typeParameters*/ undefined,
/*parameters*/ param ? [param] : [],
/*type*/ undefined,
/*equalsGreaterThanToken*/ undefined,
createBlock(statements, /*multiLine*/ true)
),
/*typeArguments*/ undefined,
/*argumentsArray*/ paramValue ? [paramValue] : []
);
}


export function createComma(left: Expression, right: Expression) {
return <Expression>createBinary(left, SyntaxKind.CommaToken, right);
}
Expand Down Expand Up @@ -4036,8 +4054,31 @@ namespace ts {
}
}

/**
* Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions.
*
* A parenthesized expression can be ignored when all of the following are true:
*
* - It's `pos` and `end` are not -1
* - It does not have a custom source map range
* - It does not have a custom comment range
* - It does not have synthetic leading or trailing comments
*
* If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around
* the expression to maintain precedence, a new parenthesized expression should be created automatically when
* the containing expression is created/updated.
*/
function isIgnorableParen(node: Expression) {
return node.kind === SyntaxKind.ParenthesizedExpression
&& nodeIsSynthesized(node)
&& nodeIsSynthesized(getSourceMapRange(node))
&& nodeIsSynthesized(getCommentRange(node))
&& !some(getSyntheticLeadingComments(node))
&& !some(getSyntheticTrailingComments(node));
}

export function recreateOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression {
if (outerExpression && isOuterExpression(outerExpression, kinds)) {
if (outerExpression && isOuterExpression(outerExpression, kinds) && !isIgnorableParen(outerExpression)) {
return updateOuterExpression(
outerExpression,
recreateOuterExpressions(outerExpression.expression, innerExpression)
Expand Down
59 changes: 4 additions & 55 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,64 +339,12 @@ namespace ts {
&& !(<ReturnStatement>node).expression;
}

function isClassLikeVariableStatement(node: Node) {
if (!isVariableStatement(node)) return false;
const variable = singleOrUndefined((<VariableStatement>node).declarationList.declarations);
return variable
&& variable.initializer
&& isIdentifier(variable.name)
&& (isClassLike(variable.initializer)
|| (isAssignmentExpression(variable.initializer)
&& isIdentifier(variable.initializer.left)
&& isClassLike(variable.initializer.right)));
}

function isTypeScriptClassWrapper(node: Node) {
const call = tryCast(node, isCallExpression);
if (!call || isParseTreeNode(call) ||
some(call.typeArguments) ||
some(call.arguments)) {
return false;
}

const func = tryCast(skipOuterExpressions(call.expression), isFunctionExpression);
if (!func || isParseTreeNode(func) ||
some(func.typeParameters) ||
some(func.parameters) ||
func.type ||
!func.body) {
return false;
}

const statements = func.body.statements;
if (statements.length < 2) {
return false;
}

const firstStatement = statements[0];
if (isParseTreeNode(firstStatement) ||
!isClassLike(firstStatement) &&
!isClassLikeVariableStatement(firstStatement)) {
return false;
}

const lastStatement = elementAt(statements, -1);
const returnStatement = tryCast(isVariableStatement(lastStatement) ? elementAt(statements, -2) : lastStatement, isReturnStatement);
if (!returnStatement ||
!returnStatement.expression ||
!isIdentifier(skipOuterExpressions(returnStatement.expression))) {
return false;
}

return true;
}

function shouldVisitNode(node: Node): boolean {
return (node.transformFlags & TransformFlags.ContainsES2015) !== 0
|| convertedLoopState !== undefined
|| (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && (isStatement(node) || (node.kind === SyntaxKind.Block)))
|| (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node))
|| isTypeScriptClassWrapper(node);
|| (getEmitFlags(node) & EmitFlags.TypeScriptClassWrapper) !== 0;
}

function visitor(node: Node): VisitResult<Node> {
Expand Down Expand Up @@ -3308,13 +3256,14 @@ namespace ts {
* @param node a CallExpression.
*/
function visitCallExpression(node: CallExpression) {
if (isTypeScriptClassWrapper(node)) {
if (getEmitFlags(node) & EmitFlags.TypeScriptClassWrapper) {
return visitTypeScriptClassWrapper(node);
}

if (node.transformFlags & TransformFlags.ES2015) {
return visitCallExpressionWithPotentialCapturedThisAssignment(node, /*assignToCapturedThis*/ true);
}

return updateCall(
node,
visitNode(node.expression, callExpressionVisitor, isExpression),
Expand Down Expand Up @@ -3357,7 +3306,7 @@ namespace ts {

// We skip any outer expressions in a number of places to get to the innermost
// expression, but we will restore them later to preserve comments and source maps.
const body = cast(skipOuterExpressions(node.expression), isFunctionExpression).body;
const body = cast(cast(skipOuterExpressions(node.expression), isArrowFunction).body, isBlock);

// The class statements are the statements generated by visiting the first statement of the
// body (1), while all other statements are added to remainingStatements (2)
Expand Down
5 changes: 4 additions & 1 deletion src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,13 +613,16 @@ namespace ts {

addRange(statements, context.endLexicalEnvironment());

const iife = createImmediatelyInvokedArrowFunction(statements);
setEmitFlags(iife, EmitFlags.TypeScriptClassWrapper);

const varStatement = createVariableStatement(
/*modifiers*/ undefined,
createVariableDeclarationList([
createVariableDeclaration(
getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ false),
/*type*/ undefined,
createImmediatelyInvokedFunctionExpression(statements)
iife
)
])
);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4184,6 +4184,7 @@ namespace ts {
HasEndOfDeclarationMarker = 1 << 22, // Declaration has an associated NotEmittedStatement to mark the end of the declaration
Iterator = 1 << 23, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable.
NoAsciiEscaping = 1 << 24, // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions.
/*@internal*/ TypeScriptClassWrapper = 1 << 25, // The node is an IIFE class wrapper created by the ts transform.
}

export interface EmitHelper {
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2068,9 +2068,9 @@ namespace ts {
|| kind === SyntaxKind.SourceFile;
}

export function nodeIsSynthesized(node: TextRange): boolean {
return positionIsSynthesized(node.pos)
|| positionIsSynthesized(node.end);
export function nodeIsSynthesized(range: TextRange): boolean {
return positionIsSynthesized(range.pos)
|| positionIsSynthesized(range.end);
}

export function getOriginalSourceFile(sourceFile: SourceFile) {
Expand Down
21 changes: 21 additions & 0 deletions tests/baselines/reference/asyncArrowInClassES5.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//// [asyncArrowInClassES5.ts]
// https://github.com/Microsoft/TypeScript/issues/16924
// Should capture `this`

class Test {
static member = async (x: string) => { };
}


//// [asyncArrowInClassES5.js]
// https://github.com/Microsoft/TypeScript/issues/16924
// Should capture `this`
var _this = this;
var Test = /** @class */ (function () {
function Test() {
}
Test.member = function (x) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
return [2 /*return*/];
}); }); };
return Test;
}());
12 changes: 12 additions & 0 deletions tests/baselines/reference/asyncArrowInClassES5.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
=== tests/cases/compiler/asyncArrowInClassES5.ts ===
// https://github.com/Microsoft/TypeScript/issues/16924
// Should capture `this`

class Test {
>Test : Symbol(Test, Decl(asyncArrowInClassES5.ts, 0, 0))

static member = async (x: string) => { };
>member : Symbol(Test.member, Decl(asyncArrowInClassES5.ts, 3, 12))
>x : Symbol(x, Decl(asyncArrowInClassES5.ts, 4, 27))
}

13 changes: 13 additions & 0 deletions tests/baselines/reference/asyncArrowInClassES5.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
=== tests/cases/compiler/asyncArrowInClassES5.ts ===
// https://github.com/Microsoft/TypeScript/issues/16924
// Should capture `this`

class Test {
>Test : Test

static member = async (x: string) => { };
>member : (x: string) => Promise<void>
>async (x: string) => { } : (x: string) => Promise<void>
>x : string
}

1 change: 1 addition & 0 deletions tests/baselines/reference/decoratorOnClassMethod11.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
};
var M;
(function (M) {
var _this = this;
var C = /** @class */ (function () {
function C() {
}
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/decoratorOnClassMethod12.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
};
var M;
(function (M) {
var _this = this;
var S = /** @class */ (function () {
function S() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ var stringOrNumberOrUndefined = C.inStaticNestedArrowFunction;


//// [output.js]
var _this = this;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which tests were incorrect before this change? This is the closest one that I've found.

var C = /** @class */ (function () {
function C() {
var _this = this;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/superAccess2.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var __extends = (this && this.__extends) || (function () {
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var _this = this;
var P = /** @class */ (function () {
function P() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Vector {
}

//// [thisInArrowFunctionInStaticInitializer1.js]
var _this = this;
function log(a) { }
var Vector = /** @class */ (function () {
function Vector() {
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/thisInConstructorParameter2.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class P {
}

//// [thisInConstructorParameter2.js]
var _this = this;
var P = /** @class */ (function () {
function P(z, zz) {
if (z === void 0) { z = this; }
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/thisInInvalidContexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ var __extends = (this && this.__extends) || (function () {
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var _this = this;
//'this' in static member initializer
var ErrClass1 = /** @class */ (function () {
function ErrClass1() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ var __extends = (this && this.__extends) || (function () {
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var _this = this;
//'this' in static member initializer
var ErrClass1 = /** @class */ (function () {
function ErrClass1() {
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/thisInOuterClassBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Foo {
}

//// [thisInOuterClassBody.js]
var _this = this;
var Foo = /** @class */ (function () {
function Foo() {
this.x = this;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/typeOfThisInStaticMembers2.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class C2<T> {
}

//// [typeOfThisInStaticMembers2.js]
var _this = this;
var C = /** @class */ (function () {
function C() {
}
Expand Down
9 changes: 9 additions & 0 deletions tests/cases/compiler/asyncArrowInClassES5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// @noEmitHelpers: true
// @lib: es2015
// @target: es5
// https://github.com/Microsoft/TypeScript/issues/16924
// Should capture `this`

class Test {
static member = async (x: string) => { };
}