pull/16015/head
Rich Harris 4 months ago
parent 4df73207b6
commit bed2f0c910

@ -603,15 +603,15 @@ const instance_script = {
);
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
// const tmp = state.scope.generate('tmp');
// const paths = extract_paths(declarator.id);
// const tmp = b.id(state.scope.generate('tmp'));
// const paths = destructure(declarator.id, tmp);
// state.props_pre.push(
// b.declaration('const', b.id(tmp), visit(declarator.init!) as Expression)
// b.declaration('const', tmp, visit(declarator.init!) as Expression)
// );
// for (const path of paths) {
// const name = (path.node as Identifier).name;
// const binding = state.scope.get(name)!;
// const value = path.expression!(b.id(tmp));
// const value = path.expression;
// if (binding.kind === 'bindable_prop' || binding.kind === 'rest_prop') {
// state.props.push({
// local: name,

@ -10,7 +10,7 @@ import {
EACH_ITEM_REACTIVE
} from '../../../../../constants.js';
import { dev } from '../../../../state.js';
import { extract_paths, object } from '../../../../utils/ast.js';
import { destructure, object } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { build_getter } from '../utils.js';
import { get_value } from './shared/declarations.js';
@ -234,13 +234,11 @@ export function EachBlock(node, context) {
} else if (node.context) {
const unwrapped = (flags & EACH_ITEM_REACTIVE) !== 0 ? b.call('$.get', item) : item;
for (const path of extract_paths(node.context)) {
for (const path of destructure(node.context, unwrapped)) {
const name = /** @type {Identifier} */ (path.node).name;
const needs_derived = path.has_default_value; // to ensure that default value is only called once
const fn = b.thunk(
/** @type {Expression} */ (context.visit(path.expression(unwrapped), child_state))
);
const fn = b.thunk(/** @type {Expression} */ (context.visit(path.expression, child_state)));
declarations.push(b.let(path.node, needs_derived ? b.call('$.derived_safe_equal', fn) : fn));
@ -249,7 +247,7 @@ export function EachBlock(node, context) {
child_state.transform[name] = {
read,
assign: (_, value) => {
const left = /** @type {Pattern} */ (path.update_expression(unwrapped));
const left = /** @type {Pattern} */ (path.update_expression);
return b.sequence([b.assignment('=', left, value), ...sequence]);
},
mutate: (_, mutation) => {

@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */
import { dev } from '../../../../state.js';
import { extract_paths } from '../../../../utils/ast.js';
import { destructure } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { get_value } from './shared/declarations.js';
@ -43,14 +43,12 @@ export function SnippetBlock(node, context) {
let arg_alias = `$$arg${i}`;
args.push(b.id(arg_alias));
const paths = extract_paths(argument);
const paths = destructure(argument, b.maybe_call(b.id(arg_alias)));
for (const path of paths) {
const name = /** @type {Identifier} */ (path.node).name;
const needs_derived = path.has_default_value; // to ensure that default value is only called once
const fn = b.thunk(
/** @type {Expression} */ (context.visit(path.expression(b.maybe_call(b.id(arg_alias)))))
);
const fn = b.thunk(/** @type {Expression} */ (context.visit(path.expression)));
declarations.push(b.let(path.node, needs_derived ? b.call('$.derived_safe_equal', fn) : fn));

@ -2,7 +2,7 @@
/** @import { Binding } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
import { dev } from '../../../../state.js';
import { extract_paths } from '../../../../utils/ast.js';
import { destructure, extract_paths } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import * as assert from '../../../../utils/assert.js';
import { get_rune } from '../../../scope.js';
@ -142,11 +142,11 @@ export function VariableDeclaration(node, context) {
);
} else {
const tmp = b.id(context.state.scope.generate('tmp'));
const paths = extract_paths(declarator.id);
const paths = destructure(declarator.id, tmp);
declarations.push(
b.declarator(tmp, value),
...paths.map((path) => {
const value = path.expression(tmp);
const value = path.expression;
const binding = context.state.scope.get(/** @type {Identifier} */ (path.node).name);
return b.declarator(
path.node,
@ -224,12 +224,12 @@ export function VariableDeclaration(node, context) {
if (declarator.id.type !== 'Identifier') {
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
const tmp = context.state.scope.generate('tmp');
const paths = extract_paths(declarator.id);
const tmp = b.id(context.state.scope.generate('tmp'));
const paths = destructure(declarator.id, tmp);
declarations.push(
b.declarator(
b.id(tmp),
tmp,
/** @type {Expression} */ (context.visit(/** @type {Expression} */ (declarator.init)))
)
);
@ -237,7 +237,7 @@ export function VariableDeclaration(node, context) {
for (const path of paths) {
const name = /** @type {Identifier} */ (path.node).name;
const binding = /** @type {Binding} */ (context.state.scope.get(name));
const value = path.expression(b.id(tmp));
const value = path.expression;
declarations.push(
b.declarator(
path.node,
@ -304,12 +304,12 @@ function create_state_declarators(declarator, { scope, analysis }, value) {
];
}
const tmp = scope.generate('tmp');
const paths = extract_paths(declarator.id);
const tmp = b.id(scope.generate('tmp'));
const paths = destructure(declarator.id, tmp);
return [
b.declarator(b.id(tmp), value),
b.declarator(tmp, value),
...paths.map((path) => {
const value = path.expression(b.id(tmp));
const value = path.expression;
const binding = scope.get(/** @type {Identifier} */ (path.node).name);
return b.declarator(
path.node,

@ -3,7 +3,7 @@
/** @import { Context } from '../types.js' */
/** @import { ComponentAnalysis } from '../../../types.js' */
/** @import { Scope } from '../../../scope.js' */
import { build_fallback, extract_paths } from '../../../../utils/ast.js';
import { build_fallback, destructure } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { get_rune } from '../../../scope.js';
import { walk } from 'zimmerframe';
@ -120,16 +120,16 @@ export function VariableDeclaration(node, context) {
if (declarator.id.type !== 'Identifier') {
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
const tmp = context.state.scope.generate('tmp');
const paths = extract_paths(declarator.id);
const tmp = b.id(context.state.scope.generate('tmp'));
const paths = destructure(declarator.id, tmp);
declarations.push(
b.declarator(
b.id(tmp),
tmp,
/** @type {Expression} */ (context.visit(/** @type {Expression} */ (declarator.init)))
)
);
for (const path of paths) {
const value = path.expression(b.id(tmp));
const value = path.expression;
const name = /** @type {Identifier} */ (path.node).name;
const binding = /** @type {Binding} */ (context.state.scope.get(name));
const prop = b.member(b.id('$$props'), b.literal(binding.prop_alias ?? name), true);
@ -189,11 +189,11 @@ function create_state_declarators(declarator, scope, value) {
}
const tmp = b.id(scope.generate('tmp'));
const paths = extract_paths(declarator.id);
const paths = destructure(declarator.id, tmp);
return [
b.declarator(tmp, value), // TODO inject declarator for opts, so we can use it below
...paths.map((path) => {
const value = path.expression(tmp);
const value = path.expression;
return b.declarator(path.node, value);
})
];

@ -1,7 +1,7 @@
/** @import { AssignmentExpression, AssignmentOperator, Expression, Node, Pattern } from 'estree' */
/** @import { Context as ClientContext } from '../client/types.js' */
/** @import { Context as ServerContext } from '../server/types.js' */
import { extract_paths, is_expression_async } from '../../../utils/ast.js';
import { destructure, is_expression_async } from '../../../utils/ast.js';
import * as b from '#compiler/builders';
/**
@ -23,8 +23,8 @@ export function visit_assignment_expression(node, context, build_assignment) {
let changed = false;
const assignments = extract_paths(node.left).map((path) => {
const value = path.expression?.(rhs);
const assignments = destructure(node.left, rhs).map((path) => {
const value = path.expression;
let assignment = build_assignment('=', path.node, value, context);
if (assignment !== null) changed = true;

@ -389,6 +389,169 @@ function _extract_paths(assignments = [], param, expression, update_expression,
return assignments;
}
/**
* Represents the path of a destructured assignment from either a declaration
* or assignment expression. For example, given `const { foo: { bar: baz } } = quux`,
* the path of `baz` is `foo.bar`
* @typedef {Object} DestructuredAssignment2
* @property {ESTree.Identifier | ESTree.MemberExpression} node The node the destructuring path end in. Can be a member expression only for assignment expressions
* @property {boolean} is_rest `true` if this is a `...rest` destructuring
* @property {boolean} has_default_value `true` if this has a fallback value like `const { foo = 'bar } = ..`
* @property {ESTree.Expression} expression Returns an expression which walks the path starting at the given expression.
* This will be a call expression if a rest element or default is involved e.g. `const { foo: { bar: baz = 42 }, ...rest } = quux` since we can't represent `baz` or `rest` purely as a path
* Will be an await expression in case of an async default value (`const { foo = await bar } = ...`)
* @property {ESTree.Expression} update_expression Like `expression` but without default values.
*/
/**
* Extracts all destructured assignments from a pattern.
* @param {ESTree.Node} param
* @param {ESTree.Expression} initial
* @returns {DestructuredAssignment2[]}
*/
export function destructure(param, initial) {
return _destructure([], param, initial, initial, false);
}
/**
* @param {DestructuredAssignment2[]} assignments
* @param {ESTree.Node} param
* @param {ESTree.Expression} expression
* @param {ESTree.Expression} update_expression
* @param {boolean} has_default_value
* @returns {DestructuredAssignment2[]}
*/
function _destructure(assignments = [], param, expression, update_expression, has_default_value) {
switch (param.type) {
case 'Identifier':
case 'MemberExpression':
assignments.push({
node: param,
is_rest: false,
has_default_value,
expression,
update_expression
});
break;
case 'ObjectPattern':
for (const prop of param.properties) {
if (prop.type === 'RestElement') {
/** @type {ESTree.Expression[]} */
const props = [];
for (const p of param.properties) {
if (p.type === 'Property' && p.key.type !== 'PrivateIdentifier') {
if (p.key.type === 'Identifier' && !p.computed) {
props.push(b.literal(p.key.name));
} else if (p.key.type === 'Literal') {
props.push(b.literal(String(p.key.value)));
} else {
props.push(b.call('String', p.key));
}
}
}
const rest_expression = b.call('$.exclude_from_object', expression, b.array(props));
if (prop.argument.type === 'Identifier') {
assignments.push({
node: prop.argument,
is_rest: true,
has_default_value,
expression: rest_expression,
update_expression: rest_expression
});
} else {
_destructure(
assignments,
prop.argument,
rest_expression,
rest_expression,
has_default_value
);
}
} else {
const object_expression = b.member(
expression,
prop.key,
prop.computed || prop.key.type !== 'Identifier'
);
_destructure(
assignments,
prop.value,
object_expression,
object_expression,
has_default_value
);
}
}
break;
case 'ArrayPattern':
for (let i = 0; i < param.elements.length; i += 1) {
const element = param.elements[i];
if (element) {
if (element.type === 'RestElement') {
const rest_expression = b.call(b.member(expression, 'slice'), b.literal(i));
if (element.argument.type === 'Identifier') {
assignments.push({
node: element.argument,
is_rest: true,
has_default_value,
expression: rest_expression,
update_expression: rest_expression
});
} else {
_destructure(
assignments,
element.argument,
rest_expression,
rest_expression,
has_default_value
);
}
} else {
const array_expression = b.member(expression, b.literal(i), true);
_destructure(
assignments,
element,
array_expression,
array_expression,
has_default_value
);
}
}
}
break;
case 'AssignmentPattern': {
const fallback_expression = build_fallback(expression, param.right);
if (param.left.type === 'Identifier') {
assignments.push({
node: param.left,
is_rest: false,
has_default_value: true,
expression: fallback_expression,
update_expression
});
} else {
_destructure(assignments, param.left, fallback_expression, update_expression, true);
}
break;
}
}
return assignments;
}
/**
* Like `path.at(x)`, but skips over `TSNonNullExpression` and `TSAsExpression` nodes and eases assertions a bit
* by removing the `| undefined` from the resulting type.

Loading…
Cancel
Save