Skip to content

ES5:Emit parameter initialiser before object rest destructuring#14090

Merged
sandersn merged 6 commits into
masterfrom
es5-emit-param-initialiser-before-object-rest
Apr 13, 2017
Merged

ES5:Emit parameter initialiser before object rest destructuring#14090
sandersn merged 6 commits into
masterfrom
es5-emit-param-initialiser-before-object-rest

Conversation

@sandersn
Copy link
Copy Markdown
Member

Fix #14026, where ES5 emit for a parameter with

  1. a default value initialiser
  2. an object binding pattern containing an object rest

incorrectly emitted the destructuring for the object rest before the default value initialisation.

This happened because, during emit, the ES next transform runs first, transforming object rest destructuring and marking it as part of the function prologue. Then the ES5 transform runs and transforms the default initialiser, also marking it as part of the prologue. Then the prologue is emitted in the order the statements were added.

The fix is to not mark the object rest destructuring as part of the prologue. I'm not 100% sure that this is the right fix, but it fixes the bug as it stands today.

Here's an example:

function foobar({ bar={}, ...opts }: any = {}) { }

which should have the ES5 emit:

function foobar(_a) {
  if (_a === void 0) { _a = {}; }
  var _b = _a.bar, bar = _b === void 0 ? {} : _b, opts = __rest(_a, ["bar"]);
}

Fix #14026, where ES5 emit for a parameter with

1. a default value initialiser
2. an object binding pattern containing an object rest

incorrectly emitted the destructuring for the object rest before the
default value initialisation.

This happened because, during emit, the ES next transform runs first,
transforming object rest destructuring and marking it as part of the
function prologue. Then the ES5 transform runs and transforms the
default initialiser, also marking it as part of the prologue. Then the
prologue is emitted in the order the statements were added.

The fix is to not mark the object rest destructuring as part of the
prologue. I'm not 100% sure that this is the right fix, but it fixes the
bug as it stands today.

Here's an example:

```ts
function foobar({ bar={}, ...opts }: any = {}) { }
```

which should have the ES5 emit:

```js
function foobar(_a) {
  if (_a === void 0) { _a = {}; }
  var _b = _a.bar, bar = _b === void 0 ? {} : _b, opts = __rest(_a, ["bar"]);
}
```
@sandersn
Copy link
Copy Markdown
Member Author

From in-person discussion with @rbuckton, I'm going to try the alternate fix of unshifting onto the prologue instead of pushing.

@sandersn
Copy link
Copy Markdown
Member Author

@rbuckton can you take another look at this?

if (constructor) {
addDefaultValueAssignmentsIfNeeded(statements, constructor);
addRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper);
if (!hasSynthesizedSuper) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We need to always emit real prologue directives first (e.g. "use strict"), then our custom prologue statements.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe we need to split addPrologueDirectives into two functions, addPrologueDirectives (which only handles real prologue directives) and addCustomPrologueDirectives (which only handles our custom leading statements).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

How do I tell the difference? Is "use strict" the only real prologue directive right now?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah, it just looks like it's any ExpressionStatement at the beginning of a file/function that is just a string literal.

Comment thread src/compiler/transformers/es2015.ts Outdated
if (isBlock(body)) {
// ensureUseStrict is false because no new prologue-directive should be added.
// addPrologueDirectives will simply put already-existing directives at the beginning of the target statement-array
const statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false, visitor);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This also would result in a "use strict" appearing after executable statements.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm surprised we either don't have a test for this or that an existing test isn't failing.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm trying to create a test and realised I don't know how to get "use strict" to be generated anywhere except at the beginning of a file. How can I get it to emit at the beginning of a function?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You write it yourself. We respect existing directives.

Copy link
Copy Markdown
Member Author

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

I split the functions as you suggested. It turns out that transformFunctionBody in esnext was not calling addPrologue, so I added it there as well.

if (constructor) {
addDefaultValueAssignmentsIfNeeded(statements, constructor);
addRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper);
if (!hasSynthesizedSuper) {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah, it just looks like it's any ExpressionStatement at the beginning of a file/function that is just a string literal.

Copy link
Copy Markdown
Contributor

@rbuckton rbuckton left a comment

Choose a reason for hiding this comment

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

Minor nit regarding naming, otherwise it looks good.

Comment thread src/compiler/factory.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

To differentiate, perhaps its name should be addStandardPrologue?

@sandersn sandersn merged commit 8a599f6 into master Apr 13, 2017
@sandersn sandersn deleted the es5-emit-param-initialiser-before-object-rest branch April 13, 2017 20:43
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible bug: Complex named function params with defaults and rest causes errors.

3 participants