Skip to content

Commit ed4a7b5

Browse files
committed
Merge branch 'adjust-boundary-error-message' into simplify-build-template
2 parents 30d3479 + 04445d4 commit ed4a7b5

File tree

27 files changed

+375
-337
lines changed

27 files changed

+375
-337
lines changed

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ export function analyze_module(source, options) {
278278
tracing: false,
279279
async_deriveds: new Set(),
280280
comments,
281-
classes: new Map()
281+
classes: new Map(),
282+
pickled_awaits: new Set()
282283
};
283284

284285
state.adjust({
@@ -304,7 +305,8 @@ export function analyze_module(source, options) {
304305
options: /** @type {ValidatedCompileOptions} */ (options),
305306
fragment: null,
306307
parent_element: null,
307-
reactive_statement: null
308+
reactive_statement: null,
309+
in_derived: false
308310
},
309311
visitors
310312
);
@@ -540,7 +542,8 @@ export function analyze_component(root, source, options) {
540542
source,
541543
snippet_renderers: new Map(),
542544
snippets: new Set(),
543-
async_deriveds: new Set()
545+
async_deriveds: new Set(),
546+
pickled_awaits: new Set()
544547
};
545548

546549
if (!runes) {
@@ -699,7 +702,8 @@ export function analyze_component(root, source, options) {
699702
expression: null,
700703
state_fields: new Map(),
701704
function_depth: scope.function_depth,
702-
reactive_statement: null
705+
reactive_statement: null,
706+
in_derived: false
703707
};
704708

705709
walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);
@@ -766,7 +770,8 @@ export function analyze_component(root, source, options) {
766770
component_slots: new Set(),
767771
expression: null,
768772
state_fields: new Map(),
769-
function_depth: scope.function_depth
773+
function_depth: scope.function_depth,
774+
in_derived: false
770775
};
771776

772777
walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);

packages/svelte/src/compiler/phases/2-analyze/types.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ export interface AnalysisState {
2727

2828
// legacy stuff
2929
reactive_statement: null | ReactiveStatement;
30+
31+
/**
32+
* True if we're directly inside a `$derived(...)` expression (but not `$derived.by(...)`)
33+
*/
34+
in_derived: boolean;
3035
}
3136

3237
export type Context<State extends AnalysisState = AnalysisState> = import('zimmerframe').Context<

packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
1-
/** @import { AwaitExpression } from 'estree' */
1+
/** @import { AwaitExpression, Expression, SpreadElement, Property } from 'estree' */
22
/** @import { Context } from '../types' */
3+
/** @import { AST } from '#compiler' */
34
import * as e from '../../../errors.js';
4-
import * as b from '#compiler/builders';
55

