fix: ignore false-positive errors of `$inspect` dependencies (#18106)

We had logic in place to ignore errors of `$inspect` effects that are
about to destroy, but we didn't take into account that we can get these
transient errors while checking for `is_dirty` in preparation for
running the effect, too. Now effects are marked as dirty in case an
error occurs while evaluating their dependencies, which guarantees we
will see the error again but we can then handle it properly.

Fixes #15741

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/18163/head
Simon H 3 months ago committed by GitHub
parent 90a70cb012
commit 572444a696
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ignore false-positive errors of `$inspect` dependencies

@ -20,6 +20,8 @@ export function inspect(get_value, inspector, show_stack = false) {
// in an error (an `$inspect(object.property)` will run before the
// `{#if object}...{/if}` that contains it)
eager_effect(() => {
error = UNINITIALIZED;
try {
var value = get_value();
} catch (e) {

@ -266,7 +266,24 @@ export function flush_eager_effects() {
eager_effects_deferred = false;
for (const effect of eager_effects) {
if (is_dirty(effect)) {
// Mark clean inspect-effects as maybe dirty and then check their dirtiness
// instead of just updating the effects - this way we avoid overfiring.
if ((effect.f & CLEAN) !== 0) {
set_signal_status(effect, MAYBE_DIRTY);
}
let dirty;
try {
dirty = is_dirty(effect);
} catch {
// Dirty-checking can evaluate derived dependencies and throw in cases where
// parent effects are about to destroy this eager effect. Run the effect so
// its own error handling can deal with transient failures.
dirty = true;
}
if (dirty) {
update_effect(effect);
}
}

@ -201,7 +201,7 @@ export const async_mode = process.env.SVELTE_NO_ASYNC !== 'true';
* @param {any[]} logs
*/
export function normalise_inspect_logs(logs) {
/** @type {string[]} */
/** @type {any[]} */
const normalised = [];
for (const log of logs) {

@ -0,0 +1,11 @@
<script>
let {things} = $props();
$inspect(things);
</script>
<ul>
{#each things as thing}
<li>thing {thing.id}</li>
{/each}
</ul>

@ -0,0 +1,21 @@
import { normalise_inspect_logs } from '../../../helpers';
import { test } from '../../test';
import { flushSync } from 'svelte';
export default test({
compileOptions: {
dev: true
},
async test({ assert, target, errors, logs }) {
const button = target.querySelector('button');
flushSync(() => {
button?.click();
});
assert.htmlEqual(target.innerHTML, '<button>clear</button>');
assert.equal(errors.length, 0);
assert.deepEqual(normalise_inspect_logs(logs), [[{ id: 1 }, { id: 2 }]]);
}
});

@ -0,0 +1,15 @@
<script>
import List from "./List.svelte"
let data = $state({things: [{id:1}, {id:2}]})
function reloadData() {
data = null
}
</script>
{#if data}
<List things={data.things.map((t) => t)} />
{/if}
<button onclick={() => reloadData()}>clear</button>
Loading…
Cancel
Save