pull/15844/head
Rich Harris 3 months ago
parent 73fb7a1904
commit 80de2ca1ef

@ -77,10 +77,6 @@ import { UseDirective } from './visitors/UseDirective.js';
import { VariableDeclarator } from './visitors/VariableDeclarator.js'; import { VariableDeclarator } from './visitors/VariableDeclarator.js';
import is_reference from 'is-reference'; import is_reference from 'is-reference';
import { mark_subtree_dynamic } from './visitors/shared/fragment.js'; import { mark_subtree_dynamic } from './visitors/shared/fragment.js';
import { is_last_evaluated_expression } from './utils/awaits.js';
/** @type {Array<ExpressionMetadata | null>} */
const metadata_stack = [];
/** /**
* @type {Visitors} * @type {Visitors}
@ -132,23 +128,9 @@ const visitors = {
ignore_map.set(node, structuredClone(ignore_stack)); ignore_map.set(node, structuredClone(ignore_stack));
metadata_stack.push(state.expression);
const scope = state.scopes.get(node); const scope = state.scopes.get(node);
next(scope !== undefined && scope !== state.scope ? { ...state, scope } : state); next(scope !== undefined && scope !== state.scope ? { ...state, scope } : state);
metadata_stack.pop();
// if this node set `state.expression`, now that we've visited it we can determine
// which `await` expressions need to be wrapped in `$.save(...)`
if (state.expression && metadata_stack[metadata_stack.length - 1] === null) {
for (const { path, node } of state.expression.awaits) {
if (!is_last_evaluated_expression(path, node)) {
state.analysis.context_preserving_awaits.add(node);
}
}
}
if (ignores.length > 0) { if (ignores.length > 0) {
pop_ignore(); pop_ignore();
} }
@ -291,7 +273,6 @@ export function analyze_module(source, options) {
immutable: true, immutable: true,
tracing: false, tracing: false,
async_deriveds: new Set(), async_deriveds: new Set(),
context_preserving_awaits: new Set(),
comments, comments,
classes: new Map() classes: new Map()
}; };
@ -538,8 +519,7 @@ export function analyze_component(root, source, options) {
undefined_exports: new Map(), undefined_exports: new Map(),
snippet_renderers: new Map(), snippet_renderers: new Map(),
snippets: new Set(), snippets: new Set(),
async_deriveds: new Set(), async_deriveds: new Set()
context_preserving_awaits: new Set()
}; };
if (!runes) { if (!runes) {

@ -1,70 +0,0 @@
/** @import { Expression, Property, SpreadElement } from 'estree' */
/** @import { AST } from '#compiler' */
/**
*
* @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;
}
}

@ -7,20 +7,15 @@ import * as e from '../../../errors.js';
* @param {Context} context * @param {Context} context
*/ */
export function AwaitExpression(node, context) { export function AwaitExpression(node, context) {
const tla = context.state.ast_type === 'instance' && context.state.function_depth === 1; let suspend = context.state.ast_type === 'instance' && context.state.function_depth === 1;
if (tla) {
context.state.analysis.context_preserving_awaits.add(node);
}
let suspend = tla;
if (context.state.expression) { if (context.state.expression) {
context.state.expression.awaits.push({ node, path: context.path.slice() });
context.state.expression.has_await = true; context.state.expression.has_await = true;
suspend = true; suspend = true;
} }
// disallow top-level `await` or `await` in template expressions
// unless a) in runes mode and b) opted into `experimental.async`
if (suspend) { if (suspend) {
if (!context.state.options.experimental.async) { if (!context.state.options.experimental.async) {
e.experimental_async(node); e.experimental_async(node);

@ -702,7 +702,8 @@ export function client_module(analysis, options) {
scopes: analysis.module.scopes, scopes: analysis.module.scopes,
state_fields: new Map(), state_fields: new Map(),
transform: {}, transform: {},
in_constructor: false in_constructor: false,
is_instance: false
}; };
const module = /** @type {ESTree.Program} */ ( const module = /** @type {ESTree.Program} */ (

@ -21,6 +21,9 @@ export interface ClientTransformState extends TransformState {
*/ */
readonly in_constructor: boolean; readonly in_constructor: boolean;
/** `true` if we're transforming the contents of `<script>` */
readonly is_instance: boolean;
readonly transform: Record< readonly transform: Record<
string, string,
{ {
@ -41,7 +44,6 @@ export interface ComponentClientTransformState extends ClientTransformState {
readonly options: ValidatedCompileOptions; readonly options: ValidatedCompileOptions;
readonly hoisted: Array<Statement | ModuleDeclaration>; readonly hoisted: Array<Statement | ModuleDeclaration>;
readonly events: Set<string>; readonly events: Set<string>;
readonly is_instance: boolean;
readonly store_to_invalidate?: string; readonly store_to_invalidate?: string;
/** Stuff that happens before the render effect(s) */ /** Stuff that happens before the render effect(s) */

@ -1,4 +1,5 @@
/** @import { AwaitExpression, Expression } from 'estree' */ /** @import { AST } from '#compiler' */
/** @import { AwaitExpression, Expression, Property, SpreadElement } from 'estree' */
/** @import { Context } from '../types' */ /** @import { Context } from '../types' */
import { dev } from '../../../../state.js'; import { dev } from '../../../../state.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
@ -8,7 +9,9 @@ import * as b from '../../../../utils/builders.js';
* @param {Context} context * @param {Context} context
*/ */
export function AwaitExpression(node, context) { export function AwaitExpression(node, context) {
const save = context.state.analysis.context_preserving_awaits.has(node); const tla = context.state.is_instance && context.state.scope.function_depth === 1;
const save = tla || !is_last_evaluated_expression(context.path, node);
if (dev || save) { if (dev || save) {
return b.call( return b.call(
@ -24,3 +27,74 @@ export function AwaitExpression(node, context) {
return context.next(); return context.next();
} }
/**
*
* @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]);
console.log(parent.start, parent.type);
// @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:
console.log('bailing', parent.start, parent.type);
return false;
}
node = parent;
}
}

@ -67,8 +67,7 @@ export function create_expression_metadata() {
has_call: false, has_call: false,
has_member_expression: false, has_member_expression: false,
has_assignment: false, has_assignment: false,
has_await: false, has_await: false
awaits: []
}; };
} }

@ -47,9 +47,6 @@ export interface Analysis {
/** A set of deriveds that contain `await` expressions */ /** A set of deriveds that contain `await` expressions */
async_deriveds: Set<CallExpression>; async_deriveds: Set<CallExpression>;
/** A set of `await` expressions that should preserve context */
context_preserving_awaits: Set<AwaitExpression>;
} }
export interface ComponentAnalysis extends Analysis { export interface ComponentAnalysis extends Analysis {

@ -299,8 +299,6 @@ export interface ExpressionMetadata {
has_member_expression: boolean; has_member_expression: boolean;
/** True if the expression includes an assignment or an update */ /** True if the expression includes an assignment or an update */
has_assignment: boolean; has_assignment: boolean;
/** An array of await expressions, so we can figure out which ones need context preservation */
awaits: Array<{ node: AwaitExpression; path: AST.SvelteNode[] }>;
} }
export interface StateField { export interface StateField {

Loading…
Cancel
Save