Skip to content

[Bug]: plugin-transform-block-scoping constant checks produce different behaviour #13245

@overlookmotel

Description

@overlookmotel

💻

  • Would you like to work on a fix?

How are you using Babel?

@babel/cli

Input code

let isCalled = false;
const fn = () => isCalled = true;

const i = 0;
try {
  i = fn();
} catch (e) {
  console.log(isCalled);
}

Babel REPL

Configuration file name

No response

Configuration

No response

Current and expected behavior

Original code logs "true".

But plugin-transform-block-scoping's constant checks produces code with a slightly different behaviour:

function _readOnlyError(name) { throw new TypeError("\"" + name + "\" is read-only"); }
var isCalled = false;
var fn = () => isCalled = true;

var i = 0;
try {
  i = (_readOnlyError("i"), fn());
} catch (e) {
  console.log(isCalled);
}

This logs "false" because _readOnlyError() throws before fn() is evaluated.

Environment

Babel REPL

Possible solution

Order of fn() and _readOnlyError("i") should be reversed:

i = (fn(), _readOnlyError("i"))

This order would produce wrong result if _readOnlyError() returned a value, but since we know for sure it'll throw, that's not relevant.

Additional context

There are two other more obscure cases where the above solution is not sufficient to solve the problem:

(I may have time to make a PR for the simple case above, but unlikely I will for these two)

Destructuring

const c = 0;
let x = 1, y = 2;
try {
  [x, c, y] = [11, 0, 22];
} catch (e) {
  console.log( {x, y} );
}

REPL

[x, c, y] = [] is transformed to [x, c, y] = (_readOnlyError("c"), [11, 0, 22]).

Original logs {x: 11, y: 2} whereas transpiled code logs {x: 1, y: 2}. It should throw only after x has been assigned, but before y is.

This is tricky to get right. Best solution I can see is:

// NB Assignment to `y` has been omitted
[x] = [11, 0, 22], _readOnlyError("c");

Assignment operators

const calls = [];
const fn = () => calls.push('fn');

const i = {
  valueOf() {
    calls.push('valueOf');
    return 1;
   }
};

try {
  i += fn();
} catch (e) {
  console.log(calls);
}

Babel REPL

Original logs [ 'fn', 'valueOf' ].

Babel transforms i += fn() to i += (_readOnlyError("i"), fn()). So transpiled code logs [].

After reversing fn() and _readOnlyError("i"), it logs [ 'fn' ] (still incorrect).

The correct transpiled output would be:

i + fn(), _readOnlyError("i")

NB &&=, ||= and ??= aren't affected by this additional complication, but I think all other assignment operators are.

i++ / ++i should be treated as same as i += 1:

i + 1, _readOnlyError("i")

Metadata

Metadata

Assignees

Labels

outdatedA closed issue/PR that is archived due to age. Recommended to make a new issue

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions