Skip to content

Commit 1ff9c0f

Browse files
authored
fix: support destructurings containing await (#9962)
Adds a traversion mechanism to found out if destructured expressions contain await Fixes #9686 Fixes #9312 Fixes #9982
1 parent d16f17c commit 1ff9c0f

File tree

5 files changed

+305
-10
lines changed

5 files changed

+305
-10
lines changed

.changeset/nasty-yaks-peel.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: support async/await in destructuring assignments

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

Lines changed: 118 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,103 @@ export function serialize_get_binding(node, state) {
115115
return node;
116116
}
117117

118+
/**
119+
* @param {import('estree').Expression | import('estree').Pattern} expression
120+
* @returns {boolean}
121+
*/
122+
function is_expression_async(expression) {
123+
switch (expression.type) {
124+
case 'AwaitExpression': {
125+
return true;
126+
}
127+
case 'ArrayPattern': {
128+
return expression.elements.some((element) => element && is_expression_async(element));
129+
}
130+
case 'ArrayExpression': {
131+
return expression.elements.some((element) => {
132+
if (!element) {
133+
return false;
134+
} else if (element.type === 'SpreadElement') {
135+
return is_expression_async(element.argument);
136+
} else {
137+
return is_expression_async(element);
138+
}
139+
});
140+
}
141+
case 'AssignmentPattern':
142+
case 'AssignmentExpression':
143+
case 'BinaryExpression':
144+
case 'LogicalExpression': {
145+
return is_expression_async(expression.left) || is_expression_async(expression.right);
146+
}
147+
case 'CallExpression':
148+
case 'NewExpression': {
149+
return (
150+
(expression.callee.type !== 'Super' && is_expression_async(expression.callee)) ||
151+
expression.arguments.some((element) => {
152+
if (element.type === 'SpreadElement') {
153+
return is_expression_async(element.argument);
154+
} else {
155+
return is_expression_async(element);
156+
}
157+
})
158+
);
159+
}
160+
case 'ChainExpression': {
161+
return is_expression_async(expression.expression);
162+
}
163+
case 'ConditionalExpression': {
164+
return (
165+
is_expression_async(expression.test) ||
166+
is_expression_async(expression.alternate) ||
167+
is_expression_async(expression.consequent)
168+
);
169+
}
170+
case 'ImportExpression': {
171+
return is_expression_async(expression.source);
172+
}
173+
case 'MemberExpression': {
174+
return (
175+
(expression.object.type !== 'Super' && is_expression_async(expression.object)) ||
176+
(expression.property.type !== 'PrivateIdentifier' &&
177+
is_expression_async(expression.property))
178+
);
179+
}
180+
case 'ObjectPattern':
181+
case 'ObjectExpression': {
182+
return expression.properties.some((property) => {
183+
if (property.type === 'SpreadElement') {
184+
return is_expression_async(property.argument);
185+
} else if (property.type === 'Property') {
186+
return (
187+
(property.key.type !== 'PrivateIdentifier' && is_expression_async(property.key)) ||
188+
is_expression_async(property.value)
189+
);
190+
}
191+
});
192+
}
193+
case 'RestElement': {
194+
return is_expression_async(expression.argument);
195+
}
196+
case 'SequenceExpression':
197+
case 'TemplateLiteral': {
198+
return expression.expressions.some((subexpression) => is_expression_async(subexpression));
199+
}
200+
case 'TaggedTemplateExpression': {
201+
return is_expression_async(expression.tag) || is_expression_async(expression.quasi);
202+
}
203+
case 'UnaryExpression':
204+
case 'UpdateExpression': {
205+
return is_expression_async(expression.argument);
206+
}
207+
case 'YieldExpression': {
208+
return expression.argument ? is_expression_async(expression.argument) : false;
209+
}
210+
default:
211+
return false;
212+
}
213+
}
214+
118215
/**
119216
* @template {import('./types').ClientTransformState} State
120217
* @param {import('estree').AssignmentExpression} node
@@ -153,17 +250,28 @@ export function serialize_set_binding(node, context, fallback) {
153250
return fallback();
154251
}
155252

156-
return b.call(
157-
b.thunk(
158-
b.block([
159-
b.const(tmp_id, /** @type {import('estree').Expression} */ (visit(node.right))),
160-
b.stmt(b.sequence(assignments)),
161-
// return because it could be used in a nested expression where the value is needed.
162-
// example: { foo: ({ bar } = { bar: 1 })}
163-
b.return(b.id(tmp_id))
164-
])
165-
)
253+
const rhs_expression = /** @type {import('estree').Expression} */ (visit(node.right));
254+
255+
const iife_is_async =
256+
is_expression_async(rhs_expression) ||
257+
assignments.some((assignment) => is_expression_async(assignment));
258+
259+
const iife = b.arrow(
260+
[],
261+
b.block([
262+
b.const(tmp_id, rhs_expression),
263+
b.stmt(b.sequence(assignments)),
264+
// return because it could be used in a nested expression where the value is needed.
265+
// example: { foo: ({ bar } = { bar: 1 })}
266+
b.return(b.id(tmp_id))
267+
])
166268
);
269+
270+
if (iife_is_async) {
271+
return b.await(b.call(b.async(iife)));
272+
} else {
273+
return b.call(iife);
274+
}
167275
}
168276

169277
if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {

packages/svelte/src/compiler/utils/builders.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ export function assignment(operator, left, right) {
4444
return { type: 'AssignmentExpression', operator, left, right };
4545
}
4646

47+
/**
48+
* @template T
49+
* @param {T & import('estree').BaseFunction} func
50+
* @returns {T & import('estree').BaseFunction}
51+
*/
52+
export function async(func) {
53+
return { ...func, async: true };
54+
}
55+
56+
/**
57+
* @param {import('estree').Expression} argument
58+
* @returns {import('estree').AwaitExpression}
59+
*/
60+
export function await_builder(argument) {
61+
return { type: 'AwaitExpression', argument };
62+
}
63+
4764
/**
4865
* @param {import('estree').BinaryOperator} operator
4966
* @param {import('estree').Expression} left
@@ -573,6 +590,7 @@ export function throw_error(str) {
573590
}
574591

575592
export {
593+
await_builder as await,
576594
new_builder as new,
577595
let_builder as let,
578596
const_builder as const,
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: `
5+
<button>Update me!</button>
6+
<p>0</p>
7+
<p>0</p>
8+
<p>0</p>
9+
<p>0</p>
10+
<p>0</p>
11+
<p>0</p>
12+
<p>0</p>
13+
<p>0</p>
14+
<p>0</p>
15+
<p>0</p>
16+
<p>0</p>
17+
<p>0</p>
18+
<p>0</p>
19+
<p>0</p>
20+
<p>0</p>
21+
<p>0</p>
22+
<p>0</p>
23+
<p>0</p>
24+
<p>0</p>
25+
<p>0</p>
26+
<p>0</p>
27+
<p>0</p>
28+
<p>0</p>
29+
<p>0</p>
30+
<p>0</p>
31+
<p>0</p>
32+
`,
33+
34+
async test({ assert, target, window }) {
35+
const btn = target.querySelector('button');
36+
const clickEvent = new window.Event('click', { bubbles: true });
37+
await btn?.dispatchEvent(clickEvent);
38+
for (let i = 1; i <= 42; i += 1) {
39+
await Promise.resolve();
40+
}
41+
42+
assert.htmlEqual(
43+
target.innerHTML,
44+
`
45+
<button>Update me!</button>
46+
<p>1</p>
47+
<p>2</p>
48+
<p>3</p>
49+
<p>4</p>
50+
<p>5</p>
51+
<p>6</p>
52+
<p>7</p>
53+
<p>8</p>
54+
<p>9</p>
55+
<p>10</p>
56+
<p>11</p>
57+
<p>12</p>
58+
<p>13</p>
59+
<p>14</p>
60+
<p>15</p>
61+
<p>16</p>
62+
<p>17</p>
63+
<p>18</p>
64+
<p>19</p>
65+
<p>20</p>
66+
<p>21</p>
67+
<p>22</p>
68+
<p>23</p>
69+
<p>24</p>
70+
<p>25</p>
71+
<p>26</p>
72+
`
73+
);
74+
}
75+
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<script>
2+
let a = $state(0);
3+
let b = $state(0);
4+
let c = $state(0);
5+
let d = $state(0);
6+
let e = $state(0);
7+
let f = $state(0);
8+
let g = $state(0);
9+
let h = $state(0);
10+
let i = $state(0);
11+
let j = $state(0);
12+
let k = $state(0);
13+
let l = $state(0);
14+
let m = $state(0);
15+
let n = $state(0);
16+
let o = $state(0);
17+
let p = $state(0);
18+
let q = $state(0);
19+
let r = $state(0);
20+
let s = $state(0);
21+
let t = $state(0);
22+
let u = $state(0);
23+
let v = $state(0);
24+
let w = $state(0);
25+
let x = $state(0);
26+
let y = $state(0);
27+
let z = $state(0);
28+
29+
const get_vwx = () => {
30+
return Promise.resolve({ v: 22, rest: [23, 24] });
31+
}
32+
33+
const get_y = () => {
34+
return Promise.resolve([24, 25]);
35+
}
36+
37+
const update = async () => {
38+
[a, b] = [1, await Promise.resolve(2)];
39+
({ c = await Promise.resolve(3), d } = { d: 4 });
40+
[e] = [await Promise.resolve(2) + await Promise.resolve(3)];
41+
({ f = false || await Promise.resolve(6) } = {});
42+
let func = Promise.resolve(() => 7);
43+
[g = (await func)()] = [];
44+
let mult = (a, b) => (a * b);
45+
({ h } = { h: mult(2, await Promise.resolve(4))});
46+
[i] = [new Date(await Promise.resolve(9)).getTime()];
47+
[j = "19" ? 10 : await Promise.resolve(11)] = [];
48+
let obj = ({ [await Promise.resolve("prop")]: k } = { prop: 11 });
49+
[l = obj[await Promise.resolve("prop")] + 1] = [];
50+
[m] = [`${1}${await Promise.resolve("3")}`];
51+
[n] = [-(await Promise.resolve(-14))];
52+
[o] = [(console.log(15), await Promise.resolve(15))];
53+
({ anotherprop: p = await Promise.resolve(16) } = obj);
54+
let val1, val2;
55+
({ val1 = (async function (x) { return await x; })(Promise.resolve(18)), r = await val1 }
56+
= ({ val2 = (async (x) => await x)(Promise.resolve(17)), q = await val2 } = []));
57+
({ u = 21 } = ({ t = await Promise.resolve(20) } = ([s] = [await Promise.resolve(19)])));
58+
({ v, rest: [w] } = await get_vwx());
59+
[x, y, ...{ z = 26 }] = await get_y();
60+
}
61+
</script>
62+
63+
<button on:click={update}>Update me!</button>
64+
<p>{a}</p>
65+
<p>{b}</p>
66+
<p>{c}</p>
67+
<p>{d}</p>
68+
<p>{e}</p>
69+
<p>{f}</p>
70+
<p>{g}</p>
71+
<p>{h}</p>
72+
<p>{i}</p>
73+
<p>{j}</p>
74+
<p>{k}</p>
75+
<p>{l}</p>
76+
<p>{m}</p>
77+
<p>{n}</p>
78+
<p>{o}</p>
79+
<p>{p}</p>
80+
<p>{q}</p>
81+
<p>{r}</p>
82+
<p>{s}</p>
83+
<p>{t}</p>
84+
<p>{u}</p>
85+
<p>{v}</p>
86+
<p>{w}</p>
87+
<p>{x}</p>
88+
<p>{y}</p>
89+
<p>{z}</p>

0 commit comments

Comments
 (0)