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

Extract for-of iterator handling to a helper #11262

Merged
merged 5 commits into from Mar 16, 2020

Conversation

nicolo-ribaudo
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo commented Mar 14, 2020

Q                       A
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes?
License MIT

This greatly reduces the code size (*) when for-of is used multiple times across the
codebase. Also, makes it easier to read what's happening in a compiled loop.

(*) I tested the difference with 3 for loops before adding the human-friendly error message about the missing Symbol.iterator. I didn't use transform-runtime or external-helpers, so these numbers include the helper size.

These are the minified sizes (with terser):

spec loose
old 563 bytes 465 bytes
new 457 bytes 349 bytes

Since the error message is a bit more than 100 bytes long, the size improvement probably needs 4 loops rather than one now. However, keep in mind that if I added the human-friendly error message to the old implementation it would have added 100 bytes 3 times, making the size improvement even bigger.

Size test code
loop: for (let x of a) {
  break loop;
}
for (var x of a) {
  a = null;
}
for (x of a) {
  continue;
}

Part 1/n

@nicolo-ribaudo nicolo-ribaudo added the PR: Internal 🏠 A type of pull request used for our changelog categories label Mar 14, 2020
@nicolo-ribaudo nicolo-ribaudo added this to Ready for review in Iterables DX Mar 14, 2020
// It must be kept for compatibility reasons.
// TODO (Babel 8): Remove this code.

export default function transformWithoutHelper(loose, path, state) {
Copy link
Member Author

Choose a reason for hiding this comment

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

This file contains the old plugin code needed when the helper isn't available. I just copy-pasted it without any edit.


try {
for (var _iterator = nums[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
for (_iteratorHelper.s(); !(_step = _iteratorHelper.n()).done;) {
var i = _step.value;
var x;
var f;
Copy link
Member Author

Choose a reason for hiding this comment

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

This is a bug unrelated to this PR: f and x should be declared in the _loop function.

Copy link
Member

Choose a reason for hiding this comment

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

I think they have to stay here. If we moved the var inside the _loop function, they would no longer declare the variable in the function scope (it's not a block let/const).

@nicolo-ribaudo nicolo-ribaudo marked this pull request as ready for review March 15, 2020 13:58

helpers.createForOfIteratorHelperLoose = helper("7.9.0")`
export default function _createForOfIteratorHelperLoose(o) {
var i = 0;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Don't share the variable here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I mostly did it for code size, since this is a runtime helper, and i could stand both for index and iterator 😅


const nodes = builder.build({
CREATE_ITERATOR_HELPER: state.addHelper(builder.helper),
ITERATOR_HELPER: scope.generateUidIdentifier("iteratorHelper"),
Copy link
Member

Choose a reason for hiding this comment

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

Nit:

Suggested change
ITERATOR_HELPER: scope.generateUidIdentifier("iteratorHelper"),
ITERATOR_HELPER: scope.generateUidIdentifier("iterator"),

// push the rest of the original loop body onto our new body
block.body = block.body.concat(node.body.body);
t.inherits(container[0], node);
t.inherits(container[0].body, node.body);
Copy link
Member

Choose a reason for hiding this comment

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

Does container ever contain more than one element? If not, we can replace the replaceWithMultiple with replaceWith and drop the parent.isLabeledStatement() check.

Copy link
Member Author

Choose a reason for hiding this comment

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

container always contains a single node. However:

  1. We are doing replaceWithMultiple(nodes), and in non-loose mode nodes contains 2 nodes (the var declaration and the try statement)
  2. We can't drop the parent.isLabeledStatement() check because in non-loose mode it the label must be moved to the inner loop. If we only did path.replaceWith() it would be attached to the try statement.

Copy link
Member

Choose a reason for hiding this comment

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

If we only did path.replaceWith() it would be attached to the try statement.

I think that would actually be equivalent. The finally block should still fire correctly.

Copy link
Member Author

Choose a reason for hiding this comment

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

But then, it doesn't work with continue.

Copy link
Member

Choose a reason for hiding this comment

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

Ohhh. I was only thinking about break.

);

if (
t.isIdentifier(left) ||
Copy link
Contributor

@JLHwung JLHwung Mar 16, 2020

Choose a reason for hiding this comment

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

nit: It suffices to check t.isVariableDeclaration first and assuming it's LVal in the else branch, because we have validated ForOfStatement.left even when BABEL_TYPES_8_BREAKING is not enabled:

return assertNodeType("VariableDeclaration", "LVal");

(Oh it is copied from the old codes.)

Dis greatly recudes the code size when for-of is used multiple times across the
codebase. Also, makes it easier to read what's happening in a compiled loop.
@github-actions github-actions bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Jun 16, 2020
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 16, 2020
@nicolo-ribaudo nicolo-ribaudo deleted the iterables-dx/for-of-helper branch June 18, 2020 23:40
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: Internal 🏠 A type of pull request used for our changelog categories
Projects
No open projects
Iterables DX
  
Done
Development

Successfully merging this pull request may close these issues.

None yet

3 participants