From 5b49610e239e69d41a036961d0695dd744e69d0d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 28 May 2025 11:12:29 -0400 Subject: [PATCH] WIP --- .../client/visitors/VariableDeclaration.js | 7 ++- packages/svelte/src/compiler/utils/ast.js | 57 ++++++++++--------- .../src/internal/client/destructuring.js | 34 +++++++++++ packages/svelte/src/internal/client/index.js | 1 + 4 files changed, 70 insertions(+), 29 deletions(-) create mode 100644 packages/svelte/src/internal/client/destructuring.js diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js index 4bb895511b..2636c59621 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js @@ -183,7 +183,12 @@ export function VariableDeclaration(node, context) { ); } - const { paths } = extract_paths(declarator.id, rhs); + const { inserts, paths } = extract_paths(declarator.id, rhs); + + for (const insert of inserts) { + insert.id.name = context.state.scope.generate('$$array'); + declarations.push(b.declarator(insert.id, insert.value)); + } for (const path of paths) { declarations.push( diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js index c4c125a60f..35fde34860 100644 --- a/packages/svelte/src/compiler/utils/ast.js +++ b/packages/svelte/src/compiler/utils/ast.js @@ -237,42 +237,35 @@ export function extract_identifiers_from_destructuring(node, nodes = []) { * Extracts all destructured assignments from a pattern. * @param {ESTree.Node} param * @param {ESTree.Expression} initial - * @returns {{ declarations: ESTree.VariableDeclaration[], paths: DestructuredAssignment[] }} + * @returns {{ inserts: Array<{ id: ESTree.Identifier, value: ESTree.Expression }>, paths: DestructuredAssignment[] }} */ export function extract_paths(param, initial) { /** * When dealing with array destructuring patterns (`let [a, b, c] = $derived(blah())`) * we need an intermediate declaration that creates an array, since `blah()` could * return a non-array-like iterator - * @type {ESTree.VariableDeclaration[]} + * @type {Array<{ id: ESTree.Identifier, value: ESTree.Expression }>} */ - const declarations = []; + const inserts = []; /** @type {DestructuredAssignment[]} */ const paths = []; - _extract_paths(paths, declarations, param, initial, initial, false); + _extract_paths(paths, inserts, param, initial, initial, false); - return { declarations, paths }; + return { inserts, paths }; } /** * @param {DestructuredAssignment[]} paths - * @param {ESTree.VariableDeclaration[]} declarations + * @param {Array<{ id: ESTree.Identifier, value: ESTree.Expression }>} inserts * @param {ESTree.Node} param * @param {ESTree.Expression} expression * @param {ESTree.Expression} update_expression * @param {boolean} has_default_value * @returns {DestructuredAssignment[]} */ -function _extract_paths( - paths, - declarations, - param, - expression, - update_expression, - has_default_value -) { +function _extract_paths(paths, inserts, param, expression, update_expression, has_default_value) { switch (param.type) { case 'Identifier': case 'MemberExpression': @@ -316,7 +309,7 @@ function _extract_paths( } else { _extract_paths( paths, - declarations, + inserts, prop.argument, rest_expression, rest_expression, @@ -332,7 +325,7 @@ function _extract_paths( _extract_paths( paths, - declarations, + inserts, prop.value, object_expression, object_expression, @@ -344,11 +337,26 @@ function _extract_paths( break; case 'ArrayPattern': { + // we create an intermediate declaration to convert iterables to arrays if necessary. + // the consumer is responsible for setting the name of the identifier + const id = b.id('#'); + + const is_rest = param.elements.at(-1)?.type === 'RestElement'; + const call = b.call( + '$.to_array', + expression, + is_rest ? undefined : b.literal(param.elements.length) + ); + + inserts.push({ id, value: b.call('$.derived', b.thunk(call)) }); + + const array = b.call('$.get', id); + for (let i = 0; i < param.elements.length; i += 1) { const element = param.elements[i]; if (element) { if (element.type === 'RestElement') { - const rest_expression = b.call(b.member(expression, 'slice'), b.literal(i)); + const rest_expression = b.call(b.member(array, 'slice'), b.literal(i)); if (element.argument.type === 'Identifier') { paths.push({ @@ -361,7 +369,7 @@ function _extract_paths( } else { _extract_paths( paths, - declarations, + inserts, element.argument, rest_expression, rest_expression, @@ -369,11 +377,11 @@ function _extract_paths( ); } } else { - const array_expression = b.member(expression, b.literal(i), true); + const array_expression = b.member(array, b.literal(i), true); _extract_paths( paths, - declarations, + inserts, element, array_expression, array_expression, @@ -398,14 +406,7 @@ function _extract_paths( update_expression }); } else { - _extract_paths( - paths, - declarations, - param.left, - fallback_expression, - update_expression, - true - ); + _extract_paths(paths, inserts, param.left, fallback_expression, update_expression, true); } break; diff --git a/packages/svelte/src/internal/client/destructuring.js b/packages/svelte/src/internal/client/destructuring.js new file mode 100644 index 0000000000..46719450b2 --- /dev/null +++ b/packages/svelte/src/internal/client/destructuring.js @@ -0,0 +1,34 @@ +/** + * When encountering a situation like `let [a, b, c] = $derived(blah())`, + * we need to stash an intermediate value that `a`, `b`, and `c` derive + * from, in case it's an iterable + * @template T + * @param {ArrayLike | Iterable} value + * @param {number} [n] + * @returns {Array} + */ +export function to_array(value, n) { + // return arrays unchanged + if (Array.isArray(value)) { + return value; + } + + // if value is not iterable, or `n` is unspecified (indicates a rest + // element, which means we're not concerned about unbounded iterables) + // convert to an array with `Array.from` + if (n === undefined || !(Symbol.iterator in value)) { + return Array.from(value); + } + + // otherwise, populate an array with `n` values + + /** @type {T[]} */ + const array = []; + + for (const element of value) { + array.push(element); + if (array.length === n) break; + } + + return array; +} diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 226d79a65f..490834addd 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -1,6 +1,7 @@ export { createAttachmentKey as attachment } from '../../attachments/index.js'; export { FILENAME, HMR, NAMESPACE_SVG } from '../../constants.js'; export { push, pop } from './context.js'; +export { to_array } from './destructuring.js'; export { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js'; export { cleanup_styles } from './dev/css.js'; export { add_locations } from './dev/elements.js';