Skip to content

Ensure that emitter calls callbacks #18284

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
9 commits merged into from
Sep 7, 2017
Merged

Ensure that emitter calls callbacks #18284

9 commits merged into from
Sep 7, 2017

Conversation

ghost
Copy link

@ghost ghost commented Sep 6, 2017

Fixes #18091

The bug was happening because in formatting.ts, in processChildNodes, we continue scanning until getting to nodes.pos. Since this is a dynamically typed language, nodes.pos may be undefined despite our best efforts to type it, so the loop continued forever, thinking it was inside of the typeParameters of the generated function declaration; which meant it didn't know that it was actually nested several levels deep and should have been indenting more.

The problem was that in textChanges.ts, the function getPos promised to return a number but was implemented with (<any>n)["__pos"], which wasn't always defined. I added an assertion that it was, which brought up more problems.

In textChanges, we rely on callbacks like onBeforeEmitNodeArray to set "__pos". But there were many situations in which the emitter did not call these callbacks, so those all had to be updated; otherwise we don't set "__pos", and then the new nodes have missing or incorrect positions.

@ghost ghost requested a review from rbuckton September 6, 2017 20:26
typeParameters: ReadonlyArray<TypeParameterDeclaration> | undefined,
parameters: ReadonlyArray<ParameterDeclaration>,
type: TypeNode | undefined,
equalsGreaterThanTokenOrBody: Token<SyntaxKind.EqualsGreaterThanToken> | ConciseBody,
Copy link
Author

Choose a reason for hiding this comment

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

Need to ensure that we use the => from the new tree, which has a correct position. The => from the old tree has a different position that causes assertion errors in the formatter.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add it at the end instead?

Copy link
Member

Choose a reason for hiding this comment

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

We should always keep elements in the create/update methods in the order they are parsed.

@ghost ghost changed the title Ensure that emitter calls calbacks Ensure that emitter calls callbacks Sep 6, 2017
@rbuckton
Copy link
Member

rbuckton commented Sep 6, 2017

#18091 does not seem related, is that the right bug?

Nevermind, your initial paragraph indicated the bug was an infinite loop which is not what #18091 is discussing, but this does address #18091.

emit(node.typeParameter.constraint);
write("]");

