From 88293b6d5b2109270c4f296351cdcc8fe30d8ad1 Mon Sep 17 00:00:00 2001 From: Vercel Date: Wed, 11 Mar 2026 13:05:44 +0000 Subject: [PATCH] Fix: SvelteSet's forEach, isDisjointFrom, isSubsetOf, isSupersetOf, difference, intersection, symmetricDifference, and union methods operate on an empty parent Set instead of the actual values stored in the #sources Map. This commit fixes the issue reported at packages/svelte/src/reactivity/set.js:96 ## Bug Explanation The `SvelteSet` class was refactored to store values only in the `#sources` Map (where `Source` values indicate presence/absence) instead of calling `super.add()` to store values in the parent `Set`. The constructor was changed from: ```javascript super(value); // Old: values stored in parent Set ``` To: ```javascript super(); // New: parent Set is empty if (value) { for (var element of value) { sources.set(element, this.#source(true)); // Values stored only in #sources } } ``` However, the `#init()` method was not updated to reflect this change. It still uses `set_proto[method].apply(this, v)` which calls Set prototype methods on `this` (the SvelteSet instance). Since the parent Set is now always empty, these methods operate on an empty set and return incorrect results: - `forEach` iterates over nothing - `isDisjointFrom` always returns true (empty set is disjoint from everything) - `isSubsetOf` always returns true (empty set is subset of everything) - `isSupersetOf` always returns false for non-empty sets - `difference`, `intersection`, `symmetricDifference`, `union` all produce incorrect results ## Fix Explanation The fix materializes the current values into a temporary native `Set` before calling the prototype methods. Since `this.values()` is already implemented to correctly iterate over the `#sources` Map and yield values where the source is truthy, we use it to create the temporary Set: ```javascript var set = new Set(this.values()); return set_proto[method].apply(set, v); // Now operates on the materialized Set ``` This ensures that: 1. All current values from `#sources` are collected into a proper native Set 2. The Set prototype methods operate on actual data, not an empty Set 3. Reactivity is preserved since `get(this.#version)` is still called before materializing 4. The `values()` method already calls `get(source)` for each source, tracking dependencies properly Co-authored-by: Vercel Co-authored-by: Rich-Harris --- packages/svelte/src/reactivity/set.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/reactivity/set.js b/packages/svelte/src/reactivity/set.js index fa3d4b41e0..7d4d3f4848 100644 --- a/packages/svelte/src/reactivity/set.js +++ b/packages/svelte/src/reactivity/set.js @@ -98,8 +98,10 @@ export class SvelteSet extends Set { // @ts-ignore proto[method] = function (...v) { get(this.#version); + // Materialize current values into a temporary Set since values are stored in #sources + var set = new Set(this.values()); // @ts-ignore - return set_proto[method].apply(this, v); + return set_proto[method].apply(set, v); }; } @@ -107,8 +109,10 @@ export class SvelteSet extends Set { // @ts-ignore proto[method] = function (...v) { get(this.#version); + // Materialize current values into a temporary Set since values are stored in #sources + var materialized = new Set(this.values()); // @ts-ignore - var set = /** @type {Set} */ (set_proto[method].apply(this, v)); + var set = /** @type {Set} */ (set_proto[method].apply(materialized, v)); return new SvelteSet(set); }; }