fix: ensure state update expressions are serialised correctly (#12109)

Fixes #12103
pull/12115/head
Dominic Gannaway 5 months ago committed by GitHub
parent 331925356e
commit eca1b7fd06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: ensure state update expressions are serialised correctly

@ -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;
@ -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])) {
@ -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,

@ -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;
@ -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

@ -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)
)
])
);
}
@ -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
@ -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);
@ -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);
};
@ -2762,6 +2764,7 @@ export const template_visitors = {
assignment,
context,
() => /** @type {import('estree').Expression} */ (visit(assignment)),
false,
{
skip_proxy_and_freeze: true
}

@ -0,0 +1,7 @@
import { test } from '../../test';
export default test({
test({ assert, logs }) {
assert.deepEqual(logs, [1, 1, 1, 1]);
}
});

@ -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>
Loading…
Cancel
Save