MatAtBread / fast-async

605 stars 21 forks source link

Babel v7: Env Preset + nodent breaks in Babel generator #56

Closed swernerx closed 6 years ago

swernerx commented 6 years ago

I have another exception I got in a pretty trivial case.

Code:

async function executeTasks(tasks) {
  for (const taskName of tasks) {
    try {
      await executeCommands()
    } catch (error) {
      console.error("Error")
    }
  }
}

Babelrc:

{
  "presets": [
    [ "@babel/env", { "exclude": ["transform-regenerator", "transform-async-to-generator"] } ]
  ],

  "plugins": [
    "module:./src/transformAsyncToPromises.js"
  ]
}

Command Line:

node_modules/.bin/babel --config-file ./.babelrc-test --no-babelrc async-for-of.js

Exception:

TypeError: Cannot read property 'length' of undefined
    at Buffer._append (/Users/swerner/Workspace/open-source/babel-preset-edge/node_modules/@babel/generator/lib/buffer.js:115:26)
    at Buffer.append (/Users/swerner/Workspace/open-source/babel-preset-edge/node_modules/@babel/generator/lib/buffer.js:81:10)
    at Generator._append (/Users/swerner/Workspace/open-source/babel-preset-edge/node_modules/@babel/generator/lib/printer.js:202:52)
    at Generator.word (/Users/swerner/Workspace/open-source/babel-preset-edge/node_modules/@babel/generator/lib/printer.js:126:10)
    at Generator.Identifier (/Users/swerner/Workspace/open-source/babel-preset-edge/node_modules/@babel/generator/lib/generators/types.js:43:8)
    at /Users/swerner/Workspace/open-source/babel-preset-edge/node_modules/@babel/generator/lib/printer.js:315:23
    at Buffer.withSource (/Users/swerner/Workspace/open-source/babel-preset-edge/node_modules/@babel/generator/lib/buffer.js:178:28)
    at Generator.withSource (/Users/swerner/Workspace/open-source/babel-preset-edge/node_modules/@babel/generator/lib/printer.js:182:15)
    at Generator.print (/Users/swerner/Workspace/open-source/babel-preset-edge/node_modules/@babel/generator/lib/printer.js:314:10)
    at Generator.printJoin (/Users/swerner/Workspace/open-source/babel-preset-edge/node_modules/@babel/generator/lib/printer.js:382:12)

