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

Node builder reuses type predicate nodes, more often reuses return position nodes #57990

Merged
merged 1 commit into from
Mar 28, 2024

Conversation

weswigham
Copy link
Member

As you can see from the baselines now, this improves how often we reuse nodes by quite a lot.

@typescript-bot typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Mar 28, 2024
}> | Extract<ast.Node98, {
kind: NodeType;
}> | Extract<ast.Node99, {
export declare const isNodeOfType: <NodeType extends ast.SyntaxKind>(nodeType: NodeType) => (node: ast.Node | null | undefined) => node is Extract<ast.Node, {
Copy link
Member Author

Choose a reason for hiding this comment

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

This change is probably the biggest "these kinds of changes [to reuse more input nodes] are obviously improvements for everybody" demonstrator in our test suite.

Copy link
Member

Choose a reason for hiding this comment

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

Very nice, though I wonder if the crash from this test case was related to the printing or not...

Comment on lines -20 to 21
Type '() => string | number' is not assignable to type '() => number'.
Type '() => number | string' is not assignable to type '() => number'.
Type 'string | number' is not assignable to type 'number'.
Copy link
Member

Choose a reason for hiding this comment

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

Kind of unfortunate that this order changes to mismatch the error above, but, that's life

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, the signature has a declaration, so can reuse its' return type node, but the union type itself has no such luck. Now, it doesn't have to be that way (intersections remember their order forever because it matters if there are overloads, unions could do the same even if it only matters for looks) - but we'd produce more type identities.

return factory.updateParameterDeclaration(node, /*modifiers*/ undefined, node.dotDotDotToken, visitEachChild(node.name, visitExistingNodeTreeSymbols, /*context*/ undefined), node.questionToken, factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), /*initializer*/ undefined);
}
if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.ComputedPropertyName && !isLateBindableName(node.name)) {
return undefined;
Copy link
Member

Choose a reason for hiding this comment

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

This is the only new return of undefined; is there a way to avoid it to prevent all of the extra !?

Copy link
Member Author

Choose a reason for hiding this comment

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

...for now. There will be more.

Copy link
Member Author

Choose a reason for hiding this comment

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

(Right now the node builder's existing node tree reuse is somewhat poor at eliding erroneous nodes from the input - this is something that the top-level declaration emitter currently does more consistently than this. Honestly, ideally this function and visitDeclarationSubtree in the declaration emitter would be largely shared (since they do the same thing: walk an existing node tree and make it fit to emit), but unifying them is going to take a bit - one needs to subsume the other's feature set entirely first, rather than them having overlap, but also unique parts)

Comment on lines +8772 to +8774
(visited as Mutable<typeof visited>).type = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
if (isParameter(node)) {
(visited as Mutable<ParameterDeclaration>).modifiers = undefined;
Copy link
Member

Choose a reason for hiding this comment

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

Are these guaranteed to be copies? This is the first time we're pulling Mutable into the checker and it feels odd to have to do this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes - right above we say "if it's not a copy, copy it so we can add a .type safely".

Honestly, this is just the bare minimum needed to keep the tests looking correct while reusing the current set of input nodes. As I mentioned in the last comment, this scenario, of transforming the node to no longer have some kinds of error states in them (erroneous modifiers, missing required type nodes), this function is actually pretty lacking in compared to the declaration emitter's visitDeclarationSubtree which is its' dual. It needs a proper cleanup in that regard, but I don't think we're quite ready for that just yet - testing it won't be easy until we're actually reusing way more nodes via the node builder (which is the plan!).

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I guess I didn't read that code that way; wasn't sure if one could return an entirely different but not synthesized node. But, makes sense.

Copy link
Member Author

Choose a reason for hiding this comment

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

For additional context: visitDeclarationSubtree in declarations.ts and tryReuseExistingTypeNodeHelper in checker.ts have the same goal: take in a type node (or part thereof), and make it fit for emit, per our declaration file rules. Except tryReuseExistingTypeNodeHelper has only really seen extensive use for JS emit - so today it mostly just copies JSDoc type nodes and transforms them, while also doing some reference and visibility tracking. Meanwhile visitDeclarationSubtree doesn't handle JSDoc at all, but beacuse it's been in use longer for declaration emit, handles more edge cases around correcting erroneous input patterns when building the output (and also does some of that same reference tracking).

Eventually we'll merge the two into one implementation used in both places (ideally), but until then, I'm just fixing the things tryReuseExistingTypeNodeHelper is obviously demonstrated as lacking when it starts returning the input node in our tests.

@weswigham weswigham merged commit 6ff28d1 into microsoft:main Mar 28, 2024
25 checks passed
@weswigham weswigham deleted the preserve-predicate-nodes branch March 28, 2024 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants