-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Conditionally emit rest parameters #518
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
Conditionally emit rest parameters #518
Conversation
(Note: I've moved this comment to the pull-request's main comments so that you can easily come back to it.) Hi @duncanmak! After re-reading your original pull request message, I realized that you may be looking for some guidance in making this contribution. Let me just say thank you for your interest and efforts so far. I think what you're trying to do is to appropriately traverse the body of a function in a smart way to short-circuit on a Firstly, you will need to recursively traverse the body - in other words, within the function, you will have to also traverse the bodies of We actually have a function that makes this pretty darn easy - Secondly, I would avoid trying to be too smart here - JavaScript has a funny tendency to surprise you when you try to do that sort of thing! For instance, if I'm right that you're trying to bail out if you hit a function f(a: string, b: number, ...rest: any[]) {
console.log(g());
return;
function g(): any {
return rest[0];
}
} This will be a problem, since such an approach will incorrectly assume that If you have any other questions, please don't hesitate to ask! |
Still need to figure out the correct way to match the parameter against the child Node.
Hello, @DanielRosenwasser! Thanks for your notes about using
For a test program like:
I get this with my patch applied:
So what's the right way to write this test? Thanks! |
|
||
function isReferenced(node: Node): boolean { | ||
|
||
if (node === undefined) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if you can ever run into this situation unless node
is explicitly passed undefined
.
When you run into an identifier, you can fetch the symbol that semantically represents it with Once you've gotten this down, we can dive a little into the constructs you need to explicitly check, and what you can get away with eliding. |
It feels a bit like fumbling in the dark, but I think I made some progress. This is my current ad-hoc set of tests:
With the latest patch, I got the expected code generated, except for P.S. |
@@ -7098,6 +7098,31 @@ module ts { | |||
return false; | |||
} | |||
|
|||
function isParameterReferencedInBody(body: FunctionDeclaration, param: ParameterDeclaration) { | |||
|
|||
function equals(a: Symbol, b: Symbol) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can actually simply check if the symbol's valueDeclaration
is equal to the parameter itself (using nothing but ===
).
|
||
function isReferenced(node: Node): boolean { | ||
switch (node.kind) { | ||
case SyntaxKind.Parameter: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little inaccurate because you have to consider default arguments:
function f(a: number, ...rest: number[]) {
function g(x: number = rest[0]): number {
return a || x;
}
return g();
}
Additionally, case
/default
clauses should all be indented in our codebase.
I figured out how to deal with default arguments, and I fixed up the indentation as well. I'm still unsure about
|
No, the problem is that in JavaScript, even if you do function f(...rest: number[]) {
var rest = [1,2,3];
return rest[0];
} you are still talking about the same In the mean time, you may be noticing that the Travis CI builds are failing on this pull request. This is because our baseline references need to be updated. Additionally, we'll need a new test for this change. Here's how to go about this:
|
switch (node.kind) { | ||
case SyntaxKind.Parameter: | ||
var p = <ParameterDeclaration> node; | ||
return (p.initializer) ? isReferenced(p.initializer) : false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might as well just let forEachChild
take care of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason why this is necessary is because when forEachChild
descends into body
(which is a FunctionDeclaration
), it will go through the parameters
first before the body
.
Without this case, isParameterReferencedInBody
will always return true
because it can always find param
in the function's parameters
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mentioned earlier that I might want to make this function a bit more general - what do you have in mind?
I'm also thinking that it might be good to rename the function -
isParameterUsedInFunction(func: FunctionDeclaration, param: ParameterDeclaration): boolean
might be a better signature. Do you have any suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can rename body
to func
, and call forEachChild(func.body, isReferenced)
.
Actually, now that I think about it, this only works if the parameter declaration is the last parameter. Perhaps you should just iterate over the parameters, calling isReferenced
on the initializers, and then call forEachChild(func.body, isReferenced)
What I was thinking was that instead of a ParameterDeclaration
, you just take a Declaration
, but I wouldn't bother with it now. We can always do that in a different commit when we need it.
I will be working on updating the baseline tests later tonight. After getting all the reviews, I can do a squash to clean up the intermediate commits within this pull request. |
Overall this change looks fairly reasonable. The code should definitely be simplified and clarified. But i think it's a nice to have. |
Renamed isParameterReferencedInBody to isParameterUsedInFunction.
@RyanCavanaugh Did you refer to this in #153? If someone take a look and let me know what remains to be done, I would love to land this. Thanks! |
Apparently your build is still failing on Travis - I think you updated a test after updating the baselines for |
@DanielRosenwasser - is there anything else left for me to do with this PR? Are we waiting on @mhegazy to review the changes I made to the baselines? |
Terribly sorry about the delay - I'm enthused for this change, but I just wanted to wait on @mhegazy regarding what we need to do with the tests before committing to anything. Hope you'll understand, as we've been so busy with 1.1, and I can vouch for Mohamed when I say he's dealing with quite a share. =) |
Oh, that's okay. Is this change to be included in 1.1, or will it be landed later? |
sorry about the delay. totally fell off my radar. I just need to run some perf tests, I am concerned about the approach, walking the function body to identify if we should optimize the arg away can be costly. |
What would be a more optimal approach? Something along the lines of |
I would say something along the lines of what we do with this capture. |
Hey @duncanmak, as Mohamed mentioned, the smarter way to go about this is to make it work the same way that As background, there are situations in which we need to perform a capture on However, we don't traverse every arrow function body looking for What I suggest you do is that when you encounter an identifier now, find out if it was declared as a rest parameter, and if so, turn on a flag (which you may have to add - maybe call it |
This is a sketch to omit code emission for the
...rest
parameter when it's not used in the body.With this patch, the example in #498 will be compiled without generating code for the
...rest
parameter.If this is the right approach, I can complete the implementation by handling the other cases in the new
isParameterReferencedInBody
method.