if (onEmitNode) {
Copy link
Member

Choose a reason for hiding this comment

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

We really should avoid calling onEmitNode directly, and should be calling emit instead, except in this case we need to emit a type parameter in a different fashion. For anyone writing a custom transform, they won't know whether their onEmitNode is being called against a normal TypeParameter or the TypeParameter of a MappedType. That is the reason we have EmitHint, as it allows us to provide a hint as to the context in which we are emitting a node.

I would recommend we add MappedTypeParameter to the EmitHint enum and add a branch to pipelineEmitWithHint for that branch that calls emitMappedTypeParameter. Then we can replace this code with a call to pipelineEmitWithNotification(EmitHint.MappedTypeParameter, node.typeParameter).

@@ -1114,6 +1115,14 @@ namespace ts {
write("}");
}

function emitMappedTypeParameter(_hint: EmitHint, node: TypeParameterDeclaration) {
write("[");
Copy link
Member

Choose a reason for hiding this comment

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

We parse the opening and closing brackets in parseMappedType, so their tokens do not belong to the type parameter. Writing the [ and ] tokens should happen in emitMappedType.

@@ -1382,7 +1381,10 @@ namespace ts {
}

function emitYieldExpression(node: YieldExpression) {
write(node.asteriskToken ? "yield*" : "yield");
write("yield");
if (node.asteriskToken) {
Copy link
Member

Choose a reason for hiding this comment

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

No need to guard against undefined, as that is already handled in emit

@@ -1662,7 +1664,11 @@ namespace ts {
function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) {
emitDecorators(node, node.decorators);
emitModifiers(node, node.modifiers);
write(node.asteriskToken ? "function* " : "function ");
write("function");
Copy link
Member

@rbuckton rbuckton Sep 6, 2017

Choose a reason for hiding this comment

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

No need to guard against undefined as that is already handled in emit

Edit: I was incorrect. Apparently emit does not have this guard.

Copy link
Author

@ghost ghost Sep 7, 2017

Choose a reason for hiding this comment

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

I get a test failure in the test PrinterAPI printFile removeComments if I try to emit the asterisk unconditionally. emit doesn't seem to check for an undefined node.

@@ -2395,6 +2399,17 @@ namespace ts {
emitList(parentNode, parameters, ListFormat.IndexSignatureParameters);
}

function emitSingleElementList(list: NodeArray<Node>) {
Copy link
Member

Choose a reason for hiding this comment

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

Can you craft suitable ListFormat bitmasks to use instead and just call emitList where applicable instead of creating a special cased list emit function?

@@ -2580,16 +2595,20 @@ namespace ts {
: writeTokenText(token, pos);
}

function writeTokenNode(node: Node) {
function writeTokenAndCallCallbacks(node: Node, text: string) {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can simplify this by replacing all calls to writeIfPresent with emit, as emit already guards against undefined, will in turn eventually call writeTokenNode, and may give us better comment preservation and source-maps.

return array.hasOwnProperty("pos")
&& array.hasOwnProperty("end");
const res = array.hasOwnProperty("pos") && array.hasOwnProperty("end");
if (res) {
Copy link
Member

Choose a reason for hiding this comment

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

We possibly call this a lot between visitor and factory. Do the added checks have any perf impact?

@ghost ghost force-pushed the extract-method-format branch 2 times, most recently from 495f1a2 to 7d931bb Compare September 7, 2017 18:14
@ghost ghost force-pushed the extract-method-format branch from 49e21b1 to 5b44de1 Compare September 7, 2017 18:44
condition: Expression,
whenTrue: Expression,
whenFalse: Expression,
questionToken: Token<SyntaxKind.QuestionToken> = node.questionToken,
Copy link
Member

Choose a reason for hiding this comment

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

We should always keep these arguments in the same order they are parsed.

typeParameters: ReadonlyArray<TypeParameterDeclaration> | undefined,
parameters: ReadonlyArray<ParameterDeclaration>,
type: TypeNode | undefined,
equalsGreaterThanTokenOrBody: Token<SyntaxKind.EqualsGreaterThanToken> | ConciseBody,
Copy link
Member

Choose a reason for hiding this comment

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

We should always keep elements in the create/update methods in the order they are parsed.

}
body: ConciseBody,
// Optional for backwards-compatibility only -- should always provide this.
equalsGreaterThanToken: Token<SyntaxKind.EqualsGreaterThanToken> = node.equalsGreaterThanToken): ArrowFunction {
Copy link
Member

Choose a reason for hiding this comment

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

As complex as it was, the previous version was better. We should always keep these arguments in the same order as they would be parsed/visited.

@@ -4261,6 +4261,7 @@ namespace ts {
SourceFile, // Emitting a SourceFile
Expression, // Emitting an Expression
IdentifierName, // Emitting an IdentifierName
MappedTypeParameter, // Emitting a TypeParameterDeclaration inside of a MappedTypeNode
Copy link
Member

Choose a reason for hiding this comment

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

nit: realign the surrounding comments

@@ -525,7 +525,9 @@ namespace ts {
return updateConditional(<ConditionalExpression>node,
visitNode((<ConditionalExpression>node).condition, visitor, isExpression),
visitNode((<ConditionalExpression>node).whenTrue, visitor, isExpression),
visitNode((<ConditionalExpression>node).whenFalse, visitor, isExpression));
visitNode((<ConditionalExpression>node).whenFalse, visitor, isExpression),
visitNode((<ConditionalExpression>node).questionToken, visitor, n => n.kind === SyntaxKind.QuestionToken),
Copy link
Member

Choose a reason for hiding this comment

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

I would just use isToken rather than be overly concerned about the specific token. No need to allocate a new function object every time this is called.

visitNode((<ConditionalExpression>node).whenFalse, visitor, isExpression));
visitNode((<ConditionalExpression>node).whenFalse, visitor, isExpression),
visitNode((<ConditionalExpression>node).questionToken, visitor, n => n.kind === SyntaxKind.QuestionToken),
visitNode((<ConditionalExpression>node).colonToken, visitor, n => n.kind === SyntaxKind.ColonToken));
Copy link
Member

Choose a reason for hiding this comment

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

I would just use isToken rather than be overly concerned about the specific token. No need to allocate a new function object every time this is called.

visitNode((<ArrowFunction>node).equalsGreaterThanToken, visitor, n => n.kind === SyntaxKind.EqualsGreaterThanToken),
visitFunctionBody((<ArrowFunction>node).body, visitor, context));
visitFunctionBody((<ArrowFunction>node).body, visitor, context),
visitNode((<ArrowFunction>node).equalsGreaterThanToken, visitor, n => n.kind === SyntaxKind.EqualsGreaterThanToken));
Copy link
Member

Choose a reason for hiding this comment

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

I would just use isToken rather than be overly concerned about the specific token. No need to allocate a new function object every time this is called.

@ghost
Copy link
Author

ghost commented Sep 7, 2017

Ran the perf tests and saw no slowdown due to this PR.

Copy link
Member

@rbuckton rbuckton left a comment

Choose a reason for hiding this comment

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

I'd still rather not have emitSingleElementList if possible, but we can remove it later.

@@ -465,6 +474,12 @@ namespace ts {
emitIdentifier(<Identifier>node);
}

function pipelineEmitMappedTypeParameter(node: TypeParameterDeclaration): void {
Copy link
Member

Choose a reason for hiding this comment

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

Just call this emitMappedTypeParameter.

@ghost ghost merged commit ed4e2e6 into master Sep 7, 2017
@ghost ghost deleted the extract-method-format branch September 7, 2017 21:30
ghost pushed a commit that referenced this pull request Sep 7, 2017
* Ensure that emitter calls calbacks

* Move new parameter to end of parameters

* Fix for ConditionalExpression

* Make suggested changes to emitter

* Fix parameter ordering

* Respond to minor comments

* Remove potentially expensive assertion

* More emitter cleanup
@ghost ghost mentioned this pull request Sep 7, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Sep 7, 2017

@Andy-MS we will need to port this to release-2.5

mhegazy pushed a commit that referenced this pull request Sep 7, 2017
* Ensure that emitter calls calbacks

* Move new parameter to end of parameters

* Fix for ConditionalExpression

* Make suggested changes to emitter

* Fix parameter ordering

* Respond to minor comments

* Remove potentially expensive assertion

* More emitter cleanup
This pull request was closed.
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