From b63a56a67ed62bbcf212edea987b7351ed878bf8 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Mon, 15 Sep 2025 17:08:40 -0600 Subject: [PATCH] compiler work --- .../src/compiler/phases/2-analyze/index.js | 15 ++- .../src/compiler/phases/2-analyze/types.d.ts | 5 + .../2-analyze/visitors/AwaitExpression.js | 117 +++++++++++++++++- .../2-analyze/visitors/CallExpression.js | 1 + .../phases/2-analyze/visitors/ConstTag.js | 6 +- .../2-analyze/visitors/VariableDeclarator.js | 6 + .../3-transform/client/transform-client.js | 2 - .../phases/3-transform/client/types.d.ts | 8 +- .../client/visitors/AwaitExpression.js | 106 +--------------- .../client/visitors/CallExpression.js | 4 +- .../3-transform/client/visitors/ConstTag.js | 9 +- .../client/visitors/VariableDeclaration.js | 14 +-- .../3-transform/server/transform-server.js | 2 + .../server/visitors/AwaitExpression.js | 17 +++ .../svelte/src/compiler/phases/types.d.ts | 11 +- .../svelte/src/internal/server/context.js | 30 +++++ packages/svelte/src/internal/server/index.js | 2 +- 17 files changed, 209 insertions(+), 146 deletions(-) create mode 100644 packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitExpression.js diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index de27c4623b..47fe37c44d 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -278,7 +278,8 @@ export function analyze_module(source, options) { tracing: false, async_deriveds: new Set(), comments, - classes: new Map() + classes: new Map(), + pickled_awaits: new Set() }; state.adjust({ @@ -304,7 +305,8 @@ export function analyze_module(source, options) { options: /** @type {ValidatedCompileOptions} */ (options), fragment: null, parent_element: null, - reactive_statement: null + reactive_statement: null, + in_derived: false }, visitors ); @@ -540,7 +542,8 @@ export function analyze_component(root, source, options) { source, snippet_renderers: new Map(), snippets: new Set(), - async_deriveds: new Set() + async_deriveds: new Set(), + pickled_awaits: new Set() }; if (!runes) { @@ -699,7 +702,8 @@ export function analyze_component(root, source, options) { expression: null, state_fields: new Map(), function_depth: scope.function_depth, - reactive_statement: null + reactive_statement: null, + in_derived: false }; walk(/** @type {AST.SvelteNode} */ (ast), state, visitors); @@ -766,7 +770,8 @@ export function analyze_component(root, source, options) { component_slots: new Set(), expression: null, state_fields: new Map(), - function_depth: scope.function_depth + function_depth: scope.function_depth, + in_derived: false }; walk(/** @type {AST.SvelteNode} */ (ast), state, visitors); diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts index 2d99a2e155..ae9c5911f6 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts +++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts @@ -27,6 +27,11 @@ export interface AnalysisState { // legacy stuff reactive_statement: null | ReactiveStatement; + + /** + * True if we're directly inside a `$derived(...)` expression (but not `$derived.by(...)`) + */ + in_derived: boolean; } export type Context = import('zimmerframe').Context< diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js index 6b8e3a37d9..9018623570 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js @@ -1,14 +1,27 @@ -/** @import { AwaitExpression } from 'estree' */ +/** @import { AwaitExpression, Expression, SpreadElement, Property } from 'estree' */ /** @import { Context } from '../types' */ +/** @import { AST } from '#compiler' */ import * as e from '../../../errors.js'; -import * as b from '#compiler/builders'; /** * @param {AwaitExpression} node * @param {Context} context */ export function AwaitExpression(node, context) { - let suspend = context.state.ast_type === 'instance' && context.state.function_depth === 1; + const tla = context.state.ast_type === 'instance' && context.state.function_depth === 1; + + // preserve context for + // a) top-level await and + // b) awaits that precede other expressions in template or `$derived(...)` + if ( + tla || + (is_reactive_expression(context.path, context.state.in_derived) && + !is_last_evaluated_expression(context.path, node)) + ) { + context.state.analysis.pickled_awaits.add(node); + } + + let suspend = tla; if (context.state.expression) { context.state.expression.has_await = true; @@ -34,3 +47,101 @@ export function AwaitExpression(node, context) { context.next(); } + +/** + * @param {AST.SvelteNode[]} path + * @param {boolean} in_derived + */ +export function is_reactive_expression(path, in_derived) { + if (in_derived) { + return true; + } + + let i = path.length; + + while (i--) { + const parent = path[i]; + + if ( + parent.type === 'ArrowFunctionExpression' || + parent.type === 'FunctionExpression' || + parent.type === 'FunctionDeclaration' + ) { + return false; + } + + // @ts-expect-error we could probably use a neater/more robust mechanism + if (parent.metadata) { + return true; + } + } + + return false; +} + +/** + * @param {AST.SvelteNode[]} path + * @param {Expression | SpreadElement | Property} node + */ +export function is_last_evaluated_expression(path, node) { + let i = path.length; + + while (i--) { + const parent = /** @type {Expression | Property | SpreadElement} */ (path[i]); + + // @ts-expect-error we could probably use a neater/more robust mechanism + if (parent.metadata) { + return true; + } + + switch (parent.type) { + case 'ArrayExpression': + if (node !== parent.elements.at(-1)) return false; + break; + + case 'AssignmentExpression': + case 'BinaryExpression': + case 'LogicalExpression': + if (node === parent.left) return false; + break; + + case 'CallExpression': + case 'NewExpression': + if (node !== parent.arguments.at(-1)) return false; + break; + + case 'ConditionalExpression': + if (node === parent.test) return false; + break; + + case 'MemberExpression': + if (parent.computed && node === parent.object) return false; + break; + + case 'ObjectExpression': + if (node !== parent.properties.at(-1)) return false; + break; + + case 'Property': + if (node === parent.key) return false; + break; + + case 'SequenceExpression': + if (node !== parent.expressions.at(-1)) return false; + break; + + case 'TaggedTemplateExpression': + if (node !== parent.quasi.expressions.at(-1)) return false; + break; + + case 'TemplateLiteral': + if (node !== parent.expressions.at(-1)) return false; + break; + + default: + return false; + } + + node = parent; + } +} diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 9b6337b9ed..53a89125a2 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -241,6 +241,7 @@ export function CallExpression(node, context) { context.next({ ...context.state, function_depth: context.state.function_depth + 1, + in_derived: true, expression }); diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js index d5f5f7b2e0..5849d828a3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js @@ -35,5 +35,9 @@ export function ConstTag(node, context) { const declaration = node.declaration.declarations[0]; context.visit(declaration.id); - context.visit(declaration.init, { ...context.state, expression: node.metadata.expression }); + context.visit(declaration.init, { + ...context.state, + expression: node.metadata.expression, + in_derived: true + }); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js index 89320f3962..f56a665de8 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js @@ -49,6 +49,12 @@ export function VariableDeclarator(node, context) { } } + if (rune === '$derived') { + context.visit(node.id); + context.visit(/** @type {Expression} */ (node.init), { ...context.state, in_derived: true }); + return; + } + if (rune === '$props') { if (node.id.type !== 'ObjectPattern' && node.id.type !== 'Identifier') { e.props_invalid_identifier(node); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 706d2b4e10..2629379f63 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -166,7 +166,6 @@ export function client_component(analysis, options) { state_fields: new Map(), transform: {}, in_constructor: false, - in_derived: false, instance_level_snippets: [], module_level_snippets: [], @@ -712,7 +711,6 @@ export function client_module(analysis, options) { state_fields: new Map(), transform: {}, in_constructor: false, - in_derived: false, is_instance: false }; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts index 59c024dfb7..932d353671 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts @@ -6,8 +6,7 @@ import type { Expression, AssignmentExpression, UpdateExpression, - VariableDeclaration, - Declaration + VariableDeclaration } from 'estree'; import type { AST, Namespace, ValidatedCompileOptions } from '#compiler'; import type { TransformState } from '../types.js'; @@ -22,11 +21,6 @@ export interface ClientTransformState extends TransformState { */ readonly in_constructor: boolean; - /** - * True if we're directly inside a `$derived(...)` expression (but not `$derived.by(...)`) - */ - readonly in_derived: boolean; - /** `true` if we're transforming the contents of `