Skip to content

Extract for-of iterator handling to a helper#11262

Merged
nicolo-ribaudo merged 5 commits into
masterfrom
iterables-dx/for-of-helper
Mar 16, 2020
Merged

Extract for-of iterator handling to a helper#11262
nicolo-ribaudo merged 5 commits into
masterfrom
iterables-dx/for-of-helper

Conversation

@nicolo-ribaudo

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

Copy link
Copy Markdown
Member
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 force-pushed the iterables-dx/for-of-helper branch from 2c08b9b to 40ce663 Compare March 14, 2020 23:34
// It must be kept for compatibility reasons.
// TODO (Babel 8): Remove this code.

export default function transformWithoutHelper(loose, path, state) {

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.

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

@nicolo-ribaudo nicolo-ribaudo force-pushed the iterables-dx/for-of-helper branch from d556229 to f505349 Compare March 15, 2020 12:10
for (_iteratorHelper.s(); !(_step = _iteratorHelper.n()).done;) {
var i = _step.value;
var x;
var f;

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.

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

Copy link
Copy Markdown
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
Copy Markdown
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
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 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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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) ||

@JLHwung JLHwung Mar 16, 2020

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.

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

None yet

Development

Successfully merging this pull request may close these issues.

3 participants