Skip to content

Commit eca1b7f

Browse files
authored
fix: ensure state update expressions are serialised correctly (#12109)
Fixes #12103
1 parent 3319253 commit eca1b7f

File tree

6 files changed

+49
-8
lines changed

6 files changed

+49
-8
lines changed

.changeset/gorgeous-boxes-design.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: ensure state update expressions are serialised correctly

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,11 @@ export function serialize_get_binding(node, state) {
119119
* @param {import('estree').AssignmentExpression} node
120120
* @param {import('zimmerframe').Context<import('#compiler').SvelteNode, State>} context
121121
* @param {() => any} fallback
122+
* @param {boolean} prefix
122123
* @param {{skip_proxy_and_freeze?: boolean}} [options]
123124
* @returns {import('estree').Expression}
124125
*/
125-
export function serialize_set_binding(node, context, fallback, options) {
126+
export function serialize_set_binding(node, context, fallback, prefix, options) {
126127
const { state, visit } = context;
127128

128129
const assignee = node.left;
@@ -146,7 +147,9 @@ export function serialize_set_binding(node, context, fallback, options) {
146147
const value = path.expression?.(b.id(tmp_id));
147148
const assignment = b.assignment('=', path.node, value);
148149
original_assignments.push(assignment);
149-
assignments.push(serialize_set_binding(assignment, context, () => assignment, options));
150+
assignments.push(
151+
serialize_set_binding(assignment, context, () => assignment, prefix, options)
152+
);
150153
}
151154

152155
if (assignments.every((assignment, i) => assignment === original_assignments[i])) {
@@ -411,6 +414,15 @@ export function serialize_set_binding(node, context, fallback, options) {
411414
)
412415
);
413416
}
417+
} else if (
418+
node.right.type === 'Literal' &&
419+
(node.operator === '+=' || node.operator === '-=')
420+
) {
421+
return b.update(
422+
node.operator === '+=' ? '++' : '--',
423+
/** @type {import('estree').Expression} */ (visit(node.left)),
424+
prefix
425+
);
414426
} else {
415427
return b.assignment(
416428
node.operator,

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const global_visitors = {
3232
next();
3333
},
3434
AssignmentExpression(node, context) {
35-
return serialize_set_binding(node, context, context.next);
35+
return serialize_set_binding(node, context, context.next, false);
3636
},
3737
UpdateExpression(node, context) {
3838
const { state, next, visit } = context;
@@ -98,7 +98,12 @@ export const global_visitors = {
9898
/** @type {import('estree').Pattern} */ (argument),
9999
b.literal(1)
100100
);
101-
const serialized_assignment = serialize_set_binding(assignment, context, () => assignment);
101+
const serialized_assignment = serialize_set_binding(
102+
assignment,
103+
context,
104+
() => assignment,
105+
node.prefix
106+
);
102107
const value = /** @type {import('estree').Expression} */ (visit(argument));
103108
if (serialized_assignment === assignment) {
104109
// No change to output -> nothing to transform -> we can keep the original update expression

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,9 @@ function serialize_inline_component(node, component_name, context) {
798798
const assignment = b.assignment('=', attribute.expression, b.id('$$value'));
799799
push_prop(
800800
b.set(attribute.name, [
801-
b.stmt(serialize_set_binding(assignment, context, () => context.visit(assignment)))
801+
b.stmt(
802+
serialize_set_binding(assignment, context, () => context.visit(assignment), false)
803+
)
802804
])
803805
);
804806
}
@@ -1026,7 +1028,7 @@ function serialize_bind_this(bind_this, context, node) {
10261028
const bind_this_id = /** @type {import('estree').Expression} */ (context.visit(bind_this));
10271029
const ids = Array.from(each_ids.values()).map((id) => b.id('$$value_' + id[0]));
10281030
const assignment = b.assignment('=', bind_this, b.id('$$value'));
1029-
const update = serialize_set_binding(assignment, context, () => context.visit(assignment));
1031+
const update = serialize_set_binding(assignment, context, () => context.visit(assignment), false);
10301032

10311033
for (const [binding, [, , expression]] of each_ids) {
10321034
// reset expressions to what they were before
@@ -2400,7 +2402,7 @@ export const template_visitors = {
24002402
if (assignment.left.type !== 'Identifier' && assignment.left.type !== 'MemberExpression') {
24012403
// serialize_set_binding turns other patterns into IIFEs and separates the assignments
24022404
// into separate expressions, at which point this is called again with an identifier or member expression
2403-
return serialize_set_binding(assignment, context, () => assignment);
2405+
return serialize_set_binding(assignment, context, () => assignment, false);
24042406
}
24052407
const left = object(assignment.left);
24062408
const value = get_assignment_value(assignment, context);
@@ -2438,7 +2440,7 @@ export const template_visitors = {
24382440
: b.id(node.index);
24392441
const item = each_node_meta.item;
24402442
const binding = /** @type {import('#compiler').Binding} */ (context.state.scope.get(item.name));
2441-
binding.expression = (id) => {
2443+
binding.expression = (/** @type {import("estree").Identifier} */ id) => {
24422444
const item_with_loc = with_loc(item, id);
24432445
return b.call('$.unwrap', item_with_loc);
24442446
};
@@ -2762,6 +2764,7 @@ export const template_visitors = {
27622764
assignment,
27632765
context,
27642766
() => /** @type {import('estree').Expression} */ (visit(assignment)),
2767+
false,
27652768
{
27662769
skip_proxy_and_freeze: true
27672770
}
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, [1, 1, 1, 1]);
6+
}
7+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
let x = $state(0);
3+
let o = $state({ x: 0 });
4+
5+
console.log(++x);
6+
console.log(x++);
7+
console.log(++o.x);
8+
console.log(o.x++);
9+
</script>

0 commit comments

Comments
 (0)