-
Notifications
You must be signed in to change notification settings - Fork 2k
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
[CS2] Nodes: Missing declarations for generated variables in parameter defaults #4413
Comments
Alternatively we could fall back to the old handling in such cases, which would give us a better scope for these temporary variables. (function(a) {
var ref
if (a === undefined) {
a = (ref = foo()) != null ? ref : bar()
}
}) |
A final option for this: (function(a = (() => {
var ref
return (ref = foo()) != null ? ref : bar()
})()) {}) I reckon falling back is the nicest option. Regardless, to fix this we need to be able to detect when an expression will need to be cached. Hopefully |
When would this really happen? I don’t see when I would put a whole expression in a function parameters list. |
I guess it's less about when it would happen, and the fact that it's allowed to happen incorrectly (i.e. the compiler generates invalid Javascript). I can't remember the context I discovered it in, but I suspect it was whilst generating contrived examples for class tests 😄 Anyway, I suspect that fixing this would be easier and more maintainable than properly disallowing it (i.e. making it not parse and/or validating the content of |
The patch for this is almost identical to the one used to fix #4426 - in both cases it feels like there's a missing scope (parameter default scope, in this case, and class body scope, in the case of #4426). Of course, syntactically, it's impossible to declare variables in parameter defaults or class bodies, so we have to choose whether to hoist such declarations to the enclosing scope (potentially polluting it with a bunch of temporary variables), or declare them in an IIFE wrapper (causing 'ugly' output, and potentially impacting performance). My feeling is that we should wrap classes, and hoist parameter defaults. i = 0
class B extends A
"#{++i}": (a = foo() ? bar()) -> super()
# ...
# B = (function () {
# var name, ref
# return class B extends A {
# [name = `${++i}`] (a = (ref = foo()) != null ? ref : bar()) {
# super[ref]()
# }
# }
# })() Ultimately most of these cases are pretty esoteric, but I think it's important that we maintain the consistency of the language (i.e. everything is an expression). |
You’re the expert on hoisting, do you feel like taking this on? I would only add that another goal is to output idiomatic ES whenever possible, such as outputting ES parameter default values with the parameter and not in the function body. We don’t want to go too far into handling edge cases that we generate convoluted (and potentially buggy) JavaScript when it isn’t necessary. |
I’m looking at this again, and I think that perhaps there isn’t actually a bug here. Consider when we modify your original example: // (a = foo() ? bar()) ->
(function(a = (ref = foo()) != null ? ref : bar()) {}); to: // (a = foo() ? bar()) -> ref
(function(a = (ref1 = foo()) != null ? ref1 : bar()) {
var ref1;
return ref;
}); (I’m on the destructuring branch.) Clearly the compiler is tracking the scope of the generated variable // (a) -> a
(function(a) {
return a;
}); Am I missing something here, or are we okay? |
On the broader point of declaring it in the function or not... Since these I think the main concern for me was that this is a change from CS1, wherein such |
Well they were in the function body in CS1 because almost anything complicated or unusual in function parameters got pushed into the function body. Now with CS2 trying to honor default values and destructuring in the parameters as opposed to the body, we necessarily will have some complicated stuff up there. I don't think we should be redeclaring variables in a function body that we've already declared as function parameters. I'm leaning toward closing this issue, unless there's something I'm missing? |
OK, the behaviour was a bit more complicated than I initially thought. However, consider the following CoffeeScript: 'use strict'
a = ->
b = -> 1
c = (d = a() ? b()) ->
c() This is currently compiled to: // Generated by CoffeeScript 2.0.0-alpha1
var a, b, c;
a = function() {};
b = function() {
return 1;
};
c = function(d = (ref = a()) != null ? ref : b()) {};
c(); Notice that
|
The solution I proposed in the first post was ensuring that An easier and (imo) better solution would be to demote |
I’m fine with this, though we would have to make sure that not all destructured parameters are |
Yes, I agree. That'll be a fun one 😛 |
I did a little digging into this one. It turns out that foo = -> null
bar = -> 33
f = (a = foo() ? bar()) -> a
f() # 33 var bar, f, foo;
// ...
f = function(a = (ref = foo()) != null ? ref : bar()) {
var ref;
return a;
}; But since |
…g. `ref`), shift the declarations for those variables into the parent scope; fixes jashkenas#4413
See #4640. I tried to do an implementation that compiled the parameter “prematurely,” just to figure out if it caused the creation of any generated variables and if so, to shift it into the function body; but I couldn’t find a safe way to do so. So in #4640 I went for a solution closer to your original suggestion: if the compilation of a function parameter results in new generated variables, I just shift those variables from the function scope into the parent scope. Since the function scope is already a child of the parent scope, I don’t need to worry about name collisions. So that solves this immediate issue, and I guess we can tackle the broader implications of expressions in function parameters when the next issue arises. But for now I’d like to stick with the simpler solution (assuming it works as I think it does) because the generated JS is closer to the author’s intent, as the expression is in the function parameter just like in the original CoffeeScript. |
…nction-parameters [CS2] Fix #4413: Generated variables in function parameters
Fixed by #4640. |
Note: this is for the
2
branch only.Note that
ref
is used despite there being novar ref
anywhere.A simple patch to fix this is
I'm not sure though if such functions should also be compiled in a safety wrapper, though
The text was updated successfully, but these errors were encountered: