Repository: babel/babel
File: packages/babel-plugin-transform-object-rest-spread/src/index.ts (TypeScript)

Code before:
              t.variableDeclarator(originalPath.node.id, t.cloneNode(initRef)),
            );

            return;
          }

          /**
           * Fix for: https://github.com/babel/babel/issues/17274
           *
           * Problem: When you have nested destructuring with computed keys and rest elements:
           *   const { [log(0)]: { [log(1)]: x, ...rest }, [log(2)]: y } = obj
           *
           * The functions log(0), log(1), log(2) must be called in that exact order.
           * But without this fix, the nested pattern gets processed first, causing the wrong order.
           *
           * Solution: Extract all computed key expressions into variables first, in source order.
           * This happens after the right-hand side (obj) is stored, but before destructuring starts.
           *
           * Transform:
           *   const { [log(0)]: { [log(1)]: x, ...rest }, [log(2)]: y } = obj
           * Into:
           *   const _obj = obj,           // Store right-hand side first
           *         _key1 = log(0),       // Then evaluate keys in order
           *         _key2 = log(1),
           *         _key3 = log(2),
           *         { [_key1]: { [_key2]: x, ...rest }, [_key3]: y } = _obj  // Then destructure
           */

          // Only do this once per variable declaration (even if multiple rest elements exist)
          const alreadyHoistedComputedKeys = originalPath.getData(
            "computedKeysAlreadyHoisted",
          );
          if (!alreadyHoistedComputedKeys) {
            originalPath.setData("computedKeysAlreadyHoisted", true);

            // Find all computed keys (like [someExpression]) in left-to-right order
            const destructuringPattern = originalPath.get(
              "id",
            ) as NodePath<t.ObjectPattern>;
            const propertiesWithComputedKeys =
              collectComputedKeysInSourceOrder(destructuringPattern);
            const tempVariableDeclarations: t.VariableDeclarator[] = [];

            // For each computed key that has side effects (not a simple variable)
            for (const property of propertiesWithComputedKeys) {
              const computedKeyExpression = property.get(
                "key",
              ) as NodePath<t.Expression>;

              // Check if the expression has side effects (function call, ++x, etc.)
              if (!computedKeyExpression.isPure()) {

Review comment (refactor):
Thank you for detailed comments. They really help the review process.

This approach memoises computed keys, such as `_key1 = log(0)`. However, it can not handle the situation where the binding initializers could have side effects. For example:

```js
const { [log(0)]: x = log(1), [log(2)]: y, ...rest } = obj;
```

If we only hoist the computed keys `log(0)` and `log(2)`, then `log(1)` will be evaluated _after_ `log(2)`:

```js
const _key1 = log(0),
_key2 = log(2),
{ [_key1]: x = log(1), [_key2]: y } = obj;
```
 Of course we may also hoist initializers, but then it will unnecessarily bloat the output. A common trick is to perform the memoisation inline:

```js
var _key1, _key2;
const { [_key1 = log(0)]: x = log(1), [_key2 = log(2)]: y } = obj;
```

With the inline memoisation, one can check whether a computed key has been memoised by checking if `key` is an assignment expression and `scope.hasUid(key.left.name)` is `true`. We have already done something similar before:

https://github.com/babel/babel/blob/10833e08a35ee57cbaf9ba80389934c026fe7213/packages/babel-helper-create-class-features-plugin/src/decorators.ts#L335

Code after:
         * Problem: When you have nested destructuring with computed keys and rest elements:
         *   const { [log(0)]: { [log(1)]: x, ...rest }, [log(2)]: y } = obj
         *
         * The functions log(0), log(1), log(2) must be called in that exact order.
         * But without this fix, the nested pattern gets processed first, causing the wrong order.
         *
         * Additionally, with default values:
         *   const { [log(0)]: x = log(1), [log(2)]: y } = obj
         *
         * The order must be: log(0), then log(1) (if x is undefined), then log(2).
         *
         * Solution: Use inline memoization with assignment expressions.
         * Transform:
         *   const { [log(0)]: { [log(1)]: x, ...rest }, [log(2)]: y } = obj
         * Into:
         *   var _log, _log2, _log3;
         *   const { [_log = log(0)]: { [_log2 = log(1)]: x, ...rest }, [_log3 = log(2)]: y } = obj
         *
         * This preserves correct evaluation order even with default values.
         */

        // Only memoize computed keys if there are rest elements that require this fix
        if (hasObjectRestElement(path.get("id"))) {
          // Find all computed keys (like [someExpression]) in left-to-right order
          const destructuringPattern = originalPath.get(
            "id",
          ) as NodePath<t.ObjectPattern>;
          const propertiesWithComputedKeys =
            collectComputedKeysInSourceOrder(destructuringPattern);

          // For each computed key that has side effects (not a simple variable)
          for (const property of propertiesWithComputedKeys) {
            const computedKeyExpression = property.get(
              "key",
            ) as NodePath<t.Expression>;

            // Skip if already memoised (assignment expression with a UID)
            if (
              computedKeyExpression.isAssignmentExpression() &&
              computedKeyExpression.get("left").isIdentifier() &&
              originalPath.scope.hasUid(
                (computedKeyExpression.node.left as t.Identifier).name,
              )
            ) {
              continue;
            }

            // Check if the expression has side effects (function call, ++x, etc.)
            if (!computedKeyExpression.isPure()) {
              // Create a temporary variable identifier
              const tempVariableName =