fix: reset context after waiting on blockers of `@const` expressions (#18100)

Regression from #18039 - we need to have each await expression (and
waiting on blockers is one) in its own entry of `(renderer.)run`. Else
context is not restored correctly and if the synchronous expression
afterwards requires it stuff breaks.

Fixes #18098
pull/18111/head
Simon H 5 months ago committed by GitHub
parent 273f1a85a4
commit 0ed8c282f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: reset context after waiting on blockers of `@const` expressions

@ -38,7 +38,7 @@ export interface AnalysisState {
/** Collected info about async `{@const }` declarations */ /** Collected info about async `{@const }` declarations */
async_consts?: { async_consts?: {
id: Identifier; id: Identifier;
/** How many `@const` declarations there are (already) in this scope */ /** How many `$.run(...)` entries are already allocated in this scope */
declaration_count: number; declaration_count: number;
}; };
} }

@ -61,7 +61,8 @@ export function ConstTag(node, context) {
// keep the counter in sync with the number of thunks pushed in ConstTag in transform // keep the counter in sync with the number of thunks pushed in ConstTag in transform
// TODO 6.0 once non-async and non-runes mode is gone investigate making this more robust // TODO 6.0 once non-async and non-runes mode is gone investigate making this more robust
// via something like the approach in https://github.com/sveltejs/svelte/pull/18032 // via something like the approach in https://github.com/sveltejs/svelte/pull/18032
const length = run.declaration_count++; const length = run.declaration_count + (blockers.length > 0 ? 1 : 0);
run.declaration_count += blockers.length > 0 ? 2 : 1;
const blocker = b.member(run.id, b.literal(length), true); const blocker = b.member(run.id, b.literal(length), true);
for (const binding of bindings) { for (const binding of bindings) {
binding.blocker = blocker; binding.blocker = blocker;

@ -96,22 +96,15 @@ function add_const_declaration(state, id, expression, metadata) {
state.consts.push(b.let(id)); state.consts.push(b.let(id));
/** @type {Statement | undefined} */
let promise_stmt;
if (blockers.length === 1) { if (blockers.length === 1) {
promise_stmt = b.stmt(b.await(b.member(/** @type {Expression} */ (blockers[0]), 'promise'))); run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
} else if (blockers.length > 0) { } else if (blockers.length > 0) {
promise_stmt = b.stmt(b.await(b.call('$.wait', b.array(blockers)))); run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
} }
// keep the number of thunks pushed in sync with ConstTag in analysis phase // keep the number of thunks pushed in sync with ConstTag in analysis phase
const assignment = b.assignment('=', id, expression); const assignment = b.assignment('=', id, expression);
if (promise_stmt) { run.thunks.push(b.thunk(assignment, metadata.expression.has_await));
run.thunks.push(b.thunk(b.block([promise_stmt, b.stmt(assignment)]), true));
} else {
run.thunks.push(b.thunk(assignment, metadata.expression.has_await));
}
} else { } else {
state.consts.push(b.const(id, expression)); state.consts.push(b.const(id, expression));
state.consts.push(...after); state.consts.push(...after);

@ -28,22 +28,15 @@ export function ConstTag(node, context) {
context.state.init.push(b.let(identifier.name)); context.state.init.push(b.let(identifier.name));
} }
/** @type {Statement | undefined} */
let promise_stmt;
if (blockers.length === 1) { if (blockers.length === 1) {
promise_stmt = b.stmt(b.await(/** @type {Expression} */ (blockers[0]))); run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0])));
} else if (blockers.length > 0) { } else if (blockers.length > 0) {
promise_stmt = b.stmt(b.await(b.call('Promise.all', b.array(blockers)))); run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
} }
// keep the number of thunks pushed in sync with ConstTag in analysis phase // keep the number of thunks pushed in sync with ConstTag in analysis phase
const assignment = b.assignment('=', id, init); const assignment = b.assignment('=', id, init);
if (promise_stmt) { run.thunks.push(b.thunk(assignment, node.metadata.expression.has_await));
run.thunks.push(b.thunk(b.block([promise_stmt, b.stmt(assignment)]), true));
} else {
run.thunks.push(b.thunk(assignment, node.metadata.expression.has_await));
}
} else { } else {
context.state.init.push(b.const(id, init)); context.state.init.push(b.const(id, init));
} }

@ -0,0 +1,11 @@
import { tick } from 'svelte';
import { test } from '../../test';
// Tests that context is restored after await (const has to wait on a blocker), so that getContext etc work correctly
export default test({
mode: ['hydrate', 'async-server', 'client'],
async test({ assert, target }) {
await tick();
assert.htmlEqual(target.innerHTML, 'hi');
}
});

@ -0,0 +1,11 @@
<script>
import { getContext } from 'svelte';
const _unused = $derived(await 1);
//This must be created after any $derived(await...)
const bar = $derived(getContext('') ?? 'hi');
</script>
{#if true}
{@const foo = bar}
{foo}
{/if}

@ -2,6 +2,8 @@ import 'svelte/internal/disclose-version';
import 'svelte/internal/flags/async'; import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/client'; import * as $ from 'svelte/internal/client';
var root = $.from_html(`<!> <!>`, 1);
export default function Async_in_derived($$anchor, $$props) { export default function Async_in_derived($$anchor, $$props) {
$.push($$props, true); $.push($$props, true);
@ -21,7 +23,7 @@ export default function Async_in_derived($$anchor, $$props) {
} }
]); ]);
var fragment = $.comment(); var fragment = root();
var node = $.first_child(fragment); var node = $.first_child(fragment);
{ {
@ -49,6 +51,23 @@ export default function Async_in_derived($$anchor, $$props) {
}); });
} }
var node_1 = $.sibling(node, 2);
{
var consequent_1 = ($$anchor) => {
let x;
var promises_1 = $.run([
() => $$promises[2].promise,
() => x = $.derived(() => $.get(no2))
]);
};
$.if(node_1, ($$render) => {
if (true) $$render(consequent_1);
});
}
$.append($$anchor, fragment); $.append($$anchor, fragment);
$.pop(); $.pop();
} }

@ -42,6 +42,17 @@ export default function Async_in_derived($$renderer, $$props) {
$$renderer.push('<!--[-1-->'); $$renderer.push('<!--[-1-->');
} }
$$renderer.push(`<!--]--> `);
if (true) {
$$renderer.push('<!--[0-->');
let x;
var promises_1 = $$renderer.run([() => $$promises[2], () => x = no2()]);
} else {
$$renderer.push('<!--[-1-->');
}
$$renderer.push(`<!--]-->`); $$renderer.push(`<!--]-->`);
}); });
} }

@ -19,3 +19,7 @@
return await 1; return await 1;
})()} })()}
{/if} {/if}
{#if true}
{@const x = no2}
{/if}

Loading…
Cancel
Save