Skip to content

Commit

Permalink
fix: ensure state update expressions are serialised correctly (svelte…
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm authored and FoHoOV committed Jun 27, 2024
1 parent 111ce2b commit 4cb978d
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/gorgeous-boxes-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

fix: ensure state update expressions are serialised correctly
16 changes: 14 additions & 2 deletions packages/svelte/src/compiler/phases/3-transform/client/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,11 @@ export function serialize_get_binding(node, state) {
* @param {import('estree').AssignmentExpression} node
* @param {import('zimmerframe').Context<import('#compiler').SvelteNode, State>} context
* @param {() => any} fallback
* @param {boolean} prefix
* @param {{skip_proxy_and_freeze?: boolean}} [options]
* @returns {import('estree').Expression}
*/
export function serialize_set_binding(node, context, fallback, options) {
export function serialize_set_binding(node, context, fallback, prefix, options) {
const { state, visit } = context;

const assignee = node.left;
Expand All @@ -146,7 +147,9 @@ export function serialize_set_binding(node, context, fallback, options) {
const value = path.expression?.(b.id(tmp_id));
const assignment = b.assignment('=', path.node, value);
original_assignments.push(assignment);
assignments.push(serialize_set_binding(assignment, context, () => assignment, options));
assignments.push(
serialize_set_binding(assignment, context, () => assignment, prefix, options)
);
}

if (assignments.every((assignment, i) => assignment === original_assignments[i])) {
Expand Down Expand Up @@ -411,6 +414,15 @@ export function serialize_set_binding(node, context, fallback, options) {
)
);
}
} else if (
node.right.type === 'Literal' &&
(node.operator === '+=' || node.operator === '-=')
) {
return b.update(
node.operator === '+=' ? '++' : '--',
/** @type {import('estree').Expression} */ (visit(node.left)),
prefix
);
} else {
return b.assignment(
node.operator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const global_visitors = {
next();
},
AssignmentExpression(node, context) {
return serialize_set_binding(node, context, context.next);
return serialize_set_binding(node, context, context.next, false);
},
UpdateExpression(node, context) {
const { state, next, visit } = context;
Expand Down Expand Up @@ -98,7 +98,12 @@ export const global_visitors = {
/** @type {import('estree').Pattern} */ (argument),
b.literal(1)
);
const serialized_assignment = serialize_set_binding(assignment, context, () => assignment);
const serialized_assignment = serialize_set_binding(
assignment,
context,
() => assignment,
node.prefix
);
const value = /** @type {import('estree').Expression} */ (visit(argument));
if (serialized_assignment === assignment) {
// No change to output -> nothing to transform -> we can keep the original update expression
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,9 @@ function serialize_inline_component(node, component_name, context) {
const assignment = b.assignment('=', attribute.expression, b.id('$$value'));
push_prop(
b.set(attribute.name, [
b.stmt(serialize_set_binding(assignment, context, () => context.visit(assignment)))
b.stmt(
serialize_set_binding(assignment, context, () => context.visit(assignment), false)
)
])
);
}
Expand Down Expand Up @@ -1026,7 +1028,7 @@ function serialize_bind_this(bind_this, context, node) {
const bind_this_id = /** @type {import('estree').Expression} */ (context.visit(bind_this));
const ids = Array.from(each_ids.values()).map((id) => b.id('$$value_' + id[0]));
const assignment = b.assignment('=', bind_this, b.id('$$value'));
const update = serialize_set_binding(assignment, context, () => context.visit(assignment));
const update = serialize_set_binding(assignment, context, () => context.visit(assignment), false);

for (const [binding, [, , expression]] of each_ids) {
// reset expressions to what they were before
Expand Down Expand Up @@ -2400,7 +2402,7 @@ export const template_visitors = {
if (assignment.left.type !== 'Identifier' && assignment.left.type !== 'MemberExpression') {
// serialize_set_binding turns other patterns into IIFEs and separates the assignments
// into separate expressions, at which point this is called again with an identifier or member expression
return serialize_set_binding(assignment, context, () => assignment);
return serialize_set_binding(assignment, context, () => assignment, false);
}
const left = object(assignment.left);
const value = get_assignment_value(assignment, context);
Expand Down Expand Up @@ -2438,7 +2440,7 @@ export const template_visitors = {
: b.id(node.index);
const item = each_node_meta.item;
const binding = /** @type {import('#compiler').Binding} */ (context.state.scope.get(item.name));
binding.expression = (id) => {
binding.expression = (/** @type {import("estree").Identifier} */ id) => {
const item_with_loc = with_loc(item, id);
return b.call('$.unwrap', item_with_loc);
};
Expand Down Expand Up @@ -2762,6 +2764,7 @@ export const template_visitors = {
assignment,
context,
() => /** @type {import('estree').Expression} */ (visit(assignment)),
false,
{
skip_proxy_and_freeze: true
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { test } from '../../test';

export default test({
test({ assert, logs }) {
assert.deepEqual(logs, [1, 1, 1, 1]);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
let x = $state(0);
let o = $state({ x: 0 });
console.log(++x);
console.log(x++);
console.log(++o.x);
console.log(o.x++);
</script>

0 comments on commit 4cb978d

Please sign in to comment.