-
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] Fix handling of parameters that are complex #4430
[CS2] Fix handling of parameters that are complex #4430
Conversation
…the function body
…to follow usual ES idiom
…ed-parameter-evaluation-order
…ed-parameter-evaluation-order
…menting variable (or more generally any complicated parameter that isComplex)
49ccded
to
c4b6d27
Compare
The problem with just testing that |
…er of execution matters; but don’t pull _all_ complex parameters out of the parameter list, so that we don’t lose parameter default values
Okay, so I think I’ve found a way to detect when parameter execution order might matter, and in those cases I pull the parameter out to define within the function body; but I feel like this is a losing battle. I feel like we’ll just be chasing more and more edge cases as people think of more creative ways to squeeze expressions into parameters. I’m not sure that the execution order of parameters should be something we’re guaranteeing. |
@GeoffreyBooth I think the approach we should take is an all or nothing one - either all parameter defaults are compiled in the signature, or they're all compiled in the body. They are functionally equivalent, after all, so people shouldn't be too hung up about where they appear. In terms of maximising the 'happy path', once we're compiling to ES2015 destructured parameters (with defaults), this issue will go away on its own. I'm not sure I understand your point about
In that case, |
@connec So the “real” fix is not this PR, but to output destructuring as ES2015? Sounds good to me. |
Amen to that. |
We support things in parameters that ES doesn't, like expansions or splats in non-final positions, and At the time I think I mentioned how it would be a pain to deal with |
The solution to handling
Expansions and splats do make things interesting. I guess the approach we currently take there would need to continue (i.e. all parameters after a splat go in the function body). |
I think maybe you should consider it again. If the mission statement for CS2 is "CoffeeScript, with all of the ES6/7 goodies", then folks are going to expect things like classes and destructuring and arrow functions to work in the same way that they do in JavaScript. If it gives you more expected behavior, is easier to implement, produces cleaner output, and will lead to less future questions of "Why is my CoffeeScript destructuring doing this strange thing?", then perhaps it's the way to go. |
@jashkenas when there’s no choice, yes. That’s what led to the breaking change for parameter default values now applying the default only for For something like a splat parameter, there is a middle ground: when it’s the last parameter, it gets output as ES; when it’s not, we do contortions and declare/assign variables in the function body. I think that’s the model we want to follow: when people stick to what’s possible in ES, we should output idiomatic ES; when they type something that isn’t possible in ES, but we can achieve it without too much drama, we should give them what they want. There’s no reason to ban non-final splats just because they’re not possible in ES, aside from sparing us the hassle of implementing them. Likewise with |
Sure, in principle. Completely sound reasoning.
I think this might be a "too much drama" case. |
It might be, but in this case we could opt to keep the 1.x approach. There’s no compatibility reason to do ES’ output, unlike classes; it just pretties up the output. That improved output might not be worth the breaking changes. |
Getting back to this PR, I did some investigating into what was wrong with f = (opts = {}) -> into f = function(opts) {
opts = opts != null ? opts : {};
}; which is not what we want. (For one thing, for ES compatibility it needs to be Because all objects are complex. So where does this leave us? Well, the safe solution would be to put back the check for Update: see latest commits for an implementation of this approach. |
…s only when undefined, not when null or undefined
… allowable in a function parameter list rather than the function body (there are lots more detections we could add to find additional “safe” parameters)
…not complex be allowed in the function parameter list (like `obj.prop`)
@GeoffreyBooth I maintain that the best approach to this would be to compile all parameter defaults to ES2015, including 'complex' parameter names (i.e. destructured ones). This will resolve the ordering problem. For the case of splats, expansions, or anything else that trips that up, we should move all default assignments after the interruption into the method body. Contrived example time: i = 0
foo = ({ a = ++i }, b = ++i, [ c = ++i ], rest..., { d = ++i }, e = ++i, [ f = ++i ]) ->
console.log a, b, c, d, e, f, rest
console.log foo {}, undefined, [], 'woah', {}, undefined, [] var foo
foo = function ({ a = ++i }, b = ++i, [ c = ++i ], ...arg) {
var rest, j, d, e, f;
rest = 4 <= arg.length ? arg.slice(0, j = arg.length - 3) : (j = 0, []);
({ d = ++i } = arg[j++]);
e = arg[j++];
if (e === undefined) {
e = ++i;
}
([ f = ++i ] = arg[j++]);
return console.log(a, b, c, d, e, f, rest);
}
console.log(foo({}, undefined, [], 'woah', {}, undefined, [])) // 1 2 3 4 5 6 [ 'woah' ] |
Note: The only departure of the above from the current |
@connec Yes, that is the plan. Compiling destructuring into ES syntax just hasn’t been implemented yet, and I didn’t want to tackle that in this PR. We can ship 2.0.0-alpha1 without it. It’s under discussion in coffeescript6/discuss#69, and it’s a big enough effort that it will require its own PR. So does this PR address everyone’s concerns with parameter ordering? I think I want to make one more change, which is to refactor But aside from unnecessary cleanup, is this PR acceptable to everyone? |
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.
I really like your idea to refactor Param::isComplex
into Param::canBeInParameterList
, and isComplex
methods into mustCache
.
val = new Op '?', ref, param.value if param.value | ||
exprs.push new Assign new Value(param.name), val, '=', param: yes | ||
if param.value? | ||
condition = new Literal param.name.value + ' === 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.
$ coffee -bpe 'undefined'
void 0;
Should we replace undefined
with void 0
for consistency?
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 could be (roughly) condition = new Op '==', param, new UndefinedLiteral
, and would then remain consistent.
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.
Why are we using void 0
in the first place? Because it’s a few characters shorter? Because apparently many years ago it was possible to redefine undefined
? SO.
I would think that undefined
is more readable, and since one of our goals is to produce human-readable code, we should replace all void 0
s with undefined
.
if @value instanceof Value | ||
unless @value.base.isComplex() | ||
no | ||
else |
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.
I'd either flip this unless…else
around or change it into if not…else
.
if node.isComplex() | ||
# This can be further refined. An empty object will evaluate as | ||
# not having any complex children, but as long as it has at least | ||
# one property it will be considered complex. |
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.
Could you give examples on ...
- things that this code does not consider complex
- things that this code does consider complex, but could be improved no to in the future?
In terms of naming, perhaps flipping it around to |
It's not even really about "must cache" — it's about "complicated enough that we should cache". A long repeated expression, even if idempotent, shouldn't be output multiple times by the CoffeeScript compiler. Cleaner to stuff the result in a temporary variable instead. |
@connec I think of atomic as meaning small and unified. At work I encourage atomic commits in Git, meaning each commit should do just one thing (as explained in the commit message) and nothing more. I would see Idempotent is much better, but it has the same problem: why is I do like starting with |
I guess from @jashkenas' comment In reference to |
As an example of "complicated enough that we should cache", |
If you look back at old tickets, a lot of the existing
Is not idempotent. JavaScript has getters, and |
It certainly is. But that doesn't make it idempotent. |
@jashkenas you beat me to it—yes, I need to revise this to not treat Currently something like destructuring (in our 1.x mode) gets declared in the function body, without triggering the remaining parameters to also go in the function body. This would need to change. Even after we implement CS2 destructuring, there will still be the case of |
And @vendethiel even if we think getters and setters are bad, we still need to support them. An object or class can be imported from a library and have a getter or setter. Using that object in a function parameter in CoffeeScript should still work. |
We've been treating them as side-effects-free ... sometimes, for some time. $ coffee -bce "a.b? c"
if (typeof a.b === "function") {
a.b(c);
} |
…g complex parameters go into the function body; no need to create lots of exceptions of when to choose whether to put a complex param in the body
Okay, I focused on resolving the immediate issue that triggered this PR. I think I’ve got it now, but I’m tired. Can someone with fresh eyes please review this? I feel like Parameter default values are preserved until the first |
Okay, I’ve refactored @lydell the sections you made comments on are no longer around. After I refactored I think this PR is ready for merge. Any other notes? |
@lydell did I satisfy your review? Do you have any further notes? |
Fixes #4406 (comment). @connec?