Skip to content

Commit 01d32e5

Browse files
fix: more robust handling of var declarations (#12949)
* fix: more robust handling of var declarations - fixes #12807: state declared with var is now retrieved using a new `safe_get` function, which works like `get` but checks for undefined - fixes #12900: ensure we're not creating another new scope for block statements of function declarations, which resulted in var declarations getting "swallowed" (because they were hoisted to the function declaration scope and never seen by our logic) * simplify --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 70ed8cd commit 01d32e5

File tree

8 files changed

+62
-3
lines changed

8 files changed

+62
-3
lines changed

.changeset/spotty-trees-provide.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+
fix: more robust handling of var declarations

packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ export function VariableDeclaration(node, context) {
284284
}
285285

286286
/**
287-
* Creates the output for a state declaration.
287+
* Creates the output for a state declaration in legacy mode.
288288
* @param {VariableDeclarator} declarator
289289
* @param {Scope} scope
290290
* @param {Expression} value

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function add_state_transformers(context) {
2323
binding.kind === 'legacy_reactive'
2424
) {
2525
context.state.transform[name] = {
26-
read: get_value,
26+
read: binding.declaration_kind === 'var' ? (node) => b.call('$.safe_get', node) : get_value,
2727
assign: (node, value) => {
2828
let call = b.call('$.set', node, value);
2929

packages/svelte/src/compiler/phases/scope.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,8 +461,20 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
461461
ForStatement: create_block_scope,
462462
ForInStatement: create_block_scope,
463463
ForOfStatement: create_block_scope,
464-
BlockStatement: create_block_scope,
465464
SwitchStatement: create_block_scope,
465+
BlockStatement(node, context) {
466+
const parent = context.path.at(-1);
467+
if (
468+
parent?.type === 'FunctionDeclaration' ||
469+
parent?.type === 'FunctionExpression' ||
470+
parent?.type === 'ArrowFunctionExpression'
471+
) {
472+
// We already created a new scope for the function
473+
context.next();
474+
} else {
475+
create_block_scope(node, context);
476+
}
477+
},
466478

467479
ClassDeclaration(node, { state, next }) {
468480
if (node.id) state.scope.declare(node.id, 'normal', 'let', node);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export {
127127
export { set_text } from './render.js';
128128
export {
129129
get,
130+
safe_get,
130131
invalidate_inner_signals,
131132
flush_sync,
132133
tick,

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,16 @@ export function get(signal) {
734734
return signal.v;
735735
}
736736

737+
/**
738+
* Like `get`, but checks for `undefined`. Used for `var` declarations because they can be accessed before being declared
739+
* @template V
740+
* @param {Value<V> | undefined} signal
741+
* @returns {V | undefined}
742+
*/
743+
export function safe_get(signal) {
744+
return signal && get(signal);
745+
}
746+
737747
/**
738748
* Invokes a function and captures all signals that are read during the invocation,
739749
* then invalidates them.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
test({ assert, logs }) {
5+
assert.deepEqual(logs, [undefined, undefined, 10, 20, 0, 1]);
6+
}
7+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script>
2+
console.log(foo, double);
3+
var foo = $state(10);
4+
var double = $derived(foo * 2);
5+
console.log(foo, double);
6+
7+
function wrap(initial) {
8+
var _value = $state(initial);
9+
10+
return {
11+
get() {
12+
return _value;
13+
},
14+
set(state) {
15+
_value = state;
16+
}
17+
};
18+
}
19+
20+
var wrapped = wrap(0);
21+
console.log(wrapped.get());
22+
wrapped.set(1);
23+
console.log(wrapped.get());
24+
</script>

0 commit comments

Comments
 (0)