66
/**
77
* @param {AwaitExpression} node
88
* @param {Context} context
99
*/
1010
export function AwaitExpression(node, context) {
11-
let suspend = context.state.ast_type === 'instance' && context.state.function_depth === 1;
11+
const tla = context.state.ast_type === 'instance' && context.state.function_depth === 1;
12+
13+
// preserve context for
14+
// a) top-level await and
15+
// b) awaits that precede other expressions in template or `$derived(...)`
16+
if (
17+
tla ||
18+
(is_reactive_expression(context.path, context.state.in_derived) &&
19+
!is_last_evaluated_expression(context.path, node))
20+
) {
21+
context.state.analysis.pickled_awaits.add(node);
22+
}
23+
24+
let suspend = tla;
1225

1326
if (context.state.expression) {
1427
context.state.expression.has_await = true;
@@ -34,3 +47,101 @@ export function AwaitExpression(node, context) {
3447

3548
context.next();
3649
}
50+
51+
/**
52+
* @param {AST.SvelteNode[]} path
53+
* @param {boolean} in_derived
54+
*/
55+
export function is_reactive_expression(path, in_derived) {
56+
if (in_derived) {
57+
return true;
58+
}
59+
60+
let i = path.length;
61+
62+
while (i--) {
63+
const parent = path[i];
64+
65+
if (
66+
parent.type === 'ArrowFunctionExpression' ||
67+
parent.type === 'FunctionExpression' ||
68+
parent.type === 'FunctionDeclaration'
69+
) {
70+
return false;
71+
}
72+
73+
// @ts-expect-error we could probably use a neater/more robust mechanism
74+
if (parent.metadata) {
75+
return true;
76+
}
77+
}
78+
79+
return false;
80+
}
81+
82+
/**
83+
* @param {AST.SvelteNode[]} path
84+
* @param {Expression | SpreadElement | Property} node
85+
*/
86+
export function is_last_evaluated_expression(path, node) {
87+
let i = path.length;
88+
89+
while (i--) {
90+
const parent = /** @type {Expression | Property | SpreadElement} */ (path[i]);
91+
92+
// @ts-expect-error we could probably use a neater/more robust mechanism
93+
if (parent.metadata) {
94+
return true;
95+
}
96+
97+
switch (parent.type) {
98+
case 'ArrayExpression':
99+
if (node !== parent.elements.at(-1)) return false;
100+
break;
101+
102+
case 'AssignmentExpression':
103+
case 'BinaryExpression':
104+
case 'LogicalExpression':
105+
if (node === parent.left) return false;
106+
break;
107+
108+
case 'CallExpression':
109+
case 'NewExpression':
110+
if (node !== parent.arguments.at(-1)) return false;
111+
break;
112+
113+
case 'ConditionalExpression':
114+
if (node === parent.test) return false;
115+
break;
116+
117+
case 'MemberExpression':
118+
if (parent.computed && node === parent.object) return false;
119+
break;
120+
121+
case 'ObjectExpression':
122+
if (node !== parent.properties.at(-1)) return false;
123+
break;
124+
125+
case 'Property':
126+
if (node === parent.key) return false;
127+
break;
128+
129+
case 'SequenceExpression':
130+
if (node !== parent.expressions.at(-1)) return false;
131+
break;
132+
133+
case 'TaggedTemplateExpression':
134+
if (node !== parent.quasi.expressions.at(-1)) return false;
135+
break;
136+
137+
case 'TemplateLiteral':
138+
if (node !== parent.expressions.at(-1)) return false;
139+
break;
140+
141+
default:
142+
return false;
143+
}
144+
145+
node = parent;
146+
}
147+
}

packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ export function CallExpression(node, context) {
241241
context.next({
242242
...context.state,
243243
function_depth: context.state.function_depth + 1,
244+
in_derived: true,
244245
expression
245246
});
246247

packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,9 @@ export function ConstTag(node, context) {
3535
const declaration = node.declaration.declarations[0];
3636

3737
context.visit(declaration.id);
38-
context.visit(declaration.init, { ...context.state, expression: node.metadata.expression });
38+
context.visit(declaration.init, {
39+
...context.state,
40+
expression: node.metadata.expression,
41+
in_derived: true
42+
});
3943
}

packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ export function VariableDeclarator(node, context) {
4949
}
5050
}
5151

52+
if (rune === '$derived') {
53+
context.visit(node.id);
54+
context.visit(/** @type {Expression} */ (node.init), { ...context.state, in_derived: true });
55+
return;
56+
}
57+
5258
if (rune === '$props') {
5359
if (node.id.type !== 'ObjectPattern' && node.id.type !== 'Identifier') {
5460
e.props_invalid_identifier(node);

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ export function client_component(analysis, options) {
166166
state_fields: new Map(),
167167
transform: {},
168168
in_constructor: false,
169-
in_derived: false,
170169
instance_level_snippets: [],
171170
module_level_snippets: [],
172171

@@ -712,7 +711,6 @@ export function client_module(analysis, options) {
712711
state_fields: new Map(),
713712
transform: {},
714713
in_constructor: false,
715-
in_derived: false,
716714
is_instance: false
717715
};
718716

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import type {
66
Expression,
77
AssignmentExpression,
88
UpdateExpression,
9-
VariableDeclaration,
10-
Declaration
9+
VariableDeclaration
1110
} from 'estree';
1211
import type { AST, Namespace, ValidatedCompileOptions } from '#compiler';
1312
import type { TransformState } from '../types.js';
@@ -22,11 +21,6 @@ export interface ClientTransformState extends TransformState {
2221
*/
2322
readonly in_constructor: boolean;
2423

25-
/**
26-
* True if we're directly inside a `$derived(...)` expression (but not `$derived.by(...)`)
27-
*/
28-
readonly in_derived: boolean;
29-
3024
/** `true` if we're transforming the contents of `<script>` */
3125
readonly is_instance: boolean;
3226

Lines changed: 2 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { AwaitExpression, Expression, Property, SpreadElement } from 'estree' */
1+
/** @import { AwaitExpression, Expression } from 'estree' */
22
/** @import { Context } from '../types' */
33
import { dev, is_ignored } from '../../../../state.js';
44
import * as b from '../../../../utils/builders.js';
@@ -10,12 +10,7 @@ import * as b from '../../../../utils/builders.js';
1010
export function AwaitExpression(node, context) {
1111
const argument = /** @type {Expression} */ (context.visit(node.argument));
1212

13-
const tla = context.state.is_instance && context.state.scope.function_depth === 1;
14-
15-
// preserve context for
16-
// a) top-level await and
17-
// b) awaits that precede other expressions in template or `$derived(...)`
18-
if (tla || (is_reactive_expression(context) && !is_last_evaluated_expression(context, node))) {
13+
if (context.state.analysis.pickled_awaits.has(node)) {
1914
return b.call(b.await(b.call('$.save', argument)));
2015
}
2116

@@ -27,100 +22,3 @@ export function AwaitExpression(node, context) {
2722

2823
return argument === node.argument ? node : { ...node, argument };
2924
}
30-
31-
/**
32-
* @param {Context} context
33-
*/
34-
function is_reactive_expression(context) {
35-
if (context.state.in_derived) {
36-
return true;
37-
}
38-
39-
let i = context.path.length;
40-
41-
while (i--) {
42-
const parent = context.path[i];
43-
44-
if (
45-
parent.type === 'ArrowFunctionExpression' ||
46-
parent.type === 'FunctionExpression' ||
47-
parent.type === 'FunctionDeclaration'
48-
) {
49-
return false;
50-
}
51-
52-
// @ts-expect-error we could probably use a neater/more robust mechanism
53-
if (parent.metadata) {
54-
return true;
55-
}
56-
}
57-
58-
return false;
59-
}
60-
61-
/**
62-
* @param {Context} context
63-
* @param {Expression | SpreadElement | Property} node
64-
*/
65-
function is_last_evaluated_expression(context, node) {
66-
let i = context.path.length;
67-
68-
while (i--) {
69-
const parent = /** @type {Expression | Property | SpreadElement} */ (context.path[i]);
70-
71-
// @ts-expect-error we could probably use a neater/more robust mechanism
72-
if (parent.metadata) {
73-
return true;
74-
}
75-
76-
switch (parent.type) {
77-
case 'ArrayExpression':
78-
if (node !== parent.elements.at(-1)) return false;
79-
break;
80-
81-
case 'AssignmentExpression':
82-
case 'BinaryExpression':
83-
case 'LogicalExpression':
84-
if (node === parent.left) return false;
85-
break;
86-
87-
case 'CallExpression':
88-
case 'NewExpression':
89-
if (node !== parent.arguments.at(-1)) return false;
90-
break;
91-
92-
case 'ConditionalExpression':
93-
if (node === parent.test) return false;
94-
break;
95-
96-
case 'MemberExpression':
97-
if (parent.computed && node === parent.object) return false;
98-
break;
99-
100-
case 'ObjectExpression':
101-
if (node !== parent.properties.at(-1)) return false;
102-
break;
103-
104-
case 'Property':
105-
if (node === parent.key) return false;
106-
break;
107-
108-
case 'SequenceExpression':
109-
if (node !== parent.expressions.at(-1)) return false;
110-
break;
111-
112-
case 'TaggedTemplateExpression':
113-
if (node !== parent.quasi.expressions.at(-1)) return false;
114-
break;
115-
116-
case 'TemplateLiteral':
117-
if (node !== parent.expressions.at(-1)) return false;
118-
break;
119-
120-
default:
121-
return false;
122-
}
123-
124-
node = parent;
125-
}
126-
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ export function CallExpression(node, context) {
4444

4545
case '$derived':
4646
case '$derived.by': {
47-
let fn = /** @type {Expression} */ (
48-
context.visit(node.arguments[0], { ...context.state, in_derived: rune === '$derived' })
49-
);
47+
let fn = /** @type {Expression} */ (context.visit(node.arguments[0]));
5048

5149
return b.call('$.derived', rune === '$derived' ? b.thunk(fn) : fn);
5250
}

0 commit comments

Comments
 (0)