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

[CS2] Destructured parameter default evaluation order is broken #4406

Closed
connec opened this issue Dec 19, 2016 · 8 comments
Closed

[CS2] Destructured parameter default evaluation order is broken #4406

connec opened this issue Dec 19, 2016 · 8 comments
Labels
Milestone

Comments

@connec
Copy link
Collaborator

connec commented Dec 19, 2016

Example:

current = 0
next    = -> ++current

foo = ({ a = next() }, b = next()) -> [ a, b ]

console.log foo {}

Expected output: [ 1, 2 ]

Output on master: [ 1, 2 ]

Output on 2: [ 2, 1 ]


This is caused by the 2 branch compiling defaults for simple parameters into the argument list, but handling complex parameters' defaults in the function body.

Output on master (simplified)

function (arg, b) {
  var a
  a = arg.a != null ? arg.a : next();
  if (b == null) {
    b = next();
  }
  return [a, b];
}

Output on 2 (simplified)

function (arg, b = next()) {
  var a;
  a = arg.a != null ? arg.a : next();
  return [a, b];
}

I encountered this whilst improving super handling in #4354 (coming soon).

I guess the way we want to fix this is to compile destructured parameters and their defaults to ES also, but I wanted to create an issue to make sure we don't forget about it!

@jashkenas jashkenas added the bug label Dec 19, 2016
@GeoffreyBooth GeoffreyBooth changed the title Destructured parameter default evaluation order is broken on 2 branch [CS2] Destructured parameter default evaluation order is broken Dec 19, 2016
@GeoffreyBooth
Copy link
Collaborator

Compiling destructured parameters and their defaults to ES means losing the ability to use this in parameters. That’s why I didn’t go down that road.

Perhaps another solution would be to detect if a function call is happening within parameters, and if so, do everything the “old way” (define the parameter variables within the function body).

@connec
Copy link
Collaborator Author

connec commented Dec 19, 2016

@GeoffreyBooth I've actually improved that on my branch for #4354 - @ parameters are now being pulled off destructured parameters and assigned separately. This probably means there's quite a bit of destructuring code to clean up...

Current output on my branch:

({ @a, b: { @b, c: { @c } } }) ->
(function(arg) {
  var a, b, c, ref, ref1;
  a = arg.a, (ref = arg.b, b = ref.b, (ref1 = ref.c, c = ref1.c));
  this.a = a;
  this.b = b;
  this.c = c;
});

@GeoffreyBooth
Copy link
Collaborator

So maybe we should fix this after #4354 is merged in? Or @connec if you have time to work on it, be my guest, I think you have more time than me 😄

@connec
Copy link
Collaborator Author

connec commented Dec 19, 2016

Lets do it after. I might get a chance to look at it then, but I don't want to have multiple PRs ongoing.

GeoffreyBooth added a commit to GeoffreyBooth/coffeescript that referenced this issue Jan 21, 2017
GeoffreyBooth added a commit that referenced this issue Jan 22, 2017
* Add failing test per #4406

* If a parameter is a function call, define it in an expression within the function body

* Remove the space between `function` and `*` for generator functions, to follow usual ES idiom

* We can collapse `isCall` into `isComplex`

* Don’t need existence check here
@connec
Copy link
Collaborator Author

connec commented Jan 23, 2017

There's another case not covered by the PR:

i = 0
({ a = ++i }, b = ++i) ->

It generates:

// Generated by CoffeeScript 2.0.0-alpha
var i;

i = 0;

(function(arg, b = ++i) { // 'b' will be set first (b === 1)
  var a, ref;
  a = (ref = arg.a) != null ? ref : ++i; // 'a' will be set second (a === 2)
});

@lydell lydell reopened this Jan 23, 2017
@GeoffreyBooth
Copy link
Collaborator

This makes me more inspired to rename isComplex.

@connec
Copy link
Collaborator Author

connec commented Jan 23, 2017

I guess what it's really trying to capture is 'may have side effects'.

GeoffreyBooth added a commit that referenced this issue Feb 1, 2017
* Add failing test per #4406

* If a parameter is a function call, define it in an expression within the function body

* Remove the space between `function` and `*` for generator functions, to follow usual ES idiom

* We can collapse `isCall` into `isComplex`

* Don’t need existence check here

* Correct destructured parameter default evaluation order with an incrementing variable (or more generally any complicated parameter that isComplex)

* Try to pull complex parameters out of the parameter list if their order of execution matters; but don’t pull _all_ complex parameters out of the parameter list, so that we don’t lose parameter default values

* Add lots of comments about node special properties

* Err on the side of caution in deciding whether a complex parameter is allowable in a function parameter list rather than the function body (there are lots more detections we could add to find additional “safe” parameters)

* Follow the ES and CS2 convention of assigning parameter default values only when undefined, not when null or undefined

* Along with arrays and empty objects, also let values whose bases are not complex be allowed in the function parameter list (like `obj.prop`)

* Better way to check for undefined parameters when declaring them in a function body

* Once we’ve put a complex parameter in the function body, all following 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

* Rename `isComplex` to `shouldCache` for clarity
@GeoffreyBooth GeoffreyBooth modified the milestone: 2.0.0 Apr 5, 2017
@GeoffreyBooth
Copy link
Collaborator

GeoffreyBooth commented May 1, 2017

In 2.0.0-beta1 @connec’s example compiles to:

var i;

i = 0;

(function({a = ++i}, b = ++i) {});

which should be in the correct order. If there are further edge cases to fix, please open a new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants