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

fix: do-statementlist behavior #11724

Merged
merged 8 commits into from Jun 25, 2020
Merged

Conversation

wlawt
Copy link
Contributor

@wlawt wlawt commented Jun 16, 2020

Q                       A
Fixed Issues? Fixes #9404
Patch: Bug Fix? Yes
Major: Breaking Change? No
Minor: New Feature? No
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes? No
License MIT

Added check for multi-use of semicolons where it was returning void 0 before.

Input

expect(do {
  x = do { 1; };
  z = do { 1;;;; };
  w = (do { 1;;;; });
})

Output

x=1
z=1
w=1

The parsing issue addressed in #9404 seems to be correct and ok (reference: 9404 comment)

@wlawt wlawt changed the title fix: statementlist behavior fix: do-statementlist behavior Jun 16, 2020
@babel-bot
Copy link
Collaborator

babel-bot commented Jun 16, 2020

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/24439/

@codesandbox-ci
Copy link

codesandbox-ci bot commented Jun 16, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit f469bdf:

Sandbox Source
falling-darkness-mn2hx Configuration
dreamy-chaum-5ktd1 Configuration

@@ -67,13 +68,16 @@ export default function gatherSequenceExpressions(
} else if (isEmptyStatement(node)) {
// empty statement so ensure the last item is undefined if we're last
ensureLastUndefined = true;
Copy link
Contributor

@JLHwung JLHwung Jun 17, 2020

Choose a reason for hiding this comment

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

If all of nodes are empty statements, it should ensureLastUndefined. i.e. both eval("{ ; }") and eval("{ ;;; }") returns undefined. Note that you can mentally think of do { ; } as eval("{ ; }").

However, when empty statements follow any value producing statements, it should not ensureLastUndefined. i.e. eval("{ 3;; }") returns 3.

Therefore we should check

  • If the empty statement is the first of nodes, we should ensureLastUndefined, otherwise it is a no-op because it can always be omitted. Conceptually it is more like ensureFirstUndefined.

I don't think we need to track multiColons because

  • the specific value of multiColons is not used as a number later
  • Transformers operates on AST level (EmptyStatement v.s. colon) while colon is a grammar structure and should be addressed by tokenizer instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup that makes sense, just have a question: if accessing the path isn't available to check if it's inList or key is there a way to do it with the array of nodes being passed to the function?

I've also explored the scope that gets passed and trying to use it's path but inList and key are undefined for it.

@nicolo-ribaudo nicolo-ribaudo added PR: Bug Fix 🐛 A type of pull request used for our changelog categories Spec: Do Expressions labels Jun 17, 2020
// checks if node is first
// checks if input is `;` or `;;` which will return `()` in where
// the length will always be 2
if (nodes.indexOf(node) === 0 || nodes.length === 2) {
Copy link
Contributor

@JLHwung JLHwung Jun 18, 2020

Choose a reason for hiding this comment

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

I don't think we need to check specifically nodes.length === 2? i.e. do { ;;; } should also return undefined.

To mark non-leading EmptyStatement as no-op, we can reset ensureLastUndefined = false only if node is not an EmptyStatement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah i added the node.length because () always threw an error regardless of the amount of ; given.

just to clarify what does no-op mean ?

and do we need to explicitly mark it false since we're in the condition that it is true?

Copy link
Contributor

Choose a reason for hiding this comment

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

() always threw an error regardless of the amount of ; given.

It is syntax error if there is only empty statement in the (), i.e. (), (;), (;;) should throw.

just to clarify what does no-op mean ?

no-op means we do not change ensureLastUndefined, as you can see it is reset as false in the beginning of loop. We can check empty statement before validating.

Let eLU be ensureLastUndefined, we can illustrate how eLU is changed between EmptyStatements ant other statements.

;                ;;;;;                              42                  ;;;;;
|--> eLU = true  |--> eLU = (previous eLU) = true   |--> eLU = false    |--> eLU= (previous eLU) = false

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if () , (;) , (;;) should throw would it be a different issue because checking if node is first fails the cases just mentioned?

Copy link
Contributor

Choose a reason for hiding this comment

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

Babel currently throws syntax error for (), (;), which is expected. Generally when you are working on AST, you may assume that it is always valid combinations of AST nodes, in this case, it is EmptyStatement nested in BlockStatement. The combinations of punctuators, i.e. (, ) should be cared by the parser, and by the time when you have a valid AST, it can not be a representation of the source like ().

Generally parentheses do not affect the semantic meaning, it is a valid combination only if there is an expression wrapped inside parentheses: https://tc39.es/ecma262/#prod-ParenthesizedExpression

ParenthesizedExpression[Yield, Await]:
  ( Expression[+In, ?Yield, ?Await] )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

right, ok the changes i just did keeps the checking if node is first and letting it throw if () , (;)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think I understand what you mean by () and (;)

In the example { ;; }, the parse AST is

[ EmptyStatement, EmptyStatement ]

In the first loop, ensureLastUndefined is set to true.
In the second loop, ensureLastUndefined is set to false in the beginning of the loop body:

Since exprs is an empty array, ensureLastUndefined is false, sequenceExpression(exprs) will be printed as (), which will throw syntax error because sequence expression can not be empty.

We can reset ensureLastUndefined only if node is not an EmptyStatement.

for (const node of nodes) {
  if (!isEmptyStatement(node)) {
    ensureLastUndefined = false;
  }
// ...
}

So this ensures that when we are processing an EmptyStatement following any statements, it becomes an no-op, which does not change ensureLastUndefined.

If we only check node is first of nodes && nodes.length === 2, it can not handle do { ;;; }: When processing the third empty statement, ensureLastUndefined will still be reset to false and gatherSequenceExpressions will still generate an empty sequence expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes so much sense, thank u!

@JLHwung JLHwung changed the base branch from master to main June 19, 2020 12:40
@wlawt wlawt requested a review from JLHwung June 19, 2020 18:41
expect(() => {
generateCode(sequence.expressions[1]);
}).toThrow("Cannot read property '1' of undefined");
// expect(generateCode(sequence.expressions[1])).toBe("undefined");
Copy link
Contributor

Choose a reason for hiding this comment

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

The test is failing because the test assumption does not hold if the empty statement is following a value producing statement, in this test case, undefined ; will not produce a sequence expression because the empty statement is not the leading statement. Therefore sequence.expression is undefined.

However, asserting an error thrown because it is in fact not an sequence expression can be confusing to other contributors. Moreover, we know that it still gathers empty statement only if the EmptyStatement is the first element:

So we need to revise the test and clearly state its new behaviour

it("gathers leading empty statement", function() {
  const node = parseCode(";");
  const sequence = t.toSequenceExpression([node, t.cloneNode(node)], scope);
  expect(generateCode(sequence)).toBe("undefined");
});

@@ -320,7 +320,7 @@ describe("converters", function () {
it("gathers empty statements", function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you also change the test title here? Because we have changed the behaviour.

Copy link
Contributor

@JLHwung JLHwung left a comment

Choose a reason for hiding this comment

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

Thanks.

@JLHwung
Copy link
Contributor

JLHwung commented Jun 25, 2020

The e2e CI error has been fixed on main.

@JLHwung JLHwung merged commit cfaa70d into babel:main Jun 25, 2020
@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 Sep 25, 2020
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 25, 2020
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: Bug Fix 🐛 A type of pull request used for our changelog categories Spec: Do Expressions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

@babel/plugin-proposal-do-expressions: StatementList behavior
5 participants