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

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

@ -1,4 +1,4 @@
/** @import { AST } from '#compiler' */ /** @import { AST, Scope } from '#compiler' */
/** @import * as ESTree from 'estree' */ /** @import * as ESTree from 'estree' */
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
@ -219,6 +219,31 @@ export function extract_identifiers_from_destructuring(node, nodes = []) {
return 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 * Represents the path of a destructured assignment from either a declaration
* or assignment expression. For example, given `const { foo: { bar: baz } } = quux`, * 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. * Extracts all destructured assignments from a pattern.
* @param {ESTree.Node} param * @param {ESTree.Node} param
* @param {boolean} [is_array]
* @returns {DestructuredAssignment[]} * @returns {DestructuredAssignment[]}
*/ */
export function extract_paths(param) { export function extract_paths(param, is_array = false) {
return _extract_paths( return _extract_paths(
[], [],
param, param,
(node) => /** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node), (node) => /** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node),
(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['expression']} expression
* @param {DestructuredAssignment['update_expression']} update_expression * @param {DestructuredAssignment['update_expression']} update_expression
* @param {boolean} has_default_value * @param {boolean} has_default_value
* @param {boolean} [is_array]
* @returns {DestructuredAssignment[]} * @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) { switch (param.type) {
case 'Identifier': case 'Identifier':
case 'MemberExpression': case 'MemberExpression':
@ -332,7 +367,13 @@ function _extract_paths(assignments = [], param, expression, update_expression,
if (element.type === 'RestElement') { if (element.type === 'RestElement') {
/** @type {DestructuredAssignment['expression']} */ /** @type {DestructuredAssignment['expression']} */
const rest_expression = (object) => 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') { if (element.argument.type === 'Identifier') {
assignments.push({ assignments.push({
node: element.argument, node: element.argument,
@ -352,7 +393,12 @@ function _extract_paths(assignments = [], param, expression, update_expression,
} }
} else { } else {
/** @type {DestructuredAssignment['expression']} */ /** @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( _extract_paths(
assignments, assignments,
element, element,

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

Loading…
Cancel
Save