MatAtBread / fast-async

605 stars 21 forks source link

Non-trivial destructuring initializers are broken #8

Closed simonbuchan closed 7 years ago

simonbuchan commented 7 years ago

fast-async badly transforms destructuring initializers using anything other than the trivial forms const [a, b, c] = ... or const { a, b, c } = ...; inside any async functions, even if the RHS is not an await expression. Further, it fails to parse array rest (ES2015, so not #6), and fails while transforming array eliding.

// work
async function trivialArray() {
  const [a, b] = await Promise.resolve([1, 2]);
  console.log(a, b);
}
async function trivialObject() {
  const { a, b } = await Promise.resolve({ a: 1, b: 2 });
  console.log(a, b);
}

// fails in parse
//async function arraySpread() {
//  const [a, ...b, c] = await Promise.resolve([1, 2, 3, 4]);
//  console.log(a, b, c);
//}

// fails in transform
//async function arrayElide() {
//  const [,a,,b] = await Promise.resolve([1, 2, 3, 4]);
//  console.log(a, b);
//}

// generate bad code, only declaring `var a, b`
async function rename() {
  const { a, b: c } = await Promise.resolve({ a: 1, b: 2 });
  console.log(a, c);
}
async function key() {
  const { a, ['b']: c } = await Promise.resolve({ a: 1, b: 2 });
  console.log(a, c);
}
async function nestedArray() {
  const { a, b: [c, d] } = await Promise.resolve({ a: 1, b: [2, 3] });
  console.log(a, c, d);
}
async function nestedObject() {
  const { a, b: { c, d } } = await Promise.resolve({ a: 1, b: { c: 2, d: 3 } });
  console.log(a, c, d);
}
async function indirect() {
  const result = await Promise.resolve({ a: 1, b: 2 });
  const { a, b: c } = result;
  console.log(a, c);
}

Output is:

% babel --preset es2015 --plugins syntax-async-functions,fast-async async.js
Function.prototype.$asyncbind = <snip>

// work
function trivialArray() {
  return new Promise(function ($return, $error) {
    var a, b;
    return Promise.resolve([1, 2]).then(function ($await_1) {
      [a, b] = $await_1;

      console.log(a, b);
      return $return();
    }.$asyncbind(this, $error), $error);
  }.$asyncbind(this));
}
function trivialObject() {
  return new Promise(function ($return, $error) {
    var a, b;
    return Promise.resolve({ a: 1, b: 2 }).then(function ($await_2) {
      ({ a, b } = $await_2);

      console.log(a, b);
      return $return();
    }.$asyncbind(this, $error), $error);
  }.$asyncbind(this));
}

// fails in parse
//async function arraySpread() {
//  const [a, ...b, c] = await Promise.resolve([1, 2, 3, 4]);
//  console.log(a, b, c);
//}

// fails in transform
//async function arrayElide() {
//  const [,a,,b] = await Promise.resolve([1, 2, 3, 4]);
//  console.log(a, b);
//}

// generate bad code, only declaring `var a, b`
function rename() {
  return new Promise(function ($return, $error) {
    var a, b;
    return Promise.resolve({ a: 1, b: 2 }).then(function ($await_3) {
      ({ a, b: c } = $await_3);

      console.log(a, c);
      return $return();
    }.$asyncbind(this, $error), $error);
  }.$asyncbind(this));
}
function key() {
  return new Promise(function ($return, $error) {
    var a, undefined;
    return Promise.resolve({ a: 1, b: 2 }).then(function ($await_4) {
      ({ a, ['b']: c } = $await_4);

      console.log(a, c);
      return $return();
    }.$asyncbind(this, $error), $error);
  }.$asyncbind(this));
}
function nestedArray() {
  return new Promise(function ($return, $error) {
    var a, b;
    return Promise.resolve({ a: 1, b: [2, 3] }).then(function ($await_5) {
      ({ a, b: [c, d] } = $await_5);

      console.log(a, c, d);
      return $return();
    }.$asyncbind(this, $error), $error);
  }.$asyncbind(this));
}
function nestedObject() {
  return new Promise(function ($return, $error) {
    var a, b;
    return Promise.resolve({ a: 1, b: { c: 2, d: 3 } }).then(function ($await_6) {
      ({ a, b: { c, d } } = $await_6);

      console.log(a, c, d);
      return $return();
    }.$asyncbind(this, $error), $error);
  }.$asyncbind(this));
}
function indirect() {
  return new Promise(function ($return, $error) {
    var result, a, b;
    return Promise.resolve({ a: 1, b: 2 }).then(function ($await_7) {
      result = $await_7;
      ({ a, b: c } = result);

      console.log(a, c);
      return $return();
    }.$asyncbind(this, $error), $error);
  }.$asyncbind(this));
}

Array rest gives error:

% babel --preset es2015 --plugins syntax-async-functions,fast-async async.js
SyntaxError: async.js: Unexpected token (10:16)
   8 | }
   9 | async function arraySpread() {
> 10 |   const [a, ...b, c] = await Promise.resolve([1, 2, 3, 4]);
     |                 ^
  11 |   console.log(a, b, c);
  12 | }
  13 | async function rename() {

Without fast-async:

% babel --preset es2015 --plugins syntax-async-functions array_rest.js
async function test() {
  const [a, ...b] = [1, 2, 3];
  console.log(a, b);
}

Array elide gives error:

% babel --preset es2015 --plugins syntax-async-functions,fast-async async.js
TypeError: async.js: Cannot read property 'type' of null
    at getDeclNames (/home/simon/code/xxx/node_modules/nodent/lib/arboriculture.js:2010:19)
    at /home/simon/code/xxx/node_modules/nodent/lib/arboriculture.js:2014:70
    at Array.reduce (native)
    at getDeclNames (/home/simon/code/xxx/node_modules/nodent/lib/arboriculture.js:2014:32)
    at /home/simon/code/xxx/node_modules/nodent/lib/arboriculture.js:2061:29
    at treeWalker (/home/simon/code/xxx/node_modules/nodent/lib/parser.js:156:5)
    at goDown (/home/simon/code/xxx/node_modules/nodent/lib/parser.js:122:9)
    at down (/home/simon/code/xxx/node_modules/nodent/lib/parser.js:146:25)
    at Object.base.Function (/home/simon/code/xxx/node_modules/acorn/dist/walk.js:269:4)
    at down (/home/simon/code/xxx/node_modules/nodent/lib/parser.js:133:60)
MatAtBread commented 7 years ago

Wow. Thanks for finding these cases. I recently had a go at getting destructuring to work, but clearly missed quite a few cases. I'm away for 10 days, but will try and patch these up when I'm back

MatAtBread commented 7 years ago

@simonbuchan - I've fixed most of these issues, but const [a, ...b, c] = [1, 2, 3, 4] fails in acorn, and according to http://astexplorer.net, almost everything else (babylon, traceur, shift, esprima). Are you sure that is syntactically vaild (in particular the rest identifier should be the final declaration and cannot be followed by another identifier, as in const [a, ...b] = [1,2,3,4])?

MatAtBread commented 7 years ago

Fixed in fast-async@6.0.32