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<boolean>` 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 <vercel[bot]@users.noreply.github.com>
Co-authored-by: Rich-Harris <hello@rich-harris.dev>
async-svelte-set-alternative
Vercel 4 months ago
parent 7176069843
commit 88293b6d5b

@ -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<T>} */ (set_proto[method].apply(this, v));
var set = /** @type {Set<T>} */ (set_proto[method].apply(materialized, v));
return new SvelteSet(set);
};
}

Loading…
Cancel
Save