Skip to content

Instantly share code, notes, and snippets.

@nicolo-ribaudo
Last active February 23, 2018 01:10
Show Gist options
  • Save nicolo-ribaudo/f8ac7916f89450f2ead77d99855b2098 to your computer and use it in GitHub Desktop.
Save nicolo-ribaudo/f8ac7916f89450f2ead77d99855b2098 to your computer and use it in GitHub Desktop.
Object rest transpilation

We want to transpile this code, ensuring that every property is accessed the correct number of times and in the correct order:

var {
  val_1_1: { val_2_1, ...val_2_rest },
  val_1_2,
  val_1_3: [
    arr_1,
    arr_2,
    { val_3_1, ...val_3_rest },
    arr_4
  ],
  val_1_4
} = foo();

Since the properties included in the rest object must be accessed after the propertie which come before the rest operator and before the properties which come after, we need to insert the transpiled declaration exactly where the rest operator is in the original code. This is easy when the rest element is the last of the destructuring pattern:

// (Assume that access to obj is known to be pure)

// Input
var { a, ...rest } = obj;

// Output
var { a } = obj,
    rest = _objectWithoutProperties(obj, ["a"]);

If the rest element is inside a nested object pattern (but it still is the last element of the whole destructuring pattern), we need to ensure that the nested object is got exactly once:

// Input
var { a, b: { c, ...rest } } = obj;

// Current output ("b" is accessed twice)
var { a, b: { c } } = obj,
    rest = _objectWithoutProperties(obj.b, ["c"]);

// Expected output
var { a, b: _b } = obj,
    { c } = _b,
    rest = _objectWithoutProperties(_b, ["c"]);

What if theere is something after the rest element?

// Input
var { a, b: { ...rest }, c } = obj;

We can't just "extract" the { ...rest } pattern after the variable declarator (like in the example before), because the properties included in rest must be accessed before c. Thus, this is wrong:

var { a, b: _b, c } = obj,
    rest = _objectWithoutProperties(_b, []);

We need to "split" the whole destructuring pattern, to make room for the _objectWithoutProperties call:

// Partial step
var { a, b: { ...rest } } = obj,
    { c } = obj;

// Output
var { a, b: _b } = obj,
    rest = _objectWithoutProperties(_b, []),
    { c } = obj;

We have only considered object destructuring, but there is also array destructuring. It is trickier to "split", since the elements are always accessed from the first to the last. It can't be split like this:

// Input
var [ a, { ...rest }, b ] = obj;

// Wrong partial step
var [ a, { ...rest } ] = obj,
    [ b ] = obj;

The solution to this problem is to have an iterable which isn't reset every time that it is used. We need to introduce a _stepsIterable helper. This helper forwards next() calls to the wrapped iterator, and calls return() only when needed:

// Input
var [ a, { ...rest }, b ] = obj;

// Partial step
var _obj = _stepsIterable(obj),
    [ a, { ...rest } ] = _obj,
    [ b ] = _obj;
_obj.return();

// Output
var var _obj = _stepsIterable(obj),
    [ a, _x1 ] = _obj,
    rest = _objectWithoutProperties(_x1, []),
    [ b ] = _obj;
_obj.return();

The _stepsIterable function is defined like this:

function _stepsIterable(iterable) {
  var iterator = iterable[Symbol.iterator]();
  return {
    [Symbol.iterator]: () => ({
      next: (arg) => iterator.next(arg),
    }),
    return: () => iterator.return(),
  };
}

Given all these considerations, the original example is transpiled to this:

// Input
var {
  val_1_1: { val_2_1, ...val_2_rest },
  val_1_2,
  val_1_3: [
    arr_1,
    arr_2,
    { val_3_1, ...val_3_rest },
    arr_4
  ],
  val_1_4
} = foo();

// Partial step 1
var _foo = foo(),
    { val_1_1: _val_1_1 } = _foo,
    { val_2_1 } = _val_1_1,
    val_2_rest = _objectWithoutProperties(_val_1_1, ["val_2_1"]),
    { val_1_2, val_1_3: _val_1_3 } = _foo,
    _val_1_3_tmp = _stepsIterable(_val_1_3),
    [ arr_1, arr_2, _x2 ] = _val_1_3,
    { val_3_1 } = _x2,
    val_3_rest = _objectWithoutProperties(_x2, ["val_3_1"]),
    [ arr_1 ] = _val_1_3_tmp;
_val_1_3_tmp.return();
var { val_1_4 } = _foo;
@Jessidhia
Copy link

Jessidhia commented Feb 23, 2018

The result looks like a chaotic mess, but correctly implementing this thing is a chaotic mess 😂

Nit: your [Symbol.iterator] impl creates a new object every time, but it should always return the same object.

There's a needs-consensus proposal to make [Symbol.iterator] only be called / cached once as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment