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

Add wrapper to emit statics/decorators inside es5 class #16120

Merged
merged 7 commits into from
May 31, 2017
Merged

Conversation

rbuckton
Copy link
Member

@rbuckton rbuckton commented May 27, 2017

This PR changes our class emit when targeting ES5 to emit static initializers and decorators inside of the ES5 class IIFE.

This is accomplished by first wrapping the ES6 class and related statements inside of an IIFE in the ts transformer, then unwrapping the IIFE and merging it in the es2015 transformer.

This PR also adds some diagnostic information to Node and Type that is only exposed when debugging, to assist with debugging the compiler. This also adds the capability to remove Debug.assert, Debug.fail and related debugging assertions from the call stack on NodeJS to better interpret where an assertion failure occurred when running tests (as our test harness sets the stack trace limit to 1).

Fixes #15857

@rbuckton rbuckton requested review from yuit, mhegazy and a user May 27, 2017 20:16
Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

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

(Note: I didn't review changes to .sourcemap.txt baselines)

return value !== undefined && test(value) ? value : undefined;
}

export function cast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut {
Copy link

Choose a reason for hiding this comment

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

Don't see any reason to allow an undefined parameter since the function will always fail in that case.

Copy link
Member Author

@rbuckton rbuckton May 30, 2017

Choose a reason for hiding this comment

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

If we ever turn on --strictNullChecks there may be cases where value may be typed undefined.

return false;
}

const returnStatement = tryCast(elementAt(statements, isVariableStatement(lastOrUndefined(statements)) ? -2 : -1), isReturnStatement);
Copy link

Choose a reason for hiding this comment

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

This could be just:

const returnStatement = statements[statements.length - (isVariableStatement(lastOrUndefined(statements)) ? 2 : 1)];
return isReturnStatement(returnStatement) &&
    returnStatement.expression &&
    isIdentifier(skipOuterExpressions(returnStatement.expression));

Copy link

Choose a reason for hiding this comment

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

I still like this better as just return condition instead of if (!condition) { return false; } return true;

@@ -3714,6 +3736,21 @@ namespace ts {
All = Parentheses | Assertions | PartiallyEmittedExpressions
}

export type OuterExpression = ParenthesizedExpression | TypeAssertion | AsExpression | PartiallyEmittedExpression;
Copy link

Choose a reason for hiding this comment

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

Why not NonNullExpression?

Copy link
Member Author

Choose a reason for hiding this comment

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

It didn't exist when skipOuterExpressions was initially added.

return false;
}

const func = <FunctionExpression>skipParentheses((<CallExpression>node).expression);
Copy link

Choose a reason for hiding this comment

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

What makes this cast valid?

Copy link
Member Author

Choose a reason for hiding this comment

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

isImmediatelyInvokedFunctionExpression already ensured the node has the correct shape.


// 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;
Copy link

Choose a reason for hiding this comment

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

In isTypeScriptClassWrapper, this just skips parentheses.