I modified the generator a bit to catch this case:

  _proto._append = function _append(str, line, column, identifierName, filename) {
    if (this._map && str[0] !== "\n") {
      this._map.mark(this._position.line, this._position.column, line, column, identifierName, filename);
    }

    // Hot fix
    if (str == null) {
      str = "[XXXXX]"
    }

   ...

Generated output with fix:

"use strict";

function executeTasks(tasks) {
  return new Promise(function ($return, $error) {
    var $Try_1_Finally = function ($Try_1_Exit) {
      return function ($Try_1_Value) {
        try {
          var $Try_3_Finally = function ($Try_3_Exit) {
            return function ($Try_3_Value) {
              try {
                if (_didIteratorError) {
                  throw _iteratorError;
                }

                return $Try_3_Exit && $Try_3_Exit.call(this, $Try_3_Value);
              } catch ($boundEx) {
                return $error($boundEx);
              }
            }.bind(this);
          }.bind(this);

          var $Try_3_Catch = function ($exception_4) {
            try {
              throw $exception_4;
            } catch ($boundEx) {
              return $Try_3_Finally($error)($boundEx);
            }
          }.bind(this);

          try {
            if (!_iteratorNormalCompletion && _iterator.return != null) {
              _iterator.return();
            }

            return $Try_3_Finally([XXXXX])();
          } catch ($exception_4) {
            $Try_3_Catch($exception_4)
          }

          return $Try_1_Exit && $Try_1_Exit.call(this, $Try_1_Value);
        } catch ($boundEx) {
          return $error($boundEx);
        }
      }.bind(this);
    }.bind(this);

    var _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, taskName;

    _iteratorNormalCompletion = true;
    _didIteratorError = false;
    _iteratorError = undefined;

    var $Try_1_Post = function () {
      try {
        return $return();
      } catch ($boundEx) {
        return $error($boundEx);
      }
    };

    var $Try_1_Catch = function (err) {
      try {
        _didIteratorError = true;
        _iteratorError = err;
        return $Try_1_Finally($Try_1_Post)();
      } catch ($boundEx) {
        return $Try_1_Finally($error)($boundEx);
      }
    };

    try {
      _iterator = tasks[Symbol.iterator]();
      var $Loop_5_trampoline;

      function $Loop_5_step() {
        _iteratorNormalCompletion = true;
        return $Loop_5;
      }

      function $Loop_5() {
        if (!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) {
          taskName = _step.value;

          var $Try_2_Post = function () {
            try {
              return $Loop_5_step;
            } catch ($boundEx) {
              return $Try_1_Catch($boundEx);
            }
          };

          var $Try_2_Catch = function (error) {
            try {
              console.error("Error");
              return $Try_2_Post();
            } catch ($boundEx) {
              return $Try_1_Catch($boundEx);
            }
          };

          try {
            return Promise.resolve(executeCommands()).then(function ($await_7) {
              try {
                return $Try_2_Post();
              } catch ($boundEx) {
                return $Try_2_Catch($boundEx);
              }
            }, $Try_2_Catch);
          } catch (error) {
            $Try_2_Catch(error)
          }
        } else return [1];
      }

      return ($Loop_5_trampoline = function (q) {
        while (q) {
          if (q.then) return void q.then($Loop_5_trampoline, $Try_1_Catch);

          try {
            if (q.pop) {
              if (q.length) return q.pop() ? $Loop_5_exit.call(this) : q;else q = $Loop_5_step;
            } else q = q.call(this);
          } catch (_exception) {
            return $Try_1_Catch(_exception);
          }
        }
      }.bind(this))($Loop_5);

      function $Loop_5_exit() {
        return $Try_1_Finally($Try_1_Post)();
      }
    } catch (err) {
      $Try_1_Catch(err)
    }
  });
}

The problematic code is at line 35:

          try {
            if (!_iteratorNormalCompletion && _iterator.return != null) {
              _iterator.return();
            }

            return $Try_3_Finally([XXXXX])();
          } catch ($exception_4) {
            $Try_3_Catch($exception_4)
          }

Do you have any idea what's wrong?

MatAtBread commented 6 years ago

Can you try with fast-async in your plugins first and last, and let me know if it's different?

swernerx commented 6 years ago

There is just one plugin. The one used is from the PR of you to Babel. Should be pretty much identical to fast-async. Plus I use the latest nodent transform.

swernerx commented 6 years ago

Hi Mat!

I have uploaded a small extracted demo case to a gist together with some results being produced: https://gist.github.com/swernerx/7ca748fcddab4c672193b614e62cc73e

Please have a look. Should be pretty much reproducible for you as well. Hopefully this helps to track the issue down.

matAtWork commented 6 years ago

I was having a few issues pushing changes to my Babel fork, which I eventually resolved by pulling the latest Babel source and re-merging before make clean and retest. My changes to transform-async-to-promises now pass all the tests (https://github.com/babel/babel/pull/7076 is green again).

Can I suggest you pull the changes again and retry? Mixing Babel beta-versions (in this case .44 and .46) is clearly not a good base from which to test.

matAtWork commented 6 years ago

@swernerx - thanks for the gist. It would be really good if you could just generate a repo I could clone and npm test. There are so many versions of Babel, etc., trying to reproduce an exact error is proving difficult.

swernerx commented 6 years ago

Here we go: https://github.com/swernerx/test-babel-async

swernerx commented 6 years ago

Unfortunately it seems that the env-preset is always executed before fast-async.

See also: https://github.com/babel/babel/issues/7815

matAtWork commented 6 years ago

Thanks for the repo :) The issue seems to be the Babel implementation of for await (....), which is probably fighting with fast-async. The exception occurs while Babel tries to create a template internally:

  async function wrapper() {
    var ITERATOR_COMPLETION = true;
    var ITERATOR_HAD_ERROR_KEY = false;
    var ITERATOR_ERROR_KEY;
    try {
      for (
        var ITERATOR_KEY = GET_ITERATOR(OBJECT), STEP_KEY, STEP_VALUE;
        (
          STEP_KEY = await ITERATOR_KEY.next(),
          ITERATOR_COMPLETION = STEP_KEY.done,
          STEP_VALUE = await STEP_KEY.value,
          !ITERATOR_COMPLETION
        );
        ITERATOR_COMPLETION = true) {
      }
    } catch (err) {
      ITERATOR_HAD_ERROR_KEY = true;
      ITERATOR_ERROR_KEY = err;
    } finally {
      try {
        if (!ITERATOR_COMPLETION && ITERATOR_KEY.return != null) {
          await ITERATOR_KEY.return();
        }
      } finally {
        if (ITERATOR_HAD_ERROR_KEY) {
          throw ITERATOR_ERROR_KEY;
        }
      }
    }
  }

I'll see if I can work out why. If you're not using the for await construct, it might be easier to simply disable it for now.

swernerx commented 6 years ago

What do you mean by disabling?

I am updating a generally used preset. I don't think I can communicate to all why for-of loops with await shouldn't be used. So it's not only about me.

matAtWork commented 6 years ago

I meant if you want to continue your development while I look into it!

matAtWork commented 6 years ago

Confirmed: this is a bug in nodent-transform - it produces an illegal Identifier transpiling the output of babel's for-of transform. I hope to be able to find/fix it soon. Thanks for the help in identifying and reproducing it!

MatAtBread commented 6 years ago

@swernerx - please let me know if this fixes the issue as expected

swernerx commented 6 years ago

Looks good for me! Thanks!