Skip to content

Conversation

rajasekarm
Copy link
Member

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

@rajasekarm rajasekarm changed the title lint fix Do expressions transform incorrectly for switch statements Dec 5, 2017
@rajasekarm rajasekarm changed the title Do expressions transform incorrectly for switch statements Do expressions transform for switch statements Dec 5, 2017
@babel-bot
Copy link
Collaborator

babel-bot commented Dec 5, 2017

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

@rajasekarm rajasekarm changed the title Do expressions transform for switch statements WIP - Do expressions transform for switch statements Dec 5, 2017
Copy link
Member

@xtuc xtuc left a comment

Choose a reason for hiding this comment

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

Great job 👍

I was wondering why we needed to change babel-traverse for that but it's because the do-expression uses our internal

export function replaceExpressionWithStatements(nodes: Array<Object>) {

@xtuc xtuc added pkg: traverse PR: Spec Compliance 👓 A type of pull request used for our changelog categories Spec: Do Expressions labels Dec 5, 2017
);
} else {
const finalStatement = cases[i].get("consequent")[consequentLength - 1];
const isBreakStatement =
Copy link
Member

Choose a reason for hiding this comment

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

hasBreakStatement would suit here better imho

paths,
);
} else {
const finalStatement = cases[i].get("consequent")[consequentLength - 1];
Copy link
Member

Choose a reason for hiding this comment

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

maybe something like lastCaseStatement?

paths,
);
} else {
const finalStatement = cases[i].get("consequent")[consequentLength - 1];
Copy link
Member

Choose a reason for hiding this comment

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

we should also check if consequentLength - 1 is a valid index, accessing -1 may cause deoptimizations in v8

function completionRecordForSwitch(cases, paths) {
for (let i = 0, caseLen = cases.length; i < caseLen; i++) {
const consequentLength = cases[i].get("consequent").length;
const isDefaultStatement = !cases[i].get("test").type;
Copy link
Member

@Andarist Andarist Dec 5, 2017

Choose a reason for hiding this comment

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

a more explicit check of cases[i].get("test").type === null could be used

also isDefaultCase would be more descriptive

@@ -17,6 +17,38 @@ function addCompletionRecords(path, paths) {
return paths;
}

function completionRecordForSwitch(cases, paths) {
for (let i = 0, caseLen = cases.length; i < caseLen; i++) {
Copy link
Member

Choose a reason for hiding this comment

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

is there any reason to cache caseLen 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 would prefer to cache the array length always. Your views please.

Copy link
Member

Choose a reason for hiding this comment

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

This should be a for (const case of cases) {} loop. You make no use of the loop variables other than cases[i].

Copy link
Member Author

@rajasekarm rajasekarm Dec 7, 2017

Choose a reason for hiding this comment

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

changed to for of

Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

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

@rajasekarm
Copy link
Member Author

@nicolo-ribaudo @Andarist I'll update the PR and keep posted here.

const isDefaultCase = switchcase.get("test").type === null;
if (isDefaultCase) {
paths = addCompletionRecords(
switchcase.get("consequent")[consequentLength - 1],
Copy link
Member

Choose a reason for hiding this comment

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

this code path is not guarded against accessing -1 index

Copy link
Member Author

Choose a reason for hiding this comment

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

updated the code to break the loop, if it has zero element

const lastCaseStatement = switchcase.get("consequent")[
consequentLength - 1
];
const hasBreakStatement =
Copy link
Member

Choose a reason for hiding this comment

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

as latter IfStatements are both checking against hasBreakStatement you could just continue here for !hasBreakStatement and remove those checks below

also the mentioned IfStatements should form if/else imho

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@Andarist
Copy link
Member

Andarist commented Dec 7, 2017

@rajzshkr

I forgot about something @nicolo-ribaudo's example should become an exec.js test. In fact each of your test could get exec.js version. It would help prevent accidental regressions in the future.

@rajasekarm
Copy link
Member Author

I've few doubts, after @nicolo-ribaudo comment with the below example,

Example 1:

var a = do {
  switch(0) {
    case 0: "foo";
    case 1: break;
  }
} || "bar"

Example 2:

var b = do {
  switch(0) {
    case 0: break;
    case 1: "foo"; break;
  }
} || "bar"

alert(a + " " + b);

Expected output: foo bar
Actual output: bar foo

I read the the spec of switch case,, so as per spec the code should be transformed as below to get the proper evaluated output.

var a = function() {
  switch(0) {
    case 0: "foo";
    case 1: 
       return "foo"; // last evaluated output should be return if `caseclause` encounter a break.
  }
}() || "bar"

but it create confusion if there is change in structure of source code. Whats is the possible/right solution?

@nicolo-ribaudo
Copy link
Member

That transformation wouldn't work, since switch(1)... should return undefined. It should become like this.

var a = function() {
  switch(0) {
    case 0: return "foo";
    case 1: return;
  }
}() || "bar"

If a case clause's first statement is break, it should be replaced with undefined, which must be marked as a completion value. Then, if the last statement of the record before isn't break, it should also be marked as a completion value.

I think you may need to loop over the case declarations from last to first, otherwise the break statement of the case before would already have been removed.

@xtuc
Copy link
Member

xtuc commented Dec 8, 2017

@nicolo-ribaudo @rajzshkr

Your example

I simplified a bit your example: REPL (removed the break because it was causing confusing I think).

Actual: abar foo
Expected: abar foo

Note that:

var a = function () {
  switch (0) {
    case 0:
      "foo";
  }
}();

returns undefined since there's not break or default case. I don't know what the spec says for this case but since it cannot fall through to another case it should return the value?

@rajasekarm
Copy link
Member Author

@xtuc but as per ECMA spec, last expression in the case block should be returned if there is no break statement.

const hasBreakStatement =
lastCaseStatement && lastCaseStatement.isBreakStatement();

if (hasBreakStatement) {
Copy link
Member

Choose a reason for hiding this comment

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

All these checks are the same of lines 62-80?

);
}

if (!hasBreakStatement && consequentLength) {
Copy link
Member

Choose a reason for hiding this comment

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

Only if the first statement of the following case is break.

e.g.

let a = do {
  switch(0) {
    case 0:
      "foo";
    case 1:
      "bar";
      break;
  }
};

Should be "bar".

Copy link
Member

Choose a reason for hiding this comment

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

Yes right.

@xtuc
Copy link
Member

xtuc commented Dec 13, 2017

@rajzshkr so the behavior I explained here #6975 (comment) is a bug, do we agree?

@rajasekarm
Copy link
Member Author

rajasekarm commented Dec 13, 2017

@xtuc Yup. The example in the comment should output as foo instead of undefined

Copy link
Member

@yavorsky yavorsky left a comment

Choose a reason for hiding this comment

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

Please, consider:
https://babeljs.io/repl/build/6104/#?babili=false&browsers=&build=&builtIns=false&code_lz=G4QwTgBCELwQJgewgbwFAQgZwO4EsAXAYwAsAKABgEpUNMIiQsBTCCgLlQjvrYG4e9AEZhmIANYDeAX0GMWEAIycARADNEiFXwgixkurNloQAG2ZgCZEFR0B6OxADKJRAFdT8XawpA&debug=false&circleciRepo=&evaluate=true&lineWrap=true&presets=es2015%2Ces2016%2Ces2017%2Cstage-0&prettier=false&targets=Node-8.9&version=7.0.0-beta.34%2Bpr.6975

Consequent could be not just ExpressionStatement, but BlockStatement. Inside this block, we can have break, which should return the value above.

  switch(0) {
    case 0: { 
      0;
      break;
    }
    case 1: "foo"; break;
  }

for now, will return undefined, because we didn't override break for this case and output is:

case 0: {
   0;
   break;
}

@rajasekarm
Copy link
Member Author

@yavorsky I've noted this issue also, I'll update the PR.

@linonetwo
Copy link

@rajzshkr Will you update this recently?

@hackwaly
Copy link

hackwaly commented May 6, 2019

Any one can pick his progress?

@rajasekarm rajasekarm deleted the bug-3872 branch August 1, 2019 08:20
@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 31, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 31, 2019
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 pkg: traverse PR: Spec Compliance 👓 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.

9 participants