fix: wrap array destructuring in spread to avoid iterator edge case

pull/15813/head
ComputerGuy 1 year ago
parent bfb969a6cc
commit 6bc1235a26

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: wrap array destructuring in spread to avoid iterator edge case

@ -2,7 +2,7 @@
/** @import { Binding } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
import { dev } from '../../../../state.js';
import { extract_paths } from '../../../../utils/ast.js';
import { extract_paths, is_array } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import * as assert from '../../../../utils/assert.js';
import { get_rune } from '../../../scope.js';
@ -142,7 +142,7 @@ export function VariableDeclaration(node, context) {
);
} else {
const tmp = context.state.scope.generate('tmp');
const paths = extract_paths(declarator.id);
const paths = extract_paths(declarator.id, is_array(value, context.state.scope));
declarations.push(
b.declarator(b.id(tmp), value),
...paths.map((path) => {
@ -170,7 +170,10 @@ export function VariableDeclaration(node, context) {
)
);
} else {
const bindings = extract_paths(declarator.id);
const bindings = extract_paths(
declarator.id,
rune === '$derived' ? is_array(value, context.state.scope) : false
);
const init = /** @type {CallExpression} */ (declarator.init);
@ -305,7 +308,7 @@ function create_state_declarators(declarator, { scope, analysis }, value) {
}
const tmp = scope.generate('tmp');
const paths = extract_paths(declarator.id);
const paths = extract_paths(declarator.id, is_array(declarator.init, scope));
return [
b.declarator(b.id(tmp), value),
...paths.map((path) => {

@ -2,7 +2,7 @@
/** @import { Binding } from '#compiler' */
/** @import { Context } from '../types.js' */
/** @import { Scope } from '../../../scope.js' */
import { build_fallback, extract_paths } from '../../../../utils/ast.js';
import { build_fallback, extract_paths, is_array } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { get_rune } from '../../../scope.js';
import { walk } from 'zimmerframe';
@ -182,7 +182,7 @@ function create_state_declarators(declarator, scope, value) {
}
const tmp = scope.generate('tmp');
const paths = extract_paths(declarator.id);
const paths = extract_paths(declarator.id, is_array(declarator.init, scope));
return [
b.declarator(b.id(tmp), value), // TODO inject declarator for opts, so we can use it below
...paths.map((path) => {

@ -1,4 +1,4 @@
/** @import { AST } from '#compiler' */
/** @import { AST, Scope } from '#compiler' */
/** @import * as ESTree from 'estree' */
import { walk } from 'zimmerframe';
import * as b from '#compiler/builders';
@ -219,6 +219,31 @@ export function extract_identifiers_from_destructuring(node, nodes = []) {
return nodes;
}
/**
* @param {ESTree.Expression | null | undefined} node
* @param {Scope} scope
*/
export function is_array(node, scope) {
switch (node?.type) {
case 'ArrayExpression':
return true;
case 'CallExpression': {
const { callee } = node;
if (
callee.type === 'Identifier' &&
callee.name === 'Array' &&
scope.get(callee.name) === null
) {
return true;
}
return false;
}
default: {
return false;
}
}
}
/**
* Represents the path of a destructured assignment from either a declaration
* or assignment expression. For example, given `const { foo: { bar: baz } } = quux`,
@ -236,15 +261,17 @@ export function extract_identifiers_from_destructuring(node, nodes = []) {
/**
* Extracts all destructured assignments from a pattern.
* @param {ESTree.Node} param
* @param {boolean} [is_array]
* @returns {DestructuredAssignment[]}
*/
export function extract_paths(param) {
export function extract_paths(param, is_array = false) {
return _extract_paths(
[],
param,
(node) => /** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node),
(node) => /** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node),
false
false,
is_array
);
}
@ -254,9 +281,17 @@ export function extract_paths(param) {
* @param {DestructuredAssignment['expression']} expression
* @param {DestructuredAssignment['update_expression']} update_expression
* @param {boolean} has_default_value
* @param {boolean} [is_array]
* @returns {DestructuredAssignment[]}
*/
function _extract_paths(assignments = [], param, expression, update_expression, has_default_value) {
function _extract_paths(
assignments = [],
param,
expression,
update_expression,
has_default_value,
is_array = false
) {
switch (param.type) {
case 'Identifier':
case 'MemberExpression':
@ -332,7 +367,13 @@ function _extract_paths(assignments = [], param, expression, update_expression,
if (element.type === 'RestElement') {
/** @type {DestructuredAssignment['expression']} */
const rest_expression = (object) =>
b.call(b.member(expression(object), 'slice'), b.literal(i));
b.call(
b.member(
is_array ? expression(object) : b.array([b.spread(expression(object))]),
'slice'
),
b.literal(i)
);
if (element.argument.type === 'Identifier') {
assignments.push({
node: element.argument,
@ -352,7 +393,12 @@ function _extract_paths(assignments = [], param, expression, update_expression,
}
} else {
/** @type {DestructuredAssignment['expression']} */
const array_expression = (object) => b.member(expression(object), b.literal(i), true);
const array_expression = (object) =>
b.member(
is_array ? expression(object) : b.array([b.spread(expression(object))]),
b.literal(i),
true
);
_extract_paths(
assignments,
element,

@ -8,8 +8,8 @@ let d = 4;
export function update(array) {
(
$.set(a, array[0], true),
$.set(b, array[1], true)
$.set(a, [...array][0], true),
$.set(b, [...array][1], true)
);
[c, d] = array;

Loading…
Cancel
Save