@@ -3074,6 +3074,7 @@ namespace ts {
export interface Type {
flags: TypeFlags; // Flags
/* @internal */ id: number; // Unique ID
/* @internal */ checker: TypeChecker;
Copy link

Choose a reason for hiding this comment

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

This should definitely be documented as only being present in debug mode... better, don't define it here at all and require a cast to DebugType.

Copy link
Member Author

@rbuckton rbuckton May 30, 2017

Choose a reason for hiding this comment

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

It's not only present in debug mode. It's also present when using the language service. See https://github.com/Microsoft/TypeScript/blob/master/src/services/services.ts#L382

@@ -3977,34 +3978,34 @@ namespace ts {
}

export const enum EmitFlags {
SingleLine = 1 << 0, // The contents of this node should be emitted on a single line.
Copy link

Choose a reason for hiding this comment

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

What's the point of the whitespace change here? It seems arbitrary how much to use.

Copy link
Member Author

Choose a reason for hiding this comment

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

Trying to keep comments aligned for readability, except where the enum member is significantly long.

@@ -850,10 +850,88 @@ namespace ts {
return node && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor);
}

export function isClassLike(node: Node): node is ClassLikeDeclaration {
export function isClassLike(node: Node | undefined): node is ClassLikeDeclaration {
Copy link

Choose a reason for hiding this comment

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

I would rather see type predicates like this take defined inputs. I have a PR (#16121) making this public and would prefer to keep just Node as the parameter type; we can keep the node && in the body for now, but if we ever start using --strictNullChecks it should become the caller's responsibility.

return node && (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression);
}

export function isImmediatelyInvokedFunctionExpression(node: Node) {
Copy link

Choose a reason for hiding this comment

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

No need to export. isTypeScriptClassWrapper could also be moved closer to its use.

}

const syntaxKindCache: string[] = [];
export function formatSyntaxKind(kind: SyntaxKind): string {
Copy link

Choose a reason for hiding this comment

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

If this is just for debugging, it shouldn't need a cache?

Copy link
Member Author

Choose a reason for hiding this comment

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

Its for debugging, yes. It probably doesn't need one, no.

HasStaticInitializedProperties = 1 << 0,
HasConstructorDecorators = 1 << 1,
HasMemberDecorators = 1 << 2,
IsNamespaceExport = 1 << 3,
Copy link
Contributor

Choose a reason for hiding this comment

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

or name IsTSNamespaceExport


HasAnyDecorators = HasConstructorDecorators | HasMemberDecorators,
NeedsName = HasStaticInitializedProperties | HasMemberDecorators,
MayNeedImmediatelyInvokedFunctionExpression = HasAnyDecorators | HasStaticInitializedProperties,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: NeedImmediatelyInvokedFunctionExpression

Copy link
Member Author

Choose a reason for hiding this comment

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

These two flags indicates that the class may need an IIFE. Whether it actually needs it is also based on the target language, which is not encoded in these flags.

}

// Write any decorators of the node.
addClassElementDecorationStatements(statements, node, /*isStatic*/ false);
addClassElementDecorationStatements(statements, node, /*isStatic*/ true);
addConstructorDecorationStatement(statements, node);

if (facts & ClassFacts.UseImmediatelyInvokedFunctionExpression) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: add comment show the transform result

*/
function createClassDeclarationHeadWithoutDecorators(node: ClassDeclaration, name: Identifier, hasExtendsClause: boolean, hasStaticProperties: boolean) {
function createClassDeclarationHeadWithoutDecorators(node: ClassDeclaration, name: Identifier, facts: ClassFacts) {
// ${modifiers} class ${name} ${heritageClauses} {
Copy link
Contributor

Choose a reason for hiding this comment

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

Comments here seems to be outdated since we not always emit modifiers

Copy link
Member Author

Choose a reason for hiding this comment

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

It just illustrates an example of the emit. I'm not too terribly concerned.

@@ -613,9 +676,9 @@ namespace ts {
* @param statements A statement list to which to add the declaration.
* @param node A ClassDeclaration node.
* @param name The name of the class.
* @param hasExtendsClause A value indicating whether the class has an extends clause.
* @param facts Precomputed facts about the clas.
Copy link
Contributor

Choose a reason for hiding this comment

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

Though i think it is useful to keep since these comments surface up in quick info

@@ -2130,6 +2130,24 @@ namespace ts {

// Compound nodes

export function createImmediatelyInvokedFunctionExpression(statements: Statement[]): CallExpression;
export function createImmediatelyInvokedFunctionExpression(statements: Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: should param and paramValue be default value of []?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, these are not arrays. As written, this function only allows zero or one parameter.

const variable = varStatement.declarationList.declarations[0];
const initializer = skipOuterExpressions(variable.initializer);

// Under certain conditions, the 'ts' transformer may may introduce a class alias, which
Copy link
Contributor

Choose a reason for hiding this comment

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

he 'ts' transformer may may is he 'ts' transformer may

@@ -3262,6 +3319,160 @@ namespace ts {
);
}

function visitTypeScriptClassWrapper(node: CallExpression) {
// This is a call to a class wrapper function (an IIFE) created by the 'ts' transformer.
Copy link
Contributor

Choose a reason for hiding this comment

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

great comment 👍

@rbuckton rbuckton merged commit 2fa59d5 into master May 31, 2017
@rbuckton rbuckton deleted the fix15857 branch May 31, 2017 23:03
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants