pull/17232/merge
Ayush Kumar Singh 21 hours ago committed by GitHub
commit 55416db469
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -29,7 +29,7 @@ export function EachBlock(node, context) {
scope: /** @type {Scope} */ (context.state.scope.parent)
};
const collection = build_expression(
let collection = build_expression(
{
...context,
state: parent_scope_state
@ -38,6 +38,30 @@ export function EachBlock(node, context) {
node.metadata.expression
);
const destructured_pattern = get_destructured_pattern(node.context);
if (destructured_pattern) {
if (destructured_pattern.type === 'ArrayPattern') {
// For array destructuring, we need to destructure immediately during iteration
// to match for...of behavior, capturing values before the generator mutates them
const indices = [];
for (let i = 0; i < destructured_pattern.elements.length; i++) {
const element = destructured_pattern.elements[i];
if (element && element.type !== 'RestElement') {
indices.push(i);
}
}
collection = b.call(
'$.to_array_destructured',
collection,
b.array(indices.map((i) => b.literal(i)))
);
} else {
// For object destructuring, we still need to snapshot to capture values
collection = b.call('$.snapshot_each_value', collection, create_object_snapshot_mapper());
}
}
if (!each_node_meta.is_controlled) {
context.state.template.push_comment();
}
@ -365,3 +389,21 @@ export function EachBlock(node, context) {
function collect_parent_each_blocks(context) {
return /** @type {AST.EachBlock[]} */ (context.path.filter((node) => node.type === 'EachBlock'));
}
/**
* @param {import('estree').Pattern | null | undefined} pattern
* @returns {import('estree').ArrayPattern | import('estree').ObjectPattern | null}
*/
function get_destructured_pattern(pattern) {
if (!pattern) return null;
if (pattern.type === 'ArrayPattern' || pattern.type === 'ObjectPattern') {
return pattern;
}
return null;
}
function create_object_snapshot_mapper() {
const value = b.id('$$value');
return b.arrow([value], b.call('$.snapshot_object', value));
}

@ -1,4 +1,4 @@
/** @import { BlockStatement, Expression, Statement } from 'estree' */
/** @import { BlockStatement, Expression, Pattern, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders';
@ -12,7 +12,29 @@ export function EachBlock(node, context) {
const state = context.state;
const each_node_meta = node.metadata;
const collection = /** @type {Expression} */ (context.visit(node.expression));
let collection = /** @type {Expression} */ (context.visit(node.expression));
const destructured_pattern = get_destructured_pattern(node.context);
if (destructured_pattern) {
if (destructured_pattern.type === 'ArrayPattern') {
// For array destructuring, destructure immediately during iteration
const indices = [];
for (let i = 0; i < destructured_pattern.elements.length; i++) {
const element = destructured_pattern.elements[i];
if (element && element.type !== 'RestElement') {
indices.push(i);
}
}
collection = b.call(
'$.to_array_destructured',
collection,
b.array(indices.map((i) => b.literal(i)))
);
} else {
// For object destructuring, we still need to snapshot
collection = b.call('$.snapshot_each_value', collection, create_object_snapshot_mapper());
}
}
const index =
each_node_meta.contains_group_binding || !node.index ? each_node_meta.index : b.id(node.index);
@ -78,3 +100,21 @@ export function EachBlock(node, context) {
state.template.push(...block.body, block_close);
}
}
/**
* @param {Pattern | null} pattern
* @returns {import('estree').ArrayPattern | import('estree').ObjectPattern | null}
*/
function get_destructured_pattern(pattern) {
if (!pattern) return null;
if (pattern.type === 'ArrayPattern' || pattern.type === 'ObjectPattern') {
return pattern;
}
return null;
}
function create_object_snapshot_mapper() {
const value = b.id('$$value');
return b.arrow([value], b.call('$.snapshot_object', value));
}

@ -171,7 +171,7 @@ export {
} from './dom/operations.js';
export { attr, clsx } from '../shared/attributes.js';
export { snapshot } from '../shared/clone.js';
export { noop, fallback, to_array } from '../shared/utils.js';
export { noop, fallback, to_array, to_array_destructured, snapshot_each_value, snapshot_object } from '../shared/utils.js';
export {
invalid_default_snippet,
validate_dynamic_element_tag,

@ -457,7 +457,7 @@ export { push_element, pop_element, validate_snippet_args } from './dev.js';
export { snapshot } from '../shared/clone.js';
export { fallback, to_array } from '../shared/utils.js';
export { fallback, to_array, to_array_destructured, snapshot_each_value, snapshot_object } from '../shared/utils.js';
export {
invalid_default_snippet,

@ -117,3 +117,72 @@ export function to_array(value, n) {
return array;
}
/**
* Convert an iterable to an array, immediately destructuring array elements
* at the specified indices. This ensures that when a generator yields the same
* array object multiple times (mutating it), we capture the values at iteration
* time, matching for...of behavior.
*
* Returns an array where each element is a new array containing the destructured
* values, so that extract_paths can process them correctly.
* @template T
* @param {ArrayLike<T> | Iterable<T> | null | undefined} collection
* @param {number[]} destructure_indices - Array indices to extract from each element
* @returns {Array<any[]>}
*/
export function to_array_destructured(collection, destructure_indices) {
if (collection == null) {
return [];
}
const result = [];
// Helper to destructure a single element
const destructure_element = (element) => {
const destructured = [];
for (let j = 0; j < destructure_indices.length; j++) {
destructured.push(element?.[destructure_indices[j]]);
}
return destructured;
};
// If already an array, destructure each element immediately
if (is_array(collection)) {
for (let i = 0; i < collection.length; i++) {
result.push(destructure_element(collection[i]));
}
return result;
}
// For iterables, destructure during iteration
for (const element of collection) {
result.push(destructure_element(element));
}
return result;
}
/**
* Snapshot items produced by an iterator so that destructured values reflect
* what was yielded before the iterator mutates the value again.
* Used for object destructuring where we need to shallow copy the object.
* @template T
* @param {ArrayLike<T> | Iterable<T> | null | undefined} collection
* @param {(value: T) => T} mapper
* @returns {Array<T>}
*/
export function snapshot_each_value(collection, mapper) {
if (collection == null) {
return [];
}
return is_array(collection) ? collection : array_from(collection, mapper);
}
/**
* @param {any} value
*/
export function snapshot_object(value) {
return value == null || typeof value !== 'object' ? value : { ...value };
}

@ -0,0 +1,12 @@
import { test } from '../../test';
export default test({
html: `
<p>0</p>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
`
});

@ -0,0 +1,17 @@
<svelte:options runes />
<script>
function* gen() {
const arr = [0];
for (let i = 0; i < 5; i += 1) {
arr[0] = i;
yield arr;
}
}
</script>
{#each gen() as [item]}
<p>{item}</p>
{/each}
Loading…
Cancel
Save