Skip to content

Commit 5b49610

Browse files
committed
WIP
1 parent 0cc2b4e commit 5b49610

File tree

4 files changed

+70
-29
lines changed

4 files changed

+70
-29
lines changed

packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,12 @@ export function VariableDeclaration(node, context) {
183183
);
184184
}
185185

186-
const { paths } = extract_paths(declarator.id, rhs);
186+
const { inserts, paths } = extract_paths(declarator.id, rhs);
187+
188+
for (const insert of inserts) {
189+
insert.id.name = context.state.scope.generate('$$array');
190+
declarations.push(b.declarator(insert.id, insert.value));
191+
}
187192

188193
for (const path of paths) {
189194
declarations.push(

packages/svelte/src/compiler/utils/ast.js

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -237,42 +237,35 @@ export function extract_identifiers_from_destructuring(node, nodes = []) {
237237
* Extracts all destructured assignments from a pattern.
238238
* @param {ESTree.Node} param
239239
* @param {ESTree.Expression} initial
240-
* @returns {{ declarations: ESTree.VariableDeclaration[], paths: DestructuredAssignment[] }}
240+
* @returns {{ inserts: Array<{ id: ESTree.Identifier, value: ESTree.Expression }>, paths: DestructuredAssignment[] }}
241241
*/
242242
export function extract_paths(param, initial) {
243243
/**
244244
* When dealing with array destructuring patterns (`let [a, b, c] = $derived(blah())`)
245245
* we need an intermediate declaration that creates an array, since `blah()` could
246246
* return a non-array-like iterator
247-
* @type {ESTree.VariableDeclaration[]}
247+
* @type {Array<{ id: ESTree.Identifier, value: ESTree.Expression }>}
248248
*/
249-
const declarations = [];
249+
const inserts = [];
250250

251251
/** @type {DestructuredAssignment[]} */
252252
const paths = [];
253253

254-
_extract_paths(paths, declarations, param, initial, initial, false);
254+
_extract_paths(paths, inserts, param, initial, initial, false);
255255

256-
return { declarations, paths };
256+
return { inserts, paths };
257257
}
258258

259259
/**
260260
* @param {DestructuredAssignment[]} paths
261-
* @param {ESTree.VariableDeclaration[]} declarations
261+
* @param {Array<{ id: ESTree.Identifier, value: ESTree.Expression }>} inserts
262262
* @param {ESTree.Node} param
263263
* @param {ESTree.Expression} expression
264264
* @param {ESTree.Expression} update_expression
265265
* @param {boolean} has_default_value
266266
* @returns {DestructuredAssignment[]}
267267
*/
268-
function _extract_paths(
269-
paths,
270-
declarations,
271-
param,
272-
expression,
273-
update_expression,
274-
has_default_value
275-
) {
268+
function _extract_paths(paths, inserts, param, expression, update_expression, has_default_value) {
276269
switch (param.type) {
277270
case 'Identifier':
278271
case 'MemberExpression':
@@ -316,7 +309,7 @@ function _extract_paths(
316309
} else {
317310
_extract_paths(
318311
paths,
319-
declarations,
312+
inserts,
320313
prop.argument,
321314
rest_expression,
322315
rest_expression,
@@ -332,7 +325,7 @@ function _extract_paths(
332325

333326
_extract_paths(
334327
paths,
335-
declarations,
328+
inserts,
336329
prop.value,
337330
object_expression,
338331
object_expression,
@@ -344,11 +337,26 @@ function _extract_paths(
344337
break;
345338

346339
case 'ArrayPattern': {
340+
// we create an intermediate declaration to convert iterables to arrays if necessary.
341+
// the consumer is responsible for setting the name of the identifier
342+
const id = b.id('#');
343+
344+
const is_rest = param.elements.at(-1)?.type === 'RestElement';
345+
const call = b.call(
346+
'$.to_array',
347+
expression,
348+
is_rest ? undefined : b.literal(param.elements.length)
349+
);
350+
351+
inserts.push({ id, value: b.call('$.derived', b.thunk(call)) });
352+
353+
const array = b.call('$.get', id);
354+
347355
for (let i = 0; i < param.elements.length; i += 1) {
348356
const element = param.elements[i];
349357
if (element) {
350358
if (element.type === 'RestElement') {
351-
const rest_expression = b.call(b.member(expression, 'slice'), b.literal(i));
359+
const rest_expression = b.call(b.member(array, 'slice'), b.literal(i));
352360

353361
if (element.argument.type === 'Identifier') {
354362
paths.push({
@@ -361,19 +369,19 @@ function _extract_paths(
361369
} else {
362370
_extract_paths(
363371
paths,
364-
declarations,
372+
inserts,
365373
element.argument,
366374
rest_expression,
367375
rest_expression,
368376
has_default_value
369377
);
370378
}
371379
} else {
372-
const array_expression = b.member(expression, b.literal(i), true);
380+
const array_expression = b.member(array, b.literal(i), true);
373381

374382
_extract_paths(
375383
paths,
376-
declarations,
384+
inserts,
377385
element,
378386
array_expression,
379387
array_expression,
@@ -398,14 +406,7 @@ function _extract_paths(
398406
update_expression
399407
});
400408
} else {
401-
_extract_paths(
402-
paths,
403-
declarations,
404-
param.left,
405-
fallback_expression,
406-
update_expression,
407-
true
408-
);
409+
_extract_paths(paths, inserts, param.left, fallback_expression, update_expression, true);
409410
}
410411

411412
break;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* When encountering a situation like `let [a, b, c] = $derived(blah())`,
3+
* we need to stash an intermediate value that `a`, `b`, and `c` derive
4+
* from, in case it's an iterable
5+
* @template T
6+
* @param {ArrayLike<T> | Iterable<T>} value
7+
* @param {number} [n]
8+
* @returns {Array<T>}
9+
*/
10+
export function to_array(value, n) {
11+
// return arrays unchanged
12+
if (Array.isArray(value)) {
13+
return value;
14+
}
15+
16+
// if value is not iterable, or `n` is unspecified (indicates a rest
17+
// element, which means we're not concerned about unbounded iterables)
18+
// convert to an array with `Array.from`
19+
if (n === undefined || !(Symbol.iterator in value)) {
20+
return Array.from(value);
21+
}
22+
23+
// otherwise, populate an array with `n` values
24+
25+
/** @type {T[]} */
26+
const array = [];
27+
28+
for (const element of value) {
29+
array.push(element);
30+
if (array.length === n) break;
31+
}
32+
33+
return array;
34+
}

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export { createAttachmentKey as attachment } from '../../attachments/index.js';
22
export { FILENAME, HMR, NAMESPACE_SVG } from '../../constants.js';
33
export { push, pop } from './context.js';
4+
export { to_array } from './destructuring.js';
45
export { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js';
56
export { cleanup_styles } from './dev/css.js';
67
export { add_locations } from './dev/elements.js';

0 commit comments

Comments
 (0)