pull/16015/head
Rich Harris 4 months ago
parent 2e27c5d8aa
commit 02e9f00fd6

@ -2,7 +2,7 @@
/** @import { Binding } from '#compiler' */ /** @import { Binding } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */ /** @import { ComponentClientTransformState, ComponentContext } from '../types' */
import { dev } from '../../../../state.js'; import { dev } from '../../../../state.js';
import { build_pattern, extract_paths } from '../../../../utils/ast.js'; import { extract_paths } from '../../../../utils/ast.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import * as assert from '../../../../utils/assert.js'; import * as assert from '../../../../utils/assert.js';
import { get_rune } from '../../../scope.js'; import { get_rune } from '../../../scope.js';
@ -141,20 +141,20 @@ export function VariableDeclaration(node, context) {
b.declarator(declarator.id, create_state_declarator(declarator.id, value)) b.declarator(declarator.id, create_state_declarator(declarator.id, value))
); );
} else { } else {
const [pattern, replacements] = build_pattern(declarator.id, context.state.scope); const tmp = context.state.scope.generate('tmp');
const paths = extract_paths(declarator.id);
declarations.push( declarations.push(
b.declarator(pattern, value), b.declarator(b.id(tmp), value),
.../** @type {[Identifier, Identifier][]} */ ([...replacements]).map( ...paths.map((path) => {
([original, replacement]) => { const value = path.expression?.(b.id(tmp));
const binding = context.state.scope.get(original.name); const binding = context.state.scope.get(/** @type {Identifier} */ (path.node).name);
return b.declarator( return b.declarator(
original, path.node,
binding?.kind === 'state' || binding?.kind === 'raw_state' binding?.kind === 'state' || binding?.kind === 'raw_state'
? create_state_declarator(binding.node, replacement) ? create_state_declarator(binding.node, value)
: replacement : value
); );
} })
)
); );
} }
@ -170,7 +170,8 @@ export function VariableDeclaration(node, context) {
) )
); );
} else { } else {
const [pattern, replacements] = build_pattern(declarator.id, context.state.scope); const bindings = extract_paths(declarator.id);
const init = /** @type {CallExpression} */ (declarator.init); const init = /** @type {CallExpression} */ (declarator.init);
/** @type {Identifier} */ /** @type {Identifier} */
@ -188,16 +189,10 @@ export function VariableDeclaration(node, context) {
); );
} }
for (let i = 0; i < replacements.size; i++) { for (let i = 0; i < bindings.length; i++) {
const [original, replacement] = [...replacements][i]; const binding = bindings[i];
declarations.push( declarations.push(
b.declarator( b.declarator(binding.node, b.call('$.derived', b.thunk(binding.expression(rhs))))
original,
b.call(
'$.derived',
b.arrow([], b.block([b.let(pattern, rhs), b.return(replacement)]))
)
)
); );
} }
} }
@ -309,19 +304,19 @@ function create_state_declarators(declarator, { scope, analysis }, value) {
]; ];
} }
const [pattern, replacements] = build_pattern(declarator.id, scope); const tmp = scope.generate('tmp');
const paths = extract_paths(declarator.id);
return [ return [
b.declarator(pattern, value), b.declarator(b.id(tmp), value),
.../** @type {[Identifier, Identifier][]} */ ([...replacements]).map( ...paths.map((path) => {
([original, replacement]) => { const value = path.expression?.(b.id(tmp));
const binding = scope.get(original.name); const binding = scope.get(/** @type {Identifier} */ (path.node).name);
return b.declarator( return b.declarator(
original, path.node,
binding?.kind === 'state' binding?.kind === 'state'
? b.call('$.mutable_source', replacement, analysis.immutable ? b.true : undefined) ? b.call('$.mutable_source', value, analysis.immutable ? b.true : undefined)
: replacement : value
); );
} })
)
]; ];
} }

@ -3,7 +3,7 @@
/** @import { Context } from '../types.js' */ /** @import { Context } from '../types.js' */
/** @import { ComponentAnalysis } from '../../../types.js' */ /** @import { ComponentAnalysis } from '../../../types.js' */
/** @import { Scope } from '../../../scope.js' */ /** @import { Scope } from '../../../scope.js' */
import { build_pattern, build_fallback, extract_paths } from '../../../../utils/ast.js'; import { build_fallback, extract_paths } from '../../../../utils/ast.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { get_rune } from '../../../scope.js'; import { get_rune } from '../../../scope.js';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
@ -188,10 +188,13 @@ function create_state_declarators(declarator, scope, value) {
return [b.declarator(declarator.id, value)]; return [b.declarator(declarator.id, value)];
} }
const [pattern, replacements] = build_pattern(declarator.id, scope); const tmp = scope.generate('tmp');
const paths = extract_paths(declarator.id);
return [ return [
b.declarator(pattern, value), b.declarator(b.id(tmp), value), // TODO inject declarator for opts, so we can use it below
// TODO inject declarator for opts, so we can use it below ...paths.map((path) => {
...[...replacements].map(([original, replacement]) => b.declarator(original, replacement)) const value = path.expression?.(b.id(tmp));
return b.declarator(path.node, value);
})
]; ];
} }

