pull/16015/head
Rich Harris 4 months ago
parent 0cc2b4ec1f
commit 5b49610e23

@ -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) { for (const path of paths) {
declarations.push( declarations.push(

@ -237,42 +237,35 @@ 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 {ESTree.Expression} initial * @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) { export function extract_paths(param, initial) {
/** /**
* When dealing with array destructuring patterns (`let [a, b, c] = $derived(blah())`) * When dealing with array destructuring patterns (`let [a, b, c] = $derived(blah())`)
* we need an intermediate declaration that creates an array, since `blah()` could * we need an intermediate declaration that creates an array, since `blah()` could
* return a non-array-like iterator * return a non-array-like iterator
* @type {ESTree.VariableDeclaration[]} * @type {Array<{ id: ESTree.Identifier, value: ESTree.Expression }>}
*/ */
const declarations = []; const inserts = [];
/** @type {DestructuredAssignment[]} */ /** @type {DestructuredAssignment[]} */
const paths = []; 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 {DestructuredAssignment[]} paths
* @param {ESTree.VariableDeclaration[]} declarations * @param {Array<{ id: ESTree.Identifier, value: ESTree.Expression }>} inserts
* @param {ESTree.Node} param * @param {ESTree.Node} param
* @param {ESTree.Expression} expression * @param {ESTree.Expression} expression
* @param {ESTree.Expression} update_expression * @param {ESTree.Expression} update_expression
* @param {boolean} has_default_value * @param {boolean} has_default_value
* @returns {DestructuredAssignment[]} * @returns {DestructuredAssignment[]}
*/ */
function _extract_paths( function _extract_paths(paths, inserts, param, expression, update_expression, has_default_value) {
paths,
declarations,
param,
expression,
update_expression,
has_default_value
) {
switch (param.type) { switch (param.type) {
case 'Identifier': case 'Identifier':
case 'MemberExpression': case 'MemberExpression':
@ -316,7 +309,7 @@ function _extract_paths(
} else { } else {
_extract_paths( _extract_paths(
paths, paths,
declarations, inserts,
prop.argument, prop.argument,
rest_expression, rest_expression,
rest_expression, rest_expression,
@ -332,7 +325,7 @@ function _extract_paths(
_extract_paths( _extract_paths(
paths, paths,
declarations, inserts,
prop.value, prop.value,
object_expression, object_expression,
object_expression, object_expression,
@ -344,11 +337,26 @@ function _extract_paths(
break; break;
case 'ArrayPattern': { 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) { for (let i = 0; i < param.elements.length; i += 1) {
const element = param.elements[i]; const element = param.elements[i];
if (element) { if (element) {
if (element.type === 'RestElement') { 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') { if (element.argument.type === 'Identifier') {
paths.push({ paths.push({
@ -361,7 +369,7 @@ function _extract_paths(
} else { } else {
_extract_paths( _extract_paths(
paths, paths,
declarations, inserts,
element.argument, element.argument,
rest_expression, rest_expression,
rest_expression, rest_expression,
@ -369,11 +377,11 @@ function _extract_paths(
); );
} }
} else { } else {
const array_expression = b.member(expression, b.literal(i), true); const array_expression = b.member(array, b.literal(i), true);
_extract_paths( _extract_paths(
paths, paths,
declarations, inserts,
element, element,
array_expression, array_expression,
array_expression, array_expression,
@ -398,14 +406,7 @@ function _extract_paths(
update_expression update_expression
}); });
} else { } else {
_extract_paths( _extract_paths(paths, inserts, param.left, fallback_expression, update_expression, true);
paths,
declarations,
param.left,
fallback_expression,
update_expression,
true
);
} }
break; break;

@ -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<T> | Iterable<T>} value
* @param {number} [n]
* @returns {Array<T>}
*/
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;
}

@ -1,6 +1,7 @@
export { createAttachmentKey as attachment } from '../../attachments/index.js'; export { createAttachmentKey as attachment } from '../../attachments/index.js';
export { FILENAME, HMR, NAMESPACE_SVG } from '../../constants.js'; export { FILENAME, HMR, NAMESPACE_SVG } from '../../constants.js';
export { push, pop } from './context.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 { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js';
export { cleanup_styles } from './dev/css.js'; export { cleanup_styles } from './dev/css.js';
export { add_locations } from './dev/elements.js'; export { add_locations } from './dev/elements.js';

Loading…
Cancel
Save