Skip to content

Commit aac929d

Browse files
authored
fix: leave update expressions untransformed unless a transformer is provided (#14507)
* fix: leave update expressions untransformed unless a transformer is provided * fix more cases
1 parent 94694a5 commit aac929d

File tree

9 files changed

+75
-34
lines changed

9 files changed

+75
-34
lines changed

.changeset/neat-news-dance.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: leave update expressions untransformed unless a transformer is provided

packages/svelte/src/compiler/phases/3-transform/client/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export interface ClientTransformState extends TransformState {
3232
/** turn `foo = bar` into e.g. `$.set(foo, bar)` */
3333
assign?: (node: Identifier, value: Expression) => Expression;
3434
/** turn `foo.bar = baz` into e.g. `$.mutate(foo, $.get(foo).bar = baz);` */
35-
mutate?: (node: Identifier, mutation: AssignmentExpression) => Expression;
35+
mutate?: (node: Identifier, mutation: AssignmentExpression | UpdateExpression) => Expression;
3636
/** turn `foo++` into e.g. `$.update(foo)` */
3737
update?: (node: UpdateExpression) => Expression;
3838
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,10 @@ export function EachBlock(node, context) {
214214

215215
return b.sequence([b.assignment('=', left, value), ...sequence]);
216216
},
217-
mutate: (_, mutation) => b.sequence([mutation, ...sequence])
217+
mutate: (_, mutation) => {
218+
uses_index = true;
219+
return b.sequence([mutation, ...sequence]);
220+
}
218221
};
219222

220223
delete key_state.transform[node.context.name];

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,15 @@ export function Program(_, context) {
7777
return b.call(
7878
'$.store_mutate',
7979
get_store(),
80-
b.assignment(
81-
mutation.operator,
82-
/** @type {MemberExpression} */ (
83-
replace(/** @type {MemberExpression} */ (mutation.left))
84-
),
85-
mutation.right
86-
),
80+
mutation.type === 'AssignmentExpression'
81+
? b.assignment(
82+
mutation.operator,
83+
/** @type {MemberExpression} */ (
84+
replace(/** @type {MemberExpression} */ (mutation.left))
85+
),
86+
mutation.right
87+
)
88+
: b.update(mutation.operator, replace(mutation.argument), mutation.prefix),
8789
untracked
8890
);
8991
},

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

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { Expression, Node, Pattern, Statement, UpdateExpression } from 'estree' */
1+
/** @import { AssignmentExpression, Expression, UpdateExpression } from 'estree' */
22
/** @import { Context } from '../types' */
33
import { is_ignored } from '../../../../state.js';
44
import { object } from '../../../../utils/ast.js';
@@ -34,34 +34,22 @@ export function UpdateExpression(node, context) {
3434
}
3535

3636
const left = object(argument);
37-
if (left === null) return context.next();
37+
const transformers = left && context.state.transform[left.name];
3838

39-
if (left === argument) {
40-
const transform = context.state.transform;
41-
const update = transform[left.name]?.update;
42-
43-
if (update && Object.hasOwn(transform, left.name)) {
44-
return update(node);
45-
}
39+
if (left === argument && transformers?.update) {
40+
// we don't need to worry about ownership_invalid_mutation here, because
41+
// we're not mutating but reassigning
42+
return transformers.update(node);
4643
}
4744

48-
const assignment = /** @type {Expression} */ (
49-
context.visit(
50-
b.assignment(
51-
node.operator === '++' ? '+=' : '-=',
52-
/** @type {Pattern} */ (argument),
53-
b.literal(1)
54-
)
55-
)
56-
);
45+
let update = /** @type {Expression} */ (context.next());
5746

58-
const parent = /** @type {Node} */ (context.path.at(-1));
59-
const is_standalone = parent.type === 'ExpressionStatement'; // TODO and possibly others, but not e.g. the `test` of a WhileStatement
60-
61-
const update =
62-
node.prefix || is_standalone
63-
? assignment
64-
: b.binary(node.operator === '++' ? '-' : '+', assignment, b.literal(1));
47+
if (left && transformers?.mutate) {
48+
update = transformers.mutate(
49+
left,
50+
/** @type {AssignmentExpression | UpdateExpression} */ (update)
51+
);
52+
}
6553

6654
return is_ignored(node, 'ownership_invalid_mutation')
6755
? b.call('$.skip_ownership_validation', b.thunk(update))
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
html: '<button>mutate</button><button>reassign</button><p>0</p>',
6+
test({ assert, target }) {
7+
const [btn1, btn2] = target.querySelectorAll('button');
8+
9+
flushSync(() => btn1.click());
10+
assert.htmlEqual(target.innerHTML, '<button>mutate</button><button>reassign</button><p>1</p>');
11+
12+
flushSync(() => btn2.click());
13+
assert.htmlEqual(target.innerHTML, '<button>mutate</button><button>reassign</button><p>0</p>');
14+
}
15+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
let object = $state({ n: 0n });
3+
4+
function reassign() {
5+
object = { n: 0n };
6+
}
7+
8+
function mutate() {
9+
return object.n++;
10+
}
11+
</script>
12+
13+
<button onclick={mutate}>mutate</button>
14+
<button onclick={reassign}>reassign</button>
15+
16+
<p>{object.n}</p>
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, [0n, 1n, 2n, 3n, 4n, 5n]);
6+
}
7+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
for (let i = 0n; i <= 5n; i++) {
3+
console.log(i);
4+
}
5+
</script>

0 commit comments

Comments
 (0)