@ -1,7 +1,7 @@
/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Node, Pattern } from 'estree' */ /** @import { AssignmentExpression, AssignmentOperator, Expression, Node, Pattern } from 'estree' */
/** @import { Context as ClientContext } from '../client/types.js' */ /** @import { Context as ClientContext } from '../client/types.js' */
/** @import { Context as ServerContext } from '../server/types.js' */ /** @import { Context as ServerContext } from '../server/types.js' */
import { build_pattern, is_expression_async } from '../../../utils/ast.js'; import { extract_paths, is_expression_async } from '../../../utils/ast.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
/** /**
@ -23,23 +23,21 @@ export function visit_assignment_expression(node, context, build_assignment) {
let changed = false; let changed = false;
const [pattern, replacements] = build_pattern(node.left, context.state.scope); const assignments = extract_paths(node.left).map((path) => {
const value = path.expression?.(rhs);
const assignments = [ let assignment = build_assignment('=', path.node, value, context);
b.let(pattern, rhs), if (assignment !== null) changed = true;
...[...replacements].map(([original, replacement]) => {
let assignment = build_assignment(node.operator, original, replacement, context); return (
if (assignment !== null) changed = true; assignment ??
return b.stmt( b.assignment(
assignment ?? '=',
b.assignment( /** @type {Pattern} */ (context.visit(path.node)),
node.operator, /** @type {Expression} */ (context.visit(value))
/** @type {Identifier} */ (context.visit(original)), )
/** @type {Expression} */ (context.visit(replacement)) );
) });
);
})
];
if (!changed) { if (!changed) {
// No change to output -> nothing to transform -> we can keep the original assignment // No change to output -> nothing to transform -> we can keep the original assignment
@ -47,36 +45,25 @@ export function visit_assignment_expression(node, context, build_assignment) {
} }
const is_standalone = /** @type {Node} */ (context.path.at(-1)).type.endsWith('Statement'); const is_standalone = /** @type {Node} */ (context.path.at(-1)).type.endsWith('Statement');
const block = b.block(assignments); const sequence = b.sequence(assignments);
if (!is_standalone) { if (!is_standalone) {
// this is part of an expression, we need the sequence to end with the value // this is part of an expression, we need the sequence to end with the value
block.body.push(b.return(rhs)); sequence.expressions.push(rhs);
} }
if (is_standalone && !should_cache) { if (should_cache) {
return block; // the right hand side is a complex expression, wrap in an IIFE to cache it
} const iife = b.arrow([rhs], sequence);
const iife = b.arrow(should_cache ? [rhs] : [], block); const iife_is_async =
is_expression_async(value) ||
assignments.some((assignment) => is_expression_async(assignment));
const iife_is_async = return iife_is_async ? b.await(b.call(b.async(iife), value)) : b.call(iife, value);
is_expression_async(value) || }
assignments.some(
(assignment) =>
(assignment.type === 'ExpressionStatement' &&
is_expression_async(assignment.expression)) ||
(assignment.type === 'VariableDeclaration' &&
assignment.declarations.some(
(declaration) =>
is_expression_async(declaration.id) ||
(declaration.init && is_expression_async(declaration.init))
))
);
return iife_is_async return sequence;
? b.await(b.call(b.async(iife), should_cache ? value : undefined))
: b.call(iife, should_cache ? value : undefined);
} }
if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') { if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {

@ -630,10 +630,9 @@ export class Scope {
/** /**
* @param {string} preferred_name * @param {string} preferred_name
* @param {(name: string, counter: number) => string} [generator]
* @returns {string} * @returns {string}
*/ */
generate(preferred_name, generator = (name, counter) => `${name}_${counter}`) { generate(preferred_name) {
if (this.#porous) { if (this.#porous) {
return /** @type {Scope} */ (this.parent).generate(preferred_name); return /** @type {Scope} */ (this.parent).generate(preferred_name);
} }
@ -648,7 +647,7 @@ export class Scope {
this.root.conflicts.has(name) || this.root.conflicts.has(name) ||
is_reserved(name) is_reserved(name)
) { ) {
name = generator(preferred_name, n++); name = `${preferred_name}_${n++}`;
} }
this.references.set(name, []); this.references.set(name, []);

@ -1,8 +1,7 @@
/** @import { AST, Scope } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import * as ESTree from 'estree' */ /** @import * as ESTree from 'estree' */
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import is_reference from 'is-reference';
/** /**
* Gets the left-most identifier of a member expression or identifier. * Gets the left-most identifier of a member expression or identifier.
@ -129,49 +128,6 @@ export function unwrap_pattern(pattern, nodes = []) {
return nodes; return nodes;
} }
/**
* @param {ESTree.Pattern} id
* @param {Scope} scope
* @returns {[ESTree.Pattern, Map<ESTree.Identifier | ESTree.MemberExpression, ESTree.Identifier>]}
*/
export function build_pattern(id, scope) {
/** @type {Map<ESTree.Identifier | ESTree.MemberExpression, ESTree.Identifier>} */
const map = new Map();
/** @type {Map<string, string>} */
const names = new Map();
let counter = 0;
for (const node of unwrap_pattern(id)) {
const name = scope.generate(`$$${++counter}`, (_, counter) => `$$${counter}`);
map.set(node, b.id(name));
if (node.type === 'Identifier') {
names.set(node.name, name);
}
}
const pattern = walk(id, null, {
Identifier(node, context) {
if (is_reference(node, /** @type {ESTree.Pattern} */ (context.path.at(-1)))) {
const name = names.get(node.name);
if (name) return b.id(name);
}
},
MemberExpression(node, context) {
const n = map.get(node);
if (n) return n;
context.next();
}
});
return [pattern, map];
}
/** /**
* Extracts all identifiers from a pattern. * Extracts all identifiers from a pattern.
* @param {ESTree.Pattern} pattern * @param {ESTree.Pattern} pattern

@ -7,12 +7,10 @@ let c = 3;
let d = 4; let d = 4;
export function update(array) { export function update(array) {
{ (
let [$$1, $$2] = array; $.set(a, array[0], true),
$.set(b, array[1], true)
$.set(a, $$1, true); );
$.set(b, $$2, true);
};
[c, d] = array; [c, d] = array;
} }
Loading…
Cancel
Save