chore: Remove rest params

pull/10320/head
S. Elliott Johnson 2 years ago
parent 46306700d2
commit 37b6fd48a2

@ -298,6 +298,14 @@ function open(parser) {
error(snippet_expression, 'TODO', 'expected a snippet name');
}
if (snippet_expression.params.at(-1)?.type === 'RestElement') {
error(
snippet_expression,
'TODO',
'snippets do not support rest parameters; use an array instead'
);
}
// slice the `{#` off the beginning since it's already been eaten
parser.eat(raw_snippet_declaration.slice(2), true);

@ -2501,25 +2501,9 @@ export const template_visitors = {
}
let arg_alias = `$$arg${i}`;
args.push(b.id(arg_alias));
if (argument.type === 'RestElement') {
args.push(b.rest(b.id(arg_alias)));
if (argument.argument.type === 'Identifier') {
declarations.push(
b.let(argument.argument.name, b.call('$.proxy_rest_array', b.id(arg_alias)))
);
continue;
}
const new_arg_alias = `$$proxied_arg${i}`;
declarations.push(b.let(new_arg_alias, b.call('$.proxy_rest_array', b.id(arg_alias))));
arg_alias = new_arg_alias;
} else {
args.push(b.id(arg_alias));
}
const paths = extract_paths(argument.type === 'RestElement' ? argument.argument : argument);
const paths = extract_paths(argument);
for (const path of paths) {
const name = /** @type {import('estree').Identifier} */ (path.node).name;
@ -2529,13 +2513,7 @@ export const template_visitors = {
path.node,
b.thunk(
/** @type {import('estree').Expression} */ (
context.visit(
path.expression?.(
argument.type === 'RestElement'
? b.id(arg_alias)
: b.call('$.maybe_call', b.id(arg_alias))
)
)
context.visit(path.expression?.(b.call('$.maybe_call', b.id(arg_alias))))
)
)
)

@ -2028,32 +2028,6 @@ export function thunkspread(iterable) {
return thunks;
}
/**
* This is meant to proxy the `...rest` parameter to a snippet function. Basically,
* this array will be full of functions that need to be invoked to unwrap their value.
* We have no way of forcing that invocation in all circumstances -- for example, if
* a user passes the rest array to a function that then accesses it via `rest[0]`, we
* would need to transform that into `rest[0]()`. That's effectively what this proxy does.
*
* @template {unknown[]} T
* @param {T} items
* @returns {T}
*/
export function proxy_rest_array(items) {
return new Proxy(items, {
get(target, property) {
// @ts-expect-error -- It thinks arrays can't have properties that aren't numeric
if (typeof property === 'symbol') return target[property];
if (!isNaN(parseInt(property))) {
// @ts-expect-error -- It thinks arrays can't have properties that aren't numeric
return target[property]?.();
}
// @ts-expect-error -- It thinks arrays can't have properties that aren't numeric
return target[property];
}
});
}
/**
* @template {Function | undefined} T
* @param {T} fn

@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { proxy_rest_array, thunkspread } from '..';
import { thunkspread } from '..';
describe('thunkspread', () => {
it('makes all of its arguments callable', () => {
@ -19,25 +19,3 @@ describe('thunkspread', () => {
expect(thunks.map((thunk) => thunk())).toEqual([...items()]);
});
});
describe('proxy_rest_array', () => {
it('calls its items on access', () => {
const items = [() => 1, () => 2, () => 3];
const proxied_items = proxy_rest_array(items);
expect(proxied_items[1]).toBe(2);
});
it('returns undefined for keys with no item', () => {
const items = [() => 1, () => 2, () => 3];
const proxied_items = proxy_rest_array(items);
expect(proxied_items[4]).toBe(undefined);
});
it('works with array methods', () => {
const items = [() => 1, () => 2, () => 3];
const proxied_items = proxy_rest_array(items);
expect(proxied_items.map((item) => item)).toEqual([1, 2, 3]);
// @ts-expect-error - This is a weird case for sure
expect(proxied_items.find((item) => item === 1)).toBe(1);
});
});

@ -37,7 +37,6 @@ export {
user_root_effect,
inspect,
unwrap,
proxy_rest_array,
thunkspread,
maybe_call,
freeze

@ -195,14 +195,20 @@ declare const SnippetReturn: unique symbol;
* ```
* You can only call a snippet through the `{@render ...}` tag.
*/
export interface Snippet<T extends unknown[] = []> {
(
this: void,
...args: T
): typeof SnippetReturn & {
_: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"';
};
}
export type Snippet<T extends unknown[] = []> =
// this conditional allows tuples but not arrays. Arrays would indicate a
// rest parameter type, which is not supported. If rest parameters are added
// in the future, the condition can be removed.
number extends T['length']
? never
: {
(
this: void,
...args: T
): typeof SnippetReturn & {
_: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"';
};
};
interface DispatchOptions {
cancelable?: boolean;

@ -1,21 +0,0 @@
import { test } from '../../test';
export default test({
html: `
<p>clicks: 0, doubled: 0, tripled: 0</p>
<button>click me</button>
`,
async test({ assert, target }) {
const btn = target.querySelector('button');
await btn?.click();
assert.htmlEqual(
target.innerHTML,
`
<p>clicks: 1, doubled: 2, tripled: 3</p>
<button>click me</button>
`
);
}
});

@ -1,15 +0,0 @@
<script lang="ts">
let count = $state(0);
let doubled = $derived(count * 2);
let tripled = $derived(count * 3);
</script>
{#snippet foo(n: number, ...[doubled, { tripled }]: number[])}
<p>clicks: {n}, doubled: {doubled}, tripled: {tripled}</p>
{/snippet}
{@render foo(count, doubled, {tripled})}
<button on:click={() => count += 1}>
click me
</button>

@ -1,21 +0,0 @@
import { test } from '../../test';
export default test({
html: `
<p>clicks: 0, doubled: 0, tripled: 0</p>
<button>click me</button>
`,
async test({ assert, target }) {
const btn = target.querySelector('button');
await btn?.click();
assert.htmlEqual(
target.innerHTML,
`
<p>clicks: 1, doubled: 2, tripled: 3</p>
<button>click me</button>
`
);
}
});

@ -1,15 +0,0 @@
<script lang="ts">
let count = $state(0);
let doubled = $derived(count * 2);
let tripled = $derived(count * 3);
</script>
{#snippet foo(n: number, ...rest: number[])}
<p>clicks: {n}, doubled: {rest[0]}, tripled: {rest[1]}</p>
{/snippet}
{@render foo(count, doubled, tripled)}
<button on:click={() => count += 1}>
click me
</button>

@ -27,8 +27,8 @@
let whatever_comes_after_that = derivedBox(count, 5);
</script>
{#snippet foo(n: number, ...[doubled, { tripled }, ...rest]: number[])}
<p>clicks: {n.value}, doubled: {doubled.value}, tripled: {tripled.value}, quadrupled: {rest[0].value}, something else: {rest[1].value}</p>
{#snippet foo(n, doubled, { tripled }, quadrupled, whatever)}
<p>clicks: {n.value}, doubled: {doubled.value}, tripled: {tripled.value}, quadrupled: {quadrupled.value}, something else: {whatever.value}</p>
{/snippet}
{@render foo(...[count, doubled, {tripled}, quadrupled, whatever_comes_after_that])}

@ -196,14 +196,20 @@ declare module 'svelte' {
* ```
* You can only call a snippet through the `{@render ...}` tag.
*/
export interface Snippet<T extends unknown[] = []> {
(
this: void,
...args: T
): typeof SnippetReturn & {
_: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"';
};
}
export type Snippet<T extends unknown[] = []> =
// this conditional allows tuples but not arrays. Arrays would indicate a
// rest parameter type, which is not supported. If rest parameters are added
// in the future, the condition can be removed.
number extends T['length']
? never
: {
(
this: void,
...args: T
): typeof SnippetReturn & {
_: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"';
};
};
interface DispatchOptions {
cancelable?: boolean;
@ -324,7 +330,9 @@ declare module 'svelte' {
new (options: ComponentConstructorOptions<Props & (Props extends {
children?: any;
} ? {} : {} | {
children?: Snippet<[]> | undefined;
children?: ((this: void) => unique symbol & {
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
}) | undefined;
})>): SvelteComponent<Props, Events, any>;
}, options: {
target: Node;
@ -347,7 +355,9 @@ declare module 'svelte' {
new (options: ComponentConstructorOptions<Props & (Props extends {
children?: any;
} ? {} : {} | {
children?: Snippet<[]> | undefined;
children?: ((this: void) => unique symbol & {
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
}) | undefined;
})>): SvelteComponent<Props, Events, any>;
}, options: {
target: Node;
@ -1725,7 +1735,9 @@ declare module 'svelte/legacy' {
} ? {} : Slots extends {
default: any;
} ? {
children?: Snippet<[]> | undefined;
children?: ((this: void) => unique symbol & {
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
}) | undefined;
} : {})>): SvelteComponent<Props, Events, Slots>;
} & Exports;
// This should contain all the public interfaces (not all of them are actually importable, check current Svelte for which ones are).
@ -1851,14 +1863,20 @@ declare module 'svelte/legacy' {
* ```
* You can only call a snippet through the `{@render ...}` tag.
*/
interface Snippet<T extends unknown[] = []> {
(
this: void,
...args: T
): typeof SnippetReturn & {
_: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"';
};
}
type Snippet<T extends unknown[] = []> =
// this conditional allows tuples but not arrays. Arrays would indicate a
// rest parameter type, which is not supported. If rest parameters are added
// in the future, the condition can be removed.
number extends T['length']
? never
: {
(
this: void,
...args: T
): typeof SnippetReturn & {
_: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"';
};
};
}
declare module 'svelte/motion' {

@ -58,7 +58,7 @@ Snippets, and _render tags_, are a way to create reusable chunks of markup insid
{/each}
```
A snippet behaves pretty much like a regular function declaration: It can have multiple parameters, those parameters can be destructured, and they can have default values. You can also use `...rest` params. ([demo](/#H4sIAAAAAAAAE2WO0YrCQAxFfyXGhVoo9L3Wot9hF6xt1IHpTJikggzz78tI2YX1MTecc2_Em7Ek2JwjumEmbPDEjBXqi_MhT7JKWKH4JYw5aWUMhrXrXa-WFCLMJDLcCQ5wMVoIOK8gHu6BBgX1IETw8ssFEhzgi4Nn2ZX73rX1n8rFrTjDTAoPstbv8pjqQ_3hLFPe0XL3piBmLG0grmDatDV3vYv1ak_vrmMgN1FYq4rBmpGKrPr_ufpr8buiTFjh7CdzMzRho2Gh9J1-AFhYxBNCAQAA)):
A snippet behaves pretty much like a regular function declaration: It can have multiple parameters, those parameters can be destructured, and they can have default values. However, you cannot use `...rest` params. ([demo](/#H4sIAAAAAAAAE2WO0YrCQAxFfyXGhVoo9L3Wot9hF6xt1IHpTJikggzz78tI2YX1MTecc2_Em7Ek2JwjumEmbPDEjBXqi_MhT7JKWKH4JYw5aWUMhrXrXa-WFCLMJDLcCQ5wMVoIOK8gHu6BBgX1IETw8ssFEhzgi4Nn2ZX73rX1n8rFrTjDTAoPstbv8pjqQ_3hLFPe0XL3piBmLG0grmDatDV3vYv1ak_vrmMgN1FYq4rBmpGKrPr_ufpr8buiTFjh7CdzMzRho2Gh9J1-AFhYxBNCAQAA)):
```svelte
{#snippet figure({ src, caption, width, height })}
@ -244,7 +244,6 @@ type SnippetWithOneArg = Snippet<[argOne: number]>;
type SnippetWithMultipleArgs = Snippet<
[argOne: number, argTwo: string]
>;
type SnippetWithAnyNumberOfArgs = Snippet<number[]>;
```
And here are the snippet declarations matching those cases (note: this example uses TypeScript):
@ -261,8 +260,4 @@ And here are the snippet declarations matching those cases (note: this example u
{#snippet withMultipleArgs(argOne: number, argTwo: string)}
<!-- -->
{/snippet}
{#snippet withAnyNumberOfArgs(...rest: number[])}
<!-- -->
{/snippet}
```

Loading…
Cancel
Save