|
|
|
@ -1,12 +1,18 @@
|
|
|
|
|
/** @import { CallExpression, Expression, Identifier, Literal, VariableDeclaration, VariableDeclarator } from 'estree' */
|
|
|
|
|
/** @import { AwaitExpression, CallExpression, Expression, Identifier, Literal, Node, Program, VariableDeclaration, VariableDeclarator } from 'estree' */
|
|
|
|
|
/** @import { Binding } from '#compiler' */
|
|
|
|
|
/** @import { ComponentContext } from '../types' */
|
|
|
|
|
/** @import { ComponentContext, ParallelizedChunk } from '../types' */
|
|
|
|
|
import { dev, is_ignored, locate_node } from '../../../../state.js';
|
|
|
|
|
import { extract_paths } from '../../../../utils/ast.js';
|
|
|
|
|
import { extract_paths, is_expression_async } from '../../../../utils/ast.js';
|
|
|
|
|
import * as b from '#compiler/builders';
|
|
|
|
|
import * as assert from '../../../../utils/assert.js';
|
|
|
|
|
import { get_rune } from '../../../scope.js';
|
|
|
|
|
import { get_prop_source, is_prop_source, is_state_source, should_proxy } from '../utils.js';
|
|
|
|
|
import {
|
|
|
|
|
can_be_parallelized,
|
|
|
|
|
get_prop_source,
|
|
|
|
|
is_prop_source,
|
|
|
|
|
is_state_source,
|
|
|
|
|
should_proxy
|
|
|
|
|
} from '../utils.js';
|
|
|
|
|
import { is_hoisted_function } from '../../utils.js';
|
|
|
|
|
import { get_value } from './shared/declarations.js';
|
|
|
|
|
|
|
|
|
@ -17,12 +23,14 @@ import { get_value } from './shared/declarations.js';
|
|
|
|
|
export function VariableDeclaration(node, context) {
|
|
|
|
|
/** @type {VariableDeclarator[]} */
|
|
|
|
|
const declarations = [];
|
|
|
|
|
const parent = /** @type {Node} */ (context.path.at(-1));
|
|
|
|
|
const position = /** @type {Program} */ (parent).body?.indexOf?.(node);
|
|
|
|
|
|
|
|
|
|
if (context.state.analysis.runes) {
|
|
|
|
|
for (const declarator of node.declarations) {
|
|
|
|
|
const init = /** @type {Expression} */ (declarator.init);
|
|
|
|
|
const rune = get_rune(init, context.state.scope);
|
|
|
|
|
|
|
|
|
|
const bindings = context.state.scope.get_bindings(declarator);
|
|
|
|
|
if (
|
|
|
|
|
!rune ||
|
|
|
|
|
rune === '$effect.tracking' ||
|
|
|
|
@ -39,6 +47,51 @@ export function VariableDeclaration(node, context) {
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
init?.type === 'AwaitExpression' &&
|
|
|
|
|
context.state.analysis.instance?.scope === context.state.scope &&
|
|
|
|
|
!is_expression_async(init.argument)
|
|
|
|
|
) {
|
|
|
|
|
const current_chunk = context.state.current_parallelized_chunk;
|
|
|
|
|
const parallelize = can_be_parallelized(
|
|
|
|
|
init.argument,
|
|
|
|
|
context.state.scope,
|
|
|
|
|
context.state.analysis,
|
|
|
|
|
[...(current_chunk?.bindings ?? []), ...bindings]
|
|
|
|
|
);
|
|
|
|
|
if (parallelize) {
|
|
|
|
|
const { id, init: visited_init } = /** @type {VariableDeclarator} */ (
|
|
|
|
|
context.visit({
|
|
|
|
|
...declarator,
|
|
|
|
|
init: init.argument
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
const _declarator = {
|
|
|
|
|
id,
|
|
|
|
|
init: /** @type {Expression} */ (visited_init)
|
|
|
|
|
};
|
|
|
|
|
if (
|
|
|
|
|
current_chunk &&
|
|
|
|
|
(current_chunk.kind === node.kind || current_chunk.kind === null)
|
|
|
|
|
) {
|
|
|
|
|
current_chunk.declarators.push(_declarator);
|
|
|
|
|
current_chunk.bindings.push(...bindings);
|
|
|
|
|
current_chunk.position = /** @type {Program} */ (parent).body.indexOf(node);
|
|
|
|
|
current_chunk.kind = node.kind;
|
|
|
|
|
} else {
|
|
|
|
|
/** @type {ParallelizedChunk} */
|
|
|
|
|
const chunk = {
|
|
|
|
|
kind: node.kind,
|
|
|
|
|
declarators: [_declarator],
|
|
|
|
|
position,
|
|
|
|
|
bindings
|
|
|
|
|
};
|
|
|
|
|
context.state.current_parallelized_chunk = chunk;
|
|
|
|
|
context.state.parallelized_chunks.push(chunk);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
declarations.push(/** @type {VariableDeclarator} */ (context.visit(declarator)));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
@ -124,47 +177,85 @@ export function VariableDeclaration(node, context) {
|
|
|
|
|
const value = /** @type {Expression} */ (args[0]) ?? b.void0; // TODO do we need the void 0? can we just omit it altogether?
|
|
|
|
|
|
|
|
|
|
if (rune === '$state' || rune === '$state.raw') {
|
|
|
|
|
const state_declarators = [];
|
|
|
|
|
const current_chunk = context.state.current_parallelized_chunk;
|
|
|
|
|
const parallelize =
|
|
|
|
|
declarator.id.type === 'Identifier' &&
|
|
|
|
|
context.state.analysis.instance?.scope === context.state.scope &&
|
|
|
|
|
value.type === 'AwaitExpression' &&
|
|
|
|
|
!is_expression_async(value.argument) &&
|
|
|
|
|
can_be_parallelized(value.argument, context.state.scope, context.state.analysis, [
|
|
|
|
|
...(current_chunk?.bindings ?? []),
|
|
|
|
|
...bindings
|
|
|
|
|
]);
|
|
|
|
|
/**
|
|
|
|
|
* @param {Identifier} id
|
|
|
|
|
* @param {Expression} visited
|
|
|
|
|
* @param {Expression} value
|
|
|
|
|
*/
|
|
|
|
|
const create_state_declarator = (id, value) => {
|
|
|
|
|
const binding = /** @type {import('#compiler').Binding} */ (
|
|
|
|
|
context.state.scope.get(id.name)
|
|
|
|
|
);
|
|
|
|
|
const create_state_declarator = (id, visited, value) => {
|
|
|
|
|
const binding = /** @type {Binding} */ (context.state.scope.get(id.name));
|
|
|
|
|
const is_state = is_state_source(binding, context.state.analysis);
|
|
|
|
|
const is_proxy = should_proxy(value, context.state.scope);
|
|
|
|
|
const is_proxy = should_proxy(visited, context.state.scope);
|
|
|
|
|
const compose = [];
|
|
|
|
|
if (parallelize) {
|
|
|
|
|
if (rune === '$state' && is_proxy) {
|
|
|
|
|
compose.push(b.id('$.proxy'));
|
|
|
|
|
|
|
|
|
|
if (dev && !is_state) {
|
|
|
|
|
compose.push(
|
|
|
|
|
b.arrow([b.id('proxy')], b.call('$.tag_proxy', b.id('proxy'), b.literal(id.name)))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rune === '$state' && is_proxy) {
|
|
|
|
|
value = b.call('$.proxy', value);
|
|
|
|
|
if (is_state) {
|
|
|
|
|
compose.push(b.id('$.state'));
|
|
|
|
|
if (dev) {
|
|
|
|
|
compose.push(
|
|
|
|
|
b.arrow([b.id('source')], b.call('$.tag', b.id('source'), b.literal(id.name)))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return b.call(
|
|
|
|
|
'$.async_compose',
|
|
|
|
|
/** @type {Expression} */ (
|
|
|
|
|
context.visit(/** @type {AwaitExpression} */ (value).argument)
|
|
|
|
|
),
|
|
|
|
|
...compose
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
let value = visited;
|
|
|
|
|
if (rune === '$state' && is_proxy) {
|
|
|
|
|
value = b.call('$.proxy', value);
|
|
|
|
|
|
|
|
|
|
if (dev && !is_state) {
|
|
|
|
|
value = b.call('$.tag_proxy', value, b.literal(id.name));
|
|
|
|
|
if (dev && !is_state) {
|
|
|
|
|
value = b.call('$.tag_proxy', value, b.literal(id.name));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_state) {
|
|
|
|
|
value = b.call('$.state', value);
|
|
|
|
|
if (is_state) {
|
|
|
|
|
value = b.call('$.state', value);
|
|
|
|
|
|
|
|
|
|
if (dev) {
|
|
|
|
|
value = b.call('$.tag', value, b.literal(id.name));
|
|
|
|
|
if (dev) {
|
|
|
|
|
value = b.call('$.tag', value, b.literal(id.name));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (declarator.id.type === 'Identifier') {
|
|
|
|
|
const expression = /** @type {Expression} */ (context.visit(value));
|
|
|
|
|
|
|
|
|
|
declarations.push(
|
|
|
|
|
b.declarator(declarator.id, create_state_declarator(declarator.id, expression))
|
|
|
|
|
state_declarators.push(
|
|
|
|
|
b.declarator(declarator.id, create_state_declarator(declarator.id, expression, value))
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
const tmp = b.id(context.state.scope.generate('tmp'));
|
|
|
|
|
const { inserts, paths } = extract_paths(declarator.id, tmp);
|
|
|
|
|
|
|
|
|
|
declarations.push(
|
|
|
|
|
state_declarators.push(
|
|
|
|
|
b.declarator(tmp, /** @type {Expression} */ (context.visit(value))),
|
|
|
|
|
...inserts.map(({ id, value }) => {
|
|
|
|
|
id.name = context.state.scope.generate('$$array');
|
|
|
|
@ -186,12 +277,36 @@ export function VariableDeclaration(node, context) {
|
|
|
|
|
return b.declarator(
|
|
|
|
|
path.node,
|
|
|
|
|
binding?.kind === 'state' || binding?.kind === 'raw_state'
|
|
|
|
|
? create_state_declarator(binding.node, value)
|
|
|
|
|
? create_state_declarator(binding.node, value, path.expression)
|
|
|
|
|
: value
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (!parallelize) {
|
|
|
|
|
declarations.push(...state_declarators);
|
|
|
|
|
} else {
|
|
|
|
|
const declarators = state_declarators.map(({ id, init }) => ({
|
|
|
|
|
id,
|
|
|
|
|
init: /** @type {Expression} */ (init)
|
|
|
|
|
}));
|
|
|
|
|
if (current_chunk && (current_chunk.kind === node.kind || current_chunk.kind === null)) {
|
|
|
|
|
current_chunk.declarators.push(...declarators);
|
|
|
|
|
current_chunk.bindings.push(...bindings);
|
|
|
|
|
current_chunk.position = position;
|
|
|
|
|
current_chunk.kind = node.kind;
|
|
|
|
|
} else {
|
|
|
|
|
/** @type {ParallelizedChunk} */
|
|
|
|
|
const chunk = {
|
|
|
|
|
kind: node.kind,
|
|
|
|
|
declarators,
|
|
|
|
|
position,
|
|
|
|
|
bindings
|
|
|
|
|
};
|
|
|
|
|
context.state.current_parallelized_chunk = chunk;
|
|
|
|
|
context.state.parallelized_chunks.push(chunk);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
@ -200,6 +315,25 @@ export function VariableDeclaration(node, context) {
|
|
|
|
|
const is_async = context.state.analysis.async_deriveds.has(
|
|
|
|
|
/** @type {CallExpression} */ (init)
|
|
|
|
|
);
|
|
|
|
|
let parallelize = false;
|
|
|
|
|
const current_chunk = context.state.current_parallelized_chunk;
|
|
|
|
|
if (
|
|
|
|
|
is_async &&
|
|
|
|
|
context.state.analysis.instance &&
|
|
|
|
|
context.state.scope === context.state.analysis.instance.scope &&
|
|
|
|
|
// TODO make it work without this
|
|
|
|
|
declarator.id.type === 'Identifier'
|
|
|
|
|
) {
|
|
|
|
|
parallelize = can_be_parallelized(value, context.state.scope, context.state.analysis, [
|
|
|
|
|
...(current_chunk?.bindings ?? []),
|
|
|
|
|
...context.state.scope.get_bindings(declarator)
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @type {VariableDeclarator[]} */
|
|
|
|
|
const derived_declarators = [];
|
|
|
|
|
/** @type {Binding[]} */
|
|
|
|
|
const bindings = [];
|
|
|
|
|
|
|
|
|
|
if (declarator.id.type === 'Identifier') {
|
|
|
|
|
let expression = /** @type {Expression} */ (
|
|
|
|
@ -217,17 +351,22 @@ export function VariableDeclaration(node, context) {
|
|
|
|
|
location ? b.literal(location) : undefined
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
call = b.call(b.await(b.call('$.save', call)));
|
|
|
|
|
if (dev) call = b.call('$.tag', call, b.literal(declarator.id.name));
|
|
|
|
|
|
|
|
|
|
declarations.push(b.declarator(declarator.id, call));
|
|
|
|
|
if (!parallelize) call = b.call(b.await(b.call('$.save', call)));
|
|
|
|
|
if (dev) {
|
|
|
|
|
call = b.call(
|
|
|
|
|
'$.tag' + (parallelize ? '_async' : ''),
|
|
|
|
|
call,
|
|
|
|
|
b.literal(declarator.id.name)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
bindings.push(/** @type {Binding} */ (context.state.scope.get(declarator.id.name)));
|
|
|
|
|
derived_declarators.push(b.declarator(declarator.id, call));
|
|
|
|
|
} else {
|
|
|
|
|
if (rune === '$derived') expression = b.thunk(expression);
|
|
|
|
|
|
|
|
|
|
let call = b.call('$.derived', expression);
|
|
|
|
|
if (dev) call = b.call('$.tag', call, b.literal(declarator.id.name));
|
|
|
|
|
|
|
|
|
|
declarations.push(b.declarator(declarator.id, call));
|
|
|
|
|
derived_declarators.push(b.declarator(declarator.id, call));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const init = /** @type {CallExpression} */ (declarator.init);
|
|
|
|
@ -260,8 +399,7 @@ export function VariableDeclaration(node, context) {
|
|
|
|
|
const label = `[$derived ${declarator.id.type === 'ArrayPattern' ? 'iterable' : 'object'}]`;
|
|
|
|
|
call = b.call('$.tag', call, b.literal(label));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
declarations.push(b.declarator(id, call));
|
|
|
|
|
derived_declarators.push(b.declarator(id, call));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { inserts, paths } = extract_paths(declarator.id, rhs);
|
|
|
|
@ -277,14 +415,13 @@ export function VariableDeclaration(node, context) {
|
|
|
|
|
const label = `[$derived ${declarator.id.type === 'ArrayPattern' ? 'iterable' : 'object'}]`;
|
|
|
|
|
call = b.call('$.tag', call, b.literal(label));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
declarations.push(b.declarator(id, call));
|
|
|
|
|
derived_declarators.push(b.declarator(id, call));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const path of paths) {
|
|
|
|
|
const expression = /** @type {Expression} */ (context.visit(path.expression));
|
|
|
|
|
const call = b.call('$.derived', b.thunk(expression));
|
|
|
|
|
declarations.push(
|
|
|
|
|
derived_declarators.push(
|
|
|
|
|
b.declarator(
|
|
|
|
|
path.node,
|
|
|
|
|
dev
|
|
|
|
@ -295,12 +432,37 @@ export function VariableDeclaration(node, context) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!parallelize) {
|
|
|
|
|
declarations.push(...derived_declarators);
|
|
|
|
|
} else if (derived_declarators.length > 0) {
|
|
|
|
|
const declarators = derived_declarators.map(({ id, init }) => ({
|
|
|
|
|
id,
|
|
|
|
|
init: /** @type {Expression} */ (init)
|
|
|
|
|
}));
|
|
|
|
|
if (current_chunk && (current_chunk.kind === node.kind || current_chunk.kind === null)) {
|
|
|
|
|
current_chunk.declarators.push(...declarators);
|
|
|
|
|
current_chunk.bindings.push(...bindings);
|
|
|
|
|
current_chunk.position = position;
|
|
|
|
|
current_chunk.kind = node.kind;
|
|
|
|
|
} else {
|
|
|
|
|
/** @type {ParallelizedChunk} */
|
|
|
|
|
const chunk = {
|
|
|
|
|
kind: node.kind,
|
|
|
|
|
declarators,
|
|
|
|
|
position,
|
|
|
|
|
bindings
|
|
|
|
|
};
|
|
|
|
|
context.state.current_parallelized_chunk = chunk;
|
|
|
|
|
context.state.parallelized_chunks.push(chunk);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (const declarator of node.declarations) {
|
|
|
|
|
const bindings = /** @type {Binding[]} */ (context.state.scope.get_bindings(declarator));
|
|
|
|
|
const bindings = context.state.scope.get_bindings(declarator);
|
|
|
|
|
const has_state = bindings.some((binding) => binding.kind === 'state');
|
|
|
|
|
const has_props = bindings.some((binding) => binding.kind === 'bindable_prop');
|
|
|
|
|
|
|
|
|
@ -403,13 +565,9 @@ export function VariableDeclaration(node, context) {
|
|
|
|
|
* @param {Expression} value
|
|
|
|
|
*/
|
|
|
|
|
function create_state_declarators(declarator, context, value) {
|
|
|
|
|
const immutable = context.state.analysis.immutable ? b.true : undefined;
|
|
|
|
|
if (declarator.id.type === 'Identifier') {
|
|
|
|
|
return [
|
|
|
|
|
b.declarator(
|
|
|
|
|
declarator.id,
|
|
|
|
|
b.call('$.mutable_source', value, context.state.analysis.immutable ? b.true : undefined)
|
|
|
|
|
)
|
|
|
|
|
];
|
|
|
|
|
return [b.declarator(declarator.id, b.call('$.mutable_source', value, immutable))];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tmp = b.id(context.state.scope.generate('tmp'));
|
|
|
|
@ -424,15 +582,13 @@ function create_state_declarators(declarator, context, value) {
|
|
|
|
|
const expression = /** @type {Expression} */ (context.visit(b.thunk(value)));
|
|
|
|
|
return b.declarator(id, b.call('$.derived', expression));
|
|
|
|
|
}),
|
|
|
|
|
...paths.map((path) => {
|
|
|
|
|
const value = /** @type {Expression} */ (context.visit(path.expression));
|
|
|
|
|
const binding = context.state.scope.get(/** @type {Identifier} */ (path.node).name);
|
|
|
|
|
...paths.map(({ expression, node }) => {
|
|
|
|
|
const value = /** @type {Expression} */ (context.visit(expression));
|
|
|
|
|
const binding = context.state.scope.get(/** @type {Identifier} */ (node).name);
|
|
|
|
|
|
|
|
|
|
return b.declarator(
|
|
|
|
|
path.node,
|
|
|
|
|
binding?.kind === 'state'
|
|
|
|
|
? b.call('$.mutable_source', value, context.state.analysis.immutable ? b.true : undefined)
|
|
|
|
|
: value
|
|
|
|
|
node,
|
|
|
|
|
binding?.kind === 'state' ? b.call('$.mutable_source', value, immutable) : value
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
];
|
|
|
|
|