From 0da9f9e2ab515da16d624c5bc48b554beb179f92 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 27 May 2026 15:58:57 -0400 Subject: [PATCH] fix: disallow effect creation after `await` (#18299) We incorrectly restore effect context after an`$derived(await ...)` if it occurs inside an async function, but only for those, no other `await` expressions. This is buggy and wrong. We only want to restore context _inside_ an async derived expression (including template expressions)` so that `await a + b` works correctly. It's possible that this will be a breaking change (albeit not semver-violating, because `experimental`) for some people if they are creating effects after an `await` (other than at the top level of a component). This is regrettable, but it should never have worked in the first place. --- .../docs/98-reference/.generated/client-errors.md | 4 ++++ packages/svelte/messages/client-errors/errors.md | 4 ++++ .../phases/2-analyze/visitors/AwaitExpression.js | 3 +-- .../client/visitors/VariableDeclaration.js | 11 ++--------- packages/svelte/src/compiler/utils/ast.js | 5 ++--- .../src/internal/client/reactivity/async.js | 15 +-------------- .../async-disconnected-effect-root/main.svelte | 3 +-- .../samples/async-effect-orphan/_config.js | 12 ++++++++++++ .../samples/async-effect-orphan/main.svelte | 14 ++++++++++++++ .../async-unset-context-after-restore/_config.js | 14 -------------- .../async-unset-context-after-restore/main.svelte | 15 --------------- 11 files changed, 41 insertions(+), 59 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-orphan/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-orphan/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-unset-context-after-restore/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/async-unset-context-after-restore/main.svelte diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 7fccac5808..2ab442afc3 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -88,6 +88,10 @@ Effect cannot be created inside a `$derived` value that was not itself created i `%rune%` can only be used inside an effect (e.g. during component initialisation) ``` +Effects can only be created while a parent effect is running. This means that they cannot, for example, be created inside an event handler or after an `await` expression (unless the `await` occurs directly inside a component's ` diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-orphan/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-orphan/_config.js new file mode 100644 index 0000000000..b76ea85bfd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-orphan/_config.js @@ -0,0 +1,12 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, logs }) { + await tick(); + + assert.deepEqual(logs, [ + `effect_orphan\n\`$effect\` can only be used inside an effect (e.g. during component initialisation)\nhttps://svelte.dev/e/effect_orphan` + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-orphan/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-orphan/main.svelte new file mode 100644 index 0000000000..086a048025 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-orphan/main.svelte @@ -0,0 +1,14 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/async-unset-context-after-restore/_config.js b/packages/svelte/tests/runtime-runes/samples/async-unset-context-after-restore/_config.js deleted file mode 100644 index ad543e91ae..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-unset-context-after-restore/_config.js +++ /dev/null @@ -1,14 +0,0 @@ -import { tick } from 'svelte'; -import { test } from '../../test'; - -export default test({ - async test({ assert, logs }) { - await new Promise((resolve) => setTimeout(resolve, 10)); - assert.deepEqual(logs, [1, 1]); - const [button] = document.querySelectorAll('button'); - - button.click(); - await tick(); - assert.deepEqual(logs, [1, 1, 2]); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-unset-context-after-restore/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-unset-context-after-restore/main.svelte deleted file mode 100644 index a39c75fb94..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/async-unset-context-after-restore/main.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - -