Skip to content

Commit d06174e

Browse files
trueadmRich-Harris
andauthored
chore: add error for derived self referencing (#12746)
* chore: add warning for derived self referencin * update build * address feedback * address feedback * build * messages shouldn't end with a period * simplify test * regenerate * newlines are free * no need to export this, we can move it closer to where it's used * fix double negative --------- Co-authored-by: Rich Harris <[email protected]>
1 parent bd9a2d2 commit d06174e

File tree

6 files changed

+78
-0
lines changed

6 files changed

+78
-0
lines changed

.changeset/new-cooks-roll.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: add error for derived self referencing

packages/svelte/messages/client-errors/errors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818

1919
> Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `compatibility.componentApi` compiler option to `4` to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information
2020
21+
## derived_references_self
22+
23+
> A derived value cannot reference itself recursively
24+
2125
## each_key_duplicate
2226

2327
> Keyed each block has duplicate key at indexes %a% and %b%

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,22 @@ export function component_api_invalid_new(component, name) {
9393
}
9494
}
9595

96+
/**
97+
* A derived value cannot reference itself recursively
98+
* @returns {never}
99+
*/
100+
export function derived_references_self() {
101+
if (DEV) {
102+
const error = new Error(`derived_references_self\nA derived value cannot reference itself recursively`);
103+
104+
error.name = 'Svelte error';
105+
throw error;
106+
} else {
107+
// TODO print a link to the documentation
108+
throw new Error("derived_references_self");
109+
}
110+
}
111+
96112
/**
97113
* Keyed each block has duplicate key `%value%` at indexes %a% and %b%
98114
* @param {string} a

packages/svelte/src/internal/client/reactivity/deriveds.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/** @import { Derived } from '#client' */
2+
import { DEV } from 'esm-env';
23
import { CLEAN, DERIVED, DESTROYED, DIRTY, MAYBE_DIRTY, UNOWNED } from '../constants.js';
34
import {
45
current_reaction,
@@ -11,6 +12,7 @@ import {
1112
increment_version
1213
} from '../runtime.js';
1314
import { equals, safe_equals } from './equality.js';
15+
import * as e from '../errors.js';
1416

1517
export let updating_derived = false;
1618

@@ -79,17 +81,36 @@ function destroy_derived_children(derived) {
7981
}
8082
}
8183

84+
/**
85+
* The currently updating deriveds, used to detect infinite recursion
86+
* in dev mode and provide a nicer error than 'too much recursion'
87+
* @type {Derived[]}
88+
*/
89+
let stack = [];
90+
8291
/**
8392
* @param {Derived} derived
8493
* @returns {void}
8594
*/
8695
export function update_derived(derived) {
96+
if (DEV) {
97+
if (stack.includes(derived)) {
98+
e.derived_references_self();
99+
}
100+
101+
stack.push(derived);
102+
}
103+
87104
var previous_updating_derived = updating_derived;
88105
updating_derived = true;
89106
destroy_derived_children(derived);
90107
var value = update_reaction(derived);
91108
updating_derived = previous_updating_derived;
92109

110+
if (DEV) {
111+
stack.pop();
112+
}
113+
93114
var status =
94115
(current_skip_reaction || (derived.f & UNOWNED) !== 0) && derived.deps !== null
95116
? MAYBE_DIRTY
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
html: `<button>increment</button>\n0`,
6+
7+
mode: ['client'],
8+
9+
test({ assert, target }) {
10+
const btn = target.querySelector('button');
11+
12+
btn?.click();
13+
14+
assert.throws(
15+
flushSync,
16+
'derived_references_self\nA derived value cannot reference itself recursively'
17+
);
18+
19+
assert.htmlEqual(target.innerHTML, `<button>increment</button>\n0`);
20+
}
21+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
let count = $state(0);
3+
let even = $derived.by(() => {
4+
return count > 0 ? even : 0;
5+
})
6+
7+
</script>
8+
9+
<button onclick={() => count++}>increment</button>
10+
11+
{even}

0 commit comments

Comments
